All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.danielflower.mavenplugins.release.LocalGitRepo Maven / Gradle / Ivy

Go to download

A maven release plugin built for multi-maven-module git repositories allowing continuous deployment

The newest version!
package com.github.danielflower.mavenplugins.release;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.LsRemoteCommand;
import org.eclipse.jgit.api.PushCommand;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.CredentialsProvider;

import java.io.File;
import java.io.IOException;
import java.util.*;

import static com.github.danielflower.mavenplugins.release.FileUtils.pathOf;

public class LocalGitRepo {

    public final Git git;
    private boolean hasReverted = false; // A premature optimisation? In the normal case, file reverting occurs twice, which this bool prevents
    private Collection tags;

    private final TagFetcher tagFetcher;
    private final TagPusher tagPusher;

    public static class Builder {

        private Set operationsAllowed = EnumSet.allOf(GitOperations.class);
        private String remoteGitUrl;
        private CredentialsProvider credentialsProvider;

        /**
         * Flag for which remote Git operations are permitted. Local values will
         * be substituted if remote operations are forbidden; this means the
         * local copy of the repository must be up to date!
         */
        public Builder remoteGitOperationsAllowed(Set operationsAllowed) {
            this.operationsAllowed = EnumSet.copyOf(operationsAllowed);
            return this;
        }

        /**
         * Overrides the URL of remote Git repository.
         */
        public Builder remoteGitUrl(String remoteUrl) {
            this.remoteGitUrl = remoteUrl;
            return this;
        }

        /**
         * Sets the username/password pair for HTTPS URLs or SSH with passwordl
         */
        public Builder credentialsProvider(CredentialsProvider credentialsProvider) {
            this.credentialsProvider = credentialsProvider;
            return this;
        }

        /**
         * Uses the current working dir to open the Git repository.
         * @throws ValidationException if anything goes wrong
         */
        public LocalGitRepo buildFromCurrentDir() throws ValidationException {
            Git git;
            File gitDir = new File(".");
            try {
                git = Git.open(gitDir);
            } catch (RepositoryNotFoundException rnfe) {
                String fullPathOfCurrentDir = pathOf(gitDir);
                File gitRoot = getGitRootIfItExistsInOneOfTheParentDirectories(new File(fullPathOfCurrentDir));
                String summary;
                List messages = new ArrayList();
                if (gitRoot == null) {
                    summary = "Releases can only be performed from Git repositories.";
                    messages.add(summary);
                    messages.add(fullPathOfCurrentDir + " is not a Git repository.");
                } else {
                    summary = "The release plugin can only be run from the root folder of your Git repository";
                    messages.add(summary);
                    messages.add(fullPathOfCurrentDir + " is not the root of a Git repository");
                    messages.add("Try running the release plugin from " + pathOf(gitRoot));
                }
                throw new ValidationException(summary, messages);
            } catch (Exception e) {
                throw new ValidationException("Could not open git repository. Is " + pathOf(gitDir) + " a git repository?", Arrays.asList("Exception returned when accessing the git repo:", e.toString()));
            }

            TagFetcher tagFetcher;
            TagPusher tagPusher;

            if (operationsAllowed.contains(GitOperations.PULL_TAGS)) {
                tagFetcher = new RemoteTagFetcher(git, remoteGitUrl, credentialsProvider);
            } else {
                tagFetcher = new LocalTagFetcher(git);
            }

            if (operationsAllowed.contains(GitOperations.PUSH_TAGS)) {
                tagPusher = new RemoteTagPusher(git, remoteGitUrl, credentialsProvider);
            } else {
                tagPusher = new LocalTagPusher(git);
            }

            return new LocalGitRepo(git, tagFetcher, tagPusher);
        }

    }

    LocalGitRepo(Git git, TagFetcher tagFetcher, TagPusher tagPusher) {
        this.git = git;
        this.tagFetcher = tagFetcher;
        this.tagPusher = tagPusher;
    }

    public void errorIfNotClean(Set ignoredUntrackedPaths) throws ValidationException {
        Status status = currentStatus();
        Set untracked = new HashSet<>(status.getUntracked());
        untracked.removeAll(ignoredUntrackedPaths);
        boolean isClean = !status.hasUncommittedChanges() && untracked.isEmpty();
        if (!isClean) {
            String summary = "Cannot release with uncommitted changes. Please check the following files:";
            List message = new ArrayList();
            message.add(summary);
            Set uncommittedChanges = status.getUncommittedChanges();
            if (!uncommittedChanges.isEmpty()) {
                message.add("Uncommitted:");
                for (String path : uncommittedChanges) {
                    message.add(" * " + path);
                }
            }
            if (!untracked.isEmpty()) {
                message.add("Untracked:");
                for (String path : untracked) {
                    message.add(" * " + path);
                }
            }
            message.add("Please commit or revert these changes before releasing.");
            throw new ValidationException(summary, message);
        }
    }

    private Status currentStatus() throws ValidationException {
        Status status;
        try {
            status = git.status().call();
        } catch (GitAPIException e) {
            throw new ValidationException("Error while checking if the Git repo is clean", e);
        }
        return status;
    }

    public boolean revertChanges(Log log, List changedFiles) throws MojoExecutionException {
        if (hasReverted) {
            return true;
        }
        boolean hasErrors = false;
        File workTree = workingDir();
        for (File changedFile : changedFiles) {
            try {
                String pathRelativeToWorkingTree = Repository.stripWorkDir(workTree, changedFile);
                git.checkout().addPath(pathRelativeToWorkingTree).call();
            } catch (Exception e) {
                hasErrors = true;
                log.error("Unable to revert changes to " + changedFile + " - you may need to manually revert this file. Error was: " + e.getMessage());
            }
        }
        hasReverted = true;
        return !hasErrors;
    }

    private File workingDir() throws MojoExecutionException {
        try {
            return git.getRepository().getWorkTree().getCanonicalFile();
        } catch (IOException e) {
            throw new MojoExecutionException("Could not locate the working directory of the Git repo", e);
        }
    }

    public boolean hasLocalTag(String tagName) throws GitAPIException {
        return GitHelper.hasLocalTag(git, tagName);
    }

    public void tagAndPushRepo(Collection tags) throws GitAPIException {
        tagPusher.pushTags(tags);
    }

    private static File getGitRootIfItExistsInOneOfTheParentDirectories(File candidateDir) {
        while (candidateDir != null && /* HACK ATTACK! Maybe.... */ !candidateDir.getName().equals("target") ) {
            if (new File(candidateDir, ".git").isDirectory()) {
                return candidateDir;
            }
            candidateDir = candidateDir.getParentFile();
        }
        return null;
    }

    public List tagsFrom(List annotatedTags) throws GitAPIException {
        List tagNames = new ArrayList();
        for (AnnotatedTag annotatedTag : annotatedTags) {
            tagNames.add(annotatedTag.name());
        }
        return getTags(tagNames);
    }

    public List getTags(List tagNamesToSearchFor) throws GitAPIException {
        List results = new ArrayList();
        Collection remoteTags = allTags();
        for (Ref remoteTag : remoteTags) {
            for (String proposedTag : tagNamesToSearchFor) {
                if (remoteTag.getName().equals("refs/tags/" + proposedTag)) {
                    results.add(proposedTag);
                }
            }
        }
        return results;
    }

    public Collection allTags() throws GitAPIException {
        if (tags == null) {
            tags = this.tagFetcher.getTags();
        }

        return tags;
    }
}

interface TagFetcher {

    Collection getTags() throws GitAPIException;

}

class RemoteTagFetcher implements TagFetcher {

    private final Git git;
    private final String remoteUrl;
    private final CredentialsProvider credentialsProvider;

    public RemoteTagFetcher(Git git, String remoteUrl, CredentialsProvider credentialsProvider) {
        this.git = git;
        this.remoteUrl = remoteUrl;
        this.credentialsProvider = credentialsProvider;
    }

    @Override
    public Collection getTags() throws GitAPIException {
        LsRemoteCommand lsRemoteCommand = git.lsRemote()
            .setTags(true).setHeads(false)
            .setCredentialsProvider(credentialsProvider);
        if (remoteUrl != null) {
            lsRemoteCommand.setRemote(remoteUrl);
        }

        return lsRemoteCommand.call();
   }

}

class LocalTagFetcher implements TagFetcher {

    private final Git git;

    public LocalTagFetcher(Git git) {
        this.git = git;
    }

    @Override
    public Collection getTags() throws GitAPIException {
        return git.tagList().call();
    }

}

interface TagPusher {

    void pushTags(Collection tags) throws GitAPIException;

}

class RemoteTagPusher implements TagPusher {

    private final Git git;
    private final String remoteUrl;
    private final CredentialsProvider credentialsProvider;

    public RemoteTagPusher(Git git, String remoteUrl, CredentialsProvider credentialsProvider) {
        this.git = git;
        this.remoteUrl = remoteUrl;
        this.credentialsProvider = credentialsProvider;
    }

    @Override
    public void pushTags(Collection tags) throws GitAPIException {
        PushCommand pushCommand = git.push()
            .setCredentialsProvider(credentialsProvider);
        if (remoteUrl != null) {
            pushCommand.setRemote(remoteUrl);
        }

        for (AnnotatedTag tag : tags) {
            pushCommand.add(tag.saveAtHEAD(git));
        }

        pushCommand.call();
    }

}

class LocalTagPusher implements TagPusher {

    private final Git git;

    public LocalTagPusher(Git git) {
        this.git = git;
    }

    @Override
    public void pushTags(Collection tags) throws GitAPIException {
        for (AnnotatedTag tag : tags) {
            tag.saveAtHEAD(git);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy