Navigation Menu

Skip to content

Commit

Permalink
JENKINS-28590 Re-use author on commit and refactor:
Browse files Browse the repository at this point in the history
The integration commit will have the author of the last commit on the
development branch being integrated.
In both accumulated and squash strategy the author of the last commit is
collected and used with the '--author' switch during commit of the
integration commit.

The implementation includes large refactoring to improve logging,
console output FIXME

- Maven pom file, updated java ncss tool from version 2.0 to 2.1 as it
  failed to parse java 7 multi catch constructs

Logging changes in general:

- user information that is written to job console is now also logged
- LOG_PREFIX which equals '[PREINT]' is added to all changed logging
  lines for consistency

Accumulated and squash strategy implementations:

- changed, restructured the work flow reporting to the job console
- made output to job console almost identical between the two strategies
- added more progress and logging during the work flow
- separated merge and commit in two phases, each handling exceptions and
  git command exit codes individually
- splitting merge and commit needed to allow to use the option --author
  to set the author of the commit

Finding the author:

- implemented as a call back method, to support remoting
- copy and re-use from a like method

GitBridge - the post-build step pushing integration branch and deleting
development branch:

- added better error reporting, showing output message and exception if
  failing

Test MultipleScm_threeRepos_IT:

- improved the wait for activity function to only return if result is
  valid, not only when there is a temporary one and the job is still
building
  • Loading branch information
Bue Petersen committed Jun 29, 2015
1 parent 539efc7 commit 79f125e
Show file tree
Hide file tree
Showing 13 changed files with 647 additions and 86 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -283,7 +283,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>javancss-maven-plugin</artifactId>
<version>2.0</version>
<version>2.1</version>
<configuration>
<failOnViolation>false</failOnViolation>
</configuration>
Expand Down
Expand Up @@ -2,16 +2,16 @@

import hudson.Extension;
import hudson.Launcher;
import hudson.model.BuildListener;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.Result;
import hudson.tasks.Publisher;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.jenkinsci.plugins.pretestedintegration.AbstractSCMBridge.LOG_PREFIX;
import org.kohsuke.stapler.DataBoundConstructor;

/**
Expand Down Expand Up @@ -51,7 +51,7 @@ public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListene
if (action == null)
return true;

listener.getLogger().println("Performing pre-verified post build steps");
listener.getLogger().println(LOG_PREFIX + "Performing pre-verified post build steps");
Boolean result = false;

try {
Expand Down
Expand Up @@ -42,7 +42,8 @@ public AccumulatedCommitStrategy() { }
@Override
public void integrate(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener, AbstractSCMBridge bridge) throws IntegationFailedExeception, NothingToDoException, UnsupportedConfigurationException {
logger.entering("AccumulatedCommitStrategy", "integrate", new Object[] { build, listener, bridge, launcher });// Generated code DONT TOUCH! Bookmark: ee74dbf7df6fa51582ccc15f5fee72da
int exitCode = -999;
int exitCodeMerge = -999;
int exitCodeCommit = -999;

GitClient client;

Expand All @@ -51,13 +52,16 @@ public void integrate(AbstractBuild<?,?> build, Launcher launcher, BuildListener
ByteArrayOutputStream out = new ByteArrayOutputStream();
BuildData gitBuildData = gitbridge.checkAndDetermineRelevantBuildData(build.getActions(BuildData.class));
String commit = gitBuildData.lastBuild.revision.getSha1String();

//TODO: Implement robustness, in which situations does this one contain
// multiple revisons, when two branches point to the same commit?
// (JENKINS-24909). Check branch spec before doing anything
Branch gitDataBranch = gitBuildData.lastBuild.revision.getBranches().iterator().next();
boolean found = false;


logger.log(Level.INFO, String.format("Preparing to merge changes in commit %s on development branch %s to integration branch %s", commit, gitDataBranch.getName(), gitbridge.getBranch()));
listener.getLogger().println(String.format(LOG_PREFIX + "Preparing to merge changes in commit %s on development branch %s to integration branch %s", commit, gitDataBranch.getName(), gitbridge.getBranch()));
try {
logger.fine("Resolving and getting git client from workspace:");
client = Git.with(listener, build.getEnvironment(listener)).in(gitbridge.resolveWorkspace(build, listener)).getClient();
Expand Down Expand Up @@ -90,7 +94,7 @@ public void integrate(AbstractBuild<?,?> build, Launcher launcher, BuildListener
throw new NothingToDoException(msg);
}

listener.getLogger().println(String.format(LOG_PREFIX + "Preparing to merge changes in commit %s to integration branch %s", commit, gitbridge.getBranch()));
String commitAuthor; //leaving un-assigned, want to fail later if not assigned
try {
// FIXME I don't like this call back design, where we collect data
// for commit message based on a series of commits which aren't
Expand All @@ -100,37 +104,121 @@ public void integrate(AbstractBuild<?,?> build, Launcher launcher, BuildListener
// using JGit, so it is complete independent from the following
// merge operation. Worst case is that the merge commit message is
// based on other commits than the actual merge commit consist of.
logger.log(Level.INFO, String.format(LOG_PREFIX + "Collecting commit messages on development branch %s", gitDataBranch.getName()));
listener.getLogger().println(String.format(LOG_PREFIX + "Collecting commit messages on development branch %s", gitDataBranch.getName()));
String commits = client.withRepository(new GetAllCommitsFromBranchCallback(listener, gitDataBranch.getSHA1(), gitbridge.getBranch()));
String headerLine = String.format("Accumulated commit of the following from branch '%s':%n", gitDataBranch.getName());
exitCode = gitbridge.git(build, launcher, listener, out, "merge", "-m", String.format("%s%n%s", headerLine, commits), commit, "--no-ff");
logger.log(Level.INFO, String.format(LOG_PREFIX + "Done collecting commit messages"));
listener.getLogger().println(String.format(LOG_PREFIX + "Done collecting commit messages"));

// Finding author of the commit, re-using a call back method like 'FindCommitMessageCallback'
logger.log(Level.INFO, String.format(LOG_PREFIX + "Collecting author of last commit on development branch"));
listener.getLogger().println(String.format(LOG_PREFIX + "Collecting author of last commit on development branch"));
commitAuthor = client.withRepository(new FindCommitAuthorCallback(listener, gitDataBranch.getSHA1()));
logger.log(Level.INFO, String.format(LOG_PREFIX + "Done colecting last commit author: %s", commitAuthor));
listener.getLogger().println(String.format(LOG_PREFIX + "Done colecting last commit author: %s", commitAuthor));

// Merge and commit must be splitted in two steps, to allow setting author which only is supported on commit command, not merge.
logger.info("Starting accumulated merge (no-ff) - without commit:");
listener.getLogger().println(String.format(LOG_PREFIX + "Starting accumulated merge (no-ff) - without commit:"));
exitCodeMerge = gitbridge.git(build, launcher, listener, out, "merge", "-m", String.format("%s%n%s", headerLine, commits), commit, "--no-ff", "--no-commit");
logger.info("Accumulated merge done");
listener.getLogger().println(String.format(LOG_PREFIX + "Accumulated merge done"));
} catch (Exception ex) {
logger.log(Level.SEVERE, "Exception while merging, logging exception", ex);
logger.exiting("AccumulatedCommitStrategy ", "integrate-mergeFailure"); // Generated code DONT TOUCH! Bookmark: 26b6ce59c6edbad7afa29f96febc6fd7
throw new IntegationFailedExeception(ex);
// We handle all exceptions here, as we will not continue with
// anything if there is problems, even if it is only null pointer
// in debug printing.
// So we throw an exception, that is handled by the build wrapper
// which also print out the exception to the job console to let the
// user easily investigate.
logger.log(Level.SEVERE, "Exception while merging. Logging exception", ex);
logger.exiting("AccumulatedCommitStrategy", "integrate-mergeFailure");
throw new IntegationFailedExeception(ex);
}

if (exitCode > 0) {
logger.log(Level.SEVERE, "Failed to merge changes.");
logger.log(Level.SEVERE, String.format("Git command failed with exit code '%d' and error message:", exitCode));
// NOTICE: The catch-throw exception above means we have handles all
// exceptions at this point, and only need to look to at exit codes.
if (exitCodeMerge != 0) {
logger.log(Level.SEVERE, "Failed to merge.");
logger.log(Level.SEVERE, String.format("Git command failed with exit code '%d' and error message:", exitCodeMerge));
logger.log(Level.SEVERE, out.toString());
listener.getLogger().println(LOG_PREFIX + "Failed to merge changes.");
listener.getLogger().println(String.format(LOG_PREFIX + "Git command failed with exit code '%d' and error message:", exitCode));
listener.getLogger().println(LOG_PREFIX + out.toString());
listener.getLogger().println(LOG_PREFIX + "Failed to merge.");
listener.getLogger().println(String.format(LOG_PREFIX + "Git command failed with exit code '%d' and error message:", exitCodeMerge));
listener.getLogger().println(out.toString());

try {
logger.fine("Setting build description 'Merge conflict':");
build.setDescription(String.format("Merge conflict"));
logger.fine("Setting build description 'Failed to merge':");
build.setDescription(String.format("Failed to merge."));
logger.fine("Done setting build description.");
} catch (IOException ex) {
} catch (IOException ex) {
logger.log(Level.SEVERE, "Failed to update build description", ex);
logger.exiting("AccumulatedCommitStrategy", "integrate-setDescription() failed");// Generated code DONT TOUCH! Bookmark: 26b6ce59c6edbad7afa29f96febc6fd7
logger.exiting("AccumulatedCommitStrategy", "integrate");
// It is not fatal to fail setting build description on the job
// throw new IntegationFailedExeception(ex);
}
logger.exiting("AccumulatedCommitStrategy", "integrate");// Generated code DONT TOUCH! Bookmark: 26b6ce59c6edbad7afa29f96febc6fd7
throw new IntegationFailedExeception();
logger.exiting("AccumulatedCommitStrategy", "integrate");
throw new IntegationFailedExeception("Could not merge changes. Git output: " + out.toString());
}
logger.log(Level.INFO, String.format(LOG_PREFIX + "Merge was successful"));
listener.getLogger().println(String.format(LOG_PREFIX + "Merge was successful"));


try
{
logger.info("Starting to commit accumulated merge changes:");
listener.getLogger().println(String.format(LOG_PREFIX + "Starting to commit accumulated merge changes:"));
exitCodeCommit = gitbridge.git(build, launcher, listener, out, "commit", "--no-edit", "--author=" + commitAuthor);
logger.info("Commit of accumulated merge done");
listener.getLogger().println(String.format(LOG_PREFIX + "Commit of accumulated merge done"));
} catch (Exception ex) {
// We handle all exceptions here, as we will not continue with
// anything if there is problems, even if it is only null pointer
// in debug printing.
// So we throw an exception, that is handled by the build wrapper
// which also print out the exception to the job console to let the
// user easily investigate.
logger.log(Level.SEVERE, "Exception while comitting. Logging exception", ex);
logger.exiting("AccumulatedCommitStrategy", "integrate-commitFailure");
throw new IntegationFailedExeception(ex);
}
// NOTICE: The catch-throw exception above means we have handles all
// exceptions at this point, and only need to look to at exit codes.
if (exitCodeCommit != 0) {
logger.log(Level.SEVERE, "Failed to commit merge changes.");
logger.log(Level.SEVERE, String.format("Git command failed with exit code '%d' and error message:", exitCodeCommit));
logger.log(Level.SEVERE, out.toString());
listener.getLogger().println(LOG_PREFIX + "Failed to commit merge changes.");
listener.getLogger().println(String.format(LOG_PREFIX + "Git command failed with exit code '%d' and error message:", exitCodeCommit));
listener.getLogger().println(out.toString());
try {
if (out.toString().contains("nothing to commit")) {
logger.fine("Git says nothing to commit.");
logger.fine("Setting build description 'Nothing to do':");
build.setDescription(String.format("Nothing to do"));
logger.fine("Done setting build description.");
} else {
logger.fine("Git could not commit merge changes.");
logger.fine("Setting build description 'Failed to commit merge changes':");
build.setDescription(String.format("Failed to commit merge changes"));
logger.fine("Done setting build description.");
}
} catch (IOException ex ) {
logger.log(Level.SEVERE, "Failed to update build description", ex);
logger.exiting("AccumulatedCommitStrategy", "integrate");
// It is not fatal to fail setting build description on the job
//throw new IntegationFailedExeception(ex);
}

if (out.toString().contains("nothing to commit")) {
logger.fine("Git says nothing to commit.");
logger.exiting("AccumulatedCommitStrategy", "integrate");// Generated code DONT TOUCH! Bookmark: c9b422ba65a6a142f9cc7f27faeea6e9
throw new NothingToDoException();
}
logger.exiting("AccumulatedCommitStrategy", "integrate");
throw new IntegationFailedExeception("Could not commit merge change. Git output: " + out.toString());
}
// if no exceptions...
logger.log(Level.INFO, String.format(LOG_PREFIX + "Commit was successful"));
listener.getLogger().println(String.format(LOG_PREFIX + "Commit was successful"));
}

@Extension
Expand Down
@@ -0,0 +1,37 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.jenkinsci.plugins.pretestedintegration.scm.git;

import hudson.model.TaskListener;
import hudson.remoting.VirtualChannel;
import java.io.IOException;
import java.util.logging.Logger;
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 FindCommitAuthorCallback extends RepositoryListenerAwareCallback<String> {

private static final Logger logger = Logger.getLogger(FindCommitAuthorCallback.class.getName());// Generated code DONT TOUCH! Bookmark: 3ca61d8e671737b5ead8aaccd31875c4

public final ObjectId id;

public FindCommitAuthorCallback(TaskListener listener, final ObjectId id) {
super(listener);
this.id = id;
}

@Override
public String invoke(Repository repo, VirtualChannel channel) throws IOException, InterruptedException {
logger.entering("FindCommitAuthorCallback", "invoke", new Object[]{channel, repo});// Generated code DONT TOUCH! Bookmark: 3e9f1bb124a68aa51ae943d0e765a528
RevWalk walk = new RevWalk(repo);
RevCommit commit = walk.parseCommit(id);
walk.dispose();
logger.exiting("FindCommitAuthorCallback", "end");// Generated code DONT TOUCH! Bookmark: 2412143d613a394b72a1b1da928ce975
return commit.getAuthorIdent().toExternalString();
}
}

0 comments on commit 79f125e

Please sign in to comment.