Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FF only strategy #150

Merged
merged 9 commits into from
Mar 25, 2022
Merged
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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
<groupId>org.jenkins-ci.tools</groupId>
<artifactId>maven-hpi-plugin</artifactId>
<configuration>
<minimumJavaVersion>1.8.0</minimumJavaVersion>
<minimumJavaVersion>8</minimumJavaVersion>
<!-- Compatible since is used to warn the user that this new version of the plugin is not compatible
with earlier version than the one we mention below.
https://wiki.jenkins.io/display/JENKINS/Marking+a+new+plugin+version+as+incompatible+with+older+versions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,16 @@ public void perform(Run<?, ?> run, FilePath ws, Launcher launcher, TaskListener
listener.getLogger().println(LOG_PREFIX + "Pretested Post Build step enabled, but Pretested Integration Git Plugin extension is not enabled");
}
} else {
listener.getLogger().println(LOG_PREFIX + "Build result not satisfied - skipped post-build step.");
if(!run.getActions(PretestTriggerCommitAction.class).isEmpty()) {
String triggeredBranch = run.getAction(PretestTriggerCommitAction.class).triggerBranch.getName();
String integrationBranch = run.getAction(PretestTriggerCommitAction.class).integrationBranch;
String integrationRepo = run.getAction(PretestTriggerCommitAction.class).integrationRepo;
listener.getLogger().println(LOG_PREFIX + "Build result not satisfied - skipped post-build step.");
GitBridge.updateBuildDescription(run, listener, "Failed:" + integrationBranch, triggeredBranch.replace(integrationRepo + "/", ""));
} else {
GitBridge.updateBuildDescription(run, listener, "", "Unknown error: " + LOG_PREFIX);
listener.getLogger().println(LOG_PREFIX + "Build result not satisfied - skipped post-build step.");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ private void doTheIntegration(Run build, TaskListener listener, GitBridge gitbri
if ( commitCount == 0 ){
throw new NothingToDoException("Commit count is 0. Already integrated/part of integration branch: " + expandedIntegrationBranch);
}

if (tryFastForward(commitId, listener.getLogger(), client, commitCount)) {
return;
if ( commitCount == 1 ){
if (tryFastForward(commitId, listener.getLogger(), client)) {
return;
}
}

String logMessage = String.format(GitMessages.LOG_PREFIX+ "Preparing to merge changes in commit %s on development branch %s to integration branch %s", commitId.getName(), triggerBranch.getName(), expandedIntegrationBranch);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package org.jenkinsci.plugins.pretestedintegration.scm.git;

import hudson.Extension;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.Revision;
import org.eclipse.jgit.lib.ObjectId;
import org.jenkinsci.Symbol;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.jenkinsci.plugins.pretestedintegration.AbstractSCMBridge;
import org.jenkinsci.plugins.pretestedintegration.IntegrationStrategyDescriptor;
import org.jenkinsci.plugins.pretestedintegration.exceptions.IntegrationFailedException;
import org.jenkinsci.plugins.pretestedintegration.exceptions.IntegrationUnknownFailureException;
import org.jenkinsci.plugins.pretestedintegration.exceptions.NothingToDoException;
import org.jenkinsci.plugins.pretestedintegration.exceptions.UnsupportedConfigurationException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Integration strategy for merging multiple commits.
* Merges in all the commits without squashing them.
* Provides a custom merge commit message.
*/
public class FFOnlyStrategy extends GitIntegrationStrategy {

private static final Logger LOGGER = Logger.getLogger(FFOnlyStrategy.class.getName());

/**
* Strategy name. Used in UI.
* Strategies used to be called Behaviors, hence the field name.
*/
private static final String B_NAME = "Fast forward only";

private void doTheIntegration(Run build, TaskListener listener, GitBridge gitbridge, ObjectId commitId,
GitClient client, String expandedIntegrationBranch, Branch triggerBranch) throws IntegrationFailedException,
NothingToDoException, UnsupportedConfigurationException, IntegrationUnknownFailureException {
// Get the commit count
int commitCount;
try {
commitCount = PretestedIntegrationGitUtils.countCommits(commitId, client, expandedIntegrationBranch);
String text = "Branch commit count: " + commitCount;
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX + text);
listener.getLogger().println(GitMessages.LOG_PREFIX + text);
} catch (IOException | InterruptedException ex) {
throw new IntegrationFailedException("Failed to count commits.", ex);
}
if (commitCount == 0) {
throw new NothingToDoException(
"Commit count is 0. Already integrated/part of integration branch: " + expandedIntegrationBranch);
}

if (tryFastForward(commitId, listener.getLogger(), client)) {
return;
} else {
throw new IntegrationFailedException("FastForward --ff-only failed");
}
}

private boolean shortCommitMessage = false;

/**
* Constructor for FFOnlyStrategy.
* DataBound to work in UI.
*/
@DataBoundConstructor
public FFOnlyStrategy() {
}

@Override
public void integrate(GitSCM scm, Run<?, ?> build, GitClient git, TaskListener listener, Revision marked,
Branch triggeredBranch, GitBridge gitbridge) throws IOException, InterruptedException {
String expandedRepoName;
try {
expandedRepoName = gitbridge.getExpandedRepository(build.getEnvironment(listener));
} catch (IOException | InterruptedException ex) {
expandedRepoName = gitbridge.getRepoName();
}

if (!PretestedIntegrationGitUtils.isRelevant(triggeredBranch, expandedRepoName)) {
throw new NothingToDoException("No revision matches configuration in 'Integration repository'");
}

String expandedIntegrationBranch = gitbridge.getExpandedIntegrationBranch(build.getEnvironment(listener));
doTheIntegration((Run) build, listener, gitbridge, triggeredBranch.getSHA1(), git, expandedIntegrationBranch,
triggeredBranch);
}

public boolean isShortCommitMessage() {
return shortCommitMessage;
}

@DataBoundSetter
public void setShortCommitMessage(boolean shortCommitMessage) {
this.shortCommitMessage = shortCommitMessage;
}

/**
* Descriptor implementation for FFOnlytStrategy
*/
@Symbol("ffonly")
@Extension
public static final class DescriptorImpl extends IntegrationStrategyDescriptor<FFOnlyStrategy> {

/**
* Constructor for the Descriptor
*/
public DescriptorImpl() {
load();
}

/**
* {@inheritDoc }
*/
@Override
public String getDisplayName() {
return B_NAME;
}

/**
* {@inheritDoc }
*/
@Override
public boolean isApplicable(Class<? extends AbstractSCMBridge> bridge) {
return GitBridge.class.equals(bridge);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,131 +19,121 @@
import org.jenkinsci.plugins.pretestedintegration.exceptions.NothingToDoException;

/**
* Abstract IntegrationStrategy containing common logic for Git integration strategies.
* Abstract IntegrationStrategy containing common logic for Git integration
* strategies.
*/
public abstract class GitIntegrationStrategy extends IntegrationStrategy implements IntegrationStrategyAsGitPluginExt {

private static final Logger LOGGER = Logger.getLogger(GitIntegrationStrategy.class.getName());

/**
* Creates a PersonIdent object from a full Git identity string.
* @param identity The Git identity string to parse. ex.: 'john Doe [email protected] 1442321765 +0200'
*
* @param identity The Git identity string to parse. ex.: 'john Doe
* [email protected] 1442321765 +0200'
* @return A PersonIdent object representing given Git author/committer
*/
public PersonIdent getPersonIdent(String identity) {
Pattern regex = Pattern.compile("^([^<(]*?)[ \\t]?<([^<>]*?)>.*$");
Matcher match = regex.matcher(identity);
if(!match.matches()) return null;
if (!match.matches())
return null;
return new PersonIdent(match.group(1), match.group(2));
}

/**
* Attempts to rebase the ready integrationBranch onto the integration integrationBranch.
* Attempts to rebase the ready integrationBranch onto the integration
* integrationBranch.
* Only when the ready integrationBranch consists of a single commit.
*
* @param commitId The sha1 from the polled integrationBranch
* @param client The GitClient
* @param integrationBranch The integrationBranch which the commitId need to be merged to
* @param logger The Printstream
* @return true if the rebase was a success, false if the integrationBranch isn't
* suitable for a rebase
* @throws IntegrationFailedException When commit counting or rebasing fails
* @param commitId The sha1 from the polled integrationBranch
* @param client The GitClient
* @param integrationBranch The integrationBranch which the commitId need to be
* merged to
* @param logger The Printstream
* @return true if the rebase was a success, false if the integrationBranch
* isn't
* suitable for a rebase
* @throws IntegrationFailedException When commit counting or rebasing
* fails
* @throws IntegrationUnknownFailureException An unforseen failure
*/
protected boolean tryRebase(ObjectId commitId, GitClient client, PrintStream logger, String integrationBranch ) throws IntegrationFailedException, IntegrationUnknownFailureException {
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX+ "Entering tryRebase");
logger.println(GitMessages.LOG_PREFIX+ "Entering tryRebase");

//Get the commit count
int commitCount;
protected boolean tryRebase(ObjectId commitId, GitClient client, PrintStream logger, String integrationBranch)
throws IntegrationFailedException, IntegrationUnknownFailureException {
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX + "Entering tryRebase");
logger.println(GitMessages.LOG_PREFIX + "Entering tryRebase");
// Rebase the commit
try {
commitCount = PretestedIntegrationGitUtils.countCommits(commitId, client, integrationBranch);
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX+ "Branch commit count: " + commitCount);
logger.println(GitMessages.LOG_PREFIX+ "Branch commit count: " + commitCount);
} catch (IOException | InterruptedException ex) {
throw new IntegrationUnknownFailureException("Failed to count commits.", ex);
}

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

//Rebase the commit
try {
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX+ "Attempting rebase.");
logger.println(GitMessages.LOG_PREFIX+ "Attempting rebase.");
//Rebase the commit, then checkout master for a fast-forward merge.
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX + "Attempting rebase.");
logger.println(GitMessages.LOG_PREFIX + "Attempting rebase.");
// Rebase the commit, then checkout master for a fast-forward merge.
client.checkout().ref(commitId.getName()).execute();
client.rebase().setUpstream(integrationBranch).execute();
ObjectId rebasedCommit = client.revParse("HEAD");
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX+ "Rebase successful. Attempting fast-forward merge.");
logger.println(GitMessages.LOG_PREFIX+ "Rebase successful. Attempting fast-forward merge.");
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX + "Rebase successful. Attempting fast-forward merge.");
logger.println(GitMessages.LOG_PREFIX + "Rebase successful. Attempting fast-forward merge.");

client.checkout().ref(integrationBranch).execute();
ObjectId integrationBranchCommitBefore = client.revParse("HEAD");
client.merge().setRevisionToMerge(rebasedCommit).setGitPluginFastForwardMode(MergeCommand.GitPluginFastForwardMode.FF_ONLY).execute();
if ( integrationBranchCommitBefore.equals(rebasedCommit) ){
String logMessage = String.format("%sThe integration branch did not change during the rebase of development branch on top of it.%n" +
"There are two known reasons:%n" +
"A) You are trying to integrate a change that was already integrated.%n" +
"B) You have pushed an empty commit( presumably used --allow-empty ) that needed a rebase. If you REALLY want the empty commit to me accepted, you can rebase your single empty commit on top of the integration branch.%n", GitMessages.LOG_PREFIX);
client.merge().setRevisionToMerge(rebasedCommit)
.setGitPluginFastForwardMode(MergeCommand.GitPluginFastForwardMode.FF_ONLY).execute();
if (integrationBranchCommitBefore.equals(rebasedCommit)) {
String logMessage = String.format(
"%sThe integration branch did not change during the rebase of development branch on top of it.%n"
+
"There are two known reasons:%n" +
"A) You are trying to integrate a change that was already integrated.%n" +
"B) You have pushed an empty commit( presumably used --allow-empty ) that needed a rebase. If you REALLY want the empty commit to me accepted, you can rebase your single empty commit on top of the integration branch.%n",
GitMessages.LOG_PREFIX);
LOGGER.log(Level.SEVERE, logMessage);
throw new IntegrationFailedException(logMessage);
} else {
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX+ "Rebasing successful.");
logger.println(GitMessages.LOG_PREFIX+ "Rebasing successful.");
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX + "Rebasing successful.");
logger.println(GitMessages.LOG_PREFIX + "Rebasing successful.");
return true;
}
} catch (GitException | InterruptedException ex) {
String logMessage = String.format(GitMessages.LOG_PREFIX+ "Exception while rebasing commit. Logging exception msg: %s", ex.getMessage());
String logMessage = String.format(
GitMessages.LOG_PREFIX + "Exception while rebasing commit. Logging exception msg: %s",
ex.getMessage());
LOGGER.log(Level.SEVERE, logMessage, ex);
logger.println(logMessage);
throw new IntegrationFailedException(ex);
}
}

/**
* Attempts to fast-forward merge the integration integrationBranch to the ready integrationBranch.
* Attempts to fast-forward merge the integration integrationBranch to the ready
* integrationBranch.
* Only when the ready integrationBranch consists of a single commit.
*
* @param commitId The commit
* @param commitId The commit
* @param commitCount The amount of commits
* @param logger The logger for console logging
* @param client The GitClient
* @return true if the FF merge was a success, false if the integrationBranch isn't
* suitable for a FF merge.
* @throws IntegrationFailedException When commit counting fails
* @throws NothingToDoException In case there is no commit to integrate/FF
* @param logger The logger for console logging
* @param client The GitClient
* @return true if the FF merge was a success, false if the integrationBranch
* isn't
* suitable for a FF merge.
*/
protected boolean tryFastForward(ObjectId commitId, PrintStream logger, GitClient client, int commitCount ) throws IntegrationFailedException, NothingToDoException {
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX+ "Entering tryFastForward");

if ( commitCount == 1) {
logger.println(GitMessages.LOG_PREFIX+ "Try FF as there is only one commit");
} else {
logger.println(GitMessages.LOG_PREFIX+ "Skip FF as there are several commits");
return false;
}

//FF merge the commit
protected boolean tryFastForward(ObjectId commitId, PrintStream logger, GitClient client) {
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX + "Entering tryFastForward"); // FF merge the commit
try {
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX+ "Attempting merge with FF.");
client.merge().setGitPluginFastForwardMode(MergeCommand.GitPluginFastForwardMode.FF_ONLY).setRevisionToMerge(commitId).execute();
logger.println(GitMessages.LOG_PREFIX+ "FF merge successful.");
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX+ " Exiting tryFastForward.");
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX + "Attempting merge with FF.");
client.merge().setGitPluginFastForwardMode(MergeCommand.GitPluginFastForwardMode.FF_ONLY)
.setRevisionToMerge(commitId).execute();
logger.println(GitMessages.LOG_PREFIX + "FF merge successful.");
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX + " Exiting tryFastForward.");
return true;
} catch (GitException | InterruptedException ex) {
logger.println(GitMessages.LOG_PREFIX+ "FF merge failed.");
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX+ " Exiting tryFastForward.");
logger.println(GitMessages.LOG_PREFIX + "FF merge failed.");
LOGGER.log(Level.INFO, GitMessages.LOG_PREFIX + " Exiting tryFastForward.");
return false;
}
}

/**
* Checks whether or not we can find the given remote integrationBranch.
*
* @param client the Git Client
* @param branch the integrationBranch to look for
* @return True if the integrationBranch was found, otherwise False.
Expand Down
Loading