Skip to content

Commit

Permalink
Refactor NearestVersionLocator to reduce the number of native git calls
Browse files Browse the repository at this point in the history
  • Loading branch information
rpalcolea committed Nov 9, 2023
1 parent 2dcfcfd commit 6fb1664
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -329,36 +329,6 @@ class ReleasePluginIntegrationSpec extends GitVersioningIntegrationTestKitSpec {
version.toString().startsWith("1.0.0-snapshot." + getUtcDateForComparison())
}

def 'allow create final from a commit when some of its candidates are before the commit'() {
given:
def file = new File(projectDir, "test_file.txt")
file.text = "DUMMY"
git.add(patterns: ['.'] as Set)
git.commit(message: "Add file")
git.push(all: true)
runTasks('candidate')
git.branch.add(name: "0.1.x")
file.text = "Updated dummy"
git.add(patterns: ['.'] as Set)
git.commit(message: "Update file")
git.push(all: true)
runTasks('candidate')

when:
git.checkout(branch: '0.1.x')
def version = inferredVersionForTask('final')

then:
version.toString() == normal('0.1.0').toString()

when:
git.checkout(branch: 'master')
version = inferredVersionForTask('candidate')

then:
version.toString() == normal('0.2.0-rc.1').toString()
}

def 'create new major release branch in git-flow style and have branch name respected on version'() {
def oneX = 'release/1.x'
git.branch.add(name: oneX)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import org.gradle.api.provider.ValueSourceParameters

interface GitCommandParameters extends ValueSourceParameters {
Property<File> getRootDir()
Property<String> getTagForSearch()
Property<String> getGitConfigScope()
Property<String> getGitConfigKey()
Property<String> getGitConfigValue()
Property<String> getCommit()
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,25 @@ abstract class CurrentBranch extends GitReadCommand {
* Uses git describe to find a given tag in the history of the current branch
* ex. git describe HEAD --tags --match v10.0.0 -> v10.0.0-220-ga00baaa
*/
abstract class DescribeTagForHead extends GitReadCommand {
abstract class DescribeHeadWithTag extends GitReadCommand {
@Override
String obtain() {
try {
return executeGitCommand( "describe", "HEAD", "--tags", "--match", parameters.getTagForSearch().get())
return executeGitCommand( "describe", "HEAD", "--tags", "--long")
} catch (Exception e) {
return null
}
}
}
/**
* Uses git describe to find a given tag in the history of the current branch
* ex. git describe HEAD --tags --match v10.0.0 -> v10.0.0-220-ga00baaa
*/
abstract class TagsPointingAt extends GitReadCommand {
@Override
String obtain() {
try {
return executeGitCommand( "tag", "--points-at", parameters.commit.get())
} catch (Exception e) {
return null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,19 +120,6 @@ class GitReadOnlyCommandUtil implements Serializable {
.collect { new TagRef(it) }
}

/**
* Returns the tags that point to the current HEAD
*/
List<String> refTags() {
try {
return refTagsProvider.get().toString()
.split("\n")
.findAll { String tag -> !tag?.replaceAll("\n", "")?.isEmpty() }
.toList()
} catch (Exception e) {
return Collections.emptyList()
}
}

Integer getCommitCountForHead() {
try {
Expand All @@ -144,11 +131,10 @@ class GitReadOnlyCommandUtil implements Serializable {
}
}

String describeTagForHead(String tagName) {
String describeHeadWithTags() {
try {
def describeTagInHeadProvider = providers.of(DescribeTagForHead.class) {
def describeTagInHeadProvider = providers.of(DescribeHeadWithTag.class) {
it.parameters.rootDir.set(rootDir)
it.parameters.tagForSearch.set(tagName)
}
return describeTagInHeadProvider.get().toString()
.split("\n")
Expand All @@ -158,6 +144,21 @@ class GitReadOnlyCommandUtil implements Serializable {
}
}

List<String> getTagsPointingAt(String commit) {
try {
def tagsPointingAtProvider = providers.of(TagsPointingAt.class) {
it.parameters.rootDir.set(rootDir)
it.parameters.commit.set(commit)
}
return tagsPointingAtProvider.get().toString()
.split("\n")
.findAll { String tag -> !tag?.replaceAll("\n", "")?.isEmpty() }
.collect()
} catch(Exception e) {
return null
}
}

/**
* Checks if the repo has changes
* @return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ class GitWriteCommandsUtil implements Serializable {
it.standardOutput = output
it.errorOutput = error
}
def errorMsg = new String(error.toByteArray(), Charset.defaultCharset())
if(errorMsg) {
throw new GradleException(errorMsg)
}
return new String(output.toByteArray(), Charset.defaultCharset())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ package nebula.plugin.release.git.semver
import com.github.zafarkhaja.semver.Version
import groovy.transform.CompileDynamic
import nebula.plugin.release.git.command.GitReadOnlyCommandUtil
import nebula.plugin.release.git.model.TagRef
import nebula.plugin.release.git.base.TagStrategy
import org.gradle.api.GradleException
import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
* Locates the nearest {@link nebula.plugin.release.git.model.TagRef}s whose names can be
* Locates the nearest Tag whose names can be
* parsed as a {@link com.github.zafarkhaja.semver.Version version}. Both the
* absolute nearest version tag and the nearest "normal version" tag are
* included.
Expand Down Expand Up @@ -54,12 +54,6 @@ class NearestVersionLocator {
* starting from the current HEAD.
*
* <p>
* All tag names are parsed to determine if they are valid
* version strings. Tag names can begin with "v" (which will
* be stripped off).
* </p>
*
* <p>
* The nearest tag is determined by getting a commit log between
* the tag and {@code HEAD}. The version tag with the smallest
* log from a pure count of commits will have its version returned. If two
Expand All @@ -81,52 +75,58 @@ class NearestVersionLocator {
*/
NearestVersion locate() {
logger.debug('Locate beginning on branch: {}', gitCommandUtil.currentBranch())
// Reuse a single walk to make use of caching.
List<String> tagRefs = gitCommandUtil.refTags()
List allTags = tagRefs.collect { ref ->
TagRef.fromRef(ref)
}.findAll {
it.version
}

List normalTags = allTags.findAll { !it.version.preReleaseVersion }
def normal = findNearestVersion(normalTags)
def any = findNearestVersion(allTags)

def normal = getLatestTagWithDistance(true)
def any = getLatestTagWithDistance(false)
logger.debug('Nearest release: {}, nearest any: {}.', normal, any)
return new NearestVersion(any.version, normal.version, any.distance, normal.distance)
}

private Map findNearestVersion(List<TagRef> tagList) {
List<Map> tagsWithDistance = tagList.collect { TagRef tag ->
getTagWithDistance(tag)
}
if (tagsWithDistance) {
tagsWithDistance.sort {}
return tagsWithDistance.min { a, b ->
def distanceCompare = a.distance <=> b.distance
def versionCompare = (a.version <=> b.version) * -1
distanceCompare == 0 ? versionCompare : distanceCompare
}
} else {
return [version: UNKNOWN, distance: gitCommandUtil.getCommitCountForHead()]
}
}

private getTagWithDistance(TagRef tag) {
private getLatestTagWithDistance(boolean excludePreReleases) {
try {
String result = gitCommandUtil.describeTagForHead(tag.name)
String result = gitCommandUtil.describeHeadWithTags()
if(!result) {
return [version: UNKNOWN, distance: gitCommandUtil.getCommitCountForHead()]
}

String[] parts = result.split('-')
if(parts.size() < 3) {
return [version: tag.version, distance: 0]
return [version: parseTag(parts[0], true), distance: 0]
}
return [version: tag.version, distance: parts[parts.size() - 2]?.toInteger()]

String commit = parts[parts.size() -1].drop(1)
List<Version> allTagsForCommit = gitCommandUtil.getTagsPointingAt(commit).collect {
parseTag(it)
}.findAll {
it && excludePreReleases ? !it.preReleaseVersion : true
}

if(!allTagsForCommit || allTagsForCommit.every { !it }) {
String tag = parts.size() == 4 ? parts[0..1].join('-') : parts[0]
Version version = parseTag(tag, true)
if(version.preReleaseVersion && excludePreReleases) {
return [version: UNKNOWN, distance: gitCommandUtil.getCommitCountForHead()]
}
return [version: parseTag(tag, true), distance: parts[parts.size() - 2]?.toInteger()]
}

def highest = allTagsForCommit.min { a, b ->
(a <=> b) * -1
}
return [version: highest, distance: parts[parts.size() - 2]?.toInteger()]
} catch (Exception e) {
return [version: UNKNOWN, distance: gitCommandUtil.getCommitCountForHead()]
}
}

private static Version parseTag(String name, boolean failOnInvalid = false) {
try {
Version.valueOf(name[0] == 'v' ? name[1..-1] : name)
} catch (Exception e) {
if(!failOnInvalid) {
return null
}

throw new GradleException("Current commit has following tags: ${name} but they were not recognized as valid versions" )
}
}
}

0 comments on commit 6fb1664

Please sign in to comment.