Skip to content
Permalink
Browse files

Implemented JENKINS-24754

  • Loading branch information...
MadsNielsen committed Nov 5, 2014
1 parent cd1c1ca commit 9a68e17c11d579aa9eacc7502acd077cf0fde8d9
Showing with 423 additions and 118 deletions.
  1. BIN docs/Illegal_JENKINS24754.png
  2. BIN docs/More_than_1_gitBuild_data.png
  3. BIN docs/legal_JENKINS24754.png
  4. +2 −16 src/main/java/org/jenkinsci/plugins/pretestedintegration/AbstractSCMBridge.java
  5. +44 −49 src/main/java/org/jenkinsci/plugins/pretestedintegration/PretestedIntegrationBuildWrapper.java
  6. +4 −0 src/main/java/org/jenkinsci/plugins/pretestedintegration/exceptions/NothingToDoException.java
  7. +1 −1 ...java/org/jenkinsci/plugins/pretestedintegration/exceptions/UnsupportedConfigurationException.java
  8. +1 −1 src/main/java/org/jenkinsci/plugins/pretestedintegration/scm/git/AccumulatedCommitStrategy.java
  9. +39 −7 src/main/java/org/jenkinsci/plugins/pretestedintegration/scm/git/GitBridge.java
  10. +1 −1 src/main/java/org/jenkinsci/plugins/pretestedintegration/scm/git/SquashCommitStrategy.java
  11. +16 −4 src/main/resources/org/jenkinsci/plugins/pretestedintegration/scm/git/GitBridge/help-branch.html
  12. +3 −2 src/main/resources/org/jenkinsci/plugins/pretestedintegration/scm/git/GitBridge/help-repoName.html
  13. +0 −26 src/test/java/org/jenkinsci/plugins/pretestedintegration/integration/scm/git/GeneralBehaviourIT.java
  14. +272 −0 src/test/java/org/jenkinsci/plugins/pretestedintegration/integration/scm/git/JENKINS_24754_IT.java
  15. +0 −2 ...t/java/org/jenkinsci/plugins/pretestedintegration/integration/scm/git/SquashCommitStrategyIT.java
  16. +40 −9 src/test/java/org/jenkinsci/plugins/pretestedintegration/integration/scm/git/TestUtilsFactory.java
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -108,26 +108,12 @@ protected void mergeChanges(AbstractBuild<?, ?> build, Launcher launcher, BuildL
}

/**
* Method that determines if we should prepare workspace for integration, it
* If this returns false, the applySkipBehaviour method is called to determine if we should proceed to the build step.
* Method that determines if we should prepare workspace for integration. If not we throw the nothing to do exception
* @param build
* @param listener
* @return
*/
public boolean isApplicable(AbstractBuild<?,?> build, BuildListener listener) {
return true;
}

/**
* Method that applies all the necessary behaviour when we have determined to skip.
* By default we return true, indicating that the pre build step was OK.
* @param build
* @param listener
* @return
*/
public boolean applySkipBehaviour(AbstractBuild<?,?> build, BuildListener listener) {
return true;
}
public void isApplicable(AbstractBuild<?,?> build, BuildListener listener) throws NothingToDoException, UnsupportedConfigurationException { }

public abstract void ensureBranch(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener, String branch) throws EstablishWorkspaceException;

@@ -7,10 +7,10 @@
import hudson.Extension;
import hudson.Launcher;
import hudson.model.BuildListener;
import hudson.model.Describable;
import hudson.model.Result;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Describable;
import hudson.model.FreeStyleProject;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrapperDescriptor;
@@ -78,57 +78,52 @@ public PretestedIntegrationBuildWrapper(final AbstractSCMBridge scmBridge) {
listener.getLogger().println(Jenkins.getInstance().getPlugin("pretested-integration").getWrapper().getVersion());

boolean proceedToBuildStep = true;

if(scmBridge.isApplicable(build, listener)) {
BuildQueue.getInstance().enqueueAndWait();
PretestedIntegrationAction action;
BuildQueue.getInstance().enqueueAndWait();
PretestedIntegrationAction action;
try {
scmBridge.validateConfiguration(build.getProject());
scmBridge.isApplicable(build, listener);
scmBridge.ensureBranch(build, launcher, listener, scmBridge.getBranch());
//Create the action. Record the state of integration branch
action = new PretestedIntegrationAction(build, launcher, listener, scmBridge);
build.addAction(action);
action.initialise(launcher, listener);
try {
scmBridge.validateConfiguration(build.getProject());
scmBridge.ensureBranch(build, launcher, listener, scmBridge.getBranch());
//Create the action. Record the state of integration branch
action = new PretestedIntegrationAction(build, launcher, listener, scmBridge);
build.addAction(action);
action.initialise(launcher, listener);
try {
ensurePublisher(build);
} catch (IOException e) {
BuildQueue.getInstance().release();
}
} catch (NothingToDoException e) {
build.setResult(Result.NOT_BUILT);
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
BuildQueue.getInstance().release();
proceedToBuildStep = false;
} catch (IntegationFailedExeception e) {
build.setResult(Result.FAILURE);
BuildQueue.getInstance().release();
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
proceedToBuildStep = false;
} catch (EstablishWorkspaceException e) {
build.setResult(Result.FAILURE);
ensurePublisher(build);
} catch (IOException ex) {
logger.log(Level.WARNING, LOG_PREFIX+" "+"Failed to add publisher", ex);
BuildQueue.getInstance().release();
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
proceedToBuildStep = false;
} catch (NextCommitFailureException e) {
build.setResult(Result.FAILURE);
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
BuildQueue.getInstance().release();
} catch (UnsupportedConfigurationException e) {
build.setResult(Result.FAILURE);
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
listener.getLogger().println(e.getMessage());
BuildQueue.getInstance().release();
proceedToBuildStep = false;
}
} else {
listener.getLogger().println(String.format("%sSkipping the workspace preparation for pre tested integration", LOG_PREFIX));
proceedToBuildStep = scmBridge.applySkipBehaviour(build, listener);
listener.getLogger().println(String.format("%sProceed to build step = %s", LOG_PREFIX, proceedToBuildStep));
} catch (NothingToDoException e) {
build.setResult(Result.NOT_BUILT);
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
BuildQueue.getInstance().release();
proceedToBuildStep = false;
} catch (IntegationFailedExeception e) {
build.setResult(Result.FAILURE);
BuildQueue.getInstance().release();
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
proceedToBuildStep = false;
} catch (EstablishWorkspaceException e) {
build.setResult(Result.FAILURE);
BuildQueue.getInstance().release();
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
proceedToBuildStep = false;
} catch (NextCommitFailureException e) {
build.setResult(Result.FAILURE);
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
BuildQueue.getInstance().release();
} catch (UnsupportedConfigurationException e) {
build.setResult(Result.FAILURE);
listener.getLogger().println(e.getMessage());
logger.log(Level.SEVERE, LOG_PREFIX + "- setUp()", e);
listener.getLogger().println(e.getMessage());
BuildQueue.getInstance().release();
proceedToBuildStep = false;
}

BuildWrapper.Environment environment = new PretestEnvironment();
@@ -16,4 +16,8 @@
public NothingToDoException() {
super("Nothing to do");
}

public NothingToDoException(String message) {
super("Nothing to do the reason is: "+message);
}
}
@@ -13,7 +13,7 @@
*/
public class UnsupportedConfigurationException extends IOException {

public static final String ILLEGAL_CONFIG_NO_REPO_NAME_DEFINED = String.format("You have not defined a repository name in your Pre Tested Integration configuration, but you have multiple scm checkouts listed in your scm config");
public static final String ILLEGAL_CONFIG_NO_REPO_NAME_DEFINED = String.format("You have multiple git repositories defined, but none of them matches your pretested integration repostiory. If using more than 1 repository, remotes must be explicitly named in the Git configuration");

public UnsupportedConfigurationException(String message) {
super(message);
@@ -79,7 +79,7 @@ public void integrate(AbstractBuild<?,?> build, Launcher launcher, BuildListener
}
logger.log(Level.WARNING, String.format("Nothing to do. The branch name (%s) contained in the git build data object, did not match a remote branch name", gitDataBranch != null ? gitDataBranch.getName() : "null"));
logger.exiting("AccumulatedCommitStrategy", "integrate");// Generated code DONT TOUCH! Bookmark: 26b6ce59c6edbad7afa29f96febc6fd7
throw new NothingToDoException();
throw new NothingToDoException(String.format("The branch name (%s) used by git, did not match a remote branch name. You might already have integrated the branch", gitDataBranch != null ? gitDataBranch.getName() : "null"));
}

listener.getLogger().println( String.format( "Preparing to merge changes in commit %s to integration branch %s", (String) commit.getId(), gitbridge.getBranch() ) );
@@ -11,6 +11,7 @@
import hudson.model.Result;
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.UserRemoteConfig;
import hudson.plugins.git.extensions.impl.RelativeTargetDirectory;
import hudson.plugins.git.util.BuildData;
import hudson.scm.SCM;
@@ -40,6 +41,7 @@
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;

@@ -169,6 +171,7 @@ public void ensureBranch(AbstractBuild<?, ?> build, Launcher launcher, BuildList
listener.getLogger().println(String.format("Checking out integration target branch %s and pulling latest changes", getBranch()));
try {
//We need to explicitly checkout the remote we have configured
//git(build, launcher, listener, "checkout", getBranch(), resolveRepoName()+"/"+getBranch());
git(build, launcher, listener, "checkout", "-B", getBranch(), resolveRepoName()+"/"+getBranch());
update(build, launcher, listener);
} catch (IOException ex) {
@@ -239,17 +242,26 @@ public void commit(AbstractBuild<?, ?> build, Launcher launcher, BuildListener l
}

@Override
public boolean isApplicable(AbstractBuild<?, ?> build, BuildListener listener) {
public void isApplicable(AbstractBuild<?, ?> build, BuildListener listener) throws NothingToDoException, UnsupportedConfigurationException {
//Check for more than one git build data object. Only observed with Multiple SCMs plugin, when using more than 1 instance of a git scm.
//Defensive check, multiple scm check handled in checkConfig, but it could happen when used with other plugins.
//See image docs/pics/More_than_1_gitBuild_data.png
if(build.getActions(BuildData.class).size() > 1) {
throw new UnsupportedConfigurationException(String.format("More than one git build data detected, currently not supported.%nPossible cause: Multiple scm configurations, custom buildswrappers (by other plugins)."));
}

BuildData gitBuildData = build.getAction(BuildData.class);

//If no build data was contributed
if(gitBuildData == null) {
return false;
throw new NothingToDoException("Not triggered by Git");
}

//Check to make sure that we do ONLY integrate to the branches specified.
Branch gitDataBranch = gitBuildData.lastBuild.revision.getBranches().iterator().next();
return gitDataBranch.getName().startsWith(resolveRepoName());
if(!gitDataBranch.getName().startsWith(resolveRepoName())) {
throw new NothingToDoException(String.format("The git repository name %s does not match pretested configuration", gitDataBranch.getName()));
}
}

@Override
@@ -383,8 +395,13 @@ public IntegrationStrategy getDefaultStrategy() {
public void validateConfiguration(AbstractProject<?, ?> project) throws UnsupportedConfigurationException {
if( project.getScm() instanceof GitSCM ) {
validateGitScm((GitSCM)project.getScm());
//we need to ask Jenkins.getInstance().getPlugin() before instnaceof because you would get a class not
//not defined on jenkins instances if the vm tried to evalute the instanceof on installations without the
//Multiple SCMs plugin.
} else if(Jenkins.getInstance().getPlugin("multiple-scms") != null && project.getScm() instanceof MultiSCM ) {
MultiSCM multiscm = (MultiSCM)project.getScm();
//Count the number of git scm's added in your configuration. We only support 1 git repository. Since having multiple
//Git repositories creates multiple git build data objects.
int gitCounter = 0;
for(SCM scm : multiscm.getConfiguredSCMs()) {
if(scm instanceof GitSCM) {
@@ -394,17 +411,32 @@ public void validateConfiguration(AbstractProject<?, ?> project) throws Unsuppor
}
}

if(gitCounter > 1 && StringUtils.isBlank(getRepoName())) {
throw new UnsupportedConfigurationException("You haave included multiple git repositories in your multi scm configuration, but have not defined a repository name in the pre tested integration configuration");
if(gitCounter > 1) {
throw new UnsupportedConfigurationException("You have included multiple git scm configurations in your 'Multiple SCMs' configuration. Use one git scm configuration, with multiple repositories");
}
} else {
throw new UnsupportedConfigurationException("We only support git and mutiple scm plugins");
throw new UnsupportedConfigurationException("We only support 'Git' and 'Multiple SCMs' plugins");
}
}

//For JENKINS-24754
/**
* Validate the git configuration. We need to make sure that in situations where
* @param scm
* @throws UnsupportedConfigurationException
*/
private void validateGitScm(GitSCM scm) throws UnsupportedConfigurationException {
if(scm.getRepositories().size() > 1 && StringUtils.isBlank(getRepoName())) {
List<UserRemoteConfig> configs = scm.getUserRemoteConfigs();
//The default git configuration. Blank name for remote (defaults to origin) and default value in pretested integration.
boolean isDefault = configs.size() == 1 && StringUtils.isBlank(configs.get(0).getName()) && resolveRepoName().equals("origin");

//If you're not using the standard values.
if(!isDefault) {
for(UserRemoteConfig config : configs) {
if(resolveRepoName().equals(config.getName())) {
return;
}
}
throw new UnsupportedConfigurationException(UnsupportedConfigurationException.ILLEGAL_CONFIG_NO_REPO_NAME_DEFINED);
}
}
@@ -85,7 +85,7 @@ public void integrate(AbstractBuild<?,?> build, Launcher launcher, BuildListener
}
logger.log(Level.WARNING, String.format("Nothing to do. The branch name (%s) contained in the git build data object, did not match a remote branch name", gitDataBranch.getName()));
logger.exiting("SquashCommitStrategy", "integrate");// Generated code DONT TOUCH! Bookmark: c9b422ba65a6a142f9cc7f27faeea6e9
throw new NothingToDoException();
throw new NothingToDoException(String.format("The branch name (%s) contained in the git build data object, did not match a remote branch name", gitDataBranch.getName()));
}

try {
@@ -6,9 +6,21 @@
-->
<html>
<body>
<div>The target integration branch</div>
<p>
<span style="font-weight: 700">A merge/push will be performed on the basis of &lt;repoName&gt;/&lt;branchName&gt;.</span>
</p>
<h3>What to specify</h3>
<p>The branch name must match your integration branch name</p>
<h3>Merge is performed the following way</h3>
<h5>Squash commit</h5>
<pre>
git checkout -B &lt;Branch name&gt; &lt;Repository name&gt;/&lt;Branch name&gt;
git merge --squash &lt;Branch matched by git&gt;
git commit -C &lt;Branch matched by git&gt;</pre>
<h5>Accumulated commit</h5>
<pre>
git checkout -B &lt;Branch name&gt; &lt;Repository name&gt;/&lt;Branch name&gt;
git merge -m &lt;commitMsg&gt; &lt;Branch matched by git&gt; --no-ff</pre>
<h3>When changes are pushed to the integration branch?</h3>
<p>Changes are only ever pushed when the build results is SUCCESS</p>
<pre>
git push &lt;Repository name&gt; &lt;Branch name&gt;</pre>
</body>
</html>
@@ -6,10 +6,11 @@
-->
<html>
<div>
<h3>What to specify</h3>
<p>
The repository name. In git the repository is always the name of the remote. So if you have specified a repository name
in your git configuration. You need to specify the exact same name here, otherwise no integration will be performed. We do the merge
based on this: &lt;repoName&gt;/&lt;branchName&gt;.
in your Git configuration. You need to specify the exact same name here, otherwise no integration will be performed. We do the merge
based on this.
</p>
<p>
<span style="font-weight:700">Remember to specify this when working with NAMED repositories in Git</span>

0 comments on commit 9a68e17

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