diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/ChangelogCommand.java b/src/main/java/org/jenkinsci/plugins/gitclient/ChangelogCommand.java index d7c81ced71..bfcc01ea82 100644 --- a/src/main/java/org/jenkinsci/plugins/gitclient/ChangelogCommand.java +++ b/src/main/java/org/jenkinsci/plugins/gitclient/ChangelogCommand.java @@ -108,6 +108,14 @@ public interface ChangelogCommand extends GitCommand { */ ChangelogCommand max(int n); + /** + * Include merge commits in the changelog. + * + * @param include a boolean. + * @return a {@link org.jenkinsci.plugins.gitclient.ChangelogCommand} object. + */ + ChangelogCommand includeMergeCommits(boolean include); + /** * Abort this ChangelogCommand without executing it, close any * open resources. The JGit implementation of changelog diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java b/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java index e3787eac94..573e465951 100644 --- a/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java +++ b/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java @@ -1249,15 +1249,10 @@ public void prune(RemoteConfig repository) throws GitException, InterruptedExcep @Override public ChangelogCommand changelog() { return new ChangelogCommand() { - - /** Equivalent to the git-log raw format but using ISO 8601 date format - also prevent to depend on git CLI future changes */ - public static final String RAW = - "commit %H%ntree %T%nparent %P%nauthor %aN <%aE> %ai%ncommitter %cN <%cE> %ci%n%n%w(0,4,4)%B"; - private final List revs = new ArrayList<>(); - private Integer n = null; private Writer out = null; + private boolean includeMergeCommits = false; @Override public ChangelogCommand excludes(String rev) { @@ -1293,6 +1288,12 @@ public ChangelogCommand max(int n) { return this; } + @Override + public ChangelogCommand includeMergeCommits(boolean include) { + this.includeMergeCommits = include; + return this; + } + @Override public void abort() { /* No cleanup needed to abort the CliGitAPIImpl ChangelogCommand */ @@ -1301,16 +1302,13 @@ public void abort() { @Override public void execute() throws GitException, InterruptedException { ArgumentListBuilder args = - new ArgumentListBuilder(gitExe, "log", "--raw", "--no-merges", "--no-abbrev", "-M"); - if (isAtLeastVersion(1, 8, 3, 0)) { - args.add("--format=" + RAW); - } else { - /* Ancient git versions don't support the required format string, use 'raw' and hope it works */ - args.add("--format=raw"); - } + new ArgumentListBuilder(gitExe, "whatchanged", "--no-abbrev", "-M", "--format=raw"); if (n != null) { args.add("-n").add(n); } + if (includeMergeCommits) { + args.add("-m"); + } for (String rev : this.revs) { args.add(rev); } diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java b/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java index 826616b2d2..0c4df334f1 100644 --- a/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java +++ b/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java @@ -63,7 +63,6 @@ import jenkins.util.SystemProperties; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.SystemUtils; -import org.apache.commons.lang3.time.FastDateFormat; import org.apache.sshd.common.util.security.SecurityUtils; import org.eclipse.jgit.api.AddNoteCommand; import org.eclipse.jgit.api.CommitCommand; @@ -118,6 +117,7 @@ import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.filter.AndRevFilter; import org.eclipse.jgit.revwalk.filter.MaxCountRevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; @@ -1348,6 +1348,8 @@ public ChangelogCommand changelog() throws GitException { private RevWalk walk = new RevWalk(or); private Writer out; private boolean hasIncludedRev = false; + private boolean includeMergeCommits = false; + private Integer maxCount = null; @Override public ChangelogCommand excludes(String rev) throws GitException { @@ -1398,7 +1400,13 @@ public ChangelogCommand to(Writer w) { @Override public ChangelogCommand max(int n) { - walk.setRevFilter(MaxCountRevFilter.create(n)); + this.maxCount = n; + return this; + } + + @Override + public ChangelogCommand includeMergeCommits(boolean include) { + this.includeMergeCommits = include; return this; } @@ -1425,6 +1433,21 @@ public void execute() throws GitException { if (out == null) { throw new IllegalStateException(); // Match CliGitAPIImpl } + List filters = new ArrayList<>(); + + if (!includeMergeCommits) { + filters.add(RevFilter.NO_MERGES); + } + if (maxCount != null) { + filters.add(MaxCountRevFilter.create(maxCount)); + } + if (filters.isEmpty()) { + walk.setRevFilter(RevFilter.ALL); + } else if (filters.size() == 1) { + walk.setRevFilter(filters.get(0)); + } else { + walk.setRevFilter(AndRevFilter.create(filters)); + } try (PrintWriter pw = new PrintWriter(out, false)) { RawFormatter formatter = new RawFormatter(); if (!hasIncludedRev) { @@ -1432,11 +1455,6 @@ public void execute() throws GitException { this.includes("HEAD"); } for (RevCommit commit : walk) { - // git log --raw --no-merges doesn't show merge commits - if (commit.getParentCount() > 1) { - continue; - } - formatter.format(commit, null, pw, true); } } catch (IOException e) { @@ -1474,8 +1492,6 @@ private String statusOf(DiffEntry d) { } } - public static final String ISO_8601 = "yyyy-MM-dd'T'HH:mm:ssZ"; - /** * Formats a commit into the raw format. * @@ -1500,11 +1516,8 @@ void format(RevCommit commit, RevCommit parent, PrintWriter pw, Boolean useRawOu for (RevCommit p : commit.getParents()) { pw.printf("parent %s\n", p.name()); } - FastDateFormat iso = FastDateFormat.getInstance(ISO_8601); - PersonIdent a = commit.getAuthorIdent(); - pw.printf("author %s <%s> %s\n", a.getName(), a.getEmailAddress(), iso.format(a.getWhen())); - PersonIdent c = commit.getCommitterIdent(); - pw.printf("committer %s <%s> %s\n", c.getName(), c.getEmailAddress(), iso.format(c.getWhen())); + pw.printf("author %s\n", commit.getAuthorIdent().toExternalString()); + pw.printf("committer %s\n", commit.getCommitterIdent().toExternalString()); // indent commit messages by 4 chars String msg = commit.getFullMessage(); diff --git a/src/test/java/org/jenkinsci/plugins/gitclient/GitAPITestUpdate.java b/src/test/java/org/jenkinsci/plugins/gitclient/GitAPITestUpdate.java index 006f47d50d..c929e48a0d 100644 --- a/src/test/java/org/jenkinsci/plugins/gitclient/GitAPITestUpdate.java +++ b/src/test/java/org/jenkinsci/plugins/gitclient/GitAPITestUpdate.java @@ -2038,11 +2038,8 @@ public void testChangelogWithMergeCommitAndMaxLogHistory() throws Exception { .setRevisionToMerge(w.git.getHeadRev(w.repoPath(), "branch-1")) .execute(); - /* JGit handles merge commits in changelog differently than CLI git. See JENKINS-40023. */ - int maxlimit = w.git instanceof CliGitAPIImpl ? 1 : 2; - StringWriter writer = new StringWriter(); - w.git.changelog().max(maxlimit).to(writer).execute(); + w.git.changelog().max(1).to(writer).execute(); assertThat(writer.toString(), is(not(""))); } diff --git a/src/test/java/org/jenkinsci/plugins/gitclient/GitClientTest.java b/src/test/java/org/jenkinsci/plugins/gitclient/GitClientTest.java index 51997bcdcf..afad77617d 100644 --- a/src/test/java/org/jenkinsci/plugins/gitclient/GitClientTest.java +++ b/src/test/java/org/jenkinsci/plugins/gitclient/GitClientTest.java @@ -3023,6 +3023,125 @@ public void testChangelogFirstLineTruncation() throws Exception { assertThat(changelogStringWriter.toString(), containsString(padLinesWithSpaces(commitMessage, 4))); } + /** + * Tests changelog with merge commits from branches with linear histories. + */ + @Test + public void testChangelogWithMergeCommitsAndLinearHistory() throws Exception { + final String branch1 = "changelog-branch-1"; + final String branch2 = "changelog-branch-2"; + final String mergeMessage = "Merge " + branch2 + " into " + branch1; + + // Create initial commit + ObjectId initialCommit = commitOneFile(); + + // Create first branch with a commit + gitClient.branch(branch1); + gitClient.checkout().ref(branch1).execute(); + ObjectId branch1Commit = commitFile("file-1", "content-1", branch1 + "-commit"); + + // Create second branch with a commit + gitClient.branch(branch2); + gitClient.checkout().ref(branch2).execute(); + ObjectId branch2Commit = commitFile("file-2", "content-2", branch2 + "-commit"); + + // Merge branch2 into branch1 with merge commit + gitClient.checkout().ref(branch1).execute(); + MergeCommand mergeCommand = gitClient.merge(); + mergeCommand.setMessage(mergeMessage); + mergeCommand.setGitPluginFastForwardMode(MergeCommand.GitPluginFastForwardMode.NO_FF); + mergeCommand.setRevisionToMerge(gitClient.getHeadRev(repoRoot.getAbsolutePath(), branch2)); + mergeCommand.execute(); + + // Get changelog with merge commits included + StringWriter writer = new StringWriter(); + gitClient.changelog().includeMergeCommits(true).to(writer).execute(); + String changelog = writer.toString(); + + assertThat( + changelog, containsString("commit " + gitClient.revParse("HEAD").name())); + assertThat(changelog, containsString(mergeMessage)); + assertThat(changelog, containsString("commit " + branch2Commit.name())); + assertThat(changelog, containsString("parent " + branch1Commit.name())); + assertThat(changelog, containsString(branch1 + "-commit")); + assertThat(changelog, containsString(branch2 + "-commit")); + + // Get changelog with merge commits excluded + writer = new StringWriter(); + gitClient.changelog().includeMergeCommits(false).to(writer).execute(); + changelog = writer.toString(); + + assertThat( + changelog, + not(containsString("commit " + gitClient.revParse("HEAD").name()))); + assertThat(changelog, not(containsString(mergeMessage))); + assertThat(changelog, containsString("commit " + branch2Commit.name())); + assertThat(changelog, containsString("parent " + branch1Commit.name())); + assertThat(changelog, containsString(branch1 + "-commit")); + assertThat(changelog, containsString(branch2 + "-commit")); + } + + /** + * Tests changelog with merge commits from branches with divergent histories. + */ + @Test + public void testChangelogWithMergeCommitsAndDivergentHistory() throws Exception { + final String branch1 = "changelog-div-1"; + final String branch2 = "changelog-div-2"; + final String mergeMessage = "Merge " + branch2 + " into " + branch1; + + ObjectId initialCommit = commitOneFile(); + + // Create first branch with a commit + gitClient.branch(branch1); + gitClient.checkout().ref(branch1).execute(); + ObjectId branch1Commit = commitFile("file-1", "content-1", branch1 + "-commit"); + + // Create second branch from branch1 and add a commit + gitClient.branch(branch2); + gitClient.checkout().ref(branch2).execute(); + ObjectId branch2Commit = commitFile("file-2", "content-2", branch2 + "-commit"); + + // Add more commits to branch1 + gitClient.checkout().ref(branch1).execute(); + commitFile("file-3", "content-3", branch1 + "-commit"); + ObjectId branch1FinalCommit = commitFile("file-4", "content-4", branch1 + "-commit"); + + // Merge branch2 into branch1 + MergeCommand mergeCommand = gitClient.merge(); + mergeCommand.setMessage(mergeMessage); + mergeCommand.setGitPluginFastForwardMode(MergeCommand.GitPluginFastForwardMode.NO_FF); + mergeCommand.setRevisionToMerge(gitClient.getHeadRev(repoRoot.getAbsolutePath(), branch2)); + mergeCommand.execute(); + ObjectId mergeCommit = gitClient.revParse("HEAD"); + + // Get changelog with merge commits included + StringWriter writer = new StringWriter(); + gitClient.changelog().includeMergeCommits(true).to(writer).execute(); + String changelog = writer.toString(); + + assertThat(changelog, containsString("commit " + mergeCommit.name())); + assertThat(changelog, containsString(mergeMessage)); + assertThat(changelog, containsString("parent " + branch1FinalCommit.name())); + assertThat(changelog, containsString("parent " + branch2Commit.name())); + assertThat(changelog, containsString(branch1 + "-commit")); + assertThat(changelog, containsString(branch2 + "-commit")); + + // Get changelog with merge commits excluded + writer = new StringWriter(); + gitClient.changelog().includeMergeCommits(false).to(writer).execute(); + changelog = writer.toString(); + + assertThat(changelog, not(containsString("commit " + mergeCommit.name()))); + assertThat(changelog, not(containsString(mergeMessage))); + assertThat(changelog, not(containsString("parent " + branch1FinalCommit.name()))); + assertThat(changelog, not(containsString("parent " + branch2Commit.name()))); + assertThat(changelog, containsString("commit " + branch1FinalCommit.name())); + assertThat(changelog, containsString("parent " + branch1Commit.name())); + assertThat(changelog, containsString(branch1 + "-commit")); + assertThat(changelog, containsString(branch2 + "-commit")); + } + @Test @Issue("JENKINS-55284") // Pull must overwrite existing tags public void testFetchOverwritesExistingTags() throws Exception {