package org.jfrog.bamboo.release.vcs.git;
import com.atlassian.bamboo.build.logger.BuildLogger;
import com.atlassian.bamboo.builder.BuildState;
import com.atlassian.bamboo.credentials.CredentialsAccessor;
import com.atlassian.bamboo.v2.build.BuildContext;
import com.atlassian.bamboo.v2.build.CurrentBuildResult;
import com.atlassian.bamboo.variable.CustomVariableContext;
import org.apache.commons.lang.StringUtils;
import org.jfrog.bamboo.context.AbstractBuildContext;
import org.jfrog.bamboo.release.vcs.AbstractVcsCoordinator;
import java.io.IOException;
import java.util.Map;
/**
* Git coordinator that will perform SCM operations that are specific to the GIT SCM.
*
* @author Tomer Cohen
*/
public class GitCoordinator extends AbstractVcsCoordinator {
private GitManager vcsManager;
private String releaseBranch;
private String checkoutBranch;
// the commit hash of the initial checkout
private String baseCommitIsh;
private State state = new State();
private final Map<String, String> configuration;
private final BuildLogger buildLogger;
private static class State {
String currentWorkingBranch;
boolean releaseBranchCreated;
boolean releaseBranchPushed;
boolean tagCreated;
boolean tagPushed;
}
public GitCoordinator(BuildContext context, Map<String, String> configuration,
BuildLogger buildLogger, CustomVariableContext customVariableContext, CredentialsAccessor credentialsAccessor) {
super(context, buildLogger, customVariableContext, credentialsAccessor);
this.configuration = configuration;
this.buildLogger = buildLogger;
}
@Override
public void prepare() throws IOException {
releaseBranch = configuration.get(AbstractBuildContext.ReleaseManagementContext.RELEASE_BRANCH);
vcsManager = new GitManager(context, buildLogger);
baseCommitIsh = vcsManager.getCurrentCommitHash();
checkoutBranch = vcsManager.getCurrentBranch();
}
@Override
public void beforeReleaseVersionChange() throws IOException {
if (Boolean.parseBoolean(configuration.get(AbstractBuildContext.ReleaseManagementContext.USE_RELEASE_BRANCH))) {
vcsManager.checkoutBranch(releaseBranch, true);
state.currentWorkingBranch = releaseBranch;
state.releaseBranchCreated = true;
} else {
// make sure we are on the checkout branch
vcsManager.checkoutBranch(checkoutBranch, false);
state.currentWorkingBranch = checkoutBranch;
}
}
@Override
public void afterSuccessfulReleaseVersionBuild() throws IOException, InterruptedException {
if (modifiedFilesForReleaseVersion) {
// commit local changes
log(String.format("Committing release version on branch '%s'", checkoutBranch));
String comment = configuration.get(AbstractBuildContext.ReleaseManagementContext.TAG_COMMENT);
if (StringUtils.isBlank(comment)) {
comment = "";
}
vcsManager.commitWorkingCopy(comment);
}
if (Boolean.parseBoolean(configuration.get(AbstractBuildContext.ReleaseManagementContext.CREATE_VCS_TAG))) {
vcsManager.createTag(configuration.get(AbstractBuildContext.ReleaseManagementContext.TAG_URL),
configuration.get(AbstractBuildContext.ReleaseManagementContext.TAG_COMMENT));
state.tagCreated = true;
}
if (state.releaseBranchCreated) {
// push the current branch
vcsManager.push(vcsManager.getRemoteUrl(), state.currentWorkingBranch);
state.releaseBranchPushed = true;
}
if (Boolean.parseBoolean(configuration.get(AbstractBuildContext.ReleaseManagementContext.CREATE_VCS_TAG))) {
// push the tag
vcsManager.pushTag(vcsManager.getRemoteUrl(),
configuration.get(AbstractBuildContext.ReleaseManagementContext.TAG_URL));
state.tagPushed = true;
}
}
@Override
public void beforeDevelopmentVersionChange() throws IOException {
if (Boolean.parseBoolean(configuration.get(AbstractBuildContext.ReleaseManagementContext.USE_RELEASE_BRANCH))) {
// done working on the release branch, checkout back to master
vcsManager.checkoutBranch(checkoutBranch, false);
state.currentWorkingBranch = checkoutBranch;
}
}
@Override
public void afterDevelopmentVersionChange(boolean modified) throws IOException, InterruptedException {
super.afterDevelopmentVersionChange(modified);
if (modified) {
log(String.format("Committing next development version on branch '%s'", state.currentWorkingBranch));
String comment = configuration.get(AbstractBuildContext.ReleaseManagementContext.NEXT_DEVELOPMENT_COMMENT);
if (StringUtils.isBlank(comment)) {
comment = "";
}
vcsManager.commitWorkingCopy(comment);
}
}
@Override
public void buildCompleted(BuildContext buildContext) throws IOException, InterruptedException {
AbstractBuildContext context = AbstractBuildContext.createContextFromMap(configuration);
CurrentBuildResult result = buildContext.getBuildResult();
if (BuildState.SUCCESS.equals(result.getBuildState())) {
if (modifiedFilesForDevVersion) {
vcsManager.push(vcsManager.getRemoteUrl(), checkoutBranch);
}
} else {
// go back to the original checkout branch (required to delete the release branch and reset the working copy)
vcsManager.checkoutBranch(checkoutBranch, false);
state.currentWorkingBranch = checkoutBranch;
if (state.releaseBranchCreated) {
safeDeleteBranch(releaseBranch);
}
if (state.releaseBranchPushed) {
safeDeleteRemoteBranch(vcsManager.getRemoteUrl(), releaseBranch);
}
if (state.tagCreated) {
safeDeleteTag(context.releaseManagementContext.getTagUrl());
}
if (state.tagPushed) {
safeDeleteRemoteTag(vcsManager.getRemoteUrl(), context.releaseManagementContext.getTagUrl());
}
// reset changes done on the original checkout branch (next dev version)
safeRevertWorkingCopy();
}
}
@Override
public String getRemoteUrlForPom() {
return null;
}
private void safeDeleteBranch(String branch) {
try {
vcsManager.deleteLocalBranch(branch);
} catch (Exception e) {
log(buildLogger.addBuildLogEntry("Failed to delete release branch: " + e.getLocalizedMessage()));
}
}
private void safeDeleteRemoteBranch(String remoteRepository, String branch) {
try {
vcsManager.deleteRemoteBranch(remoteRepository, branch);
} catch (Exception e) {
log(buildLogger.addBuildLogEntry("Failed to delete remote release branch: " + e.getLocalizedMessage()));
}
}
private void safeDeleteTag(String tag) {
try {
vcsManager.deleteLocalTag(tag);
} catch (Exception e) {
log(buildLogger.addBuildLogEntry("Failed to delete tag: " + e.getLocalizedMessage()));
}
}
private void safeDeleteRemoteTag(String remoteRepository, String tag) {
try {
vcsManager.deleteRemoteTag(remoteRepository, tag);
} catch (Exception e) {
log(buildLogger.addBuildLogEntry("Failed to delete remote tag: " + e.getLocalizedMessage()));
}
}
private void safeRevertWorkingCopy() {
try {
vcsManager.revertWorkingCopy(baseCommitIsh);
} catch (Exception e) {
log(buildLogger.addBuildLogEntry("Failed to revert working copy: " + e.getLocalizedMessage()));
}
}
@Override
public String getCheckoutBranch() {
return checkoutBranch;
}
@Override
public boolean isReleaseBranchCreated() {
return state.releaseBranchCreated;
}
@Override
public void setReleaseBranchCreated(boolean releaseBranchCreated) {
state.releaseBranchCreated = releaseBranchCreated;
}
@Override
public boolean isSubversion() {
return false;
}
@Override
public void setCheckoutBranch(String checkoutBranch) {
this.checkoutBranch = checkoutBranch;
}
@Override
public String getCurrentWorkingBranch() {
return state.currentWorkingBranch;
}
@Override
public void setCurrentWorkingBranch(String currentWorkingBranch) {
state.currentWorkingBranch = currentWorkingBranch;
}
@Override
public void setCommitIsh(String commitIsh) {
this.baseCommitIsh = commitIsh;
}
@Override
public String getCommitIsh() {
return this.baseCommitIsh;
}
}