Skip to content

Commit 130410c

Browse files
authored
Fix version hint vs git history comparison (issue #99) (#101)
- Implement intelligent version comparison between version hints and git history - For default pattern: use higher version number between hint and git history - For custom patterns: preserve existing behavior (hints take priority) - Add isVersionHintTag() method for proper pattern matching - Filter version hint tags from git history to prevent double-counting - Add comprehensive unit tests for version comparison logic - Add integration test reproducing the exact scenario from issue #99 - Maintain backward compatibility for all existing use cases Fixes #99
1 parent 1d3f1d2 commit 130410c

File tree

8 files changed

+250
-13
lines changed

8 files changed

+250
-13
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<extensions>
3+
<extension>
4+
<groupId>eu.maveniverse.maven.nisse</groupId>
5+
<artifactId>extension3</artifactId>
6+
<version>@project.version@</version>
7+
</extension>
8+
</extensions>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-D
2+
nisse.source.jgit.dynamicVersion=true
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
invoker.goals = clean validate
2+
invoker.debug = true
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>localhost</groupId>
6+
<artifactId>it-dynamic-versioning-version-hint-vs-release-tags</artifactId>
7+
<version>${nisse.jgit.dynamicVersion}</version>
8+
<packaging>pom</packaging>
9+
<name>version-hint-vs-release-tags-test</name>
10+
<url>http://localhost/</url>
11+
</project>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
void exec(String command) {
2+
def proc = command.execute(null, basedir)
3+
proc.consumeProcessOutput(System.out, System.out)
4+
proc.waitFor()
5+
assert proc.exitValue() == 0 : "Command '${command}' returned status: ${proc.exitValue()}"
6+
}
7+
8+
def testFile = new File(basedir, 'test.txt')
9+
testFile << 'content'
10+
11+
exec('git init')
12+
exec('git config user.email "you@example.com"')
13+
exec('git config user.name "Your Name"')
14+
15+
exec('git add test.txt')
16+
exec('git commit -m initial-commit')
17+
18+
// Create an old version hint tag (like the issue describes: 0.9.2-SNAPSHOT from a year ago)
19+
exec('git tag 0.9.2-SNAPSHOT')
20+
21+
// Add more commits to simulate time passing
22+
testFile << '\nmore content'
23+
exec('git add test.txt')
24+
exec('git commit -m second-commit')
25+
26+
testFile << '\neven more content'
27+
exec('git add test.txt')
28+
exec('git commit -m third-commit')
29+
30+
// Create a much newer release tag (like the issue describes: 0.13.0 is the latest)
31+
exec('git tag 0.13.0')
32+
33+
// Add one more commit to move HEAD away from the release tag
34+
testFile << '\nfinal content'
35+
exec('git add test.txt')
36+
exec('git commit -m fourth-commit')
37+
38+
// List all tags for debugging
39+
exec('git tag')
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// check that property was set
2+
3+
def mavenLogFile = new File(basedir, 'build.log')
4+
assert mavenLogFile.exists() : "Maven log file does not exist"
5+
6+
// Read the log file
7+
def logContent = mavenLogFile.text
8+
9+
// Define a pattern to match the version output
10+
def versionPattern = /\$\{nisse.jgit.dynamicVersion\}=(.+)/
11+
def matcher = (logContent =~ versionPattern)
12+
assert matcher.find() : "Version information not found in log file"
13+
14+
// Extract the version from the matched group
15+
def actualVersion = matcher[0][1]
16+
17+
// Should use the higher release tag version (0.13.1-1-SNAPSHOT) instead of the old hint tag (0.9.2-SNAPSHOT)
18+
// The version should be 0.13.1-1-SNAPSHOT because:
19+
// - 0.13.0 is the latest release tag
20+
// - We're 1 commit ahead of it, so patch gets incremented to 0.13.1
21+
// - Build number is 1 (commits ahead)
22+
// - SNAPSHOT qualifier is added because we're ahead
23+
def expectedVersion = '0.13.1-1-SNAPSHOT'
24+
25+
assert actualVersion == expectedVersion : "Expected version '${expectedVersion}', but found '${actualVersion}'. The old version hint tag (0.9.2-SNAPSHOT) should not take precedence over the newer release tag (0.13.0)."

sources/jgit-source/src/main/java/eu/maveniverse/maven/nisse/source/jgit/JGitPropertySource.java

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -245,17 +245,59 @@ public String resolveDynamicVersion(NisseConfiguration configuration, Repository
245245
vi = new VersionInformation(useVersion.get());
246246
logger.debug("Using explicit version from useVersion property: {}", useVersion.get());
247247
} else {
248-
// Try to find version hint tags first
248+
// First, get version from git history (regular release tags)
249+
VersionInformation gitHistoryVersion = getVersionFromGit(configuration, repository);
250+
logger.debug("Version from git history: {}", gitHistoryVersion.toString());
251+
252+
// Check if using custom version hint pattern
253+
String versionHintPattern = configuration
254+
.getConfiguration()
255+
.getOrDefault(JGIT_CONF_SYSTEM_PROPERTY_VERSION_HINT_PATTERN, DEFAULT_VERSION_HINT_PATTERN);
256+
boolean isCustomPattern = !DEFAULT_VERSION_HINT_PATTERN.equals(versionHintPattern);
257+
258+
// Then, check for version hint tags
249259
Optional<String> versionHint = findVersionHint(configuration, repository);
250260
if (versionHint.isPresent()) {
251-
vi = new VersionInformation(versionHint.get());
252-
// Version hints are treated as if the previous commit was tagged,
253-
// so we should add SNAPSHOT qualifier since we're ahead of that "previous commit"
254-
vi = mayAddSnapshotQualifier(configuration, vi);
255-
logger.debug("Using version hint from tag: {}", versionHint.get());
261+
VersionInformation hintVersion = new VersionInformation(versionHint.get());
262+
logger.debug("Version hint found: {}", hintVersion.toString());
263+
264+
if (isCustomPattern) {
265+
// With custom pattern, version hints take priority (git history only contains matching tags)
266+
vi = mayAddSnapshotQualifier(configuration, hintVersion);
267+
logger.debug("Using version hint (custom pattern): {}", versionHint.get());
268+
} else {
269+
// With default pattern, compare versions
270+
// Check if git history version is the default (meaning no regular release tags found)
271+
boolean isDefaultGitVersion = gitHistoryVersion.getMajor() == 0
272+
&& gitHistoryVersion.getMinor() == 0
273+
&& gitHistoryVersion.getPatch() == 1;
274+
275+
if (isDefaultGitVersion) {
276+
// No regular release tags found, use version hint directly
277+
vi = mayAddSnapshotQualifier(configuration, hintVersion);
278+
logger.debug("Using version hint (no regular release tags found): {}", versionHint.get());
279+
} else {
280+
// Compare versions - use hint only if it's higher than git history version
281+
Version gitHistoryVersionParsed = version(gitHistoryVersion.toString());
282+
Version hintVersionParsed = version(hintVersion.toString());
283+
284+
if (hintVersionParsed.compareTo(gitHistoryVersionParsed) > 0) {
285+
// Version hint is higher, use it
286+
vi = mayAddSnapshotQualifier(configuration, hintVersion);
287+
logger.debug("Using version hint (higher than git history): {}", versionHint.get());
288+
} else {
289+
// Git history version is higher or equal, use it
290+
vi = gitHistoryVersion;
291+
logger.debug(
292+
"Using git history version (higher than or equal to version hint): {}",
293+
gitHistoryVersion.toString());
294+
}
295+
}
296+
}
256297
} else {
257-
vi = getVersionFromGit(configuration, repository);
258-
logger.debug("Using version resolved from git history");
298+
// No version hint, use git history version
299+
vi = gitHistoryVersion;
300+
logger.debug("Using version resolved from git history (no version hint found)");
259301
}
260302
}
261303

@@ -274,7 +316,7 @@ protected VersionInformation getVersionFromGit(NisseConfiguration configuration,
274316
Iterable<RevCommit> commits = git.log().call();
275317
int count = 0;
276318
for (RevCommit commit : commits) {
277-
Optional<VersionInformation> ovi = getHighestVersionTagForCommit(git, commit);
319+
Optional<VersionInformation> ovi = getHighestVersionTagForCommit(configuration, git, commit);
278320

279321
if (ovi.isPresent()) {
280322
VersionInformation vi = ovi.get();
@@ -295,17 +337,24 @@ protected VersionInformation getVersionFromGit(NisseConfiguration configuration,
295337
}
296338
}
297339

298-
private Optional<VersionInformation> getHighestVersionTagForCommit(Git git, RevCommit commit)
299-
throws GitAPIException {
340+
private Optional<VersionInformation> getHighestVersionTagForCommit(
341+
NisseConfiguration configuration, Git git, RevCommit commit) throws GitAPIException {
300342
// get tags use semantic version (X.Y.Z or vX.Y.Z) for for commit
301-
List<String> versionTagsForCommit = getVersionedTagsForCommit(git, commit);
343+
List<String> versionTagsForCommit = getVersionedTagsForCommit(configuration, git, commit);
302344
logger.debug("commit {} {}: {}", commit.getId(), commit.getShortMessage(), versionTagsForCommit.toString());
303345
Optional<VersionInformation> ovi = findHighestVersion(versionTagsForCommit);
304346

305347
return ovi;
306348
}
307349

308-
protected List<String> getVersionedTagsForCommit(Git git, RevCommit commit) throws GitAPIException {
350+
protected List<String> getVersionedTagsForCommit(NisseConfiguration configuration, Git git, RevCommit commit)
351+
throws GitAPIException {
352+
// Check if using custom version hint pattern
353+
String versionHintPattern = configuration
354+
.getConfiguration()
355+
.getOrDefault(JGIT_CONF_SYSTEM_PROPERTY_VERSION_HINT_PATTERN, DEFAULT_VERSION_HINT_PATTERN);
356+
boolean isCustomPattern = !DEFAULT_VERSION_HINT_PATTERN.equals(versionHintPattern);
357+
309358
return git.tagList().call().stream()
310359
.filter(tag -> {
311360
try {
@@ -320,6 +369,15 @@ protected List<String> getVersionedTagsForCommit(Git git, RevCommit commit) thro
320369
}
321370
})
322371
.map(Ref::getName)
372+
.filter(tagName -> {
373+
if (isCustomPattern) {
374+
// With custom pattern, only consider tags that match the pattern
375+
return isVersionHintTag(configuration, tagName);
376+
} else {
377+
// With default pattern, exclude version hint tags to avoid double-counting
378+
return !isVersionHintTag(configuration, tagName);
379+
}
380+
})
323381
.map(TAG_VERSION_PATTERN::matcher)
324382
.filter(m -> m.matches() && m.groupCount() > 0)
325383
.map(m -> m.group(1))
@@ -419,4 +477,30 @@ protected List<String> findVersionHintTags(Git git, String hintPattern) throws G
419477
protected Optional<String> findHighestVersionFromHints(List<String> hintVersions) {
420478
return hintVersions.stream().max(Comparator.comparing(this::version));
421479
}
480+
481+
/**
482+
* Check if a tag name matches the version hint pattern.
483+
*
484+
* @param configuration The Nisse configuration
485+
* @param tagName The full tag name (e.g., "refs/tags/1.0.0-SNAPSHOT")
486+
* @return true if the tag matches the version hint pattern
487+
*/
488+
protected boolean isVersionHintTag(NisseConfiguration configuration, String tagName) {
489+
String versionHintPattern = configuration
490+
.getConfiguration()
491+
.getOrDefault(JGIT_CONF_SYSTEM_PROPERTY_VERSION_HINT_PATTERN, DEFAULT_VERSION_HINT_PATTERN);
492+
493+
// Convert hint pattern to regex pattern (same logic as findVersionHintTags)
494+
// First, escape special regex characters in the pattern (except the placeholder)
495+
String regexPattern = versionHintPattern
496+
.replace(".", "\\.") // Escape literal dots
497+
.replace("-", "\\-"); // Escape literal dashes
498+
499+
// Then replace the placeholder with the version regex
500+
regexPattern = regexPattern.replace("${version}", "(\\d+\\.\\d+\\.\\d+)");
501+
502+
Pattern hintTagPattern = Pattern.compile("refs/tags/v?" + regexPattern);
503+
504+
return hintTagPattern.matcher(tagName).matches();
505+
}
422506
}

sources/jgit-source/src/test/java/eu/maveniverse/maven/nisse/source/jgit/JGitPropertySourceTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.junit.jupiter.api.Assertions.*;
44

5+
import eu.maveniverse.maven.nisse.core.NisseConfiguration;
56
import eu.maveniverse.maven.nisse.core.simple.SimpleNisseConfiguration;
67
import java.io.IOException;
78
import java.util.Arrays;
@@ -10,6 +11,7 @@
1011
import java.util.Map;
1112
import java.util.Optional;
1213
import java.util.regex.Pattern;
14+
import org.eclipse.aether.version.Version;
1315
import org.junit.jupiter.api.Test;
1416

1517
public class JGitPropertySourceTest {
@@ -216,4 +218,68 @@ void testFindHighestVersionFromHintsEmpty() {
216218

217219
assertFalse(highest.isPresent());
218220
}
221+
222+
@Test
223+
void testVersionHintVsGitHistoryComparison() {
224+
JGitPropertySource source = new JGitPropertySource();
225+
226+
// Test case 1: Version hint is lower than git history - should use git history
227+
VersionInformation gitHistory1 = new VersionInformation("0.13.0");
228+
VersionInformation hint1 = new VersionInformation("0.9.2");
229+
230+
Version gitHistoryParsed1 = source.version(gitHistory1.toString());
231+
Version hintParsed1 = source.version(hint1.toString());
232+
233+
assertTrue(
234+
hintParsed1.compareTo(gitHistoryParsed1) < 0,
235+
"Version hint 0.9.2 should be lower than git history 0.13.0");
236+
237+
// Test case 2: Version hint is higher than git history - should use hint
238+
VersionInformation gitHistory2 = new VersionInformation("0.13.0");
239+
VersionInformation hint2 = new VersionInformation("0.14.0");
240+
241+
Version gitHistoryParsed2 = source.version(gitHistory2.toString());
242+
Version hintParsed2 = source.version(hint2.toString());
243+
244+
assertTrue(
245+
hintParsed2.compareTo(gitHistoryParsed2) > 0,
246+
"Version hint 0.14.0 should be higher than git history 0.13.0");
247+
248+
// Test case 3: Version hint equals git history - should use git history
249+
VersionInformation gitHistory3 = new VersionInformation("0.13.0");
250+
VersionInformation hint3 = new VersionInformation("0.13.0");
251+
252+
Version gitHistoryParsed3 = source.version(gitHistory3.toString());
253+
Version hintParsed3 = source.version(hint3.toString());
254+
255+
assertEquals(
256+
0, hintParsed3.compareTo(gitHistoryParsed3), "Version hint 0.13.0 should equal git history 0.13.0");
257+
}
258+
259+
@Test
260+
void testIsVersionHintTag() throws Exception {
261+
Map<String, String> configMap = new HashMap<>();
262+
NisseConfiguration configuration =
263+
SimpleNisseConfiguration.builder().withUserProperties(configMap).build();
264+
265+
JGitPropertySource source = new JGitPropertySource();
266+
267+
// Test default pattern: ${version}-SNAPSHOT
268+
assertTrue(source.isVersionHintTag(configuration, "refs/tags/4.2.0-SNAPSHOT"));
269+
assertTrue(source.isVersionHintTag(configuration, "refs/tags/v4.2.0-SNAPSHOT"));
270+
assertTrue(source.isVersionHintTag(configuration, "refs/tags/1.0.0-SNAPSHOT"));
271+
272+
// These should NOT match the version hint pattern
273+
assertFalse(source.isVersionHintTag(configuration, "refs/tags/4.2.0"));
274+
assertFalse(source.isVersionHintTag(configuration, "refs/tags/v4.2.0"));
275+
assertFalse(source.isVersionHintTag(configuration, "refs/tags/release-4.2.0"));
276+
277+
// Test custom pattern
278+
configMap.put("nisse.source.jgit.versionHintPattern", "hint-${version}");
279+
NisseConfiguration customConfig =
280+
SimpleNisseConfiguration.builder().withUserProperties(configMap).build();
281+
assertTrue(source.isVersionHintTag(customConfig, "refs/tags/hint-3.1.0"));
282+
assertTrue(source.isVersionHintTag(customConfig, "refs/tags/vhint-3.1.0"));
283+
assertFalse(source.isVersionHintTag(customConfig, "refs/tags/3.1.0-SNAPSHOT"));
284+
}
219285
}

0 commit comments

Comments
 (0)