Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for using git CLI when available #1

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ sourceSets {
}

group 'net.neoforged.gradleutils'
version '4.0.0'
version '4.0.1'

repositories {
mavenCentral()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ class ChangelogGenerationExtension {
this.project = project
}

void enable() {
ChangelogUtils.setupChangelogGeneration(project, null)
project.afterEvaluate {
afterEvaluate(project)
}
}

void from(final String revision) {
ChangelogUtils.setupChangelogGeneration(project, revision)
project.afterEvaluate {
Expand Down
102 changes: 72 additions & 30 deletions src/main/groovy/net/neoforged/gradleutils/ChangelogGenerator.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,106 @@ package net.neoforged.gradleutils

import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.eclipse.jgit.api.Git
import net.neoforged.gradleutils.git.GitProvider
import net.neoforged.gradleutils.specs.VersionSpec
import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.revwalk.RevWalk
import org.jetbrains.annotations.Nullable

@CompileStatic
@PackageScope
class ChangelogGenerator {
private final VersionCalculator calculator
private final VersionSpec versionSpec

ChangelogGenerator(VersionCalculator calculator) {
ChangelogGenerator(VersionCalculator calculator, VersionSpec versionSpec) {
this.calculator = calculator
this.versionSpec = versionSpec
}

String generate(Git git, String earliest, String latest = Constants.HEAD) {
// Resolve both commits
final RevCommit earliestCommit, latestCommit
try (RevWalk walk = new RevWalk(git.repository)) {
earliestCommit = walk.parseCommit(git.repository.resolve(earliest))
latestCommit = walk.parseCommit(git.repository.resolve(latest))
}
String generate(GitProvider git, @Nullable String earliest, String latest = Constants.HEAD) {

// List all commits between latest and earliest commits -- including the two ends
def logCommand = git.log().add(latestCommit)
// Exclude all parents of earliest commit
for (RevCommit parent : earliestCommit.getParents()) {
logCommand.not(parent)
}
// Map of Commit -> Tags
Map<String, List<String>> tags = git.getTags(versionSpec.tags.includeLightweightTags.get())
.findAll { calculator.isIncludedTag(it.name()) }
.groupBy { it.hash() }
.collectEntries { k, v ->
[(k): v.collect { it.name() }]
}

// List has order of latest (0) to earliest (list.size())
final List<RevCommit> commits = logCommand.call().collect()
def commits = git.getCommits(latest, earliest)

def versions = buildCommitToVersionMap(commits, tags, git)

// TODO: headers for tags -- need more hooks into version calculator
// TODO: caching for version calculation -- perhaps split version calculator to two passes?

final StringBuilder builder = new StringBuilder()
for (RevCommit commit : commits) {
String currentMajor = ""

final version = calculateVersion(git, commit.name())
for (GitProvider.CommitData commit : commits) {
final version = versions.get(commit.hash())
// " - `<version>` <message>"
// " <continuation>" if multi-line

builder.append(" - `$version` ")
buildCommitMessage(builder, commit, " ")
if (version) {
String majorVersion = version.split("\\.")[0..1].join(".")
if (majorVersion != currentMajor) {
builder.append("\n")
builder.append("# $majorVersion")
builder.append("\n\n")
currentMajor = majorVersion
}

builder.append(" - `$version` ")
} else {
// This might be a commit before first tag
builder.append(" - `${commit.shortHash()}` ")
}
buildCommitMessage(builder, commit.message(), " ")
}

return builder.toString()
}

private static void buildCommitMessage(StringBuilder builder, RevCommit commit, String continueHeader) {
private Map<String, String> buildCommitToVersionMap(List<GitProvider.CommitData> commits, Map<String, List<String>> tags, GitProvider git) {
var result = new HashMap<String, String>()
// Work on each entry in reverse
int prevTagAt = -1
String prevTag = null
String label = null
for (int i = commits.size() - 1; i >= 0; i--) {
final commit = commits[i]
final commitTags = tags.getOrDefault(commit.hash(), [])
for (var tag in commitTags) {
if (calculator.isLabelResetTag(tag)) {
label = null
continue
}

var tagLabel = calculator.getTagLabel(tag)
if (tagLabel != null) {
// found a label for the current anchor
label = tagLabel
} else {
// new version anchor found
prevTagAt = i
prevTag = tag
label = calculator.defaultLabel
}
break
}

if (prevTag != null) {
final offset = prevTagAt - i
result[commit.hash()] = calculator.calculateForTag(git, prevTag, label, offset, true, true)
}
}
return result
}

private static void buildCommitMessage(StringBuilder builder, String message, String continueHeader) {
// Assume the current line in the builder already contains the initial part of the line (with the version)

final message = commit.fullMessage
// Assume that the message contains at least one LF
// If the first and last LF in the message are at the same position, then there is only one singular LF
if (message.indexOf('\n') == message.lastIndexOf('\n')) {
Expand All @@ -77,9 +124,4 @@ class ChangelogGenerator {
}
}
}

private String calculateVersion(Git git, String rev) {
// Skip branch suffix
return calculator.calculate(git, rev, true, true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ package net.neoforged.gradleutils

import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import net.neoforged.gradleutils.git.GitProvider
import net.neoforged.gradleutils.specs.VersionSpec
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ValueSource
Expand All @@ -30,12 +28,10 @@ abstract class ChangelogGeneratorValueSource implements ValueSource<String, Para
@Override
String obtain() {
final calculator = new VersionCalculator(parameters.versionSpec.get())
final generator = new ChangelogGenerator(calculator)
final generator = new ChangelogGenerator(calculator, parameters.versionSpec.get())

try (Repository repo = new FileRepositoryBuilder().findGitDir(parameters.workingDirectory.get().asFile).build()) {
final git = Git.wrap(repo)

return generator.generate(git, parameters.earliestRevision.get())
try (GitProvider provider = GradleUtils.openGitProvider(parameters.workingDirectory.get().asFile)) {
return generator.generate(provider, parameters.earliestRevision.getOrNull())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.plugins.PublishingPlugin
import org.gradle.api.tasks.TaskProvider
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.annotations.Nullable

import java.util.function.Function
import java.util.regex.Pattern
Expand Down Expand Up @@ -584,9 +585,11 @@ class ChangelogUtils {
* @param project The project to add changelog generation to.
* @param rev The revision to start the changelog from.
*/
static void setupChangelogGeneration(final Project project, final String rev) {
static void setupChangelogGeneration(final Project project, @Nullable final String rev) {
final TaskProvider<GenerateChangelogTask> task = project.getTasks().register(CHANGELOG_GENERATION_TASK_NAME, GenerateChangelogTask.class) {
it.startingRevision.set(rev)
if (rev != null) {
it.startingRevision.set(rev)
}
}

project.plugins.withType(LifecycleBasePlugin).configureEach {
Expand Down
55 changes: 15 additions & 40 deletions src/main/groovy/net/neoforged/gradleutils/GitInfoValueSource.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,22 @@ package net.neoforged.gradleutils
import groovy.transform.CompileStatic
import groovy.transform.Immutable
import groovy.transform.PackageScope
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.errors.RepositoryNotFoundException
import org.eclipse.jgit.lib.ObjectId
import net.neoforged.gradleutils.git.GitProvider
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.logging.Logging
import org.gradle.api.provider.SetProperty
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.slf4j.Logger

import javax.annotation.Nullable

@PackageScope
@CompileStatic
abstract class GitInfoValueSource implements ValueSource<GitInfo, Parameters> {
private static final Logger LOGGER = Logging.getLogger(GitInfoValueSource.class)

static interface Parameters extends ValueSourceParameters {
DirectoryProperty getWorkingDirectory()

Expand All @@ -32,35 +33,34 @@ abstract class GitInfoValueSource implements ValueSource<GitInfo, Parameters> {

@Override
GitInfo obtain() {
try (Repository repo = new FileRepositoryBuilder().findGitDir(parameters.workingDirectory.get().asFile).build()) {
final git = Git.wrap(repo)

try (GitProvider provider = GradleUtils.openGitProvider(parameters.workingDirectory.get().asFile)) {
final filters = parameters.tagFilters.get().<String> toArray(new String[0])
final tag = git.describe().setLong(true).setTags(true).setMatch(filters).call()
final tag = provider.describe().longFormat(true).includeLightweightTags(true).matching(filters).run()
final desc = GradleUtils.rsplit(tag, '-', 2) ?: ['0.0', '0', '00000000']
final head = git.repository.exactRef('HEAD')
final String longBranch = head.symbolic ? head?.target?.name : null
final head = provider.head
final String longBranch = provider.fullBranch
// matches Repository.getFullBranch() but returning null when on a detached HEAD

Map<String, String> gitInfoMap = [:]
gitInfoMap.dir = repo.getDirectory().parentFile.absolutePath
gitInfoMap.dir = provider.dotGitDirectory.parentFile.absolutePath
gitInfoMap.tag = desc[0]
if (gitInfoMap.tag.startsWith("v") && gitInfoMap.tag.length() > 1 && gitInfoMap.tag.charAt(1).digit)
gitInfoMap.tag = gitInfoMap.tag.substring(1)
gitInfoMap.offset = desc[1]
gitInfoMap.hash = desc[2]
gitInfoMap.branch = longBranch != null ? Repository.shortenRefName(longBranch) : null
gitInfoMap.commit = ObjectId.toString(head.objectId)
gitInfoMap.abbreviatedId = head.objectId.abbreviate(8).name()
gitInfoMap.commit = head // TODO: double-check this is a commit
gitInfoMap.abbreviatedId = provider.abbreviateRef(head, 8)

// Remove any lingering null values
gitInfoMap.removeAll { it.value == null }

final originUrl = getRemotePushUrl(git, "origin")
final originUrl = transformPushUrl(provider.getRemotePushUrl("origin"))

return new GitInfo(gitInfoMap, originUrl)

} catch (RepositoryNotFoundException ignored) {
} catch (Exception ex) {
LOGGER.warn("Failed to obtain git info", ex)
return new GitInfo([
tag : '0.0',
offset : '0',
Expand All @@ -72,31 +72,6 @@ abstract class GitInfoValueSource implements ValueSource<GitInfo, Parameters> {
}
}

@Nullable
private static String getRemotePushUrl(Git git, String remoteName) {
def remotes = git.remoteList().call()
if (remotes.size() == 0)
return null

// Get the origin remote
def originRemote = remotes.toList().stream()
.filter(r -> r.getName() == remoteName)
.findFirst()
.orElse(null)

//We do not have an origin named remote
if (originRemote == null) return null

// Get the origin push url.
def originUrl = originRemote.getURIs().toList().stream()
.findFirst()
.orElse(null)

if (originUrl == null) return null // No origin URL

return transformPushUrl(originUrl.toString())
}

private static String transformPushUrl(String url) {
if (url.startsWith("ssh")) {
// Convert SSH urls to HTTPS
Expand Down
31 changes: 17 additions & 14 deletions src/main/groovy/net/neoforged/gradleutils/GradleUtils.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ package net.neoforged.gradleutils

import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import net.neoforged.gradleutils.git.CommandLineGitProvider
import net.neoforged.gradleutils.git.GitProvider
import net.neoforged.gradleutils.git.JGitProvider
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.errors.RepositoryNotFoundException
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.Repository
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.Project
Expand Down Expand Up @@ -57,10 +57,8 @@ class GradleUtils {
}

static Map<String, String> gitInfo(File dir, String... globFilters) {
def git
try {
git = openGit(dir)
} catch (RepositoryNotFoundException e) {
GitProvider provider = openGitProvider(dir)
if (provider == null) {
return [
tag: '0.0',
offset: '0',
Expand All @@ -70,10 +68,11 @@ class GradleUtils {
abbreviatedId: '00000000'
]
}
def tag = git.describe().setLong(true).setTags(true).setMatch(globFilters ?: new String[0]).call()

def tag = provider.describe().longFormat(true).includeLightweightTags(true).matching(globFilters).run()
def desc = rsplit(tag, '-', 2) ?: ['0.0', '0', '00000000']
def head = git.repository.exactRef('HEAD')
final String longBranch = head.symbolic ? head?.target?.name : null // matches Repository.getFullBranch() but returning null when on a detached HEAD
def head = provider.head
def longBranch = provider.fullBranch

Map<String, String> ret = [:]
ret.dir = dir.absolutePath
Expand All @@ -82,12 +81,12 @@ class GradleUtils {
ret.tag = ret.tag.substring(1)
ret.offset = desc[1]
ret.hash = desc[2]
ret.branch = longBranch != null ? Repository.shortenRefName(longBranch) : null
ret.commit = ObjectId.toString(head.objectId)
ret.abbreviatedId = head.objectId.abbreviate(8).name()
ret.branch = longBranch != null ? provider.shortenRef(longBranch) : null
ret.commit = head
ret.abbreviatedId = provider.abbreviateRef(head, 0)

// Remove any lingering null values
ret.removeAll {it.value == null }
ret.removeAll { it.value == null }

return ret
}
Expand Down Expand Up @@ -281,4 +280,8 @@ class GradleUtils {
}
}
}

static GitProvider openGitProvider(File projectDir) {
return CommandLineGitProvider.create(projectDir) ?: JGitProvider.create(projectDir)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class InternalAccessor {
}

static String generateChangelog(ProviderFactory providers, VersionSpec versionConfig, Directory workingDirectory,
String earliestRevision) {
@Nullable String earliestRevision) {
final changelog = providers.of(ChangelogGeneratorValueSource) {
it.parameters {
it.workingDirectory.set(workingDirectory)
Expand Down
Loading
Loading