Skip to content
Permalink
Browse files
[JENKINS-30603] Squash strategy now respects single commit messages.
  • Loading branch information
praqma-thi committed Sep 24, 2015
1 parent e93ad80 commit 9b6d14d13a44b848c756e5157fba4f3743b1b3fe
@@ -0,0 +1,40 @@
package org.jenkinsci.plugins.pretestedintegration.scm.git;

import hudson.model.TaskListener;
import hudson.remoting.VirtualChannel;
import java.io.IOException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;

public class GetCommitCountFromBranchCallback extends RepositoryListenerAwareCallback<Integer> {

public final ObjectId startObjectId;
public final String targetBranchName;

public GetCommitCountFromBranchCallback(TaskListener listener, final ObjectId startObjectId, final String targetBranchName) {
super(listener);
this.startObjectId = startObjectId;
this.targetBranchName = targetBranchName;
}

@Override
public Integer invoke(Repository repository, VirtualChannel channel) throws IOException, InterruptedException {
RevWalk walker = new RevWalk(repository);
RevCommit originCommit = walker.parseCommit(startObjectId);
ObjectId targetId = repository.resolve(targetBranchName);
RevCommit targetCommit = walker.parseCommit(targetId);

walker.markStart(originCommit);
walker.markUninteresting(targetCommit);

int commitCount = 0;
for (RevCommit rev : walker) {
commitCount++;
}
walker.dispose();

return commitCount;
}
}
@@ -1,6 +1,5 @@
package org.jenkinsci.plugins.pretestedintegration.scm.git;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
@@ -9,25 +8,19 @@
import hudson.model.TaskListener;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Cause;
import hudson.model.Result;
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.Revision;
import hudson.plugins.git.UserRemoteConfig;
import hudson.plugins.git.extensions.impl.RelativeTargetDirectory;
import hudson.plugins.git.util.BuildData;
import hudson.scm.SCM;
import hudson.triggers.SCMTrigger.SCMTriggerCause;
import hudson.util.ArgumentListBuilder;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -36,6 +29,7 @@
import jenkins.model.Jenkins;

import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.ObjectId;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.jenkinsci.plugins.multiplescms.MultiSCM;
@@ -49,7 +43,6 @@
import org.jenkinsci.plugins.pretestedintegration.PretestedIntegrationBuildWrapper;
import org.jenkinsci.plugins.pretestedintegration.exceptions.CommitChangesFailureException;
import org.jenkinsci.plugins.pretestedintegration.exceptions.DeleteIntegratedBranchException;
import org.jenkinsci.plugins.pretestedintegration.exceptions.NextCommitFailureException;
import org.jenkinsci.plugins.pretestedintegration.exceptions.NothingToDoException;
import org.jenkinsci.plugins.pretestedintegration.exceptions.UnsupportedConfigurationException;
import org.kohsuke.stapler.DataBoundConstructor;
@@ -232,7 +225,7 @@ public int git(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listen
@Override
public void ensureBranch(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener, String branch) throws EstablishWorkspaceException {
logger.entering("GitBridge", "ensureBranch", new Object[] { build, branch, listener, launcher });// Generated code DONT TOUCH! Bookmark: eb203ba8b33b4c38087310c398984c1a
listener.getLogger().println(String.format(LOG_PREFIX + "Checking out integration branch %s:", getBranch()));
listener.getLogger().println(String.format(LOG_PREFIX + "Checking out integration branch %s:", getBranch()));
try {
//We need to explicitly checkout the remote we have configured
//git(build, launcher, listener, "checkout", getBranch(), resolveRepoName()+"/"+getBranch());
@@ -543,6 +536,18 @@ private boolean validateMultiScm(List<SCM> scms) throws UnsupportedConfiguration
return true;
}

public int countCommits(AbstractBuild<?, ?> build, BuildListener listener) throws IOException, InterruptedException {
ObjectId commitId = getCommitId(build);
GitClient client = Git.with(listener, build.getEnvironment(listener)).in(resolveWorkspace(build, listener)).getClient();
GetCommitCountFromBranchCallback commitCountCallback = new GetCommitCountFromBranchCallback(listener, commitId, getBranch());
int commitCount = client.withRepository(commitCountCallback);
return commitCount;
}

public ObjectId getCommitId(AbstractBuild<?, ?> build) throws IOException, InterruptedException {
return checkAndDetermineRelevantBuildData(build.getActions(BuildData.class)).lastBuild.revision.getSha1();
}

@Override
public void handlePostBuild(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws IOException {
logger.entering("GitBridge", "handlePostBuild", new Object[] { build, listener, launcher });
@@ -11,6 +11,7 @@
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitException;
import hudson.plugins.git.util.BuildData;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;
@@ -23,6 +24,7 @@
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.jgit.lib.ObjectId;
import org.jenkinsci.plugins.pretestedintegration.exceptions.UnsupportedConfigurationException;

/**
@@ -47,6 +49,7 @@ public void integrate(AbstractBuild<?,?> build, Launcher launcher, BuildListener
int exitCodeCommit = unLikelyExitCode;
GitBridge gitbridge = (GitBridge) bridge;

if(tryRebase(build, launcher, listener, gitbridge)) return;

ByteArrayOutputStream out = new ByteArrayOutputStream();

@@ -223,6 +226,53 @@ public void integrate(AbstractBuild<?,?> build, Launcher launcher, BuildListener
listener.getLogger().println(String.format(LOG_PREFIX + "Commit was successful"));
}

/**
* Rebases the ready branch onto the integration branch.
* ONLY when the ready branch consists of a single commit.
*
* @return true if the rebase was a success, false if the branch isn't suitable for a rebase
* @throws IntegationFailedExeception when the rebase was a failure
*/
private boolean tryRebase(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener, GitBridge bridge) throws IntegationFailedExeception {
logger.log(Level.INFO, String.format(LOG_PREFIX + "Entering tryRebase"));

//Get the commit count
int commitCount;
try {
commitCount = bridge.countCommits(build, listener);
logger.log(Level.INFO, String.format(LOG_PREFIX + "Branch commit count: " + commitCount));
} catch (IOException | InterruptedException ex) {
throw new IntegationFailedExeception("Failed to count commits.", ex);
}

//Only rebase if it's a single commit
if (commitCount != 1) {
logger.log(Level.INFO, String.format(LOG_PREFIX + "Not attempting rebase. Exiting tryRebase."));
return false;
}

//Rebase the commit
try {
logger.log(Level.INFO, String.format(LOG_PREFIX + "Attempting rebase."));
GitClient client = Git.with(listener, build.getEnvironment(listener)).in(bridge.resolveWorkspace(build, listener)).getClient();
ObjectId commitId = bridge.getCommitId(build);

//Rebase the commit, then checkout master for a fast-forward merge.
int rebaseCode = bridge.git(build, launcher, listener, "rebase", bridge.getBranch(), commitId.getName());
if (rebaseCode != 0) {
throw new IntegationFailedExeception("Rebase failed with exit code " + rebaseCode);
}
ObjectId rebasedCommit = client.revParse("HEAD");
logger.log(Level.INFO, String.format(LOG_PREFIX + "Rebase successful. Attempting fast-forward merge."));
client.checkout().ref(bridge.getBranch()).execute();
client.merge().setRevisionToMerge(rebasedCommit).execute();
logger.log(Level.INFO, String.format(LOG_PREFIX + "Fast-forward merge successful. Exiting tryRebase."));
return true;
} catch (GitException | IOException | InterruptedException ex) {
throw new IntegationFailedExeception("Failed to rebase commit.", ex);
}
}

@Extension
public static final class DescriptorImpl extends IntegrationStrategyDescriptor<SquashCommitStrategy> {

@@ -0,0 +1,58 @@
package org.jenkinsci.plugins.pretestedintegration.integration.scm.git;

import hudson.model.TaskListener;
import java.io.File;
import static junit.framework.TestCase.assertEquals;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.ObjectId;
import org.jenkinsci.plugins.pretestedintegration.scm.git.GetCommitCountFromBranchCallback;
import org.junit.After;
import org.junit.Test;

public class GetCommitCountFromBranchCallbackIT {

private final String FOLDER_PREFIX = "CommitCount_";
private File dir;

@After
public void tearDown() throws Exception {
TestUtilsFactory.destroyDirectory(dir);
}

@Test
public void counts_two_commits() throws Exception {
dir = new File(FOLDER_PREFIX + "count2");
Git git = Git.init().setDirectory(dir).call();
File testFile = new File(dir + "/file");

// First commit to master
FileUtils.writeStringToFile(testFile, "master commit 1");
git.add().addFilepattern("file").call();
git.commit().setMessage("master commit 1").call();

// Create a branch
git.checkout().setCreateBranch(true).setName("branch").call();
// First commit to branch
FileUtils.writeStringToFile(testFile, "branch commit 1", true);
git.add().addFilepattern("file").call();
git.commit().setMessage("branch commit 1").call();
// Second commit to branch
FileUtils.writeStringToFile(testFile, "branch commit 2", true);
git.add().addFilepattern("file").call();
ObjectId startCommit = git.commit().setMessage("branch commit 2").call();

// Counts two commits
GetCommitCountFromBranchCallback callback = new GetCommitCountFromBranchCallback(TaskListener.NULL, startCommit, "master");
assertEquals("Commit count did not match expectations.", new Integer(2), callback.invoke(git.getRepository(), null));

// Second commit to master
FileUtils.writeStringToFile(testFile, "master commit 2");
git.add().addFilepattern("file").call();
git.commit().setMessage("master commit 2").call();

// STILL counts two commits
callback = new GetCommitCountFromBranchCallback(TaskListener.NULL, startCommit, "master");
assertEquals("Commit count did not match expectations.", new Integer(2), callback.invoke(git.getRepository(), null));
}
}
@@ -11,15 +11,14 @@
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import static junit.framework.Assert.assertNotNull;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.revwalk.RevCommit;
import static org.jenkinsci.plugins.pretestedintegration.integration.scm.git.TestUtilsFactory.STRATEGY_TYPE;
import org.junit.Before;

@@ -83,7 +82,7 @@ public void oneValidFeatureBranch_1BuildIsTriggeredTheBranchGetsIntegratedBuildM

final int COMMIT_COUNT_AFTER_EXECUTION = TestUtilsFactory.countCommits(repository);

TestCase.assertTrue(COMMIT_COUNT_AFTER_EXECUTION == COMMIT_COUNT_BEFORE_EXECUTION + 1);
TestCase.assertEquals("Commit count missmatch.", COMMIT_COUNT_AFTER_EXECUTION, COMMIT_COUNT_BEFORE_EXECUTION + 1);
}

@Test
@@ -107,7 +106,7 @@ public void oneInvalidFeatureBranch_1BuildIsTriggeredNothingGetsIntegratedBuildM

final int COMMIT_COUNT_AFTER_EXECUTION = TestUtilsFactory.countCommits(repository);

assertTrue(COMMIT_COUNT_AFTER_EXECUTION == COMMIT_COUNT_BEFORE_EXECUTION);
TestCase.assertEquals("Commit count missmatch.", COMMIT_COUNT_AFTER_EXECUTION, COMMIT_COUNT_BEFORE_EXECUTION);
}

@Test
@@ -132,7 +131,7 @@ public void twoFeatureBranchesBothValid_2BuildsAreTriggeredBothBranchesGetIntegr

final int COMMIT_COUNT_AFTER_EXECUTION = TestUtilsFactory.countCommits(repository);

assertTrue(COMMIT_COUNT_AFTER_EXECUTION == COMMIT_COUNT_BEFORE_EXECUTION + 2);
TestCase.assertEquals("Commit count missmatch.", COMMIT_COUNT_AFTER_EXECUTION, COMMIT_COUNT_BEFORE_EXECUTION + 2);
}

@Test
@@ -160,7 +159,7 @@ public void twoFeatureBranches1ValidAnd1Invalid_2BuildsAreTriggeredValidBranchGe

final int COMMIT_COUNT_AFTER_EXECUTION = TestUtilsFactory.countCommits(repository);

assertTrue(COMMIT_COUNT_AFTER_EXECUTION == COMMIT_COUNT_BEFORE_EXECUTION + 1);
TestCase.assertEquals("Commit count missmatch.", COMMIT_COUNT_AFTER_EXECUTION, COMMIT_COUNT_BEFORE_EXECUTION + 1);
}

@Test
@@ -197,7 +196,7 @@ public void oneValidFeatureBranchRunningOnSlave_1BuildIsTriggeredTheBranchGetsIn

final int COMMIT_COUNT_AFTER_EXECUTION = TestUtilsFactory.countCommits(repository);

TestCase.assertTrue(COMMIT_COUNT_AFTER_EXECUTION == COMMIT_COUNT_BEFORE_EXECUTION + 1);
TestCase.assertEquals("Commit count missmatch.", COMMIT_COUNT_AFTER_EXECUTION, COMMIT_COUNT_BEFORE_EXECUTION + 1);
}

@Test
@@ -237,7 +236,7 @@ public void oneInvalidFeatureBranchRunningOnSlave_1BuildIsTriggeredNothingGetsIn

git.close();

TestCase.assertTrue(COMMIT_COUNT_AFTER_EXECUTION == COMMIT_COUNT_BEFORE_EXECUTION);
TestCase.assertEquals("Commit count missmatch.", COMMIT_COUNT_AFTER_EXECUTION, COMMIT_COUNT_BEFORE_EXECUTION);
}

@Test
@@ -280,7 +279,7 @@ public void twoFeatureBranchesBothValidRunningOnSlave_2BuildsAreTriggeredBothBra

git.close();

assertTrue(COMMIT_COUNT_AFTER_EXECUTION == COMMIT_COUNT_BEFORE_EXECUTION + 2);
TestCase.assertEquals("Commit count missmatch.", COMMIT_COUNT_AFTER_EXECUTION, COMMIT_COUNT_BEFORE_EXECUTION + 2);
}

@Test
@@ -327,6 +326,48 @@ public void twoFeatureBranches1ValidAnd1InvalidRunningOnSlave_2BuildsAreTriggere

git.close();

assertTrue(COMMIT_COUNT_AFTER_EXECUTION == COMMIT_COUNT_BEFORE_EXECUTION + 1);
TestCase.assertEquals("Commit count missmatch.", COMMIT_COUNT_AFTER_EXECUTION, COMMIT_COUNT_BEFORE_EXECUTION + 1);
}

@Test
public void singleCommit_keepsCommitMessage() throws Exception {
// Create the test repository
String repoName = "squash_singleCommit_keepsCommitMessage";
Repository repository = TestUtilsFactory.createRepository(repoName, new ArrayList<TestCommit>() {
{
add(new TestCommit("master", "README.md", "# Test repository", "Commit 1: readme"));
add(new TestCommit("ready/feature_1", "script.groovy", "println 'Hello, world!'", "Commit 2: Groovy Script"));
add(new TestCommit("master", "README.md", "Just a test repository containing a Groovy 'Hello, world!'", "Commit 3: readme"));
}
});
repositories.add(repository);

// Clone test repo
File workDir = new File(repoName);
Git.cloneRepository().setURI("file:///" + repository.getDirectory().getAbsolutePath()).setDirectory(workDir)
.setBare(false)
.setCloneAllBranches(true)
.setNoCheckout(false)
.call().close();
Git git = Git.open(workDir);

final int COMMIT_COUNT_BEFORE_EXECUTION = TestUtilsFactory.countCommits(git);
// Build the project, assert SUCCESS
FreeStyleProject project = TestUtilsFactory.configurePretestedIntegrationPlugin(jenkinsRule, STRATEGY_TYPE.SQUASH, repository);
TestUtilsFactory.triggerProject(project);
jenkinsRule.waitUntilNoActivityUpTo(60000);
FreeStyleBuild build = project.getLastBuild();
String consoleOutput = jenkinsRule.createWebClient().getPage(build, "console").asText();
System.out.println(consoleOutput);
jenkinsRule.assertBuildStatus(Result.SUCCESS, build);
git.pull().call();
final int COMMIT_COUNT_AFTER_EXECUTION = TestUtilsFactory.countCommits(repository);
RevCommit lastCommit = git.log().setMaxCount(1).call().iterator().next();
String commitMessage = lastCommit.getFullMessage();

git.close();

assertEquals("Single commit for rebase.", COMMIT_COUNT_AFTER_EXECUTION, COMMIT_COUNT_BEFORE_EXECUTION + 1);
assertEquals("Commit message was altered.", "Commit 2: Groovy Script", commitMessage);
}
}

0 comments on commit 9b6d14d

Please sign in to comment.