Skip to content

JENKINS-56284 Make compute changelog extensible #813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 19 additions & 24 deletions src/main/java/hudson/plugins/git/GitSCM.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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}.
*
Expand Down Expand Up @@ -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) {
Expand All @@ -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<String, String> env) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, this is something I don't understand. Why extending FakeGitSCMExtension instead of GitSCMExtension

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure which one is better. If I remember correctly, extending GitSCMExtension is also OK.


/**
* 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;
}
95 changes: 95 additions & 0 deletions src/main/java/jenkins/plugins/git/ChangelogToPreviousBuild.java
Original file line number Diff line number Diff line change
@@ -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> T actOnBuild(ContextCallable<Run<?,?>, T> callable) throws IOException, InterruptedException {
return callable.invoke(build, FilePath.localChannel);
}

public <T> T actOnProject(ContextCallable<Job<?,?>, 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> T actOnBuild(ContextCallable<Run<?,?>, T> callable) throws IOException, InterruptedException {
return callable.invoke(build,Channel.current());
}

public <T> T actOnProject(ContextCallable<Job<?,?>, T> callable) throws IOException, InterruptedException {
return callable.invoke(project,Channel.current());
}

public Run<?, ?> getBuild() {
return build;
}

public EnvVars getEnvironment() {
return environment;
}
});
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}