org.springframework.cloud.release.internal.git.GitRepo.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.cloud.release.internal.git.GitRepo.java

Source

/*
 *  Copyright 2013-2017 the original author or authors.
 *
 *  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.springframework.cloud.release.internal.git;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.util.List;

import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CreateBranchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.api.errors.EmtpyCommitException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ResourceUtils;

/**
 * Abstraction over a Git repo. Can cloned repo from a given location
 * and check its branch.
 *
 * @author Marcin Grzejszczak
 */
class GitRepo {

    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private final GitRepo.JGitFactory gitFactory;

    private final File basedir;

    GitRepo(File basedir) {
        this.basedir = basedir;
        this.gitFactory = new GitRepo.JGitFactory();
    }

    GitRepo(File basedir, GitRepo.JGitFactory factory) {
        this.basedir = basedir;
        this.gitFactory = factory;
    }

    /**
     * Clones the project
     * @param projectUri - URI of the project
     * @return file where the project was cloned
     */
    File cloneProject(URI projectUri) {
        try {
            log.info("Cloning repo from [{}] to [{}]", projectUri, this.basedir);
            Git git = cloneToBasedir(projectUri, this.basedir);
            if (git != null) {
                git.close();
            }
            File clonedRepo = git.getRepository().getWorkTree();
            log.info("Cloned repo to [{}]", clonedRepo);
            return clonedRepo;
        } catch (Exception e) {
            throw new IllegalStateException("Exception occurred while cloning repo", e);
        }
    }

    /**
     * Checks out a branch for a project
     * @param project - a Git project
     * @param branch - branch to check out
     */
    void checkout(File project, String branch) {
        try {
            log.info("Checking out branch [{}] for repo [{}] to [{}]", this.basedir, branch);
            checkoutBranch(project, branch);
            log.info("Successfully checked out the branch [{}]", branch);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Performs a commit
     * @param project - a Git project
     * @param message - commit message
     */
    void commit(File project, String message) {
        try (Git git = this.gitFactory.open(file(project))) {
            git.add().addFilepattern(".").call();
            git.commit().setAllowEmpty(false).setMessage(message).call();
        } catch (EmtpyCommitException e) {
            log.info("There were no changes detected. Will not commit an empty commit");
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Creates a tag with a given name
     * @param project
     * @param tagName
     */
    void tag(File project, String tagName) {
        try (Git git = this.gitFactory.open(file(project))) {
            git.tag().setName(tagName).call();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Pushes the commits to {@code origin} remote branch
     * @param project - Git project
     * @param branch - remote branch to which the code should be pushed
     */
    void pushBranch(File project, String branch) {
        try (Git git = this.gitFactory.open(file(project))) {
            String localBranch = git.getRepository().getFullBranch();
            RefSpec refSpec = new RefSpec(localBranch + ":" + branch);
            git.push().setPushTags().setRefSpecs(refSpec).call();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Pushes the commits od current branch
     * @param project - Git project
     */
    void pushCurrentBranch(File project) {
        try (Git git = this.gitFactory.open(file(project))) {
            git.push().call();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Pushes the commits to {@code origin} remote tag
     * @param project - Git project
     * @param tagName - remote tag to which the code should be pushed
     */
    void pushTag(File project, String tagName) {
        try (Git git = this.gitFactory.open(file(project))) {
            String localBranch = git.getRepository().getFullBranch();
            RefSpec refSpec = new RefSpec(localBranch + ":" + "refs/tags/" + tagName);
            git.push().setPushTags().setRefSpecs(refSpec).call();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    void revert(File project, String message) {
        try (Git git = this.gitFactory.open(file(project))) {
            RevCommit commit = git.log().setMaxCount(1).call().iterator().next();
            log.debug("The commit to be reverted is [{}]", commit);
            git.revert().include(commit).call();
            git.commit().setAmend(true).setMessage(message).call();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private File file(File project) throws FileNotFoundException {
        return ResourceUtils.getFile(project.toURI()).getAbsoluteFile();
    }

    private Git cloneToBasedir(URI projectUrl, File destinationFolder) throws GitAPIException {
        CloneCommand command = this.gitFactory.getCloneCommandByCloneRepository()
                .setURI(projectUrl.toString() + ".git").setDirectory(destinationFolder);
        try {
            return command.call();
        } catch (GitAPIException e) {
            deleteBaseDirIfExists();
            throw e;
        }
    }

    private Ref checkoutBranch(File projectDir, String branch) throws GitAPIException {
        Git git = this.gitFactory.open(projectDir);
        CheckoutCommand command = git.checkout().setName(branch);
        try {
            if (shouldTrack(git, branch)) {
                trackBranch(command, branch);
            }
            return command.call();
        } catch (GitAPIException e) {
            deleteBaseDirIfExists();
            throw e;
        } finally {
            git.close();
        }
    }

    private boolean shouldTrack(Git git, String label) throws GitAPIException {
        return isBranch(git, label) && !isLocalBranch(git, label);
    }

    private void trackBranch(CheckoutCommand checkout, String label) {
        checkout.setCreateBranch(true).setName(label).setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
                .setStartPoint("origin/" + label);
    }

    private boolean isBranch(Git git, String label) throws GitAPIException {
        return containsBranch(git, label, ListBranchCommand.ListMode.ALL);
    }

    private boolean isLocalBranch(Git git, String label) throws GitAPIException {
        return containsBranch(git, label, null);
    }

    private boolean containsBranch(Git git, String label, ListBranchCommand.ListMode listMode)
            throws GitAPIException {
        ListBranchCommand command = git.branchList();
        if (listMode != null) {
            command.setListMode(listMode);
        }
        List<Ref> branches = command.call();
        for (Ref ref : branches) {
            if (ref.getName().endsWith("/" + label)) {
                return true;
            }
        }
        return false;
    }

    private void deleteBaseDirIfExists() {
        if (this.basedir.exists()) {
            try {
                FileUtils.delete(this.basedir, FileUtils.RECURSIVE);
            } catch (IOException e) {
                throw new IllegalStateException("Failed to initialize base directory", e);
            }
        }
    }

    /**
     * Wraps the static method calls to {@link org.eclipse.jgit.api.Git} and
     * {@link org.eclipse.jgit.api.CloneCommand} allowing for easier unit testing.
     */
    static class JGitFactory {
        CloneCommand getCloneCommandByCloneRepository() {
            return Git.cloneRepository();
        }

        Git open(File file) {
            try {
                return Git.open(file);
            } catch (IOException e) {
                throw new IllegalStateException(e);
            }
        }
    }
}