();
+ return new ArrayList<>();
}
return remoteRepositories;
}
+ /**
+ * Derives a local branch name from the remote branch name by removing the
+ * name of the remote from the remote branch name.
+ *
+ * Ex. origin/master becomes master
+ *
+ * Cycles through the list of user remotes looking for a match allowing user
+ * to configure an alternate (not origin) name for the remote.
+ *
+ * @param remoteBranchName branch name whose remote repository name will be removed
+ * @return a local branch name derived by stripping the remote repository
+ * name from the {@code remoteBranchName} parameter. If a matching
+ * remote is not found, the original {@code remoteBranchName} will
+ * be returned.
+ */
+ public String deriveLocalBranchName(String remoteBranchName) {
+ // default remoteName is 'origin' used if list of user remote configs is empty.
+ String remoteName = "origin";
+
+ for (final UserRemoteConfig remote : getUserRemoteConfigs()) {
+ remoteName = remote.getName();
+ if (remoteName == null || remoteName.isEmpty()) {
+ remoteName = "origin";
+ }
+ if (remoteBranchName.startsWith(remoteName + "/")) {
+ // found the remote config associated with remoteBranchName
+ break;
+ }
+ }
+
+ // now strip the remote name and return the resulting local branch name.
+ String localBranchName = remoteBranchName.replaceFirst("^" + remoteName + "/", "");
+ return localBranchName;
+ }
+
+ @CheckForNull
+ @Whitelisted
public String getGitTool() {
return gitTool;
}
- public static String getParameterString(String original, EnvVars env) {
+ @NonNull
+ public static String getParameterString(@CheckForNull String original, @NonNull EnvVars env) {
return env.expand(original);
}
private List getRefSpecs(RemoteConfig repo, EnvVars env) {
- List refSpecs = new ArrayList();
+ List refSpecs = new ArrayList<>();
for (RefSpec refSpec : repo.getFetchRefSpecs()) {
refSpecs.add(new RefSpec(getParameterString(refSpec.toString(), env)));
}
@@ -405,19 +599,31 @@ private List getRefSpecs(RemoteConfig repo, EnvVars env) {
* If the configuration is such that we are tracking just one branch of one repository
* return that branch specifier (in the form of something like "origin/master" or a SHA1-hash
*
- * Otherwise return null.
+ * Otherwise return [@code null}.
*/
+ @CheckForNull
private String getSingleBranch(EnvVars env) {
// if we have multiple branches skip to advanced usecase
- if (getBranches().size() != 1 || getRepositories().size() != 1) {
+ if (getBranches().size() != 1) {
return null;
}
-
String branch = getBranches().get(0).getName();
- String repository = getRepositories().get(0).getName();
+ String repository = null;
+
+ if (getRepositories().size() != 1) {
+ for (RemoteConfig repo : getRepositories()) {
+ if (branch.startsWith(repo.getName() + "/")) {
+ repository = repo.getName();
+ break;
+ }
+ }
+ } else {
+ repository = getRepositories().get(0).getName();
+ }
+
// replace repository wildcard with repository name
- if (branch.startsWith("*/")) {
+ if (branch.startsWith("*/") && repository != null) {
branch = repository + branch.substring(1);
}
@@ -439,33 +645,41 @@ private String getSingleBranch(EnvVars env) {
}
@Override
- public SCMRevisionState calcRevisionsFromBuild(AbstractBuild, ?> abstractBuild, Launcher launcher, TaskListener taskListener) throws IOException, InterruptedException {
+ public SCMRevisionState calcRevisionsFromBuild(Run, ?> abstractBuild, FilePath workspace, Launcher launcher, TaskListener taskListener) throws IOException, InterruptedException {
return SCMRevisionState.NONE;
}
@Override
public boolean requiresWorkspaceForPolling() {
+ // TODO would need to use hudson.plugins.git.util.GitUtils.getPollEnvironment
+ return requiresWorkspaceForPolling(new EnvVars());
+ }
+
+ /* Package protected for test access */
+ boolean requiresWorkspaceForPolling(EnvVars environment) {
for (GitSCMExtension ext : getExtensions()) {
if (ext.requiresWorkspaceForPolling()) return true;
}
- return getSingleBranch(new EnvVars()) == null;
+ return getSingleBranch(environment) == null;
}
@Override
- protected PollingResult compareRemoteRevisionWith(AbstractProject, ?> project, Launcher launcher, FilePath workspace, final TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
+ public PollingResult compareRemoteRevisionWith(Job, ?> project, Launcher launcher, FilePath workspace, final TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
try {
return compareRemoteRevisionWithImpl( project, launcher, workspace, listener);
} catch (GitException e){
- throw new IOException2(e);
+ throw new IOException(e);
}
}
- private PollingResult compareRemoteRevisionWithImpl(AbstractProject, ?> project, Launcher launcher, FilePath workspace, final TaskListener listener) throws IOException, InterruptedException {
+ public static final Pattern GIT_REF = Pattern.compile("^(refs/[^/]+)/(.+)");
+
+ private PollingResult compareRemoteRevisionWithImpl(Job, ?> project, Launcher launcher, FilePath workspace, final @NonNull TaskListener listener) throws IOException, InterruptedException {
// Poll for changes. Are there any unbuilt revisions that Hudson ought to build ?
listener.getLogger().println("Using strategy: " + getBuildChooser().getDisplayName());
- final AbstractBuild lastBuild = project.getLastBuild();
+ final Run lastBuild = project.getLastBuild();
if (lastBuild == null) {
// If we've never been built before, well, gotta build!
listener.getLogger().println("[poll] No previous build, so forcing an initial build.");
@@ -477,57 +691,99 @@ private PollingResult compareRemoteRevisionWithImpl(AbstractProject, ?> projec
listener.getLogger().println("[poll] Last Built Revision: " + buildData.lastBuild.revision);
}
- final String singleBranch = getSingleBranch(lastBuild.getEnvironment());
+ final EnvVars pollEnv = project instanceof AbstractProject ? GitUtils.getPollEnvironment((AbstractProject) project, workspace, launcher, listener, false) : lastBuild.getEnvironment(listener);
- // fast remote polling needs a single branch and an existing last build
- if (!requiresWorkspaceForPolling() && buildData.lastBuild != null && buildData.lastBuild.getMarked() != null) {
+ final String singleBranch = getSingleBranch(pollEnv);
- // FIXME this should not be a specific case, but have BuildChooser tell us if it can poll without workspace.
+ if (!requiresWorkspaceForPolling(pollEnv)) {
- final EnvVars environment = GitUtils.getPollEnvironment(project, workspace, launcher, listener, false);
+ final EnvVars environment = project instanceof AbstractProject ? GitUtils.getPollEnvironment((AbstractProject) project, workspace, launcher, listener, false) : new EnvVars();
- GitClient git = createClient(listener, environment, project, Jenkins.getInstance(), null);
+ GitClient git = createClient(listener, environment, project, Jenkins.get(), null);
- String gitRepo = getParamExpandedRepos(lastBuild).get(0).getURIs().get(0).toString();
- ObjectId head = git.getHeadRev(gitRepo, getBranches().get(0).getName());
+ for (RemoteConfig remoteConfig : getParamExpandedRepos(lastBuild, listener)) {
+ String remote = remoteConfig.getName();
+ List refSpecs = getRefSpecs(remoteConfig, environment);
- if (head != null && buildData.lastBuild.getMarked().getSha1().equals(head)) {
- return NO_CHANGES;
- } else {
- return BUILD_NOW;
+ for (URIish urIish : remoteConfig.getURIs()) {
+ String gitRepo = urIish.toString();
+ Map heads = git.getHeadRev(gitRepo);
+ if (heads==null || heads.isEmpty()) {
+ listener.getLogger().println("[poll] Couldn't get remote head revision");
+ return BUILD_NOW;
+ }
+
+ listener.getLogger().println("Found "+ heads.size() +" remote heads on " + urIish);
+
+ Iterator> it = heads.entrySet().iterator();
+ while (it.hasNext()) {
+ String head = it.next().getKey();
+ boolean match = false;
+ for (RefSpec spec : refSpecs) {
+ if (spec.matchSource(head)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ listener.getLogger().println("Ignoring " + head + " as it doesn't match any of the configured refspecs");
+ it.remove();
+ }
+ }
+
+ for (BranchSpec branchSpec : getBranches()) {
+ for (Entry entry : heads.entrySet()) {
+ final String head = entry.getKey();
+ // head is "refs/(heads|tags|whatever)/branchName
+
+ // first, check the a canonical git reference is configured
+ if (!branchSpec.matches(head, environment)) {
+
+ // convert head `refs/(heads|tags|whatever)/branch` into shortcut notation `remote/branch`
+ String name;
+ Matcher matcher = GIT_REF.matcher(head);
+ if (matcher.matches()) name = remote + head.substring(matcher.group(1).length());
+ else name = remote + "/" + head;
+
+ if (!branchSpec.matches(name, environment)) continue;
+ }
+
+ final ObjectId sha1 = entry.getValue();
+ Build built = buildData.getLastBuild(sha1);
+ if (built != null) {
+ listener.getLogger().println("[poll] Latest remote head revision on " + head + " is: " + sha1.getName() + " - already built by " + built.getBuildNumber());
+ continue;
+ }
+
+ listener.getLogger().println("[poll] Latest remote head revision on " + head + " is: " + sha1.getName());
+ return BUILD_NOW;
+ }
+ }
+ }
}
+ return NO_CHANGES;
}
- final EnvVars environment = GitUtils.getPollEnvironment(project, workspace, launcher, listener);
+ final Node node = GitUtils.workspaceToNode(workspace);
+ final EnvVars environment = project instanceof AbstractProject ? GitUtils.getPollEnvironment((AbstractProject) project, workspace, launcher, listener) : project.getEnvironment(node, listener);
FilePath workingDirectory = workingDirectory(project,workspace,environment,listener);
// (Re)build if the working directory doesn't exist
if (workingDirectory == null || !workingDirectory.exists()) {
+ listener.getLogger().println("[poll] Working Directory does not exist");
return BUILD_NOW;
}
- // which node is this workspace from?
- // there should be always one match, but just in case we initialize n to a non-null value
- Node n = Jenkins.getInstance();
- if (workspace.isRemote()) {
- for (Computer c : Jenkins.getInstance().getComputers()) {
- if (c.getChannel()==workspace.getChannel()) {
- n = c.getNode();
- break;
- }
- }
- }
-
- GitClient git = createClient(listener, environment, project, n, workingDirectory);
+ GitClient git = createClient(listener, environment, project, node, workingDirectory);
if (git.hasGitRepo()) {
// Repo is there - do a fetch
listener.getLogger().println("Fetching changes from the remote Git repositories");
// Fetch updates
- for (RemoteConfig remoteRepository : getParamExpandedRepos(lastBuild)) {
- fetchFrom(git, listener, remoteRepository);
+ for (RemoteConfig remoteRepository : getParamExpandedRepos(lastBuild, listener)) {
+ fetchFrom(git, null, listener, remoteRepository);
}
listener.getLogger().println("Polling for changes in");
@@ -551,14 +807,26 @@ private PollingResult compareRemoteRevisionWithImpl(AbstractProject, ?> projec
/**
* Allows {@link Builder}s and {@link Publisher}s to access a configured {@link GitClient} object to
* perform additional git operations.
+ * @param listener build log
+ * @param environment environment variables to be used
+ * @param build run context for the returned GitClient
+ * @param workspace client workspace
+ * @return git client for additional git operations
+ * @throws IOException on input or output error
+ * @throws InterruptedException when interrupted
*/
- public GitClient createClient(BuildListener listener, EnvVars environment, AbstractBuild,?> build) throws IOException, InterruptedException {
- FilePath ws = workingDirectory(build.getProject(), build.getWorkspace(), environment, listener);
- ws.mkdirs(); // ensure it exists
- return createClient(listener,environment, build.getParent(), build.getBuiltOn(), ws);
+ @NonNull
+ public GitClient createClient(TaskListener listener, EnvVars environment, Run,?> build, FilePath workspace) throws IOException, InterruptedException {
+ FilePath ws = workingDirectory(build.getParent(), workspace, environment, listener);
+ /* ws will be null if the node which ran the build is offline */
+ if (ws != null) {
+ ws.mkdirs(); // ensure it exists
+ }
+ return createClient(listener,environment, build.getParent(), GitUtils.workspaceToNode(workspace), ws);
}
- /*package*/ GitClient createClient(TaskListener listener, EnvVars environment, AbstractProject project, Node n, FilePath ws) throws IOException, InterruptedException {
+ @NonNull
+ /*package*/ GitClient createClient(TaskListener listener, EnvVars environment, Job project, Node n, FilePath ws) throws IOException, InterruptedException {
String gitExe = getGitExe(n, listener);
Git git = Git.with(listener, environment).in(ws).using(gitExe);
@@ -569,16 +837,30 @@ public GitClient createClient(BuildListener listener, EnvVars environment, Abstr
}
for (UserRemoteConfig uc : getUserRemoteConfigs()) {
- if (uc.getCredentialsId() != null) {
- String url = uc.getUrl();
- StandardUsernameCredentials credentials = CredentialsMatchers
- .firstOrNull(
- CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, project,
- ACL.SYSTEM, URIRequirementBuilder.fromUri(url).build()),
- CredentialsMatchers.allOf(CredentialsMatchers.withId(uc.getCredentialsId()),
- GitClient.CREDENTIALS_MATCHER));
+ String ucCredentialsId = uc.getCredentialsId();
+ if (ucCredentialsId == null) {
+ listener.getLogger().println("No credentials specified");
+ } else {
+ String url = getParameterString(uc.getUrl(), environment);
+ List urlCredentials = CredentialsProvider.lookupCredentials(
+ StandardUsernameCredentials.class,
+ project,
+ project instanceof Queue.Task
+ ? Tasks.getDefaultAuthenticationOf((Queue.Task)project)
+ : ACL.SYSTEM,
+ URIRequirementBuilder.fromUri(url).build()
+ );
+ CredentialsMatcher ucMatcher = CredentialsMatchers.withId(ucCredentialsId);
+ CredentialsMatcher idMatcher = CredentialsMatchers.allOf(ucMatcher, GitClient.CREDENTIALS_MATCHER);
+ StandardUsernameCredentials credentials = CredentialsMatchers.firstOrNull(urlCredentials, idMatcher);
if (credentials != null) {
c.addCredentials(url, credentials);
+ listener.getLogger().println(format("using credential %s", credentials.getId()));
+ if (project != null && project.getLastBuild() != null) {
+ CredentialsProvider.track(project.getLastBuild(), credentials);
+ }
+ } else {
+ listener.getLogger().println(format("Warning: CredentialId \"%s\" could not be found.", ucCredentialsId));
}
}
}
@@ -587,6 +869,7 @@ public GitClient createClient(BuildListener listener, EnvVars environment, Abstr
return c;
}
+ @NonNull
private BuildData fixNull(BuildData bd) {
return bd != null ? bd : new BuildData(getScmName(), getUserRemoteConfigs()) /*dummy*/;
}
@@ -594,13 +877,15 @@ private BuildData fixNull(BuildData bd) {
/**
* Fetch information from a particular remote repository.
*
- * @param git
- * @param listener
- * @param remoteRepository
- * @throws InterruptedException
- * @throws IOException
+ * @param git git client
+ * @param run run context if it's running for build
+ * @param listener build log
+ * @param remoteRepository remote git repository
+ * @throws InterruptedException when interrupted
+ * @throws IOException on input or output error
*/
private void fetchFrom(GitClient git,
+ @CheckForNull Run, ?> run,
TaskListener listener,
RemoteConfig remoteRepository) throws InterruptedException, IOException {
@@ -616,7 +901,7 @@ private void fetchFrom(GitClient git,
FetchCommand fetch = git.fetch_().from(url, remoteRepository.getFetchRefSpecs());
for (GitSCMExtension extension : extensions) {
- extension.decorateFetchCommand(this, git, listener, fetch);
+ extension.decorateFetchCommand(this, run, git, listener, fetch);
}
fetch.execute();
} catch (GitException ex) {
@@ -632,7 +917,7 @@ private RemoteConfig newRemoteConfig(String name, String refUrl, RefSpec... refS
// Make up a repo config from the request parameters
repoConfig.setString("remote", name, "url", refUrl);
- List str = new ArrayList();
+ List str = new ArrayList<>();
if(refSpec != null && refSpec.length > 0)
for (RefSpec rs: refSpec)
str.add(rs.toString());
@@ -644,14 +929,9 @@ private RemoteConfig newRemoteConfig(String name, String refUrl, RefSpec... refS
}
}
+ @CheckForNull
public GitTool resolveGitTool(TaskListener listener) {
- if (gitTool == null) return GitTool.getDefaultInstallation();
- GitTool git = Jenkins.getInstance().getDescriptorByType(GitTool.DescriptorImpl.class).getInstallation(gitTool);
- if (git == null) {
- listener.getLogger().println("selected Git installation does not exists. Using Default");
- git = GitTool.getDefaultInstallation();
- }
- return git;
+ return GitUtils.resolveGitTool(gitTool, listener);
}
public String getGitExe(Node builtOn, TaskListener listener) {
@@ -660,71 +940,41 @@ public String getGitExe(Node builtOn, TaskListener listener) {
/**
* Exposing so that we can get this from GitPublisher.
+ * @param builtOn node where build was performed
+ * @param env environment variables used in the build
+ * @param listener build log
+ * @return git exe for builtOn node, often "Default" or "jgit"
*/
public String getGitExe(Node builtOn, EnvVars env, TaskListener listener) {
-
- GitClientType client = GitClientType.ANY;
- for (GitSCMExtension ext : extensions) {
- try {
- client = client.combine(ext.getRequiredClient());
- } catch (GitClientConflictException e) {
- throw new RuntimeException(ext.getDescriptor().getDisplayName() + " extended Git behavior is incompatible with other behaviors");
- }
- }
- if (client == GitClientType.JGIT) return JGitTool.MAGIC_EXENAME;
-
- GitTool tool = resolveGitTool(listener);
- if (builtOn != null) {
- try {
- tool = tool.forNode(builtOn, listener);
- } catch (IOException e) {
- listener.getLogger().println("Failed to get git executable");
- } catch (InterruptedException e) {
- listener.getLogger().println("Failed to get git executable");
- }
- }
- if (env != null) {
- tool = tool.forEnvironment(env);
+ GitTool tool = GitUtils.resolveGitTool(gitTool, builtOn, env, listener);
+ if(tool == null) {
+ return null;
}
-
return tool.getGitExe();
}
- /**
- * Web-bound method to let people look up a build by their SHA1 commit.
- */
- public AbstractBuild,?> getBySHA1(String sha1) {
- AbstractProject,?> p = Stapler.getCurrentRequest().findAncestorObject(AbstractProject.class);
- for (AbstractBuild b : p.getBuilds()) {
- BuildData d = b.getAction(BuildData.class);
- if (d!=null && d.lastBuild!=null) {
- Build lb = d.lastBuild;
- if (lb.isFor(sha1)) return b;
- }
- }
- return null;
- }
-
/*package*/ static class BuildChooserContextImpl implements BuildChooserContext, Serializable {
- final AbstractProject project;
- final AbstractBuild build;
+ @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(AbstractProject project, AbstractBuild build, 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,Hudson.MasterComputer.localChannel);
+ 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, MasterComputer.localChannel);
+ public T actOnProject(ContextCallable, T> callable) throws IOException, InterruptedException {
+ return callable.invoke(project, FilePath.localChannel);
}
- public AbstractBuild, ?> getBuild() {
+ public Run, ?> getBuild() {
return build;
}
@@ -734,15 +984,15 @@ public EnvVars getEnvironment() {
private Object writeReplace() {
return Channel.current().export(BuildChooserContext.class,new BuildChooserContext() {
- public T actOnBuild(ContextCallable, T> callable) throws IOException, InterruptedException {
+ public T actOnBuild(ContextCallable, T> callable) throws IOException, InterruptedException {
return callable.invoke(build,Channel.current());
}
- public T actOnProject(ContextCallable, T> callable) throws IOException, InterruptedException {
+ public T actOnProject(ContextCallable, T> callable) throws IOException, InterruptedException {
return callable.invoke(project,Channel.current());
}
- public AbstractBuild, ?> getBuild() {
+ public Run, ?> getBuild() {
return build;
}
@@ -763,59 +1013,76 @@ public EnvVars getEnvironment() {
* messed up (such as HEAD pointing to a random branch.) It is expected that this method brings it back
* to the predictable clean state by the time this method returns.
*/
- private @NonNull Build determineRevisionToBuild(final AbstractBuild build,
- final BuildData buildData,
+ private @NonNull Build determineRevisionToBuild(final Run build,
+ final @NonNull BuildData buildData,
final EnvVars environment,
- final GitClient git,
- final BuildListener listener) throws IOException, InterruptedException {
+ final @NonNull GitClient git,
+ final @NonNull TaskListener listener) throws IOException, InterruptedException {
PrintStream log = listener.getLogger();
+ Collection candidates = Collections.emptyList();
+ final BuildChooserContext context = new BuildChooserContextImpl(build.getParent(), build, environment);
+ getBuildChooser().prepareWorkingTree(git, listener, context);
- // every MatrixRun should build the exact same commit ID
- if (build instanceof MatrixRun) {
- MatrixBuild parentBuild = ((MatrixRun) build).getParentBuild();
- if (parentBuild != null) {
- BuildData parentBuildData = getBuildData(parentBuild);
- if (parentBuildData != null) {
- Build lastBuild = parentBuildData.lastBuild;
- if (lastBuild!=null)
- return lastBuild;
- }
- }
+ if (build.getClass().getName().equals("hudson.matrix.MatrixRun")) {
+ candidates = GitSCMMatrixUtil.populateCandidatesFromRootBuild((AbstractBuild) build, this);
}
// parameter forcing the commit ID to build
- final RevisionParameterAction rpa = build.getAction(RevisionParameterAction.class);
- if (rpa != null)
- return new Build(rpa.toRevision(git), build.getNumber(), null);
+ if (candidates.isEmpty() ) {
+ final RevisionParameterAction rpa = build.getAction(RevisionParameterAction.class);
+ if (rpa != null) {
+ // in case the checkout is due to a commit notification on a
+ // multiple scm configuration, it should be verified if the triggering repo remote
+ // matches current repo remote to avoid JENKINS-26587
+ if (rpa.canOriginateFrom(this.getRepositories())) {
+ candidates = Collections.singleton(rpa.toRevision(git));
+ } else {
+ log.println("skipping resolution of commit " + rpa.commit + ", since it originates from another repository");
+ }
+ }
+ }
- final String singleBranch = environment.expand( getSingleBranch(environment) );
+ if (candidates.isEmpty() ) {
+ final String singleBranch = environment.expand( getSingleBranch(environment) );
- final BuildChooserContext context = new BuildChooserContextImpl(build.getProject(), build, environment);
- Collection candidates = getBuildChooser().getCandidateRevisions(
- false, singleBranch, git, listener, buildData, context);
+ candidates = getBuildChooser().getCandidateRevisions(
+ false, singleBranch, git, listener, buildData, context);
+ }
- if (candidates.size() == 0) {
+ if (candidates.isEmpty()) {
// getBuildCandidates should make the last item the last build, so a re-build
// will build the last built thing.
throw new AbortException("Couldn't find any revision to build. Verify the repository and branch configuration for this job.");
}
+ Revision marked = candidates.iterator().next();
+ Revision rev = marked;
+ // Modify the revision based on extensions
+ for (GitSCMExtension ext : extensions) {
+ rev = ext.decorateRevisionToBuild(this,build,git,listener,marked,rev);
+ }
+ Build revToBuild = new Build(marked, rev, build.getNumber(), null);
+ buildData.saveBuild(revToBuild);
+
+ if (buildData.getBuildsByBranchName().size() >= 100) {
+ log.println("JENKINS-19022: warning: possible memory leak due to Git plugin usage; see: https://wiki.jenkins.io/display/JENKINS/Remove+Git+Plugin+BuildsByBranch+BuildData");
+ }
+
if (candidates.size() > 1) {
log.println("Multiple candidate revisions");
- AbstractProject, ?> project = build.getProject();
- if (!project.isDisabled()) {
- log.println("Scheduling another build to catch up with " + project.getFullDisplayName());
- if (!project.scheduleBuild(0, new SCMTrigger.SCMTriggerCause())) {
- log.println("WARNING: multiple candidate revisions, but unable to schedule build of " + project.getFullDisplayName());
+ Job, ?> job = build.getParent();
+ if (job instanceof AbstractProject) {
+ AbstractProject project = (AbstractProject) job;
+ if (!project.isDisabled()) {
+ log.println("Scheduling another build to catch up with " + project.getFullDisplayName());
+ if (!project.scheduleBuild(0, new SCMTrigger.SCMTriggerCause("This build was triggered by build "
+ + build.getNumber() + " because more than one build candidate was found."))) {
+ log.println("WARNING: multiple candidate revisions, but unable to schedule build of " + project.getFullDisplayName());
+ }
}
}
}
- Revision rev = candidates.iterator().next();
- Revision marked = rev;
- for (GitSCMExtension ext : extensions) {
- rev = ext.decorateRevisionToBuild(this,build,git,listener,rev);
- }
- return new Build(marked, rev, build.getNumber(), null);
+ return revToBuild;
}
/**
@@ -823,10 +1090,10 @@ public EnvVars getEnvironment() {
*
* By the end of this method, remote refs are updated to include all the commits found in the remote servers.
*/
- private void retrieveChanges(AbstractBuild build, GitClient git, BuildListener listener) throws IOException, InterruptedException {
+ private void retrieveChanges(Run build, GitClient git, TaskListener listener) throws IOException, InterruptedException {
final PrintStream log = listener.getLogger();
- List repos = getParamExpandedRepos(build);
+ List repos = getParamExpandedRepos(build, listener);
if (repos.isEmpty()) return; // defensive check even though this is an invalid configuration
if (git.hasGitRepo()) {
@@ -846,32 +1113,44 @@ private void retrieveChanges(AbstractBuild build, GitClient git, BuildListener l
}
cmd.execute();
} catch (GitException ex) {
- ex.printStackTrace(listener.error("Error cloning remote repo '%s'", rc.getName()));
- throw new AbortException();
+ ex.printStackTrace(listener.error("Error cloning remote repo '" + rc.getName() + "'"));
+ throw new AbortException("Error cloning remote repo '" + rc.getName() + "'");
}
}
for (RemoteConfig remoteRepository : repos) {
- fetchFrom(git, listener, remoteRepository);
+ try {
+ fetchFrom(git, build, listener, remoteRepository);
+ } catch (GitException ex) {
+ /* Allow retry by throwing AbortException instead of
+ * GitException. See JENKINS-20531. */
+ ex.printStackTrace(listener.error("Error fetching remote repo '" + remoteRepository.getName() + "'"));
+ throw new AbortException("Error fetching remote repo '" + remoteRepository.getName() + "'");
+ }
}
}
@Override
- public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile)
+ public void checkout(Run, ?> build, Launcher launcher, FilePath workspace, TaskListener listener, File changelogFile, SCMRevisionState baseline)
throws IOException, InterruptedException {
if (VERBOSE)
- listener.getLogger().println("Using strategy: " + getBuildChooser().getDisplayName());
+ listener.getLogger().println("Using checkout strategy: " + getBuildChooser().getDisplayName());
BuildData previousBuildData = getBuildData(build.getPreviousBuild()); // read only
BuildData buildData = copyBuildData(build.getPreviousBuild());
- build.addAction(buildData);
+
if (VERBOSE && buildData.lastBuild != null) {
listener.getLogger().println("Last Built Revision: " + buildData.lastBuild.revision);
}
EnvVars environment = build.getEnvironment(listener);
- GitClient git = createClient(listener,environment,build);
+ GitClient git = createClient(listener, environment, build, workspace);
+
+ if (launcher instanceof Launcher.DecoratedLauncher) {
+ // We cannot check for git instanceof CliGitAPIImpl vs. JGitAPIImpl here since (when running on an agent) we will actually have a RemoteGitImpl which is opaque.
+ listener.getLogger().println("Warning: JENKINS-30600: special launcher " + launcher + " will be ignored (a typical symptom is the Git executable not being run inside a designated container)");
+ }
for (GitSCMExtension ext : extensions) {
ext.beforeCheckout(this, build, git, listener);
@@ -880,36 +1159,93 @@ public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspa
retrieveChanges(build, git, listener);
Build revToBuild = determineRevisionToBuild(build, buildData, environment, git, listener);
+ // Track whether we're trying to add a duplicate BuildData, now that it's been updated with
+ // revision info for this build etc. The default assumption is that it's a duplicate.
+ boolean buildDataAlreadyPresent = false;
+ List actions = build.getActions(BuildData.class);
+ for (BuildData d: actions) {
+ if (d.similarTo(buildData)) {
+ buildDataAlreadyPresent = true;
+ break;
+ }
+ }
+ if (!actions.isEmpty()) {
+ buildData.setIndex(actions.size()+1);
+ }
+
+ // If the BuildData is not already attached to this build, add it to the build and mark that
+ // it wasn't already present, so that we add the GitTagAction and changelog after the checkout
+ // finishes.
+ if (!buildDataAlreadyPresent) {
+ build.addAction(buildData);
+ }
+
environment.put(GIT_COMMIT, revToBuild.revision.getSha1String());
- Branch branch = Iterables.getFirst(revToBuild.revision.getBranches(),null);
- if (branch!=null) // null for a detached HEAD
- environment.put(GIT_BRANCH, branch.getName());
+ Branch localBranch = Iterables.getFirst(revToBuild.revision.getBranches(),null);
+ String localBranchName = getParamLocalBranch(build, listener);
+ if (localBranch != null && localBranch.getName() != null) { // null for a detached HEAD
+ String remoteBranchName = getBranchName(localBranch);
+ environment.put(GIT_BRANCH, remoteBranchName);
+
+ LocalBranch lb = getExtensions().get(LocalBranch.class);
+ if (lb != null) {
+ String lbn = lb.getLocalBranch();
+ if (lbn == null || lbn.equals("**")) {
+ // local branch is configured with empty value or "**" so use remote branch name for checkout
+ localBranchName = deriveLocalBranchName(remoteBranchName);
+ }
+ environment.put(GIT_LOCAL_BRANCH, localBranchName);
+ }
+ }
listener.getLogger().println("Checking out " + revToBuild.revision);
- CheckoutCommand checkoutCommand = git.checkout().branch(getParamLocalBranch(build)).ref(revToBuild.revision.getSha1String()).deleteBranchIfExist(true);
+ CheckoutCommand checkoutCommand = git.checkout().branch(localBranchName).ref(revToBuild.revision.getSha1String()).deleteBranchIfExist(true);
for (GitSCMExtension ext : this.getExtensions()) {
ext.decorateCheckoutCommand(this, build, git, listener, checkoutCommand);
}
try {
checkoutCommand.execute();
- } catch(GitLockFailedException e) {
+ } catch (GitLockFailedException e) {
// Rethrow IOException so the retry will be able to catch it
throw new IOException("Could not checkout " + revToBuild.revision.getSha1String(), e);
}
- buildData.saveBuild(revToBuild);
- build.addAction(new GitTagAction(build, buildData));
+ // Needs to be after the checkout so that revToBuild is in the workspace
+ try {
+ printCommitMessageToLog(listener, git, revToBuild);
+ } catch (GitException ge) {
+ listener.getLogger().println("Exception logging commit message for " + revToBuild + ": " + ge.getMessage());
+ }
- computeChangeLog(git, revToBuild.revision, listener, previousBuildData, new FilePath(changelogFile),
- new BuildChooserContextImpl(build.getProject(), build, environment));
+ // Don't add the tag and changelog if we've already processed this BuildData before.
+ if (!buildDataAlreadyPresent) {
+ if (build.getActions(AbstractScmTagAction.class).isEmpty()) {
+ // only add the tag action if we can be unique as AbstractScmTagAction has a fixed UrlName
+ // so only one of the actions is addressable by users
+ build.addAction(new GitTagAction(build, workspace, revToBuild.revision));
+ }
+
+ if (changelogFile != null) {
+ computeChangeLog(git, revToBuild.revision, listener, previousBuildData, new FilePath(changelogFile),
+ new BuildChooserContextImpl(build.getParent(), build, environment));
+ }
+ }
for (GitSCMExtension ext : extensions) {
ext.onCheckoutCompleted(this, build, git,listener);
}
+ }
- return true;
+ private void printCommitMessageToLog(TaskListener listener, GitClient git, final Build revToBuild)
+ throws IOException {
+ try {
+ RevCommit commit = git.withRepository(new RevCommitRepositoryCallback(revToBuild));
+ listener.getLogger().println("Commit message: \"" + commit.getShortMessage() + "\"");
+ } catch (InterruptedException | MissingObjectException e) {
+ e.printStackTrace(listener.error("Unable to retrieve commit message"));
+ }
}
/**
@@ -929,7 +1265,7 @@ public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspa
*
*
*
- * If Jenkin built B1, C1, B2, C3 in that order, then one'd prefer that the changelog of B2 only shows
+ * If Jenkins built B1, C1, B2, C3 in that order, then one'd prefer that the changelog of B2 only shows
* just B1..B2, not C1..B2. To do this, we attribute every build to specific branches, and when we say
* "since the previous build", what we really mean is "since the last build that built the same branch".
*
@@ -954,21 +1290,27 @@ public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspa
* 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, BuildListener listener, BuildData previousBuildData, FilePath changelogFile, BuildChooserContext context) throws IOException, InterruptedException {
- Writer out = new OutputStreamWriter(changelogFile.write(),"UTF-8");
-
+ private void computeChangeLog(GitClient git, Revision revToBuild, TaskListener listener, BuildData previousBuildData, FilePath changelogFile, BuildChooserContext context) throws IOException, InterruptedException {
boolean executed = false;
ChangelogCommand changelog = git.changelog();
changelog.includes(revToBuild.getSha1());
- try {
+ try (Writer out = new OutputStreamWriter(changelogFile.write(),"UTF-8")) {
boolean exclusion = false;
- for (Branch b : revToBuild.getBranches()) {
- Build lastRevWas = getBuildChooser().prevBuildForChangelog(b.getName(), previousBuildData, git, context);
- if (lastRevWas != null && git.isCommitInRepo(lastRevWas.getSHA1())) {
- changelog.excludes(lastRevWas.getSHA1());
- exclusion = true;
+ 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;
+ }
}
}
+
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.
@@ -981,28 +1323,60 @@ private void computeChangeLog(GitClient git, Revision revToBuild, BuildListener
ge.printStackTrace(listener.error("Unable to retrieve changeset"));
} finally {
if (!executed) changelog.abort();
- IOUtils.closeQuietly(out);
}
}
- public void buildEnvVars(AbstractBuild, ?> build, java.util.Map env) {
- super.buildEnvVars(build, env);
+ public void buildEnvVars(AbstractBuild, ?> build, Map env) {
+ buildEnvironment(build, env);
+ }
+
+ @Override
+ public void buildEnvironment(Run, ?> build, java.util.Map env) {
Revision rev = fixNull(getBuildData(build)).getLastBuiltRevision();
if (rev!=null) {
Branch branch = Iterables.getFirst(rev.getBranches(), null);
- if (branch!=null) {
- env.put(GIT_BRANCH, branch.getName());
+ if (branch!=null && branch.getName()!=null) {
+ String remoteBranchName = getBranchName(branch);
+ env.put(GIT_BRANCH, remoteBranchName);
+
+ // TODO this is unmodular; should rather override LocalBranch.populateEnvironmentVariables
+ LocalBranch lb = getExtensions().get(LocalBranch.class);
+ if (lb != null) {
+ // Set GIT_LOCAL_BRANCH variable from the LocalBranch extension
+ String localBranchName = lb.getLocalBranch();
+ if (localBranchName == null || localBranchName.equals("**")) {
+ // local branch is configured with empty value or "**" so use remote branch name for checkout
+ localBranchName = deriveLocalBranchName(remoteBranchName);
+ }
+ env.put(GIT_LOCAL_BRANCH, localBranchName);
+ }
+ RelativeTargetDirectory rtd = getExtensions().get(RelativeTargetDirectory.class);
+ if (rtd != null) {
+ String localRelativeTargetDir = rtd.getRelativeTargetDir();
+ if ( localRelativeTargetDir == null ){
+ localRelativeTargetDir = "";
+ }
+ env.put(GIT_CHECKOUT_DIR, localRelativeTargetDir);
+ }
String prevCommit = getLastBuiltCommitOfBranch(build, branch);
if (prevCommit != null) {
env.put(GIT_PREVIOUS_COMMIT, prevCommit);
}
+
+ String prevSuccessfulCommit = getLastSuccessfulBuiltCommitOfBranch(build, branch);
+ if (prevSuccessfulCommit != null) {
+ env.put(GIT_PREVIOUS_SUCCESSFUL_COMMIT, prevSuccessfulCommit);
+ }
}
- env.put(GIT_COMMIT, fixEmpty(rev.getSha1String()));
+ String sha1 = Util.fixEmpty(rev.getSha1String());
+ if (sha1 != null && !sha1.isEmpty()) {
+ env.put(GIT_COMMIT, sha1);
+ }
}
-
+
if (userRemoteConfigs.size()==1){
env.put("GIT_URL", userRemoteConfigs.get(0).getUrl());
} else {
@@ -1010,7 +1384,7 @@ public void buildEnvVars(AbstractBuild, ?> build, java.util.Map build, java.util.Map build, Branch branch) {
+ private String getBranchName(Branch branch)
+ {
+ String name = branch.getName();
+ if(name.startsWith("refs/remotes/")) {
+ //Restore expected previous behaviour
+ name = name.substring("refs/remotes/".length());
+ }
+ return name;
+ }
+
+ private String getLastBuiltCommitOfBranch(Run, ?> build, Branch branch) {
String prevCommit = null;
if (build.getPreviousBuiltBuild() != null) {
final Build lastBuildOfBranch = fixNull(getBuildData(build.getPreviousBuiltBuild())).getLastBuildOfBranch(branch.getName());
@@ -1033,9 +1417,30 @@ private String getLastBuiltCommitOfBranch(AbstractBuild, ?> build, Branch bran
return prevCommit;
}
+ private String getLastSuccessfulBuiltCommitOfBranch(Run, ?> build, Branch branch) {
+ String prevCommit = null;
+ if (build.getPreviousSuccessfulBuild() != null) {
+ final Build lastSuccessfulBuildOfBranch = fixNull(getBuildData(build.getPreviousSuccessfulBuild())).getLastBuildOfBranch(branch.getName());
+ if (lastSuccessfulBuildOfBranch != null) {
+ Revision previousRev = lastSuccessfulBuildOfBranch.getRevision();
+ if (previousRev != null) {
+ prevCommit = previousRev.getSha1String();
+ }
+ }
+ }
+
+ return prevCommit;
+ }
+
@Override
public ChangeLogParser createChangeLogParser() {
- return new GitChangeLogParser(getExtensions().get(AuthorInChangelog.class)!=null);
+ try {
+ GitClient gitClient = Git.with(TaskListener.NULL, new EnvVars()).in(new File(".")).using(gitTool).getClient();
+ return new GitChangeLogParser(gitClient, getExtensions().get(AuthorInChangelog.class) != null);
+ } catch (IOException | InterruptedException e) {
+ LOGGER.log(Level.WARNING, "Git client using '" + gitTool + "' changelog parser failed, using deprecated changelog parser", e);
+ }
+ return new GitChangeLogParser(null, getExtensions().get(AuthorInChangelog.class) != null);
}
@Extension
@@ -1045,23 +1450,37 @@ public static final class DescriptorImpl extends SCMDescriptor {
private String globalConfigName;
private String globalConfigEmail;
private boolean createAccountBasedOnEmail;
+ private boolean useExistingAccountWithSameEmail;
// private GitClientType defaultClientType = GitClientType.GITCLI;
+ private boolean showEntireCommitSummaryInChanges;
public DescriptorImpl() {
super(GitSCM.class, GitRepositoryBrowser.class);
load();
}
+ public boolean isShowEntireCommitSummaryInChanges() {
+ return showEntireCommitSummaryInChanges;
+ }
+
+ public void setShowEntireCommitSummaryInChanges(boolean showEntireCommitSummaryInChanges) {
+ this.showEntireCommitSummaryInChanges = showEntireCommitSummaryInChanges;
+ }
+
public String getDisplayName() {
return "Git";
}
+ @Override public boolean isApplicable(Job project) {
+ return true;
+ }
+
public List getExtensionDescriptors() {
return GitSCMExtensionDescriptor.all();
}
public boolean showGitToolOptions() {
- return Jenkins.getInstance().getDescriptorByType(GitTool.DescriptorImpl.class).getInstallations().length>1;
+ return Jenkins.get().getDescriptorByType(GitTool.DescriptorImpl.class).getInstallations().length>1;
}
/**
@@ -1069,7 +1488,7 @@ public boolean showGitToolOptions() {
* @return list of available git tools
*/
public List getGitTools() {
- GitTool[] gitToolInstallations = Hudson.getInstance().getDescriptorByType(GitTool.DescriptorImpl.class).getInstallations();
+ GitTool[] gitToolInstallations = Jenkins.get().getDescriptorByType(GitTool.DescriptorImpl.class).getInstallations();
return Arrays.asList(gitToolInstallations);
}
@@ -1085,6 +1504,7 @@ public ListBoxModel doFillGitToolItems() {
* Path to git executable.
* @deprecated
* @see GitTool
+ * @return git executable
*/
@Deprecated
public String getGitExe() {
@@ -1093,22 +1513,32 @@ public String getGitExe() {
/**
* Global setting to be used in call to "git config user.name".
+ * @return user.name value
*/
public String getGlobalConfigName() {
- return fixEmptyAndTrim(globalConfigName);
+ return Util.fixEmptyAndTrim(globalConfigName);
}
+ /**
+ * Global setting to be used in call to "git config user.name".
+ * @param globalConfigName user.name value to be assigned
+ */
public void setGlobalConfigName(String globalConfigName) {
this.globalConfigName = globalConfigName;
}
/**
* Global setting to be used in call to "git config user.email".
+ * @return user.email value
*/
public String getGlobalConfigEmail() {
- return fixEmptyAndTrim(globalConfigEmail);
+ return Util.fixEmptyAndTrim(globalConfigEmail);
}
+ /**
+ * Global setting to be used in call to "git config user.email".
+ * @param globalConfigEmail user.email value to be assigned
+ */
public void setGlobalConfigEmail(String globalConfigEmail) {
this.globalConfigEmail = globalConfigEmail;
}
@@ -1121,9 +1551,18 @@ public void setCreateAccountBasedOnEmail(boolean createAccountBasedOnEmail) {
this.createAccountBasedOnEmail = createAccountBasedOnEmail;
}
+ public boolean isUseExistingAccountWithSameEmail() {
+ return useExistingAccountWithSameEmail;
+ }
+
+ public void setUseExistingAccountWithSameEmail(boolean useExistingAccountWithSameEmail) {
+ this.useExistingAccountWithSameEmail = useExistingAccountWithSameEmail;
+ }
+
/**
* Old configuration of git executable - exposed so that we can
* migrate this setting to GitTool without deprecation warnings.
+ * @return git executable
*/
public String getOldGitExe() {
return gitExe;
@@ -1132,7 +1571,7 @@ public String getOldGitExe() {
/**
* Determine the browser from the scmData contained in the {@link StaplerRequest}.
*
- * @param scmData
+ * @param scmData data read for SCM browser
* @return browser based on request scmData
*/
private GitRepositoryBrowser getBrowserFromRequest(final StaplerRequest req, final JSONObject scmData) {
@@ -1168,7 +1607,7 @@ public static List createRepositoryConfigurations(String[] urls,
}
repoConfig.setString("remote", name, "url", url);
- repoConfig.setStringList("remote", name, "fetch", new ArrayList(Arrays.asList(refs[i].split("\\s+"))));
+ repoConfig.setStringList("remote", name, "fetch", new ArrayList<>(Arrays.asList(refs[i].split("\\s+"))));
}
try {
@@ -1202,6 +1641,7 @@ public static PreBuildMergeOptions createMergeOptions(UserMergeOptions mergeOpti
mergeOptions.setMergeRemote(mergeRemote);
mergeOptions.setMergeTarget(mergeOptionsBean.getMergeTarget());
mergeOptions.setMergeStrategy(mergeOptionsBean.getMergeStrategy());
+ mergeOptions.setFastForwardMode(mergeOptionsBean.getFastForwardMode());
}
return mergeOptions;
@@ -1236,6 +1676,7 @@ public boolean configure(StaplerRequest req, JSONObject formData) throws FormExc
/**
* Fill in the environment variables for launching git
+ * @param env base environment variables
*/
public void populateEnvironmentVariables(Map env) {
String name = getGlobalConfigName();
@@ -1261,17 +1702,35 @@ public void populateEnvironmentVariables(Map env) {
private static final long serialVersionUID = 1L;
+ @Whitelisted
public boolean isDoGenerateSubmoduleConfigurations() {
return this.doGenerateSubmoduleConfigurations;
}
@Exported
+ @Whitelisted
public List getBranches() {
return branches;
}
+ @Override public String getKey() {
+ String name = getScmName();
+ if (name != null) {
+ return name;
+ }
+ StringBuilder b = new StringBuilder("git");
+ for (RemoteConfig cfg : getRepositories()) {
+ for (URIish uri : cfg.getURIs()) {
+ b.append(' ').append(uri.toString());
+ }
+ }
+ return b.toString();
+ }
+
/**
- * Use {@link PreBuildMerge}.
+ * @deprecated Use {@link PreBuildMerge}.
+ * @return pre-build merge options
+ * @throws FormException on form error
*/
@Exported
@Deprecated
@@ -1290,6 +1749,9 @@ private boolean isRelevantBuildData(BuildData bd) {
/**
* @deprecated
+ * @param build run whose build data is returned
+ * @param clone true if returned build data should be copied rather than referenced
+ * @return build data for build run
*/
public BuildData getBuildData(Run build, boolean clone) {
return clone ? copyBuildData(build) : getBuildData(build);
@@ -1298,20 +1760,25 @@ public BuildData getBuildData(Run build, boolean clone) {
/**
* Like {@link #getBuildData(Run)}, but copy the data into a new object,
* which is used as the first step for updating the data for the next build.
+ * @param build run whose BuildData is returned
+ * @return copy of build data for build
*/
public BuildData copyBuildData(Run build) {
BuildData base = getBuildData(build);
if (base==null)
return new BuildData(getScmName(), getUserRemoteConfigs());
- else
- return base.clone();
+ else {
+ BuildData buildData = base.clone();
+ buildData.setScmName(getScmName());
+ return buildData;
+ }
}
/**
* Find the build log (BuildData) recorded with the last build that completed. BuildData
* may not be recorded if an exception occurs in the plugin logic.
*
- * @param build
+ * @param build run whose build data is returned
* @return the last recorded build data
*/
public @CheckForNull BuildData getBuildData(Run build) {
@@ -1337,10 +1804,15 @@ public BuildData copyBuildData(Run build) {
* Given the workspace, gets the working directory, which will be the workspace
* if no relative target dir is specified. Otherwise, it'll be "workspace/relativeTargetDir".
*
- * @param workspace
+ * @param context job context for working directory
+ * @param workspace initial FilePath of job workspace
+ * @param environment environment variables used in job context
+ * @param listener build log
* @return working directory or null if workspace is null
+ * @throws IOException on input or output error
+ * @throws InterruptedException when interrupted
*/
- protected FilePath workingDirectory(AbstractProject,?> context, FilePath workspace, EnvVars environment, TaskListener listener) throws IOException, InterruptedException {
+ protected FilePath workingDirectory(Job,?> context, FilePath workspace, EnvVars environment, TaskListener listener) throws IOException, InterruptedException {
// JENKINS-10880: workspace can be null
if (workspace == null) {
return null;
@@ -1358,14 +1830,18 @@ protected FilePath workingDirectory(AbstractProject,?> context, FilePath works
*
* @param git GitClient object
* @param r Revision object
- * @param listener
+ * @param listener build log
* @return true if any exclusion files are matched, false otherwise.
*/
private boolean isRevExcluded(GitClient git, Revision r, TaskListener listener, BuildData buildData) throws IOException, InterruptedException {
try {
List revShow;
if (buildData != null && buildData.lastBuild != null) {
- revShow = git.showRevision(buildData.lastBuild.revision.getSha1(), r.getSha1());
+ if (getExtensions().get(PathRestriction.class) != null) {
+ revShow = git.showRevision(buildData.lastBuild.revision.getSha1(), r.getSha1());
+ } else {
+ revShow = git.showRevision(buildData.lastBuild.revision.getSha1(), r.getSha1(), false);
+ }
} else {
revShow = git.showRevision(r.getSha1());
}
@@ -1375,7 +1851,8 @@ private boolean isRevExcluded(GitClient git, Revision r, TaskListener listener,
int start=0, idx=0;
for (String line : revShow) {
if (line.startsWith("commit ") && idx!=0) {
- GitChangeSet change = new GitChangeSet(revShow.subList(start,idx), getExtensions().get(AuthorInChangelog.class)!=null);
+ boolean showEntireCommitSummary = GitChangeSet.isShowEntireCommitSummaryInChanges() || !(git instanceof CliGitAPIImpl);
+ GitChangeSet change = new GitChangeSet(revShow.subList(start,idx), getExtensions().get(AuthorInChangelog.class)!=null, showEntireCommitSummary);
Boolean excludeThisCommit=null;
for (GitSCMExtension ext : extensions) {
@@ -1401,10 +1878,10 @@ private boolean isRevExcluded(GitClient git, Revision r, TaskListener listener,
}
}
-
@Initializer(after=PLUGINS_STARTED)
public static void onLoaded() {
- DescriptorImpl desc = Jenkins.getInstance().getDescriptorByType(DescriptorImpl.class);
+ Jenkins jenkins = Jenkins.get();
+ DescriptorImpl desc = jenkins.getDescriptorByType(DescriptorImpl.class);
if (desc.getOldGitExe() != null) {
String exe = desc.getOldGitExe();
@@ -1429,6 +1906,7 @@ public static void configureXtream() {
* Set to true to enable more logging to build's {@link TaskListener}.
* Used by various classes in this package.
*/
+ @SuppressFBWarnings(value="MS_SHOULD_BE_FINAL", justification="Not final so users can adjust log verbosity")
public static boolean VERBOSE = Boolean.getBoolean(GitSCM.class.getName() + ".verbose");
/**
diff --git a/src/main/java/hudson/plugins/git/GitSCMBackwardCompatibility.java b/src/main/java/hudson/plugins/git/GitSCMBackwardCompatibility.java
index dfb24a186a..408e9cb57d 100644
--- a/src/main/java/hudson/plugins/git/GitSCMBackwardCompatibility.java
+++ b/src/main/java/hudson/plugins/git/GitSCMBackwardCompatibility.java
@@ -17,6 +17,7 @@
import java.util.Set;
import static org.apache.commons.lang.StringUtils.isNotBlank;
+import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted;
/**
* This is a portion of {@link GitSCM} for the stuff that's used to be in {@link GitSCM}
@@ -178,6 +179,7 @@ public abstract class GitSCMBackwardCompatibility extends SCM implements Seriali
private transient BuildChooser buildChooser;
+ @Whitelisted
abstract DescribableList getExtensions();
@Override
@@ -204,7 +206,7 @@ void readBackExtensionsFromLegacy() {
skipTag = null;
}
if (disableSubmodules || recursiveSubmodules || trackingSubmodules) {
- addIfMissing(new SubmoduleOption(disableSubmodules, recursiveSubmodules, trackingSubmodules));
+ addIfMissing(new SubmoduleOption(disableSubmodules, recursiveSubmodules, trackingSubmodules, null, null, false));
}
if (isNotBlank(gitConfigName) || isNotBlank(gitConfigEmail)) {
addIfMissing(new UserIdentity(gitConfigName,gitConfigEmail));
@@ -364,6 +366,7 @@ public UserMergeOptions getUserMergeOptions() {
/**
* @deprecated
* Moved to {@link CleanCheckout}
+ * @return true if clean before checkout extension is enabled
*/
public boolean getClean() {
return getExtensions().get(CleanCheckout.class)!=null;
@@ -372,6 +375,7 @@ public boolean getClean() {
/**
* @deprecated
* Moved to {@link WipeWorkspace}
+ * @return true if wipe workspace extension is enabled
*/
public boolean getWipeOutWorkspace() {
return getExtensions().get(WipeWorkspace.class)!=null;
@@ -380,6 +384,7 @@ public boolean getWipeOutWorkspace() {
/**
* @deprecated
* Moved to {@link CloneOption}
+ * @return true if shallow clone extension is enabled and shallow clone is configured
*/
public boolean getUseShallowClone() {
CloneOption m = getExtensions().get(CloneOption.class);
@@ -389,6 +394,7 @@ public boolean getUseShallowClone() {
/**
* @deprecated
* Moved to {@link CloneOption}
+ * @return reference repository or null if reference repository is not defined
*/
public String getReference() {
CloneOption m = getExtensions().get(CloneOption.class);
@@ -398,6 +404,7 @@ public String getReference() {
/**
* @deprecated
* Moved to {@link hudson.plugins.git.extensions.impl.DisableRemotePoll}
+ * @return true if remote polling is allowed
*/
public boolean getRemotePoll() {
return getExtensions().get(DisableRemotePoll.class)==null;
@@ -409,6 +416,7 @@ public boolean getRemotePoll() {
*
* @deprecated
* Moved to {@link AuthorInChangelog}
+ * @return true if commit author is used as the changeset author
*/
public boolean getAuthorOrCommitter() {
return getExtensions().get(AuthorInChangelog.class)!=null;
@@ -417,6 +425,7 @@ public boolean getAuthorOrCommitter() {
/**
* @deprecated
* Moved to {@link IgnoreNotifyCommit}
+ * @return true if commit notifications are ignored
*/
public boolean isIgnoreNotifyCommit() {
return getExtensions().get(IgnoreNotifyCommit.class)!=null;
@@ -425,6 +434,7 @@ public boolean isIgnoreNotifyCommit() {
/**
* @deprecated
* Moved to {@link ScmName}
+ * @return configured SCM name or null if none if not configured
*/
public String getScmName() {
ScmName sn = getExtensions().get(ScmName.class);
@@ -434,6 +444,7 @@ public String getScmName() {
/**
* @deprecated
* Moved to {@link LocalBranch}
+ * @return name of local branch used for checkout or null if LocalBranch extension is not enabled
*/
public String getLocalBranch() {
LocalBranch lb = getExtensions().get(LocalBranch.class);
diff --git a/src/main/java/hudson/plugins/git/GitStatus.java b/src/main/java/hudson/plugins/git/GitStatus.java
index 177b5bff92..126530b558 100644
--- a/src/main/java/hudson/plugins/git/GitStatus.java
+++ b/src/main/java/hudson/plugins/git/GitStatus.java
@@ -1,78 +1,144 @@
package hudson.plugins.git;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.Util;
-import hudson.model.AbstractModelObject;
-import hudson.model.AbstractProject;
-import hudson.model.Cause;
-import hudson.model.Hudson;
-import hudson.model.ParametersAction;
-import hudson.model.UnprotectedRootAction;
+import hudson.model.*;
import hudson.plugins.git.extensions.impl.IgnoreNotifyCommit;
import hudson.scm.SCM;
import hudson.security.ACL;
+import hudson.security.ACLContext;
import hudson.triggers.SCMTrigger;
-import jenkins.model.Jenkins;
-import org.acegisecurity.context.SecurityContextHolder;
-import org.apache.commons.lang.StringUtils;
-import org.eclipse.jgit.transport.RemoteConfig;
-import org.eclipse.jgit.transport.URIish;
-import org.kohsuke.stapler.*;
-
-import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
+import jenkins.model.Jenkins;
+import jenkins.scm.api.SCMEvent;
+import jenkins.triggers.SCMTriggerItem;
+import org.apache.commons.lang.StringUtils;
import static org.apache.commons.lang.StringUtils.isNotEmpty;
-
-import org.acegisecurity.context.SecurityContext;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.kohsuke.stapler.*;
/**
* Information screen for the use of Git in Hudson.
*/
@Extension
-public class GitStatus extends AbstractModelObject implements UnprotectedRootAction {
+public class GitStatus implements UnprotectedRootAction {
+ @Override
public String getDisplayName() {
return "Git";
}
- public String getSearchUrl() {
- return getUrlName();
- }
-
public String getIconFileName() {
// TODO
return null;
}
+ @Override
public String getUrlName() {
return "git";
}
- public HttpResponse doNotifyCommit(@QueryParameter(required=true) String url,
+ /* Package protected - not part of API, needed for testing */
+ /* package */
+ static void setAllowNotifyCommitParameters(boolean allowed) {
+ allowNotifyCommitParameters = allowed;
+ }
+
+ private String lastURL = ""; // Required query parameter
+ private String lastBranches = null; // Optional query parameter
+ private String lastSHA1 = null; // Optional query parameter
+ private List lastBuildParameters = null;
+ private static List lastStaticBuildParameters = null;
+
+ private static void clearLastStaticBuildParameters() {
+ lastStaticBuildParameters = null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+
+ s.append("URL: ");
+ s.append(lastURL);
+
+ if (lastSHA1 != null) {
+ s.append(" SHA1: ");
+ s.append(lastSHA1);
+ }
+
+ if (lastBranches != null) {
+ s.append(" Branches: ");
+ s.append(lastBranches);
+ }
+
+ if (lastBuildParameters != null && !lastBuildParameters.isEmpty()) {
+ s.append(" Parameters: ");
+ for (ParameterValue buildParameter : lastBuildParameters) {
+ s.append(buildParameter.getName());
+ s.append("='");
+ s.append(buildParameter.getValue());
+ s.append("',");
+ }
+ s.delete(s.length() - 1, s.length());
+ }
+
+ if (lastStaticBuildParameters != null && !lastStaticBuildParameters.isEmpty()) {
+ s.append(" More parameters: ");
+ for (ParameterValue buildParameter : lastStaticBuildParameters) {
+ s.append(buildParameter.getName());
+ s.append("='");
+ s.append(buildParameter.getValue());
+ s.append("',");
+ }
+ s.delete(s.length() - 1, s.length());
+ }
+
+ return s.toString();
+ }
+
+ public HttpResponse doNotifyCommit(HttpServletRequest request, @QueryParameter(required=true) String url,
@QueryParameter(required=false) String branches,
@QueryParameter(required=false) String sha1) throws ServletException, IOException {
+ lastURL = url;
+ lastBranches = branches;
+ lastSHA1 = sha1;
+ lastBuildParameters = null;
+ GitStatus.clearLastStaticBuildParameters();
URIish uri;
+ List buildParameters = new ArrayList<>();
+
try {
uri = new URIish(url);
} catch (URISyntaxException e) {
return HttpResponses.error(SC_BAD_REQUEST, new Exception("Illegal URL: " + url, e));
}
+ if (allowNotifyCommitParameters || !safeParameters.isEmpty()) { // Allow SECURITY-275 bug
+ final Map parameterMap = request.getParameterMap();
+ for (Map.Entry entry : parameterMap.entrySet()) {
+ if (!(entry.getKey().equals("url")) && !(entry.getKey().equals("branches")) && !(entry.getKey().equals("sha1")))
+ if (entry.getValue()[0] != null && (allowNotifyCommitParameters || safeParameters.contains(entry.getKey())))
+ buildParameters.add(new StringParameterValue(entry.getKey(), entry.getValue()[0]));
+ }
+ }
+ lastBuildParameters = buildParameters;
+
branches = Util.fixEmptyAndTrim(branches);
+
String[] branchesArray;
if (branches == null) {
branchesArray = new String[0];
@@ -80,46 +146,38 @@ public HttpResponse doNotifyCommit(@QueryParameter(required=true) String url,
branchesArray = branches.split(",");
}
- final List contributors = new ArrayList();
- for (Listener listener : Jenkins.getInstance().getExtensionList(Listener.class)) {
- contributors.addAll(listener.onNotifyCommit(uri, sha1, branchesArray));
+ final List contributors = new ArrayList<>();
+ Jenkins jenkins = Jenkins.get();
+ String origin = SCMEvent.originOf(request);
+ for (Listener listener : jenkins.getExtensionList(Listener.class)) {
+ contributors.addAll(listener.onNotifyCommit(origin, uri, sha1, buildParameters, branchesArray));
}
- return new HttpResponse() {
- public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node)
- throws IOException, ServletException {
- rsp.setStatus(SC_OK);
- rsp.setContentType("text/plain");
- for (ResponseContributor c : contributors) {
- c.addHeaders(req, rsp);
- }
- PrintWriter w = rsp.getWriter();
- for (ResponseContributor c : contributors) {
- c.writeBody(req, rsp, w);
+ return (StaplerRequest req, StaplerResponse rsp, Object node) -> {
+ rsp.setStatus(SC_OK);
+ rsp.setContentType("text/plain");
+ for (int i = 0; i < contributors.size(); i++) {
+ if (i == MAX_REPORTED_CONTRIBUTORS) {
+ rsp.addHeader("Triggered", "<" + (contributors.size() - i) + " more>");
+ break;
+ } else {
+ contributors.get(i).addHeaders(req, rsp);
}
}
- };
- }
-
- private static Collection getProjectScms(AbstractProject, ?> project) {
- Set projectScms = Sets.newHashSet();
- if (Jenkins.getInstance().getPlugin("multiple-scms") != null) {
- MultipleScmResolver multipleScmResolver = new MultipleScmResolver();
- multipleScmResolver.resolveMultiScmIfConfigured(project, projectScms);
- }
- if (projectScms.isEmpty()) {
- SCM scm = project.getScm();
- if (scm instanceof GitSCM) {
- projectScms.add(((GitSCM) scm));
+ PrintWriter w = rsp.getWriter();
+ for (ResponseContributor c : contributors) {
+ c.writeBody(req, rsp, w);
}
- }
- return projectScms;
+ };
}
/**
* Used to test if what we have in the job configuration matches what was submitted to the notification endpoint.
* It is better to match loosely and wastes a few polling calls than to be pedantic and miss the push notification,
* especially given that Git tends to support multiple access protocols.
+ * @param lhs left-hand side of comparison
+ * @param rhs right-hand side of comparison
+ * @return true if left-hand side loosely matches right-hand side
*/
public static boolean looselyMatches(URIish lhs, URIish rhs) {
return StringUtils.equals(lhs.getHost(),rhs.getHost())
@@ -134,7 +192,7 @@ private static String normalizePath(String path) {
}
/**
- * Contributes to a {@link #doNotifyCommit(String, String, String)} response.
+ * Contributes to a {@link #doNotifyCommit(HttpServletRequest, String, String, String)} response.
*
* @since 1.4.1
*/
@@ -179,25 +237,73 @@ public void writeBody(PrintWriter w) {
public static abstract class Listener implements ExtensionPoint {
/**
- * Called when there is a change notification on a specific repository url.
- *
- * @param uri the repository uri.
- * @param branches the (optional) branch information.
+ * @deprecated implement {@link #onNotifyCommit(org.eclipse.jgit.transport.URIish, String, List, String...)}
+ * @param uri the repository uri.
+ * @param branches the (optional) branch information.
* @return any response contributors for the response to the push request.
- * @since 1.4.1
- * @deprecated implement #onNotifyCommit(org.eclipse.jgit.transport.URIish, String, String...)
*/
public List onNotifyCommit(URIish uri, String[] branches) {
- return onNotifyCommit(uri, null, branches);
+ throw new AbstractMethodError();
}
+ /**
+ * @deprecated implement {@link #onNotifyCommit(org.eclipse.jgit.transport.URIish, String, List, String...)}
+ * @param uri the repository uri.
+ * @param sha1 SHA1 hash of commit to build
+ * @param branches the (optional) branch information.
+ * @return any response contributors for the response to the push request.
+ */
public List onNotifyCommit(URIish uri, @Nullable String sha1, String... branches) {
- return Collections.EMPTY_LIST;
+ return onNotifyCommit(uri, branches);
}
+
+ /**
+ * Called when there is a change notification on a specific repository url.
+ *
+ * @param uri the repository uri.
+ * @param sha1 SHA1 hash of commit to build
+ * @param buildParameters parameters to be passed to the build.
+ * Ignored unless build parameter flag is set
+ * due to security risk of accepting parameters from
+ * unauthenticated sources
+ * @param branches the (optional) branch information.
+ * @return any response contributors for the response to the push request.
+ * @since 2.4.0
+ * @deprecated use {@link #onNotifyCommit(String, URIish, String, List, String...)}
+ */
+ @Deprecated
+ public List onNotifyCommit(URIish uri, @Nullable String sha1, List buildParameters, String... branches) {
+ return onNotifyCommit(uri, sha1, branches);
+ }
+
+ /**
+ * Called when there is a change notification on a specific repository url.
+ *
+ * @param origin the origin of the notification (use {@link SCMEvent#originOf(HttpServletRequest)} if in
+ * doubt) or {@code null} if the origin is unknown.
+ * @param uri the repository uri.
+ * @param sha1 SHA1 hash of commit to build
+ * @param buildParameters parameters to be passed to the build.
+ * Ignored unless build parameter flag is set
+ * due to security risk of accepting parameters from
+ * unauthenticated sources
+ * @param branches the (optional) branch information.
+ * @return any response contributors for the response to the push request.
+ * @since 2.6.5
+ */
+ public List onNotifyCommit(@CheckForNull String origin,
+ URIish uri,
+ @Nullable String sha1,
+ List buildParameters,
+ String... branches) {
+ return onNotifyCommit(uri, sha1, buildParameters, branches);
+ }
+
+
}
/**
- * Handle standard {@link AbstractProject} instances with a standard {@link SCMTrigger}.
+ * Handle standard {@link SCMTriggerItem} instances with a standard {@link SCMTrigger}.
*
* @since 1.4.1
*/
@@ -209,28 +315,46 @@ public static class JenkinsAbstractProjectListener extends Listener {
* {@inheritDoc}
*/
@Override
- public List onNotifyCommit(URIish uri, String sha1, String... branches) {
- List result = new ArrayList();
+ public List onNotifyCommit(String origin, URIish uri, String sha1, List buildParameters, String... branches) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.log(Level.FINE, "Received notification from {0} for uri = {1} ; sha1 = {2} ; branches = {3}",
+ new Object[]{StringUtils.defaultIfBlank(origin, "?"), uri, sha1, Arrays.toString(branches)});
+ }
+
+ GitStatus.clearLastStaticBuildParameters();
+ List allBuildParameters = new ArrayList<>(buildParameters);
+ List result = new ArrayList<>();
// run in high privilege to see all the projects anonymous users don't see.
// this is safe because when we actually schedule a build, it's a build that can
// happen at some random time anyway.
- SecurityContext old = ACL.impersonate(ACL.SYSTEM);
- try {
-
- final List> projects = Lists.newArrayList();
+ try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
boolean scmFound = false,
urlFound = false;
- for (final AbstractProject, ?> project : Hudson.getInstance().getAllItems(AbstractProject.class)) {
- Collection projectSCMs = getProjectScms(project);
- for (GitSCM git : projectSCMs) {
+ Jenkins jenkins = Jenkins.getInstanceOrNull();
+ if (jenkins == null) {
+ LOGGER.severe("Jenkins.getInstance() is null in GitStatus.onNotifyCommit");
+ return result;
+ }
+ for (final Item project : jenkins.getAllItems()) {
+ SCMTriggerItem scmTriggerItem = SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(project);
+ if (scmTriggerItem == null) {
+ continue;
+ }
+ SCMS: for (SCM scm : scmTriggerItem.getSCMs()) {
+ if (!(scm instanceof GitSCM)) {
+ continue;
+ }
+ GitSCM git = (GitSCM) scm;
scmFound = true;
for (RemoteConfig repository : git.getRepositories()) {
boolean repositoryMatches = false,
branchMatches = false;
+ URIish matchedURL = null;
for (URIish remoteURL : repository.getURIs()) {
if (looselyMatches(uri, remoteURL)) {
repositoryMatches = true;
+ matchedURL = remoteURL;
break;
}
}
@@ -239,39 +363,78 @@ public List onNotifyCommit(URIish uri, String sha1, String.
continue;
}
- SCMTrigger trigger = project.getTrigger(SCMTrigger.class);
- if (trigger != null && trigger.isIgnorePostCommitHooks()) {
- LOGGER.info("PostCommitHooks are disabled on " + project.getFullDisplayName());
+ SCMTrigger trigger = scmTriggerItem.getSCMTrigger();
+ if (trigger == null || trigger.isIgnorePostCommitHooks()) {
+ LOGGER.log(Level.INFO, "no trigger, or post-commit hooks disabled, on {0}", project.getFullDisplayName());
continue;
}
- Boolean branchFound = false;
+ boolean branchFound = false,
+ parametrizedBranchSpec = false;
if (branches.length == 0) {
branchFound = true;
} else {
OUT: for (BranchSpec branchSpec : git.getBranches()) {
- for (String branch : branches) {
- if (branchSpec.matches(repository.getName() + "/" + branch)) {
- branchFound = true;
- break OUT;
+ if (branchSpec.getName().contains("$")) {
+ // If the branchspec is parametrized, always run the polling
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.log(Level.FINE, "Branch Spec is parametrized for {0}", project.getFullDisplayName());
+ }
+ branchFound = true;
+ parametrizedBranchSpec = true;
+ } else {
+ for (String branch : branches) {
+ if (branchSpec.matchesRepositoryBranch(repository.getName(), branch)) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.log(Level.FINE, "Branch Spec {0} matches modified branch {1} for {2}", new Object[]{branchSpec, branch, project.getFullDisplayName()});
+ }
+ branchFound = true;
+ break OUT;
+ }
}
}
}
}
if (!branchFound) continue;
urlFound = true;
+ if (!(project instanceof AbstractProject && ((AbstractProject) project).isDisabled())) {
+ //JENKINS-30178 Add default parameters defined in the job
+ if (project instanceof Job) {
+ Set buildParametersNames = new HashSet<>();
+ if (allowNotifyCommitParameters || !safeParameters.isEmpty()) {
+ for (ParameterValue parameterValue: allBuildParameters) {
+ if (allowNotifyCommitParameters || safeParameters.contains(parameterValue.getName())) {
+ buildParametersNames.add(parameterValue.getName());
+ }
+ }
+ }
- if (!project.isDisabled()) {
- if (isNotEmpty(sha1)) {
- LOGGER.info("Scheduling " + project.getFullDisplayName() + " to build commit " + sha1);
- project.scheduleBuild2(project.getQuietPeriod(),
- new CommitHookCause(sha1),
- new RevisionParameterAction(sha1));
+ List jobParametersValues = getDefaultParametersValues((Job) project);
+ for (ParameterValue defaultParameterValue : jobParametersValues) {
+ if (!buildParametersNames.contains(defaultParameterValue.getName())) {
+ allBuildParameters.add(defaultParameterValue);
+ }
+ }
+ }
+ if (!parametrizedBranchSpec && isNotEmpty(sha1)) {
+ /* If SHA1 and not a parameterized branch spec, then schedule build.
+ * NOTE: This is SCHEDULING THE BUILD, not triggering polling of the repo.
+ * If no SHA1 or the branch spec is parameterized, it will only poll.
+ */
+ LOGGER.log(Level.INFO, "Scheduling {0} to build commit {1}", new Object[]{project.getFullDisplayName(), sha1});
+ scmTriggerItem.scheduleBuild2(scmTriggerItem.getQuietPeriod(),
+ new CauseAction(new CommitHookCause(sha1)),
+ new RevisionParameterAction(sha1, matchedURL), new ParametersAction(allBuildParameters));
result.add(new ScheduledResponseContributor(project));
- } else if (trigger != null) {
- LOGGER.info("Triggering the polling of " + project.getFullDisplayName());
+ } else {
+ /* Poll the repository for changes
+ * NOTE: This is not scheduling the build, just polling for changes
+ * If the polling detects changes, it will schedule the build
+ */
+ LOGGER.log(Level.INFO, "Triggering the polling of {0}", project.getFullDisplayName());
trigger.run();
result.add(new PollingScheduledResponseContributor(project));
+ break SCMS; // no need to trigger the same project twice, so do not consider other GitSCMs in it
}
}
break;
@@ -287,14 +450,42 @@ public List onNotifyCommit(URIish uri, String sha1, String.
.join(branches, ",")));
}
+ lastStaticBuildParameters = allBuildParameters;
return result;
- } finally {
- SecurityContextHolder.setContext(old);
}
}
/**
- * A response contributor for triggering polling of an {@link AbstractProject}.
+ * Get the default parameters values from a job
+ *
+ */
+ private ArrayList getDefaultParametersValues(Job,?> job) {
+ ArrayList defValues;
+ ParametersDefinitionProperty paramDefProp = job.getProperty(ParametersDefinitionProperty.class);
+
+ if (paramDefProp != null) {
+ List parameterDefinition = paramDefProp.getParameterDefinitions();
+ defValues = new ArrayList<>(parameterDefinition.size());
+
+ } else {
+ defValues = new ArrayList<>();
+ return defValues;
+ }
+
+ /* Scan for all parameter with an associated default values */
+ for (ParameterDefinition paramDefinition : paramDefProp.getParameterDefinitions()) {
+ ParameterValue defaultValue = paramDefinition.getDefaultParameterValue();
+
+ if (defaultValue != null) {
+ defValues.add(defaultValue);
+ }
+ }
+
+ return defValues;
+ }
+
+ /**
+ * A response contributor for triggering polling of a project.
*
* @since 1.4.1
*/
@@ -302,14 +493,14 @@ private static class PollingScheduledResponseContributor extends ResponseContrib
/**
* The project
*/
- private final AbstractProject, ?> project;
+ private final Item project;
/**
* Constructor.
*
* @param project the project.
*/
- public PollingScheduledResponseContributor(AbstractProject, ?> project) {
+ public PollingScheduledResponseContributor(Item project) {
this.project = project;
}
@@ -334,14 +525,14 @@ private static class ScheduledResponseContributor extends ResponseContributor {
/**
* The project
*/
- private final AbstractProject, ?> project;
+ private final Item project;
/**
* Constructor.
*
* @param project the project.
*/
- public ScheduledResponseContributor(AbstractProject, ?> project) {
+ public ScheduledResponseContributor(Item project) {
this.project = project;
}
@@ -407,5 +598,71 @@ public String getShortDescription() {
}
private static final Logger LOGGER = Logger.getLogger(GitStatus.class.getName());
-}
+ private static final int MAX_REPORTED_CONTRIBUTORS = 10;
+ /** Allow arbitrary notify commit parameters.
+ *
+ * SECURITY-275 detected that allowing arbitrary parameters through
+ * the notifyCommit URL allows an unauthenticated user to set
+ * environment variables for a job.
+ *
+ * If this property is set to true, then the bug exposed by
+ * SECURITY-275 will be brought back. Only enable this if you
+ * trust all unauthenticated users to not pass harmful arguments
+ * to your jobs.
+ *
+ * -Dhudson.plugins.git.GitStatus.allowNotifyCommitParameters=true on command line
+ *
+ * Also honors the global Jenkins security setting
+ * "hudson.model.ParametersAction.keepUndefinedParameters" if it
+ * is set to true.
+ */
+ public static final boolean ALLOW_NOTIFY_COMMIT_PARAMETERS = Boolean.valueOf(System.getProperty(GitStatus.class.getName() + ".allowNotifyCommitParameters", "false"))
+ || Boolean.valueOf(System.getProperty("hudson.model.ParametersAction.keepUndefinedParameters", "false"));
+ private static boolean allowNotifyCommitParameters = ALLOW_NOTIFY_COMMIT_PARAMETERS;
+
+ /* Package protected for test.
+ * If null is passed as argument, safe parameters are reset to defaults.
+ */
+ static void setSafeParametersForTest(String parameters) {
+ safeParameters = csvToSet(parameters != null ? parameters : SAFE_PARAMETERS);
+ }
+
+ private static Set csvToSet(String csvLine) {
+ String[] tokens = csvLine.split(",");
+ Set set = new HashSet<>(Arrays.asList(tokens));
+ return set;
+ }
+
+ @NonNull
+ private static String getSafeParameters() {
+ String globalSafeParameters = System.getProperty("hudson.model.ParametersAction.safeParameters", "").trim();
+ String gitStatusSafeParameters = System.getProperty(GitStatus.class.getName() + ".safeParameters", "").trim();
+ if (globalSafeParameters.isEmpty()) {
+ return gitStatusSafeParameters;
+ }
+ if (gitStatusSafeParameters.isEmpty()) {
+ return globalSafeParameters;
+ }
+ return globalSafeParameters + "," + gitStatusSafeParameters;
+ }
+
+ /**
+ * Allow specifically declared safe parameters.
+ *
+ * SECURITY-275 detected that allowing arbitrary parameters through the
+ * notifyCommit URL allows an unauthenticated user to set environment
+ * variables for a job.
+ *
+ * If this property is set to a comma separated list of parameters, then
+ * those parameters will be allowed for any job. Only set this value for
+ * parameters you trust in all the jobs in your system.
+ *
+ * -Dhudson.plugins.git.GitStatus.safeParameters=PARM1,PARM1 on command line
+ *
+ * Also honors the global Jenkins safe parameter list
+ * "hudson.model.ParametersAction.safeParameters" if set.
+ */
+ public static final String SAFE_PARAMETERS = getSafeParameters();
+ private static Set safeParameters = csvToSet(SAFE_PARAMETERS);
+}
diff --git a/src/main/java/hudson/plugins/git/GitStatusCrumbExclusion.java b/src/main/java/hudson/plugins/git/GitStatusCrumbExclusion.java
new file mode 100644
index 0000000000..72ac266b44
--- /dev/null
+++ b/src/main/java/hudson/plugins/git/GitStatusCrumbExclusion.java
@@ -0,0 +1,32 @@
+package hudson.plugins.git;
+
+import hudson.Extension;
+import hudson.security.csrf.CrumbExclusion;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Make POST to /git/notifyCommit work with CSRF protection on.
+ */
+@Extension
+public class GitStatusCrumbExclusion extends CrumbExclusion {
+
+ @Override
+ public boolean process(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)
+ throws IOException, ServletException {
+ String pathInfo = req.getPathInfo();
+ if (pathInfo != null && pathInfo.equals(getExclusionPath())) {
+ chain.doFilter(req, resp);
+ return true;
+ }
+ return false;
+ }
+
+ public String getExclusionPath() {
+ return "/git/notifyCommit";
+ }
+}
diff --git a/src/main/java/hudson/plugins/git/GitTagAction.java b/src/main/java/hudson/plugins/git/GitTagAction.java
index f2a0eb7bf3..adc2fdcff0 100644
--- a/src/main/java/hudson/plugins/git/GitTagAction.java
+++ b/src/main/java/hudson/plugins/git/GitTagAction.java
@@ -4,8 +4,6 @@
import hudson.Extension;
import hudson.FilePath;
import hudson.model.*;
-import hudson.plugins.git.util.BuildData;
-import hudson.remoting.VirtualChannel;
import hudson.scm.AbstractScmTagAction;
import hudson.security.Permission;
import hudson.util.CopyOnWriteMap;
@@ -17,6 +15,7 @@
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
+import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.servlet.ServletException;
import java.io.File;
@@ -24,7 +23,7 @@
import java.util.*;
/**
- * @author Vivek Pandey
+ * @author Nicolas de Loof
*/
@ExportedBean
public class GitTagAction extends AbstractScmTagAction implements Describable {
@@ -34,21 +33,24 @@ public class GitTagAction extends AbstractScmTagAction implements Describable> tags = new CopyOnWriteMap.Tree>();
+ private final Map> tags = new CopyOnWriteMap.Tree<>();
private final String ws;
- protected GitTagAction(AbstractBuild build, BuildData buildData) {
+ private String lastTagName = null;
+ private GitException lastTagException = null;
+
+ protected GitTagAction(Run build, FilePath workspace, Revision revision) {
super(build);
- List val = new ArrayList();
- this.ws = build.getWorkspace().getRemote();
- for (Branch b : buildData.lastBuild.revision.getBranches()) {
- tags.put(b.getName(), new ArrayList());
+ this.ws = workspace.getRemote();
+ for (Branch b : revision.getBranches()) {
+ tags.put(b.getName(), new ArrayList<>());
}
}
public Descriptor getDescriptor() {
- return Jenkins.getInstance().getDescriptorOrDie(getClass());
+ Jenkins jenkins = Jenkins.get();
+ return jenkins.getDescriptorOrDie(getClass());
}
@Override
@@ -59,12 +61,14 @@ public boolean isTagged() {
return false;
}
+ @Override
public String getIconFileName() {
if (!isTagged() && !getACL().hasPermission(getPermission()))
return null;
return "save.gif";
}
+ @Override
public String getDisplayName() {
int nonNullTag = 0;
for (List v : tags.values()) {
@@ -77,13 +81,14 @@ public String getDisplayName() {
if (nonNullTag == 0)
return "No Tags";
if (nonNullTag == 1)
- return "There is one tag";
+ return "One tag";
else
- return "There are more than one tag";
+ return "Multiple tags";
}
/**
* @see #tags
+ * @return tag names and annotations for this repository
*/
public Map> getTags() {
return Collections.unmodifiableMap(tags);
@@ -91,7 +96,7 @@ public Map> getTags() {
@Exported(name = "tags")
public List getTagInfo() {
- List data = new ArrayList();
+ List data = new ArrayList<>();
for (Map.Entry> e : tags.entrySet()) {
String module = e.getKey();
for (String tag : e.getValue())
@@ -102,7 +107,8 @@ public List getTagInfo() {
@ExportedBean
public static class TagInfo {
- private String module, url;
+ private final String module;
+ private final String url;
private TagInfo(String branch, String tag) {
this.module = branch;
@@ -135,25 +141,43 @@ public String getTooltip() {
/**
* Invoked to actually tag the workspace.
+ * @param req request for submit
+ * @param rsp response used to send result
+ * @throws IOException on input or output error
+ * @throws ServletException on servlet error
*/
+ @RequirePOST
public synchronized void doSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
getACL().checkPermission(getPermission());
- MultipartFormDataParser parser = new MultipartFormDataParser(req);
+ try (MultipartFormDataParser parser = new MultipartFormDataParser(req)) {
- Map newTags = new HashMap();
+ Map newTags = new HashMap<>();
- int i = -1;
- for (String e : tags.keySet()) {
- i++;
- if (tags.size() > 1 && parser.get("tag" + i) == null)
- continue; // when tags.size()==1, UI won't show the checkbox.
- newTags.put(e, parser.get("name" + i));
- }
+ int i = -1;
+ for (String e : tags.keySet()) {
+ i++;
+ if (tags.size() > 1 && parser.get("tag" + i) == null)
+ continue; // when tags.size()==1, UI won't show the checkbox.
+ newTags.put(e, parser.get("name" + i));
+ }
- new TagWorkerThread(newTags, parser.get("comment")).start();
+ scheduleTagCreation(newTags, parser.get("comment"));
- rsp.sendRedirect(".");
+ rsp.sendRedirect(".");
+ }
+ }
+
+ /**
+ * Schedule creation of a tag. For test purposes only, not to be called outside this package.
+ *
+ * @param newTags tags to be created
+ * @param comment tag comment to be included with created tags
+ * @throws IOException on IO error
+ * @throws ServletException on servlet exception
+ */
+ void scheduleTagCreation(Map newTags, String comment) throws IOException, ServletException {
+ new TagWorkerThread(newTags, comment).start();
}
/**
@@ -167,35 +191,37 @@ public final class TagWorkerThread extends TaskThread {
private final String comment;
public TagWorkerThread(Map tagSet,String comment) {
- super(GitTagAction.this, ListenerAndText.forMemory());
+ super(GitTagAction.this, ListenerAndText.forMemory(null));
this.tagSet = tagSet;
this.comment = comment;
}
@Override
protected void perform(final TaskListener listener) throws Exception {
- final EnvVars environment = build.getEnvironment(listener);
+ final EnvVars environment = getRun().getEnvironment(listener);
final FilePath workspace = new FilePath(new File(ws));
final GitClient git = Git.with(listener, environment)
.in(workspace)
.getClient();
- for (String b : tagSet.keySet()) {
+ for (Map.Entry entry : tagSet.entrySet()) {
try {
String buildNum = "jenkins-"
- + build.getProject().getName().replace(" ", "_")
- + "-" + tagSet.get(b);
- git.tag(tagSet.get(b), "Jenkins Build #" + buildNum);
+ + getRun().getParent().getName().replace(" ", "_")
+ + "-" + entry.getValue();
+ git.tag(entry.getValue(), "Jenkins Build #" + buildNum);
+ lastTagName = entry.getValue();
for (Map.Entry e : tagSet.entrySet())
GitTagAction.this.tags.get(e.getKey()).add(e.getValue());
- getBuild().save();
+ getRun().save();
workerThread = null;
}
catch (GitException ex) {
- ex.printStackTrace(listener.error("Error taggin repo '%s' : %s", b, ex.getMessage()));
+ lastTagException = ex;
+ ex.printStackTrace(listener.error("Error tagging repo '%s' : %s", entry.getKey(), ex.getMessage()));
// Failed. Try the next one
listener.getLogger().println("Trying next branch");
}
@@ -214,8 +240,19 @@ public Permission getPermission() {
*/
@Extension
public static class DescriptorImpl extends Descriptor {
+ @Override
public String getDisplayName() {
return "Tag";
}
}
+
+ /* Package protected for use only by tests */
+ String getLastTagName() {
+ return lastTagName;
+ }
+
+ /* Package protected for use only by tests */
+ GitException getLastTagException() {
+ return lastTagException;
+ }
}
diff --git a/src/main/java/hudson/plugins/git/MultipleScmResolver.java b/src/main/java/hudson/plugins/git/MultipleScmResolver.java
deleted file mode 100644
index 401d6405ae..0000000000
--- a/src/main/java/hudson/plugins/git/MultipleScmResolver.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package hudson.plugins.git;
-
-import hudson.model.AbstractProject;
-import hudson.scm.SCM;
-import org.jenkinsci.plugins.multiplescms.MultiSCM;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * @author Noam Y. Tenne
- */
-public class MultipleScmResolver {
-
- public void resolveMultiScmIfConfigured(AbstractProject, ?> project, Set projectScms) {
- SCM projectScm = project.getScm();
- if (projectScm instanceof MultiSCM) {
- List configuredSCMs = ((MultiSCM) projectScm).getConfiguredSCMs();
- for (SCM configuredSCM : configuredSCMs) {
- if (configuredSCM instanceof GitSCM) {
- projectScms.add(((GitSCM) configuredSCM));
- }
- }
-
- }
- }
-}
diff --git a/src/main/java/hudson/plugins/git/ObjectIdConverter.java b/src/main/java/hudson/plugins/git/ObjectIdConverter.java
index 21fb862b18..d6ec3ffb05 100644
--- a/src/main/java/hudson/plugins/git/ObjectIdConverter.java
+++ b/src/main/java/hudson/plugins/git/ObjectIdConverter.java
@@ -37,8 +37,8 @@ public void marshal(Object source, HierarchicalStreamWriter writer,
/**
* Is the current reader node a legacy node?
*
- * @param reader
- * @param context
+ * @param reader stream reader
+ * @param context usage context of reader
* @return true if legacy, false otherwise
*/
protected boolean isLegacyNode(HierarchicalStreamReader reader,
@@ -50,8 +50,8 @@ protected boolean isLegacyNode(HierarchicalStreamReader reader,
/**
* Legacy unmarshalling of object id
*
- * @param reader
- * @param context
+ * @param reader stream reader
+ * @param context usage context of reader
* @return object id
*/
protected Object legacyUnmarshal(HierarchicalStreamReader reader,
diff --git a/src/main/java/hudson/plugins/git/RemoteConfigConverter.java b/src/main/java/hudson/plugins/git/RemoteConfigConverter.java
index 9c11bacd9e..e1446f9af1 100644
--- a/src/main/java/hudson/plugins/git/RemoteConfigConverter.java
+++ b/src/main/java/hudson/plugins/git/RemoteConfigConverter.java
@@ -90,21 +90,32 @@ private void fromMap(Map> map) {
for (Entry> entry : map.entrySet()) {
String key = entry.getKey();
Collection values = entry.getValue();
- if (KEY_URL.equals(key))
- uris = values.toArray(new String[values.size()]);
- else if (KEY_FETCH.equals(key))
- fetch = values.toArray(new String[values.size()]);
- else if (KEY_PUSH.equals(key))
- push = values.toArray(new String[values.size()]);
- else if (KEY_UPLOADPACK.equals(key))
- for (String value : values)
- uploadpack = value;
- else if (KEY_RECEIVEPACK.equals(key))
- for (String value : values)
- receivepack = value;
- else if (KEY_TAGOPT.equals(key))
- for (String value : values)
- tagopt = value;
+ if (null != key)
+ switch (key) {
+ case KEY_URL:
+ uris = values.toArray(new String[0]);
+ break;
+ case KEY_FETCH:
+ fetch = values.toArray(new String[0]);
+ break;
+ case KEY_PUSH:
+ push = values.toArray(new String[0]);
+ break;
+ case KEY_UPLOADPACK:
+ for (String value : values)
+ uploadpack = value;
+ break;
+ case KEY_RECEIVEPACK:
+ for (String value : values)
+ receivepack = value;
+ break;
+ case KEY_TAGOPT:
+ for (String value : values)
+ tagopt = value;
+ break;
+ default:
+ break;
+ }
}
}
@@ -112,13 +123,13 @@ public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
name = in.readUTF();
final int items = in.readInt();
- Map> map = new HashMap>();
+ Map> map = new HashMap<>();
for (int i = 0; i < items; i++) {
String key = in.readUTF();
String value = in.readUTF();
Collection values = map.get(key);
if (values == null) {
- values = new ArrayList();
+ values = new ArrayList<>();
map.put(key, values);
}
values.add(value);
@@ -132,7 +143,7 @@ public void writeExternal(ObjectOutput out) throws IOException {
/**
* @return remote config
- * @throws URISyntaxException
+ * @throws URISyntaxException on incorrect URI syntax
*/
public RemoteConfig toRemote() throws URISyntaxException {
return new RemoteConfig(this, name);
@@ -143,9 +154,9 @@ public RemoteConfig toRemote() throws URISyntaxException {
private final SerializableConverter converter;
/**
- * Create remote config converter
+ * Create remote config converter.
*
- * @param xStream
+ * @param xStream XStream used for remote configuration conversion
*/
public RemoteConfigConverter(XStream xStream) {
mapper = xStream.getMapper();
@@ -165,8 +176,8 @@ public void marshal(Object source, HierarchicalStreamWriter writer,
/**
* Is the current reader node a legacy node?
*
- * @param reader
- * @param context
+ * @param reader stream reader
+ * @param context usage context of reader
* @return true if legacy, false otherwise
*/
protected boolean isLegacyNode(HierarchicalStreamReader reader,
@@ -177,8 +188,8 @@ protected boolean isLegacyNode(HierarchicalStreamReader reader,
/**
* Legacy unmarshalling of remote config
*
- * @param reader
- * @param context
+ * @param reader stream reader
+ * @param context usage context of reader
* @return remote config
*/
protected Object legacyUnmarshal(final HierarchicalStreamReader reader,
@@ -218,11 +229,7 @@ public void close() {
proxy.readExternal(objectInput);
objectInput.popCallback();
return proxy.toRemote();
- } catch (IOException e) {
- throw new ConversionException("Unmarshal failed", e);
- } catch (ClassNotFoundException e) {
- throw new ConversionException("Unmarshal failed", e);
- } catch (URISyntaxException e) {
+ } catch (IOException | ClassNotFoundException | URISyntaxException e) {
throw new ConversionException("Unmarshal failed", e);
}
}
diff --git a/src/main/java/hudson/plugins/git/RevisionParameterAction.java b/src/main/java/hudson/plugins/git/RevisionParameterAction.java
index 608544902b..881b70253e 100644
--- a/src/main/java/hudson/plugins/git/RevisionParameterAction.java
+++ b/src/main/java/hudson/plugins/git/RevisionParameterAction.java
@@ -29,10 +29,14 @@
import hudson.model.Queue;
import hudson.model.Queue.QueueAction;
import hudson.model.queue.FoldableAction;
+
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
import org.jenkinsci.plugins.gitclient.GitClient;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
@@ -50,15 +54,25 @@ public class RevisionParameterAction extends InvisibleAction implements Serializ
public final String commit;
public final boolean combineCommits;
public final Revision revision;
+ private final URIish repoURL;
public RevisionParameterAction(String commit) {
- this(commit, false);
+ this(commit, false, null);
+ }
+
+ public RevisionParameterAction(String commit, URIish repoURL) {
+ this(commit, false, repoURL);
}
public RevisionParameterAction(String commit, boolean combineCommits) {
+ this(commit, combineCommits, null);
+ }
+
+ public RevisionParameterAction(String commit, boolean combineCommits, URIish repoURL) {
this.commit = commit;
this.combineCommits = combineCommits;
this.revision = null;
+ this.repoURL = repoURL;
}
public RevisionParameterAction(Revision revision) {
@@ -69,6 +83,7 @@ public RevisionParameterAction(Revision revision, boolean combineCommits) {
this.revision = revision;
this.commit = revision.getSha1String();
this.combineCommits = combineCommits;
+ this.repoURL = null;
}
@Deprecated
@@ -82,11 +97,62 @@ public Revision toRevision(GitClient git) throws InterruptedException {
}
ObjectId sha1 = git.revParse(commit);
Revision revision = new Revision(sha1);
- // all we have is a sha1 so make the branch 'detached'
- revision.getBranches().add(new Branch("detached", sha1));
+ // Here we do not have any local branches, containing the commit. So...
+ // we are to get all the remote branches, and show them to users, as
+ // they are local
+ final List branches = normalizeBranches(git.getBranchesContaining(
+ ObjectId.toString(sha1), true));
+ revision.getBranches().addAll(branches);
return revision;
}
+ /**
+ * This method tries to determine whether the commit is from given remotes.
+ * To achieve that it uses remote URL supplied during construction of this instance.
+ *
+ * @param remotes candidate remotes for this commit
+ * @return false if remote URL was supplied during construction and matches none
+ * of given remote URLs, otherwise true
+ */
+ public boolean canOriginateFrom(Iterable remotes) {
+ if (repoURL == null) {
+ return true;
+ }
+
+ for (RemoteConfig remote : remotes) {
+ for (URIish remoteURL : remote.getURIs()) {
+ if (remoteURL.equals(repoURL)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This method is aimed to normalize all the branches to the same naming
+ * convention, as {@link GitClient#getBranchesContaining(String, boolean)}
+ * returns branches with "remotes/" prefix.
+ * @param branches branches, retrieved from git client
+ * @return list of branches without the "remote/" prefix.
+ */
+ private List normalizeBranches(List branches) {
+ final List normalBranches = new ArrayList<>(branches.size());
+ final String remotesPrefix = "remotes/";
+ for (Branch initialBranch : branches) {
+ final String initialBranchName = initialBranch.getName();
+ final Branch normalBranch;
+ if (initialBranchName.startsWith(remotesPrefix)) {
+ final String normalName = initialBranchName.substring(remotesPrefix.length());
+ normalBranch = new Branch(normalName, initialBranch.getSHA1());
+ } else {
+ normalBranch = initialBranch;
+ }
+ normalBranches.add(normalBranch);
+ }
+ return normalBranches;
+ }
+
@Override
public String toString() {
return super.toString()+"[commit="+commit+"]";
@@ -128,19 +194,13 @@ public boolean shouldSchedule(List actions) {
public void foldIntoExisting(Queue.Item item, Queue.Task owner, List otherActions) {
// only do this if we are asked to.
if(combineCommits) {
- RevisionParameterAction existing = item.getAction(RevisionParameterAction.class);
- if (existing!=null) {
- //because we cannot modify the commit in the existing action remove it and add self
- item.getActions().remove(existing);
- item.getActions().add(this);
- return;
- }
- // no CauseAction found, so add a copy of this one
- item.getActions().add(this);
+ //because we cannot modify the commit in the existing action remove it and add self
+ // or no CauseAction found, so add a copy of this one
+ item.replaceAction(this);
}
}
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 2L;
private static final Logger LOGGER = Logger.getLogger(RevisionParameterAction.class.getName());
}
diff --git a/src/main/java/hudson/plugins/git/SubmoduleCombinator.java b/src/main/java/hudson/plugins/git/SubmoduleCombinator.java
index a0d1347a10..83f22237ec 100644
--- a/src/main/java/hudson/plugins/git/SubmoduleCombinator.java
+++ b/src/main/java/hudson/plugins/git/SubmoduleCombinator.java
@@ -1,6 +1,5 @@
package hudson.plugins.git;
-import hudson.FilePath;
import hudson.model.TaskListener;
import hudson.plugins.git.util.GitUtils;
import org.eclipse.jgit.lib.ObjectId;
@@ -11,7 +10,7 @@
import java.util.Map.Entry;
/**
- * A common usecase for git submodules is to have child submodules, and a parent 'configuration' project that ties the
+ * A common use case for git submodules is to have child submodules, and a parent 'configuration' project that ties the
* correct versions together. It is useful to be able to speculatively compile all combinations of submodules, so that
* you can _know_ if a particular combination is no longer compatible.
*
@@ -19,7 +18,6 @@
*/
public class SubmoduleCombinator {
GitClient git;
- FilePath workspace;
TaskListener listener;
long tid = new Date().getTime();
@@ -30,13 +28,11 @@ public class SubmoduleCombinator {
public SubmoduleCombinator(GitClient git, TaskListener listener, Collection cfg) {
this.git = git;
this.listener = listener;
-
- this.workspace = git.getWorkTree();
this.submoduleConfig = cfg;
}
public void createSubmoduleCombinations() throws GitException, IOException, InterruptedException {
- Map> moduleBranches = new HashMap