org.jfrog.hudson.release.scm.git.GitCoordinator.java Source code

Java tutorial

Introduction

Here is the source code for org.jfrog.hudson.release.scm.git.GitCoordinator.java

Source

/*
 * Copyright (C) 2011 JFrog Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jfrog.hudson.release.scm.git;

import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Result;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.ObjectId;
import org.jfrog.hudson.release.ReleaseAction;
import org.jfrog.hudson.release.scm.AbstractScmCoordinator;

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

/**
 * Git scm coordinator. Interacts with the {@link GitManager} to fulfill the release process.
 *
 * @author Yossi Shaul
 */
public class GitCoordinator extends AbstractScmCoordinator {
    private static Logger debuggingLogger = Logger.getLogger(GitCoordinator.class.getName());

    private final ReleaseAction releaseAction;
    private GitManager scmManager;
    private String releaseBranch;
    private String checkoutBranch;

    private State state = new State();

    public GitCoordinator(AbstractBuild build, BuildListener listener, ReleaseAction releaseAction) {
        super(build, listener);
        this.releaseAction = releaseAction;
    }

    public void prepare() throws IOException, InterruptedException {
        releaseBranch = releaseAction.getReleaseBranch();

        scmManager = new GitManager(build, listener);
        scmManager.setGitCredentials(releaseAction.getGitCredentials());

        // find the current local built branch
        String gitBranchName = build.getEnvironment(listener).get("GIT_BRANCH");
        state.initialGitRevision = scmManager.revParse(gitBranchName);
        checkoutBranch = scmManager.getBranchNameWithoutRemote(gitBranchName);
    }

    public void beforeReleaseVersionChange() throws IOException, InterruptedException {
        if (releaseAction.isCreateReleaseBranch()) {
            // create a new branch for the release and start it
            scmManager.checkoutBranch(releaseBranch, true);
            state.currentWorkingBranch = releaseBranch;
            state.releaseBranchCreated = true;
        } else {
            // make sure we are on the checkout branch
            scmManager.checkoutBranch(checkoutBranch, false);
            state.currentWorkingBranch = checkoutBranch;
        }
    }

    public void afterReleaseVersionChange(boolean modified) throws IOException, InterruptedException {
        super.afterReleaseVersionChange(modified);
        if (modifiedFilesForReleaseVersion) {
            // commit local changes immediately - so that e.g. if the build uses it's own git commit, that commit
            // correctly points to the release commit. In general, it's good to avoid "dirty" builds.
            log(String.format("Committing release version on branch '%s'", state.currentWorkingBranch));
            scmManager.commitWorkingCopy(releaseAction.getTagComment());
        }

        if (!releaseAction.isCreateReleaseBranch() && modifiedFilesForReleaseVersion) {
            //if we're not on the release branch, we must have modified the original branch
            state.checkoutBranchModified = true;
        }

        if (releaseAction.isCreateVcsTag()) {
            scmManager.createTag(releaseAction.getTagUrl(), releaseAction.getTagComment());
            state.tagCreated = true;
        }
    }

    public void afterSuccessfulReleaseVersionBuild() throws Exception {
        // nop
    }

    public void beforeDevelopmentVersionChange() throws IOException, InterruptedException {
        if (releaseAction.isCreateReleaseBranch()) {
            // done working on the release branch, checkout back to master
            scmManager.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));
            scmManager.commitWorkingCopy(releaseAction.getNextDevelCommitComment());
            state.checkoutBranchModified = true;
        }
    }

    public void buildCompleted() throws Exception {
        if (build.getResult().isBetterOrEqualTo(Result.SUCCESS)) {
            // pull before attempting to push changes?
            //scmManager.pull(scmManager.getRemoteUrl(), checkoutBranch);

            // only push at the very end - this avoid unnecessary cleanup and makes the build as atomic as possible
            // from the point of view of the git repo.
            if (state.releaseBranchCreated) {
                scmManager.push(scmManager.getRemoteConfig(releaseAction.getTargetRemoteName()), releaseBranch);
            }
            if (state.checkoutBranchModified) {
                scmManager.push(scmManager.getRemoteConfig(releaseAction.getTargetRemoteName()), checkoutBranch);
            }
        } else {
            // go back to the original checkout branch (required to delete the release branch and reset the working copy)
            scmManager.checkoutBranch(checkoutBranch, false);
            state.currentWorkingBranch = checkoutBranch;

            if (state.releaseBranchCreated) {
                safeDeleteBranch(releaseBranch);
            }
            if (state.tagCreated) {
                safeDeleteTag(releaseAction.getTagUrl());
            }
            // reset changes done on the original checkout branch (next dev version)
            safeRevertWorkingCopy(checkoutBranch, state.initialGitRevision);
        }
    }

    private void safeDeleteBranch(String branch) {
        try {
            scmManager.deleteLocalBranch(branch);
        } catch (Exception e) {
            debuggingLogger.log(Level.FINE, "Failed to delete release branch: ", e);
            log("Failed to delete release branch: " + e.getLocalizedMessage());
        }
    }

    private void safeDeleteTag(String tag) {
        try {
            scmManager.deleteLocalTag(tag);
        } catch (Exception e) {
            debuggingLogger.log(Level.FINE, "Failed to delete tag: ", e);
            log("Failed to delete tag: " + e.getLocalizedMessage());
        }
    }

    private void safeRevertWorkingCopy(String branch, ObjectId revision) {
        try {
            log("Reverting git checkout to original version " + revision.name());
            scmManager.revertWorkingCopyTo(branch, revision.name());
        } catch (Exception e) {
            debuggingLogger.log(Level.FINE, "Failed to revert working copy: ", e);
            log("Failed to revert working copy: " + e.getLocalizedMessage());
        }
    }

    public String getRemoteUrlForPom() {
        return null;
    }

    private static class State {
        ObjectId initialGitRevision;
        String currentWorkingBranch;
        boolean releaseBranchCreated;
        boolean checkoutBranchModified;
        boolean tagCreated;
    }
}