Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions docs/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,62 @@ node {
Note though that with this approach the changelog will not show
correctly.

== Declarative Pipeline Jobs

You can configure the pipeline checkout in the job configuration to use the
"Gerrit Trigger with merge commit support" choosing strategy. This strategy
behaves similar to the original "Gerrit Trigger" strategy with the exception
that for change-merged events where Gerrit automatically creates a merge commit,
it will select the revision for the merge commit rather than the revision for
the patchset that was submitted. This ensures that the Jenkinsfile is checked
out correctly in the event the Gerrit resolved a merge for that file. See
https://issues.jenkins.io/browse/JENKINS-65481 for all the details.

Configure your job as normal for patchset-created and change-merged event triggers then use
the following settings in the Pipeline section of your job configuration
(you can use a similar setup for freestyle jobs):

Definition: Pipeline script from SCM
- SCM: Git
- Repositories
- Set your repository URL and credentials
- Name: origin
- Click the "Advanced" button
- Refspec: $GERRIT_REFSPEC +refs/heads/$GERRIT_BRANCH:refs/remotes/origin/$GERRIT_BRANCH
- Branches to build
- Branch specifier: refs/heads/$GERRIT_BRANCH
- Additional Behaviours
- Strategy for choosing what to build: Gerrit Trigger with merge commit support

then in your Jenkinsfile pipeline code rely on the automatic checkout or disable the automatic checkout and use
"scm checkout" as shown below:

[source,syntaxhighlighter-pre]
----
pipeline {
options {
skipDefaultCheckout true
// ...
}
stages {
stage('checkout') {
checkout scm
}
//...
}
}
----

This method of skipping the default checkout is useful if you want to checkout into a subdirectory of your
workspace by wrapping the checkout in a dir directive, for example:

[source,syntaxhighlighter-pre]
----
dir("${env.WORKSPACE}/scm") {
checkout scm
}
----

== Tips & Tricks

This section contains some useful tips and tricks that users has come up
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
/*
* The MIT License
*
* Copyright 2010 Andrew Bayer. All rights reserved.
* Copyright 2013 Sony Mobile Communications AB. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger;


import com.sonymobile.tools.gerrit.gerritevents.dto.events.ChangeBasedEvent;
import com.sonymobile.tools.gerrit.gerritevents.dto.events.ChangeMerged;
import com.sonymobile.tools.gerrit.gerritevents.dto.events.GerritTriggeredEvent;
import com.sonymobile.tools.gerrit.gerritevents.dto.events.RefUpdated;

import hudson.Extension;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.Result;
import hudson.plugins.git.GitException;
import hudson.plugins.git.Revision;
import hudson.plugins.git.Branch;
import hudson.plugins.git.util.Build;
import hudson.plugins.git.util.BuildChooser;
import hudson.plugins.git.util.BuildChooserContext;
import hudson.plugins.git.util.BuildChooserDescriptor;
import hudson.plugins.git.util.BuildData;
import hudson.remoting.VirtualChannel;

import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.jenkinsci.plugins.gitclient.RepositoryCallback;
import org.kohsuke.stapler.DataBoundConstructor;
import org.eclipse.jgit.lib.ObjectId;


import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Logger;

import com.sonyericsson.hudson.plugins.gerrit.trigger.Messages;

/**
* Used by the git plugin to determine the revision to build. This forks off
* the original build chooser to add support for handling automatic merge commits.
* It was necessary to add an additional chooser as the change could not be made
* in a backward compatible way.
*/
public class GerritTriggerBuildChooserWithMergeCommitSupport extends BuildChooser {
private static final long serialVersionUID = 2003462680723330645L;

/**
* Used by XStream for something.
*/
@SuppressWarnings("unused")
private final String separator = "#";
Comment on lines +73 to +77

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't think this is needed.

Suggested change
/**
* Used by XStream for something.
*/
@SuppressWarnings("unused")
private final String separator = "#";


/**
* Default constructor.
*/
@DataBoundConstructor
public GerritTriggerBuildChooserWithMergeCommitSupport() {
}

//CS IGNORE RedundantThrows FOR NEXT 30 LINES. REASON: Informative, and could happen.

/**
* Determines which Revisions to build.
*
* Doesn't care about branches.
*
* @param isPollCall whether this is being called from Git polling
* @param singleBranch The branch
* @param git The GitClient API object
* @param listener TaskListener for logging, etc
* @param data the historical BuildData object
* @param context the remote context
* @return A Collection containing the new revision.
*
* @throws GitException in case of error
* @throws IOException In case of error
* @throws InterruptedException In case of error
*/
@Override
public Collection<Revision> getCandidateRevisions(boolean isPollCall, String singleBranch,
GitClient git, TaskListener listener,
BuildData data, BuildChooserContext context)
throws GitException, IOException, InterruptedException {

try {
String rev = context.actOnBuild(new GetGerritEventRevision());
if (rev == null) {
rev = "FETCH_HEAD";
}

String refspec = context.actOnBuild(new GetGerritEventRefspec());
if (refspec == null) {
refspec = singleBranch;
}

ObjectId sha1 = git.revParse(rev);

Revision revision = new Revision(sha1);
revision.getBranches().add(new Branch(refspec, sha1));

return Collections.singletonList(revision);
} catch (GitException e) {
// branch does not exist, there is nothing to build
return Collections.<Revision>emptyList();
}
}

@Override
public Build prevBuildForChangelog(String singleBranch, BuildData data, GitClient git,
BuildChooserContext context) throws InterruptedException, IOException {
if (data != null) {
ObjectId sha1 = git.revParse("FETCH_HEAD");

// Now we cheat and add the parent as the last build on the branch, so we can
// get the changelog working properly-ish.
ObjectId parentSha1 = getFirstParent(sha1, git);
Revision parentRev = new Revision(parentSha1);
parentRev.getBranches().add(new Branch(singleBranch, parentSha1));

int prevBuildNum = 0;
Result r = null;

Build lastBuild = data.getLastBuildOfBranch(singleBranch);
if (lastBuild != null) {
prevBuildNum = lastBuild.getBuildNumber();
r = lastBuild.getBuildResult();
}

return new Build(parentRev, prevBuildNum, r);
} else {
//Hmm no sure what to do here, but the git plugin can handle us returning null here
return null;
}
}

//CS IGNORE RedundantThrows FOR NEXT 30 LINES. REASON: Informative, and could happen.
/**
* Gets the top parent of the given revision.
*
* @param id Revision
* @param git GitClient API object
* @return object id of Revision's parent, or of Revision itself if there is no parent
* @throws GitException In case of error in git call
* @throws InterruptedException if the repository handling gets interrupted
* @throws IOException in case of communication errors.
*/
@SuppressWarnings("serial")
private ObjectId getFirstParent(final ObjectId id, GitClient git)
throws GitException, IOException, InterruptedException {
return git.withRepository(new RepositoryCallback<ObjectId>() {
@Override
public ObjectId invoke(Repository repository, VirtualChannel virtualChannel)
throws IOException, InterruptedException {
ObjectId result = null;
try (RevWalk walk = new RevWalk(repository)) {
RevCommit commit = walk.parseCommit(id);
if (commit.getParentCount() > 0) {
result = commit.getParent(0);
} else {
// If this is the first commit in the git, there is no parent.
result = id;
}
} catch (Exception e) {
throw new GitException("Failed to find parent id. ", e);
}
return result;

Check warning on line 192 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 128-192 are not covered by tests

Check warning on line 192 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 128-192 are not covered by tests
}
});
}

/**
* Descriptor for GerritTriggerBuildChooserWithMergeCommitSupport.
*/
@Extension(optional = true)
public static final class DescriptorImpl extends BuildChooserDescriptor {
@Override
public String getDisplayName() {
return Messages.GerritTriggerBuildChooserWithMergeCommitSupport_DisplayName();
}

@Override
public String getLegacyId() {
return Messages.GerritTriggerBuildChooserWithMergeCommitSupport_DisplayName();

Check warning on line 209 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 209 is not covered by tests

Check warning on line 209 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 209 is not covered by tests
}
}

/**
* Retrieve the Gerrit event revision
*/
private static class GetGerritEventRevision
implements BuildChooserContext.ContextCallable<Run<?, ?>, String> {
static final long serialVersionUID = 0L;
@Override
public String invoke(Run<?, ?> build, VirtualChannel channel) {
GerritCause cause = build.getCause(GerritCause.class);
if (cause != null) {
GerritTriggeredEvent event = cause.getEvent();
if (event instanceof ChangeMerged) {
// when gerrit creates a merge commit for some submit types, we need the newrev or we get the
// changes from the patchset before the merge and build the wrong source, in the case that
// there is no merge commit, the newrev will match the patchset revision
String newRev = ((ChangeMerged)event).getNewRev();
if (newRev != null) {
return newRev;
}
// else we are on an old version of gerrit that is not reporting newrev
// so fall through to the old behavior
}
if (event instanceof ChangeBasedEvent) {

Check warning on line 235 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 235 is only partially covered, one branch is missing

Check warning on line 235 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 235 is only partially covered, one branch is missing
return ((ChangeBasedEvent)event).getPatchSet().getRevision();
}
if (event instanceof RefUpdated) {
return ((RefUpdated)event).getRefUpdate().getNewRev();

Check warning on line 239 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 238-239 are not covered by tests

Check warning on line 239 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 238-239 are not covered by tests
}
}
return null;
}
}

/**
* Retrieve the Gerrit refspec
*/
private static class GetGerritEventRefspec
implements BuildChooserContext.ContextCallable<Run<?, ?>, String> {
static final long serialVersionUID = 0L;
@Override
public String invoke(Run<?, ?> build, VirtualChannel channel) {
GerritCause cause = build.getCause(GerritCause.class);
if (cause != null) {
GerritTriggeredEvent event = cause.getEvent();
// For change-merged we need the project refname which is the ref for the branch we merged onto in
// case Gerrit created an automatic merge commit. The project object from the event stream is not
// available in the DTO objects so use the next best thing which is the branch name from the Change.
if (event instanceof ChangeMerged) {
return "refs/heads/" + ((ChangeMerged)event).getChange().getBranch();
}
if (event instanceof ChangeBasedEvent) {

Check warning on line 263 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 263 is only partially covered, one branch is missing

Check warning on line 263 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 263 is only partially covered, one branch is missing
return ((ChangeBasedEvent)event).getPatchSet().getRef();
}
if (event instanceof RefUpdated) {
return ((RefUpdated)event).getRefUpdate().getRefName();

Check warning on line 267 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 266-267 are not covered by tests

Check warning on line 267 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/hudsontrigger/GerritTriggerBuildChooserWithMergeCommitSupport.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 266-267 are not covered by tests
}
}
return null;
}
}

private static final Logger LOGGER = Logger.getLogger(GerritTriggerBuildChooser.class.getName());
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,4 @@ GerritProjectListUpdater.For=GerritProjectListUpdater for server: {0}
GerritMissedEventsPlaybackManager.For=GerritMissedEventsPlaybackManager for server: {0}
NotANumber=Not a number
NoSuchJobExists=No such job \u2018{0}\u2019 exists. Perhaps you meant \u2018{1}\u2019?
GerritTriggerBuildChooserWithMergeCommitSupport.DisplayName=Gerrit Trigger with merge commit support
Loading
Loading