package com.atlassian.jgitflow.core.command; import java.util.concurrent.Callable; import com.atlassian.jgitflow.core.GitFlowConfiguration; import com.atlassian.jgitflow.core.JGitFlowConstants; import com.atlassian.jgitflow.core.JGitFlowReporter; import com.atlassian.jgitflow.core.exception.BranchOutOfDateException; import com.atlassian.jgitflow.core.exception.JGitFlowExtensionException; import com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException; import com.atlassian.jgitflow.core.exception.JGitFlowIOException; import com.atlassian.jgitflow.core.extension.ExtensionCommand; import com.atlassian.jgitflow.core.extension.ExtensionFailStrategy; import com.atlassian.jgitflow.core.extension.JGitFlowExtension; import com.atlassian.jgitflow.core.util.GitHelper; import com.atlassian.jgitflow.core.util.RequirementHelper; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.TrackingRefUpdate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.atlassian.jgitflow.core.util.Preconditions.checkNotNull; /** * The base class for all JGitFlow commands. * <p> * Most commands should extend this class as it provides common helper methods * and methods to ensure valid state. * </p> * * @param <T> The return type of the call() method */ public abstract class AbstractGitFlowCommand<C, T> implements Callable<T>, JGitFlowCommand { private static final Logger log = LoggerFactory.getLogger(AbstractGitFlowCommand.class); protected final Git git; protected final GitFlowConfiguration gfConfig; protected final JGitFlowReporter reporter = JGitFlowReporter.get(); protected final RequirementHelper requirementHelper; private boolean allowUntracked; private String scmMessagePrefix; private String scmMessageSuffix; private boolean fetch; private boolean push; private final String branchName; protected AbstractGitFlowCommand(String branchName, Git git, GitFlowConfiguration gfConfig) { checkNotNull(branchName); checkNotNull(git); checkNotNull(gfConfig); this.requirementHelper = new RequirementHelper(git, gfConfig, getCommandName()); this.git = git; this.gfConfig = gfConfig; this.allowUntracked = false; this.scmMessagePrefix = ""; this.scmMessageSuffix = ""; this.fetch = false; this.push = false; this.branchName = branchName; } protected void doFetchIfNeeded(JGitFlowExtension fetchingExtension) throws GitAPIException, JGitFlowGitAPIException, JGitFlowExtensionException { if (fetch) { runExtensionCommands(fetchingExtension.beforeFetch()); git.fetch().setRemote(Constants.DEFAULT_REMOTE_NAME).call(); runExtensionCommands(fetchingExtension.afterFetch()); } } protected void doPushIfNeeded(JGitFlowExtension pushExtension, boolean includeTags, String... branchesToPush) throws GitAPIException, JGitFlowGitAPIException, JGitFlowExtensionException { if (push) { reporter.infoText(getCommandName(), "pushing changes to origin..."); for (String branchToPush : branchesToPush) { if (GitHelper.remoteBranchExists(git, branchToPush)) { reporter.infoText(getCommandName(), "pushing '" + branchToPush + "'"); RefSpec branchSpec = new RefSpec(branchToPush); Iterable<PushResult> i = git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(branchSpec).call(); for (PushResult pr : i) { reporter.infoText(getCommandName(), "messages: '" + pr.getMessages() + "'"); for(RemoteRefUpdate update : pr.getRemoteUpdates()) { if (update.hasTrackingRefUpdate()) { RefUpdate.Result trackingResult = update.getTrackingRefUpdate().getResult(); if (failedResult(trackingResult)) { if (pr.getMessages() != null && pr.getMessages().length() > 0) { throw new JGitFlowGitAPIException("error pushing to " + branchToPush + " - status: " + trackingResult.name() + " - " + pr.getMessages()); } else { throw new JGitFlowGitAPIException("error pushing to " + branchToPush + " - " + trackingResult.name()); } } } } } } } if (includeTags) { reporter.infoText(getCommandName(), "pushing tags"); // TODO: check for errors or at least log error messages received git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setPushTags().call(); } git.fetch().setRemote(Constants.DEFAULT_REMOTE_NAME).call(); runExtensionCommands(pushExtension.afterPush()); } } private boolean failedResult(RefUpdate.Result trackingResult) { boolean isFailed = false; switch(trackingResult) { case LOCK_FAILURE: case REJECTED: case REJECTED_CURRENT_BRANCH: case IO_FAILURE: isFailed = true; break; } return isFailed; } protected String runBeforeAndGetPrefixedBranchName(Iterable<ExtensionCommand> before, JGitFlowConstants.PREFIXES prefix) throws JGitFlowExtensionException { reporter.commandCall(getCommandName()); runExtensionCommands(before); return gfConfig.getPrefixValue(prefix.configKey()) + branchName; } protected void ensureLocalBranchesNotBehindRemotes(String... branchesToTest) throws JGitFlowGitAPIException, BranchOutOfDateException, JGitFlowIOException { for (String branchToTest : branchesToTest) { if (GitHelper.remoteBranchExists(git, branchToTest)) { enforcer().requireLocalBranchNotBehindRemote(branchToTest); } } } @Override public C setAllowUntracked(boolean allow) { this.allowUntracked = allow; return (C) this; } @Override public boolean isAllowUntracked() { return allowUntracked; } @Override public String getScmMessagePrefix() { return scmMessagePrefix; } @Override public C setScmMessagePrefix(String scmMessagePrefix) { this.scmMessagePrefix = scmMessagePrefix; return (C) this; } @Override public String getScmMessageSuffix() { return scmMessageSuffix; } @Override public C setScmMessageSuffix(String scmMessageSuffix) { this.scmMessageSuffix = scmMessageSuffix; return (C) this; } /** * Set whether to perform a git fetch of the remote branches before doing the merge * * @param fetch {@code true} to do the fetch, {@code false}(default) otherwise * @return {@code this} */ @Override public C setFetch(boolean fetch) { this.fetch = fetch; return (C) this; } @Override public boolean isFetch() { return fetch; } /** * Set whether to push the changes to the remote repository * * @param push {@code true} to do the push, {@code false}(default) otherwise * @return {@code this} */ @Override public C setPush(boolean push) { this.push = push; return (C) this; } @Override public boolean isPush() { return push; } @Override public String getBranchName() { return branchName; } protected abstract String getCommandName(); protected void runExtensionCommands(Iterable<ExtensionCommand> commands) throws JGitFlowExtensionException { for (final ExtensionCommand command : commands) { try { command.execute(gfConfig, git, this); } catch (JGitFlowExtensionException e) { if (ExtensionFailStrategy.ERROR.equals(command.failStrategy())) { throw e; } else { log.warn("Error running JGitFlow Extension", e); } } } } protected RequirementHelper enforcer() { return requirementHelper; } }