Skip to content
Permalink
Browse files

[FIXED JENKINS-7594] Merges across named branches should not be ignored.

Rewriting compareRemoteRevisionWith to use 'hg st --rev ... --rev ...' which is simpler and more robust than examining intermediate csets.
  • Loading branch information...
Jesse Glick
Jesse Glick committed Jan 4, 2012
1 parent 3dd018a commit 3b54094736fc31fe159c2f5fa80d91b64db47851
@@ -24,6 +24,7 @@
package hudson.plugins.mercurial;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.FilePath;
@@ -166,9 +167,10 @@ public ProcStarter run(ArgumentListBuilder args) {

/**
* Gets the revision ID of the tip of the workspace.
* @param rev the revision to identify; defaults to {@code .}, i.e. working copy
*/
public @CheckForNull String tip(FilePath repository) throws IOException, InterruptedException {
String id = popen(repository, listener, false, new ArgumentListBuilder("log", "--rev", ".", "--template", "{node}"));
public @CheckForNull String tip(FilePath repository, @Nullable String rev) throws IOException, InterruptedException {
String id = popen(repository, listener, false, new ArgumentListBuilder("log", "--rev", rev != null ? rev : ".", "--template", "{node}"));
if (!REVISIONID_PATTERN.matcher(id).matches()) {
listener.error("Expected to get an id but got '" + id + "' instead.");
return null; // HUDSON-7723
@@ -25,13 +25,11 @@
import hudson.util.ArgumentListBuilder;
import hudson.util.ForkOutputStream;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.Serializable;
@@ -225,12 +223,10 @@ public SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?, ?> build, Launch
throws IOException, InterruptedException {
// tag action is added during checkout, so this shouldn't be called, but just in case.
HgExe hg = new HgExe(this, launcher, build, listener, build.getEnvironment(listener));
String tip = hg.tip(workspace2Repo(build.getWorkspace()));
String tip = hg.tip(workspace2Repo(build.getWorkspace()), null);
return tip != null ? new MercurialTagAction(tip) : null;
}

private static final String FILES_STYLE = "changeset = 'id:{node}\\nfiles:{files}\\n'\n" + "file = '{file}:'";

@Override
protected PollingResult compareRemoteRevisionWith(AbstractProject<?, ?> project, Launcher launcher, FilePath workspace,
TaskListener listener, SCMRevisionState _baseline) throws IOException, InterruptedException {
@@ -240,30 +236,24 @@ protected PollingResult compareRemoteRevisionWith(AbstractProject<?, ?> project,

// XXX do canUpdate check similar to in checkout, and possibly return INCOMPARABLE

// Mercurial requires the style file to be in a file..
Set<String> changedFileNames = new HashSet<String>();
FilePath tmpFile = workspace.createTextTempFile("tmp", "style", FILES_STYLE);
try {
// Get the list of changed files.
Node node = project.getLastBuiltOn(); // HUDSON-5984: ugly but matches what AbstractProject.poll uses

FilePath repository = workspace2Repo(workspace);
pull(launcher, repository, listener, output, node,getBranch());

ArgumentListBuilder logCmd = findHgExe(node, listener, false);
logCmd.add("log", "--style", tmpFile.getRemote());
logCmd.add("--branch", getBranch());
logCmd.add("--no-merges");

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ForkOutputStream fos = new ForkOutputStream(baos, output);

logCmd.add("--prune", baseline.id);
joinWithPossibleTimeout(
launch(launcher).cmds(logCmd).stdout(fos).pwd(repository),
true, listener);
HgExe hg = new HgExe(this, launcher, node, listener, /*XXX*/new EnvVars());
String remote = hg.tip(repository, getBranch());
if (remote == null) {
throw new IOException("failed to find ID of branch head");
}
if (remote.equals(baseline.id)) { // shortcut
return new PollingResult(baseline, new MercurialTagAction(remote), Change.NONE);
}
Set<String> changedFileNames = parseStatus(hg.popen(repository, listener, false, new ArgumentListBuilder("status", "--rev", baseline.id, "--rev", remote)));

MercurialTagAction cur = parsePollingLogOutput(baos, baseline, changedFileNames);
MercurialTagAction cur = new MercurialTagAction(remote);
return new PollingResult(baseline,cur,computeDegreeOfChanges(changedFileNames,output));
} catch(IOException e) {
if (causedByMissingHg(e)) {
@@ -274,11 +264,18 @@ protected PollingResult compareRemoteRevisionWith(AbstractProject<?, ?> project,
IOException ex = new IOException("Failed to compare with remote repository");
ex.initCause(e);
throw ex;
} finally {
tmpFile.delete();
}
}

static Set<String> parseStatus(String status) {
Set<String> result = new HashSet<String>();
Matcher m = Pattern.compile("(?m)^[ARM] (.+)").matcher(status);
while (m.find()) {
result.add(m.group(1));
}
return result;
}

private void pull(Launcher launcher, FilePath repository, TaskListener listener, PrintStream output, Node node, String branch) throws IOException, InterruptedException {
ArgumentListBuilder cmd = findHgExe(node, listener, false);
cmd.add("pull");
@@ -338,36 +335,6 @@ private Change computeDegreeOfChanges(Set<String> changedFileNames, PrintStream
return affecting;
}

private static Pattern FILES_LINE = Pattern.compile("files:(.*)");

private MercurialTagAction parsePollingLogOutput(ByteArrayOutputStream output, MercurialTagAction baseline, Set<String> result) throws IOException {
String headId = null; // the tip of the remote revision
BufferedReader in = new BufferedReader(new InputStreamReader(
new ByteArrayInputStream(output.toByteArray())));
String line;
while ((line = in.readLine()) != null) {
Matcher matcher = FILES_LINE.matcher(line);
if (matcher.matches()) {
for (String s : matcher.group(1).split(":")) {
if (s.length() > 0) {
result.add(s);
}
}
}
if (line.startsWith("id:")) {
String id = line.substring(3);
if (headId == null) {
headId = id;
}
}
}

if (headId==null) {
return baseline; // no new revisions found
}
return new MercurialTagAction(headId);
}

public static MercurialInstallation findInstallation(String name) {
for (MercurialInstallation inst : MercurialInstallation.allInstallations()) {
if (inst.getName().equals(name)) {
@@ -548,7 +515,7 @@ private void update(AbstractBuild<?, ?> build, Launcher launcher, FilePath repos
}
}

String tip = hg.tip(repository);
String tip = hg.tip(repository, null);
if (tip != null) {
build.addAction(new MercurialTagAction(tip));
}
@@ -627,7 +594,7 @@ private void clone(AbstractBuild<?,?> build, Launcher launcher, FilePath reposit
upArgs.add("--rev", getBranch(env));
hg.run(upArgs).pwd(repository).join();

String tip = hg.tip(repository);
String tip = hg.tip(repository, null);
if (tip != null) {
build.addAction(new MercurialTagAction(tip));
}
@@ -657,7 +624,7 @@ public String getModules() {

private boolean causedByMissingHg(IOException e) {
String message = e.getMessage();
return message.startsWith("Cannot run program") && message.endsWith("No such file or directory");
return message != null && message.startsWith("Cannot run program") && message.endsWith("No such file or directory");
}

static boolean CACHE_LOCAL_REPOS = false;
@@ -105,6 +105,7 @@ public void testPollingLimitedToModules() throws Exception {
// No support for partial checkouts yet, so workspace will contain
// everything.
buildAndCheck(p, "dir3/f");
/* superseded by JENKINS-7594:
// HUDSON-4972: do not pay attention to merges
// (reproduce using the pathological scenario, since reproducing the
// actual scenario
@@ -117,6 +118,7 @@ public void testPollingLimitedToModules() throws Exception {
pr = pollSCMChanges(p);
assertEquals(PollingResult.Change.INSIGNIFICANT, pr.change);
buildAndCheck(p, "dir4/f");
*/
}

@Bug(6337)
@@ -138,6 +140,61 @@ public void testPollingLimitedToModules2() throws Exception {
buildAndCheck(p, "dir1/f");
}

public void testParseStatus() throws Exception {
assertEquals(new HashSet<String>(Arrays.asList("whatever", "added", "mo-re", "whatever-c", "initial", "more")), MercurialSCM.parseStatus(
"M whatever\n"
+ "A added\n"
+ "A mo-re\n"
+ " more\n"
+ "A whatever-c\n"
+ " whatever\n"
+ "R initial\n"
+ "R more\n"));
}

@Bug(7594)
public void testPollingHonorsBranchMerges() throws Exception {
FreeStyleProject p = createFreeStyleProject();
p.setScm(new MercurialSCM(hgInstallation, repo.getPath(), null, null, null, null, false));
hg(repo, "init");
touchAndCommit(repo, "starter");
pollSCMChanges(p);
buildAndCheck(p, "starter");
hg(repo, "branch", "b");
touchAndCommit(repo, "feature");
hg(repo, "update", "default");
hg(repo, "merge", "b");
hg(repo, "commit", "--message", "merged");
PollingResult pr = pollSCMChanges(p);
assertEquals(PollingResult.Change.SIGNIFICANT, pr.change);
buildAndCheck(p, "feature");
}

@Bug(7594)
public void testPollingHonorsBranchMergesWithModules() throws Exception {
FreeStyleProject p = createFreeStyleProject();
p.setScm(new MercurialSCM(hgInstallation, repo.getPath(), null, "mod1", null, null, false));
hg(repo, "init");
touchAndCommit(repo, "starter");
pollSCMChanges(p);
buildAndCheck(p, "starter");
hg(repo, "branch", "mod1dev");
touchAndCommit(repo, "mod1/feature");
hg(repo, "update", "default");
hg(repo, "merge", "mod1dev");
hg(repo, "commit", "--message", "merged");
PollingResult pr = pollSCMChanges(p);
assertEquals(PollingResult.Change.SIGNIFICANT, pr.change);
buildAndCheck(p, "mod1/feature");
hg(repo, "branch", "mod2dev");
touchAndCommit(repo, "mod2/feature");
hg(repo, "update", "default");
hg(repo, "merge", "mod2dev");
hg(repo, "commit", "--message", "merged");
pr = pollSCMChanges(p);
assertEquals(PollingResult.Change.INSIGNIFICANT, pr.change);
}

@Bug(4702)
public void testChangelogLimitedToModules() throws Exception {
FreeStyleProject p = createFreeStyleProject();

0 comments on commit 3b54094

Please sign in to comment.
You can’t perform that action at this time.