diff --git a/README.adoc b/README.adoc index 9ea6d9b6ae..b35fec2fe9 100644 --- a/README.adoc +++ b/README.adoc @@ -1220,6 +1220,23 @@ Inverse:: This is useful, for example, when you have jobs building your master and various release branches and you want a second job which builds all new feature branches. For example, branches which do not match these patterns without redundantly building master and the release branches again each time they change. +[#first-build-changelog] +==== First build changelog + +image:/images/git-extension-for-first-build.png[First build changelog] + +The Jenkins git plugin provides an option to trigger a Pipeline build on the first commit on a branch. +By default, no changelog is generated for the first build because the first build has no predecessor build for comparison. +When the first build changelog option is enabled, the most recent commit will be used as the changelog of the first build. + +[source,groovy] +---- +checkout scmGit( + branches: [[name: 'master']], + extensions: [ firstBuildChangelog() ], + userRemoteConfigs: [[url: 'https://github.com/jenkinsci/git-plugin.git']]) +---- + [#merge-extensions] === Merge Extensions diff --git a/images/git-extension-for-first-build.png b/images/git-extension-for-first-build.png new file mode 100644 index 0000000000..7c38342c55 Binary files /dev/null and b/images/git-extension-for-first-build.png differ diff --git a/src/main/java/hudson/plugins/git/GitSCM.java b/src/main/java/hudson/plugins/git/GitSCM.java index cc399cebb4..e35b2ac452 100644 --- a/src/main/java/hudson/plugins/git/GitSCM.java +++ b/src/main/java/hudson/plugins/git/GitSCM.java @@ -28,6 +28,7 @@ import hudson.plugins.git.extensions.impl.BuildSingleRevisionOnly; import hudson.plugins.git.extensions.impl.ChangelogToBranch; import hudson.plugins.git.extensions.impl.CloneOption; +import hudson.plugins.git.extensions.impl.FirstBuildChangelog; import hudson.plugins.git.extensions.impl.PathRestriction; import hudson.plugins.git.extensions.impl.LocalBranch; import hudson.plugins.git.extensions.impl.RelativeTargetDirectory; @@ -1493,9 +1494,14 @@ private void computeChangeLog(GitClient git, Revision revToBuild, TaskListener l } if (!exclusion) { - // this is the first time we are building this branch, so there's no base line to compare against. - // if we force the changelog, it'll contain all the changes in the repo, which is not what we want. - listener.getLogger().println("First time build. Skipping changelog."); + FirstBuildChangelog firstBuildChangelog = getExtensions().get(FirstBuildChangelog.class); + if (firstBuildChangelog != null && firstBuildChangelog.isMakeChangelog()) { + changelog.to(out).max(1).execute(); + executed = true; + listener.getLogger().println("First time build. Latest changes added to changelog."); + } else { + listener.getLogger().println("First time build. Skipping changelog."); + } } else { changelog.to(out).max(MAX_CHANGELOG).execute(); executed = true; diff --git a/src/main/java/hudson/plugins/git/extensions/impl/FirstBuildChangelog.java b/src/main/java/hudson/plugins/git/extensions/impl/FirstBuildChangelog.java new file mode 100644 index 0000000000..91301c0f2a --- /dev/null +++ b/src/main/java/hudson/plugins/git/extensions/impl/FirstBuildChangelog.java @@ -0,0 +1,76 @@ +package hudson.plugins.git.extensions.impl; + +import hudson.Extension; +import hudson.plugins.git.extensions.GitSCMExtension; +import hudson.plugins.git.extensions.GitSCMExtensionDescriptor; +import java.util.Objects; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +/** + * First Build generates a changelog. + * + * @author Derek Inskeep + */ +public class FirstBuildChangelog extends GitSCMExtension { + private boolean makeChangelog; + + @DataBoundConstructor + public FirstBuildChangelog() { + makeChangelog = true; + } + + public boolean isMakeChangelog() { + return makeChangelog; + } + + @DataBoundSetter + public void setMakeChangelog(boolean makeChangelog) { + this.makeChangelog = makeChangelog; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FirstBuildChangelog that = (FirstBuildChangelog) o; + return makeChangelog == that.makeChangelog; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash(makeChangelog); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "FirstBuildChangelog{" + "makeChangelog=" + makeChangelog + '}'; + } + + @Extension + @Symbol("firstBuildChangelog") + public static class DescriptorImpl extends GitSCMExtensionDescriptor { + + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() { + return "First build changelog"; + } + } +} diff --git a/src/main/java/jenkins/plugins/git/traits/FirstBuildChangelogTrait.java b/src/main/java/jenkins/plugins/git/traits/FirstBuildChangelogTrait.java new file mode 100644 index 0000000000..2547586181 --- /dev/null +++ b/src/main/java/jenkins/plugins/git/traits/FirstBuildChangelogTrait.java @@ -0,0 +1,49 @@ +package jenkins.plugins.git.traits; + +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.Extension; +import hudson.plugins.git.extensions.impl.FirstBuildChangelog; +import jenkins.scm.api.trait.SCMSourceTrait; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; + +/** + * Exposes {@link FirstBuildChangelog} as a {@link SCMSourceTrait}. + * + * @since 5.3.0 + */ +public class FirstBuildChangelogTrait extends GitSCMExtensionTrait { + + /** + * @deprecated Use constructor that accepts extension instead. + */ + @Deprecated + public FirstBuildChangelogTrait() { + this(null); + } + + /** + * Stapler constructor. + * + * @param extension the option to force first build to have a non-empty changelog. + */ + @DataBoundConstructor + public FirstBuildChangelogTrait(@CheckForNull FirstBuildChangelog extension) { + super(extension == null ? new FirstBuildChangelog() : extension); + } + + /** + * Our {@link hudson.model.Descriptor} + */ + @Extension + @Symbol("firstBuildChangelog") + public static class DescriptorImpl extends GitSCMExtensionTraitDescriptor { + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() { + return "First Build Changelog"; + } + } +} diff --git a/src/main/resources/hudson/plugins/git/extensions/impl/FirstBuildChangelog/help.html b/src/main/resources/hudson/plugins/git/extensions/impl/FirstBuildChangelog/help.html new file mode 100644 index 0000000000..d8a8150dd2 --- /dev/null +++ b/src/main/resources/hudson/plugins/git/extensions/impl/FirstBuildChangelog/help.html @@ -0,0 +1,5 @@ +
+ First builds will populate the changelog with the latest commit, if any, to allow Pipelines to check and test for file changes. + By default, no changelog is generated for the first build because the first build has no predecessor build for comparison. + When the first build changelog option is enabled, the most recent commit on the branch will be used as the changelog of the first build. +
diff --git a/src/test/java/hudson/plugins/git/GitSCMTest.java b/src/test/java/hudson/plugins/git/GitSCMTest.java index 7c56275f06..b5bd75005a 100644 --- a/src/test/java/hudson/plugins/git/GitSCMTest.java +++ b/src/test/java/hudson/plugins/git/GitSCMTest.java @@ -1188,6 +1188,25 @@ public void testCleanBeforeCheckout() throws Exception { assertThat("Cleaning should happen before fetch", cleaningLogLine, is(lessThan(fetchingLogLine))); } + @Test + public void testFirstBuiltChangelog() throws Exception { + assumeTrue("Test class max time " + MAX_SECONDS_FOR_THESE_TESTS + " exceeded", isTimeAvailable()); + FreeStyleProject p = setupProject("master", false, null, null, "Jane Doe", null); + FirstBuildChangelog fbc = new FirstBuildChangelog(); + ((GitSCM) p.getScm()).getExtensions().add(fbc); + + /* First build should should generate a changelog */ + final String commitFile1 = "commitFile1"; + commit(commitFile1, johnDoe, janeDoe, "Commit number 1"); + final FreeStyleBuild firstBuild = build(p, Result.SUCCESS, commitFile1); + assertThat(firstBuild.getLog(50), hasItem("First time build. Latest changes added to changelog.")); + /* Second build should have normal behavior */ + final String commitFile2 = "commitFile2"; + commit(commitFile2, johnDoe, janeDoe, "Commit number 2"); + final FreeStyleBuild secondBuild = build(p, Result.SUCCESS, commitFile2); + assertThat(secondBuild.getLog(50), not(hasItem("First time build. Latest changes added to changelog."))); + } + @Issue("JENKINS-8342") @Test public void testExcludedRegionMultiCommit() throws Exception {