From 490162998bbdce7962ea84fb7103424917fef0da Mon Sep 17 00:00:00 2001
From: GabriRuflex <6223348+GabriRuflex@users.noreply.github.com>
Date: Wed, 1 Jul 2026 20:27:29 +0200
Subject: [PATCH 1/3] Add skipMergeCommits parameter to Git info command
The Git "info" command always passed --no-merges to git log, so the
reported revision could differ from the actual HEAD in merge-based
release workflows (regression surfaced in buildnumber-maven-plugin#229).
Introduce CommandParameter.SCM_SKIP_MERGE_COMMITS (default true, fully
backward compatible) to make this behavior configurable. When set to
false, merge commits are included so the reported revision matches HEAD.
Both providers honor the flag: gitexe adds --no-merges conditionally,
and the JGit provider now applies RevFilter.NO_MERGES (previously it
never filtered merge commits, diverging from gitexe).
Fixes #1327
---
.../apache/maven/scm/CommandParameter.java | 11 ++
.../gitexe/command/info/GitInfoCommand.java | 30 +++-
.../command/info/GitInfoCommandTest.java | 35 +++++
.../jgit/command/info/JGitInfoCommand.java | 54 +++++++-
.../command/info/JGitInfoCommandTest.java | 128 ++++++++++++++++++
5 files changed, 249 insertions(+), 9 deletions(-)
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java
diff --git a/maven-scm-api/src/main/java/org/apache/maven/scm/CommandParameter.java b/maven-scm-api/src/main/java/org/apache/maven/scm/CommandParameter.java
index 121bb7097..2c7d87354 100644
--- a/maven-scm-api/src/main/java/org/apache/maven/scm/CommandParameter.java
+++ b/maven-scm-api/src/main/java/org/apache/maven/scm/CommandParameter.java
@@ -79,6 +79,17 @@ public class CommandParameter implements Serializable {
*/
public static final CommandParameter SCM_SHORT_REVISION_LENGTH = new CommandParameter("shortRevisionLength");
+ /**
+ * Parameter used only for the Git SCM {@code info} command to control whether merge commits are skipped
+ * (adds --no-merges to the underlying git log invocation).
+ * Defaults to {@code true} (skip merge commits) to keep backward compatibility. Set to {@code false} to
+ * include merge commits, e.g. so that the reported revision matches the actual {@code HEAD} in merge-based
+ * release workflows.
+ *
+ * @since 2.2.2
+ */
+ public static final CommandParameter SCM_SKIP_MERGE_COMMITS = new CommandParameter("skipMergeCommits");
+
/**
* Parameter to force add.
*
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommand.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommand.java
index 08262db6b..dc2046287 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommand.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommand.java
@@ -47,13 +47,20 @@ public class GitInfoCommand extends AbstractCommand implements GitCommand {
public static final int NO_REVISION_LENGTH = -1;
+ /** Default value applied when the {@link CommandParameter#SCM_SKIP_MERGE_COMMITS} parameter is absent. */
+ public static final boolean DEFAULT_SKIP_MERGE_COMMITS = true;
+
@Override
protected ScmResult executeCommand(
ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) throws ScmException {
+ boolean skipMergeCommits = isSkipMergeCommits(parameters);
+
Commandline baseCli = GitCommandLineUtils.getBaseGitCommandLine(fileSet.getBasedir(), "log");
baseCli.createArg().setValue("-1"); // only most recent commit matters
- baseCli.createArg().setValue("--no-merges"); // skip merge commits
+ if (skipMergeCommits) {
+ baseCli.createArg().setValue("--no-merges"); // skip merge commits
+ }
baseCli.addArg(GitInfoConsumer.getFormatArgument());
List infoItems = new LinkedList<>();
@@ -64,7 +71,9 @@ protected ScmResult executeCommand(
for (File scmFile : fileSet.getFileList()) {
baseCli = GitCommandLineUtils.getBaseGitCommandLine(fileSet.getBasedir(), "log");
baseCli.createArg().setValue("-1"); // only most recent commit matters
- baseCli.createArg().setValue("--no-merges"); // skip merge commits
+ if (skipMergeCommits) {
+ baseCli.createArg().setValue("--no-merges"); // skip merge commits
+ }
baseCli.addArg(GitInfoConsumer.getFormatArgument());
// Insert a separator to make sure that files aren't interpreted as part of the version spec
baseCli.createArg().setValue("--");
@@ -102,4 +111,21 @@ private static int getRevisionLength(final CommandParameters parameters) throws
return parameters.getInt(CommandParameter.SCM_SHORT_REVISION_LENGTH, NO_REVISION_LENGTH);
}
}
+
+ /**
+ * Whether merge commits should be skipped (i.e. whether {@code --no-merges} should be added).
+ *
+ * @param parameters the command parameters
+ * @return {@link #DEFAULT_SKIP_MERGE_COMMITS} if parameter {@link CommandParameter#SCM_SKIP_MERGE_COMMITS}
+ * (or the whole {@code parameters}) is absent, and otherwise the requested value
+ * @throws ScmException if the parameter has the wrong type
+ * @since 2.2.2
+ */
+ private static boolean isSkipMergeCommits(final CommandParameters parameters) throws ScmException {
+ if (parameters == null) {
+ return DEFAULT_SKIP_MERGE_COMMITS;
+ } else {
+ return parameters.getBoolean(CommandParameter.SCM_SKIP_MERGE_COMMITS, DEFAULT_SKIP_MERGE_COMMITS);
+ }
+ }
}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java
index 9bbc2f305..85aa778f5 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java
@@ -32,6 +32,7 @@
import static org.apache.maven.scm.provider.git.GitScmTestUtils.GIT_COMMAND_LINE;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -92,6 +93,40 @@ void testInfoCommandWithNegativeShortRevision() throws Exception {
"revision should not be short");
}
+ @Test
+ void testInfoCommandSkipsMergeCommitsByDefault() throws Exception {
+ checkSystemCmdPresence(GIT_COMMAND_LINE);
+
+ GitScmTestUtils.initRepo("src/test/resources/git/info", getRepositoryRoot(), getWorkingCopy());
+
+ ScmProvider provider = getScmManager().getProviderByUrl(getScmUrl());
+ ScmProviderRepository repository = provider.makeProviderScmRepository(getRepositoryRoot());
+ assertNotNull(repository);
+ InfoScmResult result = provider.info(repository, new ScmFileSet(getRepositoryRoot()), new CommandParameters());
+ assertNotNull(result);
+ assertTrue(
+ result.getCommandLine().contains("--no-merges"),
+ "merge commits must be skipped by default (--no-merges present)");
+ }
+
+ @Test
+ void testInfoCommandIncludeMergeCommits() throws Exception {
+ checkSystemCmdPresence(GIT_COMMAND_LINE);
+
+ GitScmTestUtils.initRepo("src/test/resources/git/info", getRepositoryRoot(), getWorkingCopy());
+
+ ScmProvider provider = getScmManager().getProviderByUrl(getScmUrl());
+ ScmProviderRepository repository = provider.makeProviderScmRepository(getRepositoryRoot());
+ assertNotNull(repository);
+ CommandParameters commandParameters = new CommandParameters();
+ commandParameters.setString(CommandParameter.SCM_SKIP_MERGE_COMMITS, Boolean.FALSE.toString());
+ InfoScmResult result = provider.info(repository, new ScmFileSet(getRepositoryRoot()), commandParameters);
+ assertNotNull(result);
+ assertFalse(
+ result.getCommandLine().contains("--no-merges"),
+ "merge commits must be included when skipMergeCommits=false (no --no-merges)");
+ }
+
@Test
void testInfoCommandWithZeroShortRevision() throws Exception {
checkSystemCmdPresence(GIT_COMMAND_LINE);
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java
index 75582bb4d..3f2ae9223 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java
@@ -24,6 +24,7 @@
import java.util.List;
import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.scm.CommandParameter;
import org.apache.maven.scm.CommandParameters;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFileSet;
@@ -42,6 +43,7 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
@@ -54,6 +56,7 @@ public class JGitInfoCommand extends AbstractCommand implements GitCommand {
protected ScmResult executeCommand(
ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) throws ScmException {
File basedir = fileSet.getBasedir();
+ boolean skipMergeCommits = isSkipMergeCommits(parameters);
Git git = null;
try {
git = JGitUtils.openRepo(basedir);
@@ -64,13 +67,13 @@ protected ScmResult executeCommand(
List infoItems = new LinkedList<>();
if (fileSet.getFileList().isEmpty()) {
- RevCommit headCommit = git.getRepository().parseCommit(objectId);
- infoItems.add(getInfoItem(headCommit, fileSet.getBasedir()));
+ RevCommit commit = getMostRecentCommit(git.getRepository(), objectId, skipMergeCommits);
+ infoItems.add(getInfoItem(commit, fileSet.getBasedir()));
} else {
// iterate over all files
for (File file : JGitUtils.getWorkingCopyRelativePaths(
git.getRepository().getWorkTree(), fileSet)) {
- infoItems.add(getInfoItem(git.getRepository(), objectId, file));
+ infoItems.add(getInfoItem(git.getRepository(), objectId, file, skipMergeCommits));
}
}
return new InfoScmResult(infoItems, new ScmResult("JGit.resolve(HEAD)", "", objectId.toString(), true));
@@ -81,11 +84,30 @@ protected ScmResult executeCommand(
}
}
- protected InfoItem getInfoItem(Repository repository, ObjectId headObjectId, File file) throws IOException {
- RevCommit commit = getMostRecentCommitForPath(repository, headObjectId, JGitUtils.toNormalizedFilePath(file));
+ protected InfoItem getInfoItem(Repository repository, ObjectId headObjectId, File file, boolean skipMergeCommits)
+ throws IOException {
+ RevCommit commit = getMostRecentCommitForPath(
+ repository, headObjectId, JGitUtils.toNormalizedFilePath(file), skipMergeCommits);
return getInfoItem(commit, file);
}
+ /**
+ * Returns the most recent commit reachable from {@code headObjectId}, optionally ignoring merge commits
+ * (mimics {@code git log -1 --no-merges} when {@code skipMergeCommits} is {@code true}).
+ */
+ private RevCommit getMostRecentCommit(Repository repository, ObjectId headObjectId, boolean skipMergeCommits)
+ throws IOException {
+ try (RevWalk revWalk = new RevWalk(repository)) {
+ RevCommit headCommit = revWalk.parseCommit(headObjectId);
+ if (!skipMergeCommits) {
+ return headCommit;
+ }
+ revWalk.markStart(headCommit);
+ revWalk.setRevFilter(RevFilter.NO_MERGES);
+ return revWalk.next();
+ }
+ }
+
protected InfoItem getInfoItem(RevCommit fileCommit, File file) {
InfoItem infoItem = new InfoItem();
infoItem.setPath(file.getPath());
@@ -100,16 +122,34 @@ protected InfoItem getInfoItem(RevCommit fileCommit, File file) {
return infoItem;
}
- private RevCommit getMostRecentCommitForPath(Repository repository, ObjectId headObjectId, String path)
- throws IOException {
+ private RevCommit getMostRecentCommitForPath(
+ Repository repository, ObjectId headObjectId, String path, boolean skipMergeCommits) throws IOException {
RevCommit latestCommit = null;
try (RevWalk revWalk = new RevWalk(repository)) {
RevCommit headCommit = revWalk.parseCommit(headObjectId);
revWalk.markStart(headCommit);
revWalk.sort(RevSort.COMMIT_TIME_DESC);
+ if (skipMergeCommits) {
+ revWalk.setRevFilter(RevFilter.NO_MERGES);
+ }
revWalk.setTreeFilter(AndTreeFilter.create(PathFilter.create(path), TreeFilter.ANY_DIFF));
latestCommit = revWalk.next();
}
return latestCommit;
}
+
+ /**
+ * Whether merge commits should be skipped for the {@code info} command.
+ *
+ * @param parameters the command parameters (may be {@code null})
+ * @return {@code true} if parameter {@link CommandParameter#SCM_SKIP_MERGE_COMMITS} (or the whole
+ * {@code parameters}) is absent, and otherwise the requested value
+ * @throws ScmException if the parameter has the wrong type
+ */
+ private static boolean isSkipMergeCommits(CommandParameters parameters) throws ScmException {
+ if (parameters == null) {
+ return true;
+ }
+ return parameters.getBoolean(CommandParameter.SCM_SKIP_MERGE_COMMITS, true);
+ }
}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java
new file mode 100644
index 000000000..b65d99164
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.scm.provider.git.jgit.command.info;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+import org.apache.maven.scm.CommandParameter;
+import org.apache.maven.scm.CommandParameters;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.info.InfoScmResult;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeCommand;
+import org.eclipse.jgit.api.MergeResult;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Unit tests for the {@code skipMergeCommits} handling of {@link JGitInfoCommand}.
+ * A repository whose {@code HEAD} is a merge commit is built with the JGit API, then the
+ * {@code info} command is invoked with and without the {@link CommandParameter#SCM_SKIP_MERGE_COMMITS} flag.
+ */
+class JGitInfoCommandTest {
+
+ @TempDir
+ File workDir;
+
+ @Test
+ void includesMergeCommitWhenSkipMergeCommitsIsFalse() throws Exception {
+ ObjectId mergeCommit = buildRepositoryWithMergeHead();
+
+ CommandParameters parameters = new CommandParameters();
+ parameters.setString(CommandParameter.SCM_SKIP_MERGE_COMMITS, Boolean.FALSE.toString());
+
+ InfoScmResult result = info(parameters);
+
+ assertNotNull(result);
+ assertEquals(
+ mergeCommit.getName(),
+ result.getInfoItems().get(0).getRevision(),
+ "HEAD merge commit must be reported when skipMergeCommits=false");
+ }
+
+ @Test
+ void skipsMergeCommitByDefault() throws Exception {
+ ObjectId mergeCommit = buildRepositoryWithMergeHead();
+
+ InfoScmResult result = info(new CommandParameters());
+
+ assertNotNull(result);
+ assertNotEquals(
+ mergeCommit.getName(),
+ result.getInfoItems().get(0).getRevision(),
+ "merge commit must be skipped by default, a non-merge commit must be reported");
+ }
+
+ private InfoScmResult info(CommandParameters parameters) throws Exception {
+ // executeCommand is package-private accessible and ignores the repository argument
+ return (InfoScmResult) new JGitInfoCommand().executeCommand(null, new ScmFileSet(workDir), parameters);
+ }
+
+ /**
+ * Builds a repository whose {@code HEAD} is a no-fast-forward merge commit (two parents).
+ *
+ * @return the id of the merge commit which is now {@code HEAD}
+ */
+ private ObjectId buildRepositoryWithMergeHead() throws Exception {
+ try (Git git = Git.init().setDirectory(workDir).call()) {
+ StoredConfig config = git.getRepository().getConfig();
+ config.setString("user", null, "name", "Test User");
+ config.setString("user", null, "email", "test@example.com");
+ config.setBoolean("commit", null, "gpgsign", false);
+ config.save();
+
+ commit(git, "a.txt");
+ String mainBranch = git.getRepository().getBranch();
+
+ git.checkout().setCreateBranch(true).setName("feature").call();
+ commit(git, "b.txt");
+
+ git.checkout().setName(mainBranch).call();
+ commit(git, "c.txt");
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve("feature"))
+ .setFastForward(MergeCommand.FastForwardMode.NO_FF)
+ .setMessage("merge feature")
+ .call();
+ return mergeResult.getNewHead();
+ }
+ }
+
+ private RevCommit commit(Git git, String fileName) throws Exception {
+ File file = new File(git.getRepository().getWorkTree(), fileName);
+ Files.write(file.toPath(), ("content of " + fileName).getBytes(StandardCharsets.UTF_8));
+ git.add().addFilepattern(fileName).call();
+ return git.commit()
+ .setMessage("add " + fileName)
+ .setAuthor("Test User", "test@example.com")
+ .setCommitter("Test User", "test@example.com")
+ .setSign(false)
+ .call();
+ }
+}
From 379540b455bf087ee91a617e20a98949c6985d79 Mon Sep 17 00:00:00 2001
From: GabriRuflex <6223348+GabriRuflex@users.noreply.github.com>
Date: Wed, 8 Jul 2026 11:15:47 +0200
Subject: [PATCH 2/3] Address Copilot review: deterministic walk order and
null-safety in JGitInfoCommand
---
.../git/jgit/command/info/JGitInfoCommand.java | 9 ++++++++-
.../git/jgit/command/info/JGitInfoCommandTest.java | 10 +++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java
index 3f2ae9223..cb892a660 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java
@@ -94,6 +94,7 @@ protected InfoItem getInfoItem(Repository repository, ObjectId headObjectId, Fil
/**
* Returns the most recent commit reachable from {@code headObjectId}, optionally ignoring merge commits
* (mimics {@code git log -1 --no-merges} when {@code skipMergeCommits} is {@code true}).
+ * May return {@code null} when no non-merge commit is reachable (e.g. shallow history of merge commits only).
*/
private RevCommit getMostRecentCommit(Repository repository, ObjectId headObjectId, boolean skipMergeCommits)
throws IOException {
@@ -103,6 +104,7 @@ private RevCommit getMostRecentCommit(Repository repository, ObjectId headObject
return headCommit;
}
revWalk.markStart(headCommit);
+ revWalk.sort(RevSort.COMMIT_TIME_DESC);
revWalk.setRevFilter(RevFilter.NO_MERGES);
return revWalk.next();
}
@@ -111,8 +113,13 @@ private RevCommit getMostRecentCommit(Repository repository, ObjectId headObject
protected InfoItem getInfoItem(RevCommit fileCommit, File file) {
InfoItem infoItem = new InfoItem();
infoItem.setPath(file.getPath());
- infoItem.setRevision(StringUtils.trim(fileCommit.name()));
infoItem.setURL(file.toPath().toUri().toASCIIString());
+ if (fileCommit == null) {
+ // no matching commit (e.g. path without history, or only merge commits while skipping them):
+ // like gitexe on empty "git log" output, report the item without revision metadata
+ return infoItem;
+ }
+ infoItem.setRevision(StringUtils.trim(fileCommit.name()));
PersonIdent authorIdent = fileCommit.getAuthorIdent();
infoItem.setLastChangedDateTime(authorIdent
.getWhen()
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java
index b65d99164..c64a0c8b6 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java
@@ -38,6 +38,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Unit tests for the {@code skipMergeCommits} handling of {@link JGitInfoCommand}.
@@ -59,6 +60,7 @@ void includesMergeCommitWhenSkipMergeCommitsIsFalse() throws Exception {
InfoScmResult result = info(parameters);
assertNotNull(result);
+ assertEquals(1, result.getInfoItems().size(), "exactly one info item is expected");
assertEquals(
mergeCommit.getName(),
result.getInfoItems().get(0).getRevision(),
@@ -72,6 +74,7 @@ void skipsMergeCommitByDefault() throws Exception {
InfoScmResult result = info(new CommandParameters());
assertNotNull(result);
+ assertEquals(1, result.getInfoItems().size(), "exactly one info item is expected");
assertNotEquals(
mergeCommit.getName(),
result.getInfoItems().get(0).getRevision(),
@@ -110,7 +113,12 @@ private ObjectId buildRepositoryWithMergeHead() throws Exception {
.setFastForward(MergeCommand.FastForwardMode.NO_FF)
.setMessage("merge feature")
.call();
- return mergeResult.getNewHead();
+ assertTrue(
+ mergeResult.getMergeStatus().isSuccessful(),
+ "merge must succeed but was: " + mergeResult.getMergeStatus());
+ ObjectId mergeHead = mergeResult.getNewHead();
+ assertNotNull(mergeHead, "merge must produce a new HEAD commit");
+ return mergeHead;
}
}
From ab81a46f043e1b201705455784abf0055027d161 Mon Sep 17 00:00:00 2001
From: GabriRuflex <6223348+GabriRuflex@users.noreply.github.com>
Date: Sun, 19 Jul 2026 14:30:23 +0200
Subject: [PATCH 3/3] Move skipMergeCommits tests to shared
GitInfoCommandTckTest
---
.../command/info/GitInfoCommandTest.java | 35 -----
.../maven-scm-provider-gittest/pom.xml | 6 +
.../command/info/GitInfoCommandTckTest.java | 110 ++++++++++++++
.../maven-scm-provider-jgit/pom.xml | 5 -
.../command/info/JGitInfoCommandTest.java | 136 ------------------
.../maven-scm-providers-git/pom.xml | 2 +
6 files changed, 118 insertions(+), 176 deletions(-)
delete mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java
index 85aa778f5..9bbc2f305 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java
@@ -32,7 +32,6 @@
import static org.apache.maven.scm.provider.git.GitScmTestUtils.GIT_COMMAND_LINE;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -93,40 +92,6 @@ void testInfoCommandWithNegativeShortRevision() throws Exception {
"revision should not be short");
}
- @Test
- void testInfoCommandSkipsMergeCommitsByDefault() throws Exception {
- checkSystemCmdPresence(GIT_COMMAND_LINE);
-
- GitScmTestUtils.initRepo("src/test/resources/git/info", getRepositoryRoot(), getWorkingCopy());
-
- ScmProvider provider = getScmManager().getProviderByUrl(getScmUrl());
- ScmProviderRepository repository = provider.makeProviderScmRepository(getRepositoryRoot());
- assertNotNull(repository);
- InfoScmResult result = provider.info(repository, new ScmFileSet(getRepositoryRoot()), new CommandParameters());
- assertNotNull(result);
- assertTrue(
- result.getCommandLine().contains("--no-merges"),
- "merge commits must be skipped by default (--no-merges present)");
- }
-
- @Test
- void testInfoCommandIncludeMergeCommits() throws Exception {
- checkSystemCmdPresence(GIT_COMMAND_LINE);
-
- GitScmTestUtils.initRepo("src/test/resources/git/info", getRepositoryRoot(), getWorkingCopy());
-
- ScmProvider provider = getScmManager().getProviderByUrl(getScmUrl());
- ScmProviderRepository repository = provider.makeProviderScmRepository(getRepositoryRoot());
- assertNotNull(repository);
- CommandParameters commandParameters = new CommandParameters();
- commandParameters.setString(CommandParameter.SCM_SKIP_MERGE_COMMITS, Boolean.FALSE.toString());
- InfoScmResult result = provider.info(repository, new ScmFileSet(getRepositoryRoot()), commandParameters);
- assertNotNull(result);
- assertFalse(
- result.getCommandLine().contains("--no-merges"),
- "merge commits must be included when skipMergeCommits=false (no --no-merges)");
- }
-
@Test
void testInfoCommandWithZeroShortRevision() throws Exception {
checkSystemCmdPresence(GIT_COMMAND_LINE);
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml
index ee2938a3d..a5343a445 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml
@@ -44,6 +44,12 @@
org.codehaus.plexus
plexus-utils
+
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+ ${jgitVersion}
+
org.apache.sshd
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/command/info/GitInfoCommandTckTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/command/info/GitInfoCommandTckTest.java
index fa0a91570..cd6bb1b19 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/command/info/GitInfoCommandTckTest.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/command/info/GitInfoCommandTckTest.java
@@ -18,8 +18,29 @@
*/
package org.apache.maven.scm.provider.git.command.info;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Date;
+
+import org.apache.maven.scm.CommandParameter;
+import org.apache.maven.scm.CommandParameters;
+import org.apache.maven.scm.command.info.InfoScmResult;
+import org.apache.maven.scm.provider.ScmProvider;
import org.apache.maven.scm.provider.git.GitScmTestUtils;
import org.apache.maven.scm.tck.command.info.InfoCommandTckTest;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeCommand;
+import org.eclipse.jgit.api.MergeResult;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Mark Struberg
@@ -31,4 +52,93 @@ public abstract class GitInfoCommandTckTest extends InfoCommandTckTest {
public void initRepo() throws Exception {
GitScmTestUtils.initRepo("src/test/resources/repository/", getRepositoryRoot(), getWorkingDirectory());
}
+
+ @Test
+ void testInfoCommandSkipsMergeCommitsByDefault() throws Exception {
+ MergeScenario scenario = createMergeCommitInWorkingCopy();
+ ScmProvider scmProvider = getScmManager().getProviderByUrl(getScmUrl());
+ InfoScmResult result = scmProvider.info(getScmRepository().getProviderRepository(), getScmFileSet(), null);
+ assertResultIsSuccess(result);
+ assertEquals(1, result.getInfoItems().size());
+ assertEquals(
+ scenario.lastNonMergeCommit.getName(),
+ result.getInfoItems().get(0).getRevision(),
+ "the most recent non-merge commit must be reported by default");
+ }
+
+ @Test
+ void testInfoCommandIncludesMergeCommitsOnDemand() throws Exception {
+ MergeScenario scenario = createMergeCommitInWorkingCopy();
+ ScmProvider scmProvider = getScmManager().getProviderByUrl(getScmUrl());
+ CommandParameters parameters = new CommandParameters();
+ parameters.setString(CommandParameter.SCM_SKIP_MERGE_COMMITS, Boolean.FALSE.toString());
+ InfoScmResult result =
+ scmProvider.info(getScmRepository().getProviderRepository(), getScmFileSet(), parameters);
+ assertResultIsSuccess(result);
+ assertEquals(1, result.getInfoItems().size());
+ assertEquals(
+ scenario.mergeCommit.getName(),
+ result.getInfoItems().get(0).getRevision(),
+ "the merge commit at HEAD must be reported when skipMergeCommits=false");
+ }
+
+ /**
+ * Turns the working copy's {@code HEAD} into a no-fast-forward merge commit (two parents):
+ * commits once on a side branch and once on the default branch, then merges the side branch.
+ * Distinct commit times make "the most recent non-merge commit" unambiguous.
+ */
+ private MergeScenario createMergeCommitInWorkingCopy() throws Exception {
+ try (Git git = Git.open(getWorkingCopy())) {
+ StoredConfig config = git.getRepository().getConfig();
+ config.setString("user", null, "name", "Test User");
+ config.setString("user", null, "email", "test@example.com");
+ config.setBoolean("commit", null, "gpgsign", false);
+ config.save();
+
+ Date sideBranchTime = new Date(System.currentTimeMillis() - 120_000L);
+ Date defaultBranchTime = new Date(sideBranchTime.getTime() + 60_000L);
+
+ String defaultBranch = git.getRepository().getBranch();
+ git.checkout().setCreateBranch(true).setName("side-branch").call();
+ commitNewFile(git, "side.txt", sideBranchTime);
+
+ git.checkout().setName(defaultBranch).call();
+ RevCommit lastNonMergeCommit = commitNewFile(git, "default.txt", defaultBranchTime);
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve("side-branch"))
+ .setFastForward(MergeCommand.FastForwardMode.NO_FF)
+ .setMessage("merge side-branch")
+ .call();
+ assertTrue(
+ mergeResult.getMergeStatus().isSuccessful(),
+ "merge must succeed but was: " + mergeResult.getMergeStatus());
+ ObjectId mergeCommit = mergeResult.getNewHead();
+ assertNotNull(mergeCommit, "merge must produce a new HEAD commit");
+ return new MergeScenario(lastNonMergeCommit, mergeCommit);
+ }
+ }
+
+ private RevCommit commitNewFile(Git git, String fileName, Date commitTime) throws Exception {
+ File file = new File(git.getRepository().getWorkTree(), fileName);
+ Files.write(file.toPath(), ("content of " + fileName).getBytes(StandardCharsets.UTF_8));
+ git.add().addFilepattern(fileName).call();
+ PersonIdent ident = new PersonIdent("Test User", "test@example.com", commitTime, GMT_TIME_ZONE);
+ return git.commit()
+ .setMessage("add " + fileName)
+ .setAuthor(ident)
+ .setCommitter(ident)
+ .setSign(false)
+ .call();
+ }
+
+ private static final class MergeScenario {
+ private final RevCommit lastNonMergeCommit;
+ private final ObjectId mergeCommit;
+
+ private MergeScenario(RevCommit lastNonMergeCommit, ObjectId mergeCommit) {
+ this.lastNonMergeCommit = lastNonMergeCommit;
+ this.mergeCommit = mergeCommit;
+ }
+ }
}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/pom.xml b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/pom.xml
index d1c8c8b6d..209dc369a 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/pom.xml
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/pom.xml
@@ -31,11 +31,6 @@
JGIT implementation for SCM Git Provider.
see http://eclipse.org/jgit/
-
-
- 5.13.5.202508271544-r
-
-
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java
deleted file mode 100644
index c64a0c8b6..000000000
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.maven.scm.provider.git.jgit.command.info;
-
-import java.io.File;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-
-import org.apache.maven.scm.CommandParameter;
-import org.apache.maven.scm.CommandParameters;
-import org.apache.maven.scm.ScmFileSet;
-import org.apache.maven.scm.command.info.InfoScmResult;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.MergeCommand;
-import org.eclipse.jgit.api.MergeResult;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * Unit tests for the {@code skipMergeCommits} handling of {@link JGitInfoCommand}.
- * A repository whose {@code HEAD} is a merge commit is built with the JGit API, then the
- * {@code info} command is invoked with and without the {@link CommandParameter#SCM_SKIP_MERGE_COMMITS} flag.
- */
-class JGitInfoCommandTest {
-
- @TempDir
- File workDir;
-
- @Test
- void includesMergeCommitWhenSkipMergeCommitsIsFalse() throws Exception {
- ObjectId mergeCommit = buildRepositoryWithMergeHead();
-
- CommandParameters parameters = new CommandParameters();
- parameters.setString(CommandParameter.SCM_SKIP_MERGE_COMMITS, Boolean.FALSE.toString());
-
- InfoScmResult result = info(parameters);
-
- assertNotNull(result);
- assertEquals(1, result.getInfoItems().size(), "exactly one info item is expected");
- assertEquals(
- mergeCommit.getName(),
- result.getInfoItems().get(0).getRevision(),
- "HEAD merge commit must be reported when skipMergeCommits=false");
- }
-
- @Test
- void skipsMergeCommitByDefault() throws Exception {
- ObjectId mergeCommit = buildRepositoryWithMergeHead();
-
- InfoScmResult result = info(new CommandParameters());
-
- assertNotNull(result);
- assertEquals(1, result.getInfoItems().size(), "exactly one info item is expected");
- assertNotEquals(
- mergeCommit.getName(),
- result.getInfoItems().get(0).getRevision(),
- "merge commit must be skipped by default, a non-merge commit must be reported");
- }
-
- private InfoScmResult info(CommandParameters parameters) throws Exception {
- // executeCommand is package-private accessible and ignores the repository argument
- return (InfoScmResult) new JGitInfoCommand().executeCommand(null, new ScmFileSet(workDir), parameters);
- }
-
- /**
- * Builds a repository whose {@code HEAD} is a no-fast-forward merge commit (two parents).
- *
- * @return the id of the merge commit which is now {@code HEAD}
- */
- private ObjectId buildRepositoryWithMergeHead() throws Exception {
- try (Git git = Git.init().setDirectory(workDir).call()) {
- StoredConfig config = git.getRepository().getConfig();
- config.setString("user", null, "name", "Test User");
- config.setString("user", null, "email", "test@example.com");
- config.setBoolean("commit", null, "gpgsign", false);
- config.save();
-
- commit(git, "a.txt");
- String mainBranch = git.getRepository().getBranch();
-
- git.checkout().setCreateBranch(true).setName("feature").call();
- commit(git, "b.txt");
-
- git.checkout().setName(mainBranch).call();
- commit(git, "c.txt");
-
- MergeResult mergeResult = git.merge()
- .include(git.getRepository().resolve("feature"))
- .setFastForward(MergeCommand.FastForwardMode.NO_FF)
- .setMessage("merge feature")
- .call();
- assertTrue(
- mergeResult.getMergeStatus().isSuccessful(),
- "merge must succeed but was: " + mergeResult.getMergeStatus());
- ObjectId mergeHead = mergeResult.getNewHead();
- assertNotNull(mergeHead, "merge must produce a new HEAD commit");
- return mergeHead;
- }
- }
-
- private RevCommit commit(Git git, String fileName) throws Exception {
- File file = new File(git.getRepository().getWorkTree(), fileName);
- Files.write(file.toPath(), ("content of " + fileName).getBytes(StandardCharsets.UTF_8));
- git.add().addFilepattern(fileName).call();
- return git.commit()
- .setMessage("add " + fileName)
- .setAuthor("Test User", "test@example.com")
- .setCommitter("Test User", "test@example.com")
- .setSign(false)
- .call();
- }
-}
diff --git a/maven-scm-providers/maven-scm-providers-git/pom.xml b/maven-scm-providers/maven-scm-providers-git/pom.xml
index d5e7733e4..81e8653b4 100644
--- a/maven-scm-providers/maven-scm-providers-git/pom.xml
+++ b/maven-scm-providers/maven-scm-providers-git/pom.xml
@@ -41,6 +41,8 @@
2.19.0
+
+ 5.13.5.202508271544-r