Class VersionResolver
See the versioning documentation for the full picture (why this exists, the
develop/release/main flow, worked examples). In short, the algorithm for an ordinary build
(resolveBuildVersion(java.io.File, java.lang.String, java.lang.String)) is:
- If HEAD is exactly on a real tag matching tagPrefix, use it verbatim - no computation needed.
- Otherwise, find the last final release tag (a plain "X.Y.Z", no "-RC"/"-SNAPSHOT"
suffix) reachable from HEAD under tagPrefix. If this module has no final tag of its own
yet, retry each of fallbackPrefixes in turn (see
lastFinalVersionLookup(org.eclipse.jgit.lib.Repository, java.lang.String, java.util.List<java.lang.String>)) - e.g. a brand-new subproject in a multi-module build inherits the root project's version line instead of starting over at "0.0.0". - Walk the commits between that tag and HEAD, optionally restricted to ones that touched
modulePath (see
commitMessagesSince(org.eclipse.jgit.lib.Repository, org.eclipse.jgit.lib.ObjectId, java.lang.String, java.util.List<java.lang.String>)), and classify them withCommitAnalyzerusing typeRules (feat/fix/perf/BREAKING CHANGE -> minor/patch/patch/major by default, but configurable viaCommitAnalyzerExtension). - Bump the last-final version by the highest classification found (see
bumpVersion(java.lang.String, io.github.duckasteroid.gradle.versioning.CommitAnalyzer.Bump)), and decorate it with "-SNAPSHOT" (plus the branch name, for feature branches) since we're not exactly on a tag.
nextReleaseCandidateTag(java.io.File, java.lang.String, java.lang.String) and promoteTag(java.io.File, java.lang.String) are the two release-engineering
operations built on the same primitives, used by the release-flow plugin's tasks rather than by
ordinary builds: the former mints the next "-RCn" tag for the computed candidate version, the
latter strips the "-RCn" suffix off the nearest reachable RC tag to produce the final release
tag.
commitMessagesForChangelog(java.io.File, java.lang.String, java.lang.String, java.util.List<java.lang.String>, io.github.duckasteroid.gradle.versioning.ChangelogScope) is the equivalent supporting operation for release notes:
it reuses the same tag-lookup machinery to decide which commits are "new" for a changelog, just
with its own scope rules rather than the version-bump rules above.
Deliberately uses JGit rather than shelling out to the git CLI:
resolveBuildVersion is called from version = ... in VersionPlugin at
Gradle configuration time, and starting an external process during configuration is
incompatible with the Gradle configuration cache. JGit is a pure-Java implementation, but it is
NOT automatically config-cache-safe on its own - the first time any code in the JVM constructs a
Repository, JGit itself shells out to the real git binary to discover the
system-level git config (see ConfigCacheSafeSystemReader, which withRepo(java.io.File, io.github.duckasteroid.gradle.versioning.VersionResolver.RepoFunction<T>)
installs before every RepositoryBuilder().build() call in this class to suppress that).
The release-flow plugin's tasks that call into this class only do so from doLast { }
(task execution time, where external processes are fine), but they share the same
JGit-based code for simplicity and to avoid two parallel implementations of the same logic.
Every public method here that would otherwise propagate a checked JGit/IO exception wraps it
in an unchecked RuntimeException instead (see withRepo(java.io.File, io.github.duckasteroid.gradle.versioning.VersionResolver.RepoFunction<T>)) - callers (this
project's own VersionPlugin, and any consumer script) can catch a plain
catch (Exception e) around a call into this class exactly as they would in a
dynamically-typed script, without every call site needing its own checked-exception handling.
-
Method Summary
Modifier and TypeMethodDescriptioncommitMessagesForChangelog(File repoDir, String tagPrefix, String modulePath, List<String> fallbackPrefixes, ChangelogScope scope) commitMessagesForChangelog(File repoDir, String tagPrefix, String modulePath, List<String> fallbackPrefixes, ChangelogScope scope, List<String> excludedModulePaths) Full commit messages to build a changelog from.currentCycleReleaseCandidateTags(File repoDir, String tagPrefix) currentCycleReleaseCandidateTags(File repoDir, String tagPrefix, List<String> fallbackPrefixes) Every release-candidate tag (tagPrefix + "X.Y.Z-RCn") reachable from HEAD but not reachable from the last final release tag's commit - i.e.static StringlastFinalVersion(File repoDir, String tagPrefix) static StringlastFinalVersion(File repoDir, String tagPrefix, List<String> fallbackPrefixes) Highest final release tag (prefix + strict X.Y.Z, no suffix) reachable from HEAD.static StringnextReleaseCandidateTag(File repoDir, String tagPrefix, String candidateVersion) The full tag name (with prefix) for the next release-candidate to mint, e.g.static StringpromoteTag(File repoDir, String tagPrefix) The full tag name (with prefix) for the final release, derived by stripping the "-RCn" suffix off the nearest reachable release-candidate tag.static StringresolveBuildVersion(File repoDir, String tagPrefix, String branchName) static StringresolveBuildVersion(File repoDir, String tagPrefix, String branchName, String modulePath) static StringresolveBuildVersion(File repoDir, String tagPrefix, String branchName, String modulePath, List<String> fallbackPrefixes) static StringresolveBuildVersion(File repoDir, String tagPrefix, String branchName, String modulePath, List<String> fallbackPrefixes, Map<CommitAnalyzer.Bump, Set<String>> typeRules) static StringresolveBuildVersion(File repoDir, String tagPrefix, String branchName, String modulePath, List<String> fallbackPrefixes, Map<CommitAnalyzer.Bump, Set<String>> typeRules, List<String> excludedModulePaths) The version to use for an ordinary build (e.g.static StringresolveCandidateVersion(File repoDir, String tagPrefix) static StringresolveCandidateVersion(File repoDir, String tagPrefix, String modulePath) static StringresolveCandidateVersion(File repoDir, String tagPrefix, String modulePath, List<String> fallbackPrefixes) static StringresolveCandidateVersion(File repoDir, String tagPrefix, String modulePath, List<String> fallbackPrefixes, Map<CommitAnalyzer.Bump, Set<String>> typeRules) static StringresolveCandidateVersion(File repoDir, String tagPrefix, String modulePath, List<String> fallbackPrefixes, Map<CommitAnalyzer.Bump, Set<String>> typeRules, List<String> excludedModulePaths) The last final release version, bumped by the conventional commits since it - with no "-SNAPSHOT"/branch decoration.
-
Method Details
-
resolveBuildVersion
-
resolveBuildVersion
-
resolveBuildVersion
-
resolveBuildVersion
-
resolveBuildVersion
public static String resolveBuildVersion(File repoDir, String tagPrefix, String branchName, String modulePath, List<String> fallbackPrefixes, Map<CommitAnalyzer.Bump, Set<String>> typeRules, List<String> excludedModulePaths) The version to use for an ordinary build (e.g../gradlew build,publishToMavenLocal).- Parameters:
repoDir- any directory inside the git repo (JGit walks upward to find the .git dir)tagPrefix- this module's own tag prefix, e.g. "v" at the root or "sub/module/v"branchName- the current git branch, used to fold a sanitized form into the -SNAPSHOT suffix on feature branches (e.g. "1.0.1-cool-stuff-SNAPSHOT") so a build made while working on a feature branch is visibly distinguishable from one made on develop/releasemodulePath- this module's directory relative to the repo root ("" at the root); commits that didn't touch this path don't count towards this module's bumpfallbackPrefixes- tried in order if tagPrefix has no final tag reachable from HEAD yettypeRules- seeCommitAnalyzer.DEFAULT_TYPE_RULES; defaults to the built-in mapping. In the Gradle plugin this normally comes from thecommitAnalyzer { }extension (seeCommitAnalyzerExtension.toTypeRules()), not the bare default.excludedModulePaths- directories to exclude from modulePath's scope even though they'd otherwise match - e.g. a root project (modulePath "") in "mixed mode" excluding the modulePaths of subprojects that independently apply the release-flow plugin, so root doesn't also bump/release on a commit that's entirely one of those subprojects' own. A commit only needs to touch one path outside every excluded directory to still count - excluding "api" doesn't hide a commit that touches both "api/" and a root-level file.
-
resolveCandidateVersion
-
resolveCandidateVersion
-
resolveCandidateVersion
-
resolveCandidateVersion
-
resolveCandidateVersion
public static String resolveCandidateVersion(File repoDir, String tagPrefix, String modulePath, List<String> fallbackPrefixes, Map<CommitAnalyzer.Bump, Set<String>> typeRules, List<String> excludedModulePaths) The last final release version, bumped by the conventional commits since it - with no "-SNAPSHOT"/branch decoration. This is what an actual release tag (RC or final) should be based on;resolveBuildVersion(java.io.File, java.lang.String, java.lang.String)calls this too and then decorates the result for ordinary (non-release) builds. -
lastFinalVersion
-
lastFinalVersion
public static String lastFinalVersion(File repoDir, String tagPrefix, List<String> fallbackPrefixes) Highest final release tag (prefix + strict X.Y.Z, no suffix) reachable from HEAD. If none exists under tagPrefix, retries each of fallbackPrefixes in order (e.g. a brand-new module inherits the root project's version line instead of starting over at "0.0.0"). Falls all the way back to "0.0.0" if nothing matches under any prefix - a fresh repo with no releases yet. -
nextReleaseCandidateTag
public static String nextReleaseCandidateTag(File repoDir, String tagPrefix, String candidateVersion) The full tag name (with prefix) for the next release-candidate to mint, e.g. "v1.1.0-RC2". Does not create or push anything itself - that's the caller's job (see the release-flow plugin'stagReleaseCandidatetask). candidateVersion is normally whateverresolveCandidateVersion(java.io.File, java.lang.String)just computed, but the caller may instead pass a value straight from-Prelease.forceVersionto bypass commit analysis entirely. -
promoteTag
The full tag name (with prefix) for the final release, derived by stripping the "-RCn" suffix off the nearest reachable release-candidate tag. Used by the release-flow plugin'spromoteReleaseCandidatetask when an accepted RC on thereleasebranch is merged tomain. Throws if there's no RC tag reachable from HEAD at all - promoting only makes sense once at least one RC has actually been cut. -
currentCycleReleaseCandidateTags
-
currentCycleReleaseCandidateTags
public static List<String> currentCycleReleaseCandidateTags(File repoDir, String tagPrefix, List<String> fallbackPrefixes) Every release-candidate tag (tagPrefix + "X.Y.Z-RCn") reachable from HEAD but not reachable from the last final release tag's commit - i.e. every RC minted during the current release cycle, regardless of which candidate version it was minted under (a later commit can raise the bump mid-cycle, "leapfrogging" an earlier RC's version - both still belong to the same cycle). Ordered nearest-to-HEAD first (most recently created). Used by the release-flow plugin'stagReleaseCandidatesto warn when the freshly computed candidate leapfrogs the most recent RC's version, and to decide which of this cycle's RC GitHub Releases are superseded and safe to prune. -
commitMessagesForChangelog
-
commitMessagesForChangelog
public static List<String> commitMessagesForChangelog(File repoDir, String tagPrefix, String modulePath, List<String> fallbackPrefixes, ChangelogScope scope, List<String> excludedModulePaths) Full commit messages to build a changelog from. The "since" boundary depends on scope:ChangelogScope.SINCE_LAST_RELEASE: the last final release tag (falling back through fallbackPrefixes exactly likeresolveCandidateVersion(java.io.File, java.lang.String)) - the complete picture for this release cycle so far.ChangelogScope.SINCE_PREVIOUS_RC: the nearest reachable release-candidate tag - a delta since the last RC - falling back to the last final release tag if there is no previous RC yet (the first RC in a cycle has nothing to diff against).
Same modulePath restriction as version resolution: only commits that touched this module's own directory are included, so a changelog generated for one subproject in a monorepo doesn't list changes from an unrelated sibling.
Caller must run this BEFORE minting any new tag for the current HEAD (see the release-flow plugin's
changelogForReleaseCandidate/changelogForReleasetasks, which always run beforetagReleaseCandidate/promoteReleaseCandidatein the same job). Both scopes locate their "since" boundary by looking for a tag reachable from HEAD - if HEAD were already tagged with the release currently being prepared, that brand-new tag would itself be "the nearest reachable RC tag" or "the last final release tag", making the commit range (and therefore the generated changelog) come back empty.- Parameters:
repoDir- any directory inside the git repo (JGit walks upward to find the .git dir)tagPrefix- this module's own tag prefix, e.g. "v" at the root or "sub/module/v"modulePath- this module's directory relative to the repo root ("" at the root)fallbackPrefixes- tried in order if tagPrefix has no final tag reachable from HEAD yet (only relevant for the SINCE_LAST_RELEASE fallback path, same as resolveCandidateVersion)scope- seeChangelogScopeexcludedModulePaths- same exclusion semantics asresolveBuildVersion(java.io.File, java.lang.String, java.lang.String)- a changelog generated for one project shouldn't list commits that belong entirely to a sibling project it excludes.
-