diff --git a/src/main/java/hudson/plugins/git/GitSCM.java b/src/main/java/hudson/plugins/git/GitSCM.java index e93c27ef85..170a05f629 100644 --- a/src/main/java/hudson/plugins/git/GitSCM.java +++ b/src/main/java/hudson/plugins/git/GitSCM.java @@ -21,6 +21,9 @@ import hudson.model.*; import hudson.model.Descriptor.FormException; import hudson.plugins.git.browser.GitRepositoryBrowser; +import hudson.plugins.git.extensions.GitClientConflictException; +import hudson.plugins.git.extensions.GitClientType; +import hudson.plugins.git.extensions.GitSCMChangelogExtension; import hudson.plugins.git.extensions.GitSCMExtension; import hudson.plugins.git.extensions.GitSCMExtensionDescriptor; import hudson.plugins.git.extensions.impl.AuthorInChangelog; @@ -52,6 +55,7 @@ import hudson.util.ListBoxModel; import jenkins.model.Jenkins; import jenkins.plugins.git.GitSCMMatrixUtil; +import jenkins.plugins.git.LegacyGitSCMChangelogExtension; import jenkins.plugins.git.GitToolChooser; import net.sf.json.JSONObject; @@ -1382,7 +1386,7 @@ public void checkout(Run build, Launcher launcher, FilePath workspace, Tas if (changelogFile != null) { computeChangeLog(git, revToBuild.revision, listener, previousBuildData, new FilePath(changelogFile), - new BuildChooserContextImpl(build.getParent(), build, environment)); + build); } } @@ -1401,6 +1405,7 @@ private void printCommitMessageToLog(TaskListener listener, GitClient git, final } } + // TODO update JavaDoc /** * Build up change log from all the branches that we've merged into {@code revToBuild}. * @@ -1443,33 +1448,16 @@ private void printCommitMessageToLog(TaskListener listener, GitClient git, final * Information that captures what we did during the last build. We need this for changelog, * or else we won't know where to stop. */ - private void computeChangeLog(GitClient git, Revision revToBuild, TaskListener listener, BuildData previousBuildData, FilePath changelogFile, BuildChooserContext context) throws IOException, InterruptedException { + private void computeChangeLog(GitClient git, Revision revToBuild, TaskListener listener, BuildData previousBuildData, FilePath changelogFile, Run build) throws IOException, InterruptedException { boolean executed = false; ChangelogCommand changelog = git.changelog(); - changelog.includes(revToBuild.getSha1()); + changelog.max(MAX_CHANGELOG); // default to allow override by extensions try (Writer out = new OutputStreamWriter(changelogFile.write(),"UTF-8")) { - boolean exclusion = false; - ChangelogToBranch changelogToBranch = getExtensions().get(ChangelogToBranch.class); - if (changelogToBranch != null) { - listener.getLogger().println("Using 'Changelog to branch' strategy."); - changelog.excludes(changelogToBranch.getOptions().getRef()); - exclusion = true; - } else { - for (Branch b : revToBuild.getBranches()) { - Build lastRevWas = getBuildChooser().prevBuildForChangelog(b.getName(), previousBuildData, git, context); - if (lastRevWas != null && lastRevWas.revision != null && git.isCommitInRepo(lastRevWas.getSHA1())) { - changelog.excludes(lastRevWas.getSHA1()); - exclusion = true; - } - } - } + GitSCMChangelogExtension ext = getGitSCMChangelogExtension(); + boolean decorated = ext.decorateChangelogCommand(this, build, git, listener, changelog, revToBuild); - 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."); - } else { - changelog.to(out).max(MAX_CHANGELOG).execute(); + if (decorated) { + changelog.to(out).execute(); executed = true; } } catch (GitException ge) { @@ -1479,6 +1467,13 @@ private void computeChangeLog(GitClient git, Revision revToBuild, TaskListener l } } + private GitSCMChangelogExtension getGitSCMChangelogExtension() { + GitSCMChangelogExtension ext = getExtensions().get(GitSCMChangelogExtension.class); + if (ext == null) + ext = new LegacyGitSCMChangelogExtension(); + return ext; + } + @Override @Deprecated // Overrides a deprecated implementation, must also be deprecated public void buildEnvVars(AbstractBuild build, Map env) { diff --git a/src/main/java/hudson/plugins/git/extensions/GitSCMChangelogExtension.java b/src/main/java/hudson/plugins/git/extensions/GitSCMChangelogExtension.java new file mode 100644 index 0000000000..8533cb1bcc --- /dev/null +++ b/src/main/java/hudson/plugins/git/extensions/GitSCMChangelogExtension.java @@ -0,0 +1,33 @@ +package hudson.plugins.git.extensions; + +import java.io.IOException; + +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.plugins.git.GitException; +import hudson.plugins.git.GitSCM; +import hudson.plugins.git.Revision; +import org.jenkinsci.plugins.gitclient.ChangelogCommand; +import org.jenkinsci.plugins.gitclient.GitClient; + +/** + * FIXME JavaDoc + * @author Zhenlei Huang + */ +public abstract class GitSCMChangelogExtension extends FakeGitSCMExtension { + + /** + * Called before a {@link ChangelogCommand} is executed to allow extensions to alter its behaviour. + * @param scm GitSCM object + * @param build run context + * @param git GitClient + * @param listener build log + * @param cmd changelog command to be decorated + * @param revToBuild The revision selected for this build + * @return true in case decorated, false otherwise + * @throws IOException on input or output error + * @throws InterruptedException when interrupted + * @throws GitException on git error + */ + public abstract boolean decorateChangelogCommand(GitSCM scm, Run build, GitClient git, TaskListener listener, ChangelogCommand cmd, Revision revToBuild) throws IOException, InterruptedException, GitException; +} diff --git a/src/main/java/jenkins/plugins/git/ChangelogToPreviousBuild.java b/src/main/java/jenkins/plugins/git/ChangelogToPreviousBuild.java new file mode 100644 index 0000000000..4db09afaa2 --- /dev/null +++ b/src/main/java/jenkins/plugins/git/ChangelogToPreviousBuild.java @@ -0,0 +1,95 @@ +package jenkins.plugins.git; + +import java.io.IOException; +import java.io.Serializable; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import hudson.EnvVars; +import hudson.FilePath; +import hudson.model.Job; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.plugins.git.Branch; +import hudson.plugins.git.GitException; +import hudson.plugins.git.GitSCM; +import hudson.plugins.git.Revision; +import hudson.plugins.git.extensions.GitSCMChangelogExtension; +import hudson.plugins.git.util.Build; +import hudson.plugins.git.util.BuildChooserContext; +import hudson.remoting.Channel; +import org.jenkinsci.plugins.gitclient.ChangelogCommand; +import org.jenkinsci.plugins.gitclient.GitClient; + +/** + * FIXME JavaDoc + * @author Zhenlei Huang + */ +public class ChangelogToPreviousBuild extends GitSCMChangelogExtension { + + @Override + public boolean decorateChangelogCommand(GitSCM scm, Run build, GitClient git, TaskListener listener, ChangelogCommand cmd, Revision revToBuild) throws IOException, InterruptedException, GitException { + boolean decorated = false; + + for (Branch b : revToBuild.getBranches()) { + Build lastRevWas = scm.getBuildChooser().prevBuildForChangelog(b.getName(), scm.getBuildData(build.getPreviousBuild()), git, new BuildChooserContextImpl(build.getParent(), build, build.getEnvironment(listener))); + if (lastRevWas != null && lastRevWas.revision != null && git.isCommitInRepo(lastRevWas.getSHA1())) { + cmd.includes(revToBuild.getSha1()); + cmd.excludes(lastRevWas.getSHA1()); + decorated = true; + } + } + + return decorated; + } + + // Copy of GitSCM.BuildChooserContextImpl + /*package*/ static class BuildChooserContextImpl implements BuildChooserContext, Serializable { + @SuppressFBWarnings(value="SE_BAD_FIELD", justification="known non-serializable field") + final Job project; + @SuppressFBWarnings(value="SE_BAD_FIELD", justification="known non-serializable field") + final Run build; + final EnvVars environment; + + BuildChooserContextImpl(Job project, Run build, EnvVars environment) { + this.project = project; + this.build = build; + this.environment = environment; + } + + public T actOnBuild(ContextCallable, T> callable) throws IOException, InterruptedException { + return callable.invoke(build, FilePath.localChannel); + } + + public T actOnProject(ContextCallable, T> callable) throws IOException, InterruptedException { + return callable.invoke(project, FilePath.localChannel); + } + + public Run getBuild() { + return build; + } + + public EnvVars getEnvironment() { + return environment; + } + + private Object writeReplace() { + return Channel.current().export(BuildChooserContext.class,new BuildChooserContext() { + public T actOnBuild(ContextCallable, T> callable) throws IOException, InterruptedException { + return callable.invoke(build,Channel.current()); + } + + public T actOnProject(ContextCallable, T> callable) throws IOException, InterruptedException { + return callable.invoke(project,Channel.current()); + } + + public Run getBuild() { + return build; + } + + public EnvVars getEnvironment() { + return environment; + } + }); + } + } +} diff --git a/src/main/java/jenkins/plugins/git/LegacyGitSCMChangelogExtension.java b/src/main/java/jenkins/plugins/git/LegacyGitSCMChangelogExtension.java new file mode 100644 index 0000000000..ef6348d7bd --- /dev/null +++ b/src/main/java/jenkins/plugins/git/LegacyGitSCMChangelogExtension.java @@ -0,0 +1,46 @@ +package jenkins.plugins.git; + +import java.io.IOException; + +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.plugins.git.GitException; +import hudson.plugins.git.GitSCM; +import hudson.plugins.git.Revision; +import hudson.plugins.git.extensions.GitSCMChangelogExtension; +import hudson.plugins.git.extensions.impl.ChangelogToBranch; +import org.jenkinsci.plugins.gitclient.ChangelogCommand; +import org.jenkinsci.plugins.gitclient.GitClient; + + +/** + * FIXME JavaDoc + * Legacy changelog impl. + */ +public class LegacyGitSCMChangelogExtension extends GitSCMChangelogExtension { + + + @Override + public boolean decorateChangelogCommand(GitSCM scm, Run build, GitClient git, TaskListener listener, ChangelogCommand cmd, Revision revToBuild) throws IOException, InterruptedException, GitException { + boolean exclusion = false; + + // TODO Refactor ChangelogToBranch + ChangelogToBranch changelogToBranch = scm.getExtensions().get(ChangelogToBranch.class); + if (changelogToBranch != null) { + listener.getLogger().println("Using 'Changelog to branch' strategy."); + cmd.includes(revToBuild.getSha1()); + cmd.excludes(changelogToBranch.getOptions().getRef()); + exclusion = true; + } else { + exclusion = new ChangelogToPreviousBuild().decorateChangelogCommand(scm, build, git, listener, cmd, revToBuild); + } + + 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."); + } + + return exclusion; + } +}