Skip to content
Permalink
Browse files

[JENKINS-26587] Fix multi-scm checkout sporadic failures

- add test for the bug

- fix bug by excluding revisions which didn't come from the specific
  repository being evaluated

- remote URL is stored in RevisionParameterAction

- extend TestGitRepo to return commit SHA1 for the committed file

- commit notification log message no longer contains "null" when no
  repository URL is supplied
  • Loading branch information
toyPoodle authored and MarkEWaite committed Oct 10, 2015
1 parent c84fc2c commit 1d2fb9a41e71607321c2b4838e72d37ab1cd9fbe
@@ -934,7 +934,14 @@ public EnvVars getEnvironment() {
if (candidates.isEmpty() ) {
final RevisionParameterAction rpa = build.getAction(RevisionParameterAction.class);
if (rpa != null) {
candidates = Collections.singleton(rpa.toRevision(git));
// 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");
}
}
}

@@ -231,9 +231,11 @@ public void writeBody(PrintWriter w) {
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;
}
}
@@ -282,7 +284,7 @@ public void writeBody(PrintWriter w) {
LOGGER.info("Scheduling " + project.getFullDisplayName() + " to build commit " + sha1);
scmTriggerItem.scheduleBuild2(scmTriggerItem.getQuietPeriod(),
new CauseAction(new CommitHookCause(sha1)),
new RevisionParameterAction(sha1), new ParametersAction(buildParameters));
new RevisionParameterAction(sha1, matchedURL), new ParametersAction(buildParameters));
result.add(new ScheduledResponseContributor(project));
} else {
LOGGER.info("Triggering the polling of " + project.getFullDisplayName());
@@ -31,6 +31,8 @@
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;
@@ -52,15 +54,25 @@
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) {
@@ -71,6 +83,7 @@ public RevisionParameterAction(Revision revision, boolean combineCommits) {
this.revision = revision;
this.commit = revision.getSha1String();
this.combineCommits = combineCommits;
this.repoURL = null;
}

@Deprecated
@@ -93,6 +106,29 @@ public Revision toRevision(GitClient git) throws InterruptedException {
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 <code>false</code> if remote URL was supplied during construction and matches none
* of given remote URLs, otherwise <code>true</code>
*/
public boolean canOriginateFrom(Iterable<RemoteConfig> 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)}
@@ -170,7 +206,7 @@ public void foldIntoExisting(Queue.Item item, Queue.Task owner, List<Action> oth
}
}

private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
private static final Logger LOGGER = Logger.getLogger(RevisionParameterAction.class.getName());
}

@@ -0,0 +1,119 @@
package hudson.plugins.git;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.jenkinsci.plugins.multiplescms.MultiSCM;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.CaptureEnvironmentBuilder;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;

import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.FreeStyleProject;
import hudson.model.TaskListener;
import hudson.plugins.git.extensions.GitSCMExtension;
import hudson.scm.SCM;
import hudson.triggers.SCMTrigger;
import hudson.util.StreamTaskListener;

import javax.servlet.http.HttpServletRequest;

import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

public class GitStatusMultipleSCMTest {

@Rule public JenkinsRule r = new JenkinsRule();
@Rule public TemporaryFolder tmp = new TemporaryFolder();

private GitStatus gitStatus;
private TestGitRepo repo0;
private TestGitRepo repo1;
private FreeStyleProject testProject;

@Before
public void setUp() throws Exception {
gitStatus = new GitStatus();
TaskListener listener = StreamTaskListener.fromStderr();
repo0 = new TestGitRepo("repo0", tmp.newFolder(), listener);
repo1 = new TestGitRepo("repo1", tmp.newFolder(), listener);
testProject = r.createFreeStyleProject();
}

@Test
@Issue("JENKINS-26587")
public void commitNotificationIsPropagatedOnlyToSourceRepository() throws Exception {
setupProject("master", false);

repo0.commit("repo0", repo1.johnDoe, "repo0 commit 1");
final String repo1sha1 = repo1.commit("repo1", repo1.janeDoe, "repo1 commit 1");

this.gitStatus.doNotifyCommit(requestWithoutParameters(), repo1.remoteConfigs().get(0).getUrl(), null, repo1sha1);
assertTrue("expected a build start on notify", r.isSomethingHappening());

r.waitUntilNoActivity();

final List<AbstractProject> projects = r.getInstance().getAllItems(AbstractProject.class);
assertThat("should contain previously created project", projects.size(), greaterThan(0));

for (AbstractProject project : projects) {
final AbstractBuild lastBuild = project.getLastBuild();
assertNotNull("one build should've been built after notification", lastBuild);
r.assertBuildStatusSuccess(lastBuild);
}
}

private HttpServletRequest requestWithoutParameters() {
return mock(HttpServletRequest.class);
}

private SCMTrigger setupProject(String branchString, boolean ignoreNotifyCommit) throws Exception {
SCMTrigger trigger = new SCMTrigger("", ignoreNotifyCommit);
setupProject(branchString, trigger);
return trigger;
}

private void setupProject(String branchString, SCMTrigger trigger) throws Exception {
List<BranchSpec> branch = Collections.singletonList(new BranchSpec(branchString));

SCM repo0Scm = new GitSCM(
repo0.remoteConfigs(),
branch,
false,
Collections.<SubmoduleConfig>emptyList(),
null,
null,
Collections.<GitSCMExtension>emptyList());

SCM repo1Scm = new GitSCM(
repo1.remoteConfigs(),
branch,
false,
Collections.<SubmoduleConfig>emptyList(),
null,
null,
Collections.<GitSCMExtension>emptyList());

List<SCM> testScms = new ArrayList<SCM>();
testScms.add(repo0Scm);
testScms.add(repo1Scm);

MultiSCM scm = new MultiSCM(testScms);

testProject.setScm(scm);
testProject.getBuildersList().add(new CaptureEnvironmentBuilder());

if (trigger != null) {
testProject.addTrigger(trigger);
}
}
}
@@ -0,0 +1,66 @@
package hudson.plugins.git;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class RevisionParameterActionRemoteUrlTest {

@Test
public void noRemoteURLSet() throws Exception {
RevisionParameterAction target = new RevisionParameterAction("sha1");
URIish remoteURL = new URIish("https://github.com/jenkinsci/git-plugin.git");
assertTrue("should always return true when no remote set", target.canOriginateFrom(remotes(remoteURL)));
}

@Test
public void remoteURLSetButDoesntMatch() throws Exception {
URIish actionURL = new URIish("https://github.com/jenkinsci/multiple-scms-plugin.git");
RevisionParameterAction target = new RevisionParameterAction("sha1", actionURL);

URIish remoteURL = new URIish("https://github.com/jenkinsci/git-plugin.git");
assertFalse("should return false on different remotes", target.canOriginateFrom(remotes(remoteURL)));
}

@Test
public void remoteURLSetAndMatches() throws Exception {
URIish actionURL = new URIish("https://github.com/jenkinsci/git-plugin.git");
RevisionParameterAction target = new RevisionParameterAction("sha1", actionURL);

URIish remoteURL = new URIish("https://github.com/jenkinsci/git-plugin.git");
assertTrue("should return true on same remotes", target.canOriginateFrom(remotes(remoteURL)));
}

@Test
public void multipleRemoteURLsSetAndOneMatches() throws Exception {
URIish actionURL = new URIish("https://github.com/jenkinsci/git-plugin.git");
RevisionParameterAction target = new RevisionParameterAction("sha1", actionURL);

URIish remoteURL1 = new URIish("https://github.com/jenkinsci/multiple-scms-plugin.git");
URIish remoteURL2 = new URIish("https://github.com/jenkinsci/git-plugin.git");
assertTrue("should return true when any remote matches", target.canOriginateFrom(remotes(remoteURL1, remoteURL2)));
}

private List<RemoteConfig> remotes(URIish... remoteURLs) {
List<RemoteConfig> result = new ArrayList<RemoteConfig>();
for (URIish remoteURL : remoteURLs) {
result.add(remote(remoteURL));
}
return result;
}

private RemoteConfig remote(URIish remoteURL) {
RemoteConfig result = mock(RemoteConfig.class);
when(result.getURIs()).thenReturn(Arrays.asList(remoteURL));
return result;
}
}

0 comments on commit 1d2fb9a

Please sign in to comment.