java 使用JGit操作 git - 工具类

 

注意先引入依赖包。

 

        <dependency>
            <groupId>org.eclipse.jgit</groupId>
            <artifactId>org.eclipse.jgit</artifactId>
            <version>5.13.0.202109080827-r</version>
        </dependency>

 

工具类:

@Slf4j
public class GitTools implements InitializingBean {

    protected Log logger = LogFactory.getLog(getClass());

    private String uri = "https://gitlab.xxxxx.git";
    private File basedir;

    public String getUri() {
        return uri;
    }

    /**
     * Username for authentication with remote repository.
     */
    private String username;

    /**
     * Password for authentication with remote repository.
     */
    private String password;

    /**
     * Error message for URI for git repo.
     */
    public static final String MESSAGE = "You need to configure a uri for the git repository.";

    private static final String FILE_URI_PREFIX = "file:";

    private static final String LOCAL_BRANCH_REF_PREFIX = "refs/remotes/origin/";

    /**
     * Timeout (in seconds) for obtaining HTTP or SSH connection (if applicable). Default 5 seconds.
     */
    private int timeout;

    /**
     * Time (in seconds) between refresh of the git repository.
     */
    private int refreshRate = 0;

    /**
     * Time of the last refresh of the git repository.
     */
    private long lastRefresh;

    /**
     * Flag to indicate that the repository should be cloned on startup (not on demand). Generally leads to slower
     * startup but faster first query.
     */
    private boolean cloneOnStart;

    private JGitEnvironmentRepository.JGitFactory gitFactory;

    private String defaultLabel;

    /**
     * Factory used to create the credentials provider to use to connect to the Git repository.
     */
    private GitCredentialsProviderFactory gitCredentialsProviderFactory = new GitCredentialsProviderFactory();

    /**
     * Transport configuration callback for JGit commands.
     */
    private TransportConfigCallback transportConfigCallback;

    /**
     * Flag to indicate that the repository should force pull. If true discard any local changes and take from remote
     * repository.
     */
    private boolean forcePull;

    private boolean initialized;

    /**
     * Flag to indicate that the branch should be deleted locally if it's origin tracked branch was removed.
     */
    private boolean deleteUntrackedBranches;

    /**
     * Flag to indicate that SSL certificate validation should be bypassed when communicating with a repository served
     * over an HTTPS connection.
     */
    private boolean skipSslValidation;

    public GitTools(String basedir, String username, String password, boolean skipSslValidation) {
        this.username = username;
        this.password = password;
        this.cloneOnStart = true;
        this.defaultLabel = "master";
        this.forcePull = true;
        this.timeout = 2000;
        this.deleteUntrackedBranches = true;
        this.refreshRate = 5; // 5s刷新一次
        this.skipSslValidation = skipSslValidation; //是否跳过ssl
        this.gitFactory = new JGitEnvironmentRepository.JGitFactory(true);
        if (!StringUtils.hasText(basedir)) {
            this.basedir = createBaseDir();
        } else {
            this.basedir = new File(basedir);
        }
        logger.info("basedir => " + basedir);
    }

    public void setTransportConfigCallback(TransportConfigCallback transportConfigCallback) {
        this.transportConfigCallback = transportConfigCallback;
    }

    public JGitEnvironmentRepository.JGitFactory getGitFactory() {
        return this.gitFactory;
    }

    public void setGitFactory(JGitEnvironmentRepository.JGitFactory gitFactory) {
        this.gitFactory = gitFactory;
    }

    public void setGitCredentialsProviderFactory(GitCredentialsProviderFactory gitCredentialsProviderFactory) {
        this.gitCredentialsProviderFactory = gitCredentialsProviderFactory;
    }

    GitCredentialsProviderFactory getGitCredentialsProviderFactory() {
        return gitCredentialsProviderFactory;
    }

    public String getDefaultLabel() {
        return this.defaultLabel;
    }

    public void setDefaultLabel(String defaultLabel) {
        this.defaultLabel = defaultLabel;
    }

    public boolean isForcePull() {
        return this.forcePull;
    }

    public void setForcePull(boolean forcePull) {
        this.forcePull = forcePull;
    }

    public boolean isDeleteUntrackedBranches() {
        return this.deleteUntrackedBranches;
    }

    public void setDeleteUntrackedBranches(boolean deleteUntrackedBranches) {
        this.deleteUntrackedBranches = deleteUntrackedBranches;
    }

    public boolean isSkipSslValidation() {
        return this.skipSslValidation;
    }

    public void setSkipSslValidation(boolean skipSslValidation) {
        this.skipSslValidation = skipSslValidation;
    }

    @Override
    public synchronized void afterPropertiesSet() throws Exception {
        Assert.state(getUri() != null, MESSAGE);
        initialize();
        if (this.cloneOnStart) {
            initClonedRepository();
        }
    }

    public static final String SEPARATOR = "/";

    public String getBranchRealName(String ref) {
        return ref.substring(ref.lastIndexOf(SEPARATOR) + 1);
    }

    public void refreshAllBranch() throws Exception {
        afterPropertiesSet();
        Git git = null;
        try {
            git = createGitClient();
            List<Ref> refs = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call();
            for (Ref ref : refs) {
                getFilesFromRemote(git, getBranchRealName(ref.getName()));
            }

        } catch (RefNotFoundException e) {
            throw new NoSuchLabelException("No such label: " + e);
        } catch (NoRemoteRepositoryException e) {
            throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
        } catch (GitAPIException e) {
            throw new NoSuchRepositoryException("Cannot clone or checkout repository: " + getUri(), e);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot load environment", e);
        } finally {
            try {
                if (git != null) {
                    git.close();
                }
            } catch (Exception e) {
                this.logger.warn("Could not close git repository", e);
            }
        }

    }

    public void getFilesFromRemote(Git git, String label) throws GitAPIException, IOException {

        // checkout after fetch so we can get any new branches, tags, ect.
        // if nothing to update so just checkout and merge.
        // Merge because remote branch could have been updated before
        FetchResult fetchStatus = fetch(git, label);
        if (this.deleteUntrackedBranches && fetchStatus != null) {
            deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(), git);
        }
        checkout(git, label);
        tryMerge(git, label);

        System.out.println("============= branch now  =>" + label);
        readFile(new File(git.getRepository().getWorkTree().getAbsolutePath() + "/env"));
    }

    public void readFile(File file) throws IOException {
        if (file.isDirectory()) {
            for (final File listFile : file.listFiles()) {

                if (listFile.isDirectory()) {
                    readFile(listFile);
                } else {
                    showFileContent(listFile);
                }
            }
        } else {
            showFileContent(file);
        }
    }

    public void showFileContent(File file) throws IOException {
        if (file.isFile()) {

            System.out.println(System.lineSeparator() + "开始读取文件: " + file.getName());

            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
            String str;
            while ((str = randomAccessFile.readLine()) != null) {
                System.out.println(str);
            }

            randomAccessFile.close();
        }
    }

    /**
     * Get the working directory ready.
     *
     * @param label label to refresh
     * @return head id
     */
    public String refresh(String label) {
        Git git = null;
        try {
            git = createGitClient();
            if (shouldPull(git)) {
                FetchResult fetchStatus = fetch(git, label);
                if (this.deleteUntrackedBranches && fetchStatus != null) {
                    deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(), git);
                }
            }

            // checkout after fetch so we can get any new branches, tags, ect.
            // if nothing to update so just checkout and merge.
            // Merge because remote branch could have been updated before
            checkout(git, label);
            tryMerge(git, label);

            // always return what is currently HEAD as the version
            return git.getRepository().findRef("HEAD").getObjectId().getName();
        } catch (RefNotFoundException e) {
            throw new NoSuchLabelException("No such label: " + label, e);
        } catch (NoRemoteRepositoryException e) {
            throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
        } catch (GitAPIException e) {
            throw new NoSuchRepositoryException("Cannot clone or checkout repository: " + getUri(), e);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot load environment", e);
        } finally {
            try {
                if (git != null) {
                    git.close();
                }
            } catch (Exception e) {
                this.logger.warn("Could not close git repository", e);
            }
        }
    }

    private void tryMerge(Git git, String label) {
        try {
            if (isBranch(git, label)) {
                // merge results from fetch
                merge(git, label);
                if (!isClean(git, label)) {
                    this.logger.warn("The local repository is dirty or ahead of origin. Resetting" + " it to origin/"
                                         + label + ".");
                    //                    resetHard(git, label, LOCAL_BRANCH_REF_PREFIX + label);
                }
            }
        } catch (GitAPIException e) {
            throw new NoSuchRepositoryException("Cannot clone or checkout repository: " + getUri(), e);
        }
    }

    /**
     * Clones the remote repository and then opens a connection to it. Checks out to the defaultLabel if specified.
     *
     * @throws GitAPIException when cloning fails
     * @throws IOException     when repo opening fails
     */
    private void initClonedRepository() throws GitAPIException, IOException {

        deleteBaseDirIfExists();
        Git git = cloneToBasedir();
        if (git != null) {
            git.close();
        }
        git = openGitRepository();

        // Check if git points to valid repository and default label is not empty or
        // null.
        if (null != git && git.getRepository() != null && !StringUtils.isEmpty(getDefaultLabel())) {
            // Checkout the default branch set for repo in git. This may not always be
            // master. It depends on the
            // admin and organization settings.
            String defaultBranchInGit = git.getRepository().getBranch();
            // If default branch is not empty and NOT equal to defaultLabel, then
            // checkout the branch/tag/commit-id.
            if (!StringUtils.isEmpty(defaultBranchInGit)
                && !getDefaultLabel().equalsIgnoreCase(defaultBranchInGit)) {
                checkout(git, getDefaultLabel());
            }
        }

        if (git != null) {
            git.close();
        }

    }

    /**
     * Deletes local branches if corresponding remote branch was removed.
     *
     * @param trackingRefUpdates list of tracking ref updates
     * @param git                git instance
     * @return list of deleted branches
     */
    private Collection<String> deleteUntrackedLocalBranches(Collection<TrackingRefUpdate> trackingRefUpdates, Git git) {
        if (CollectionUtils.isEmpty(trackingRefUpdates)) {
            return Collections.emptyList();
        }

        Collection<String> branchesToDelete = new ArrayList<>();
        for (TrackingRefUpdate trackingRefUpdate : trackingRefUpdates) {
            ReceiveCommand receiveCommand = trackingRefUpdate.asReceiveCommand();
            if (receiveCommand.getType() == DELETE) {
                String localRefName = trackingRefUpdate.getLocalName();
                if (StringUtils.startsWithIgnoreCase(localRefName, LOCAL_BRANCH_REF_PREFIX)) {
                    String localBranchName = localRefName.substring(
                        LOCAL_BRANCH_REF_PREFIX.length(),
                        localRefName.length()
                    );
                    branchesToDelete.add(localBranchName);
                }
            }
        }

        if (CollectionUtils.isEmpty(branchesToDelete)) {
            return Collections.emptyList();
        }

        try {
            // make sure that deleted branch not a current one
            checkout(git, this.defaultLabel);
            return deleteBranches(git, branchesToDelete);
        } catch (Exception ex) {
            String message = String.format("Failed to delete %s branches.", branchesToDelete);
            warn(message, ex);
            return Collections.emptyList();
        }
    }

    private List<String> deleteBranches(Git git, Collection<String> branchesToDelete) throws GitAPIException {
        DeleteBranchCommand deleteBranchCommand = git.branchDelete()
                                                     .setBranchNames(branchesToDelete.toArray(new String[0]))
                                                     // local branch can contain data which is not merged to HEAD - force
                                                     // delete it anyway, since local copy should be R/O
                                                     .setForce(true);
        List<String> resultList = deleteBranchCommand.call();
        this.logger.info(
            String.format("Deleted %s branches from %s branches to delete.", resultList, branchesToDelete));
        return resultList;
    }

    private Ref checkout(Git git, String label) throws GitAPIException {
        CheckoutCommand checkout = git.checkout();
        if (shouldTrack(git, label)) {
            trackBranch(git, checkout, label);
        } else {
            // works for tags and local branches
            checkout.setName(label);
        }
        return checkout.call();
    }

    protected boolean shouldPull(Git git) throws GitAPIException {
        boolean shouldPull;

        if (this.refreshRate > 0 && System.currentTimeMillis() - this.lastRefresh < (this.refreshRate * 1000)) {
            return false;
        }

        Status gitStatus;
        try {
            gitStatus = git.status().call();
        } catch (JGitInternalException e) {
            onPullInvalidIndex(git, e);
            gitStatus = git.status().call();
        }

        boolean isWorkingTreeClean = gitStatus.isClean();
        String originUrl = git.getRepository().getConfig().getString("remote", "origin", "url");

        if (this.forcePull && !isWorkingTreeClean) {
            shouldPull = true;
            logDirty(gitStatus);
        } else {
            shouldPull = isWorkingTreeClean && originUrl != null;
        }
        if (!isWorkingTreeClean && !this.forcePull) {
            this.logger.info("Cannot pull from remote " + originUrl + ", the working tree is not clean.");
        }
        return shouldPull;
    }

    protected void onPullInvalidIndex(Git git, JGitInternalException e) {
        if (!e.getMessage().contains("Short read of block.")) {
            throw e;
        }
        if (!this.forcePull) {
            throw e;
        }
        try {
            new File(getWorkingDirectory(), ".git/index").delete();
            git.reset().setMode(ResetCommand.ResetType.HARD).setRef("HEAD").call();
        } catch (GitAPIException ex) {
            e.addSuppressed(ex);
            throw e;
        }
    }

    @SuppressWarnings("unchecked")
    private void logDirty(Status status) {
        Set<String> dirties = dirties(status.getAdded(), status.getChanged(), status.getRemoved(), status.getMissing(),
                                      status.getModified(), status.getConflicting(), status.getUntracked()
        );
        this.logger.warn(String.format("Dirty files found: %s", dirties));
    }

    @SuppressWarnings("unchecked")
    private Set<String> dirties(Set<String>... changes) {
        Set<String> dirties = new HashSet<>();
        for (Set<String> files : changes) {
            dirties.addAll(files);
        }
        return dirties;
    }

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

    protected FetchResult fetch(Git git, String label) {
        FetchCommand fetch = git.fetch();
        fetch.setRemote("origin");
        fetch.setTagOpt(TagOpt.FETCH_TAGS);
        fetch.setRemoveDeletedRefs(this.deleteUntrackedBranches);
        if (this.refreshRate > 0) {
            this.setLastRefresh(System.currentTimeMillis());
        }

        configureCommand(fetch);
        try {
            FetchResult result = fetch.call();
            if (result.getTrackingRefUpdates() != null && result.getTrackingRefUpdates().size() > 0) {
                this.logger.info("Fetched for remote " + label + " and found " + result.getTrackingRefUpdates().size()
                                     + " updates");
            }
            return result;
        } catch (Exception ex) {
            String message = "Could not fetch remote for " + label + " remote: "
                + git.getRepository().getConfig().getString("remote", "origin", "url");
            warn(message, ex);
            return null;
        }
    }

    private MergeResult merge(Git git, String label) {
        try {
            MergeCommand merge = git.merge();
            merge.include(git.getRepository().findRef("origin/" + label));
            MergeResult result = merge.call();
            if (!result.getMergeStatus().isSuccessful()) {
                this.logger.warn("Merged from remote " + label + " with result " + result.getMergeStatus());
            }
            return result;
        } catch (Exception ex) {
            String message = "Could not merge remote for " + label + " remote: "
                + git.getRepository().getConfig().getString("remote", "origin", "url");
            warn(message, ex);
            return null;
        }
    }

    //    private Ref resetHard(Git git, String label, String ref) {
    //        ResetCommand reset = git.reset();
    //        reset.setRef(ref);
    //        reset.setMode(ResetType.HARD);
    //        try {
    //            Ref resetRef = reset.call();
    //            if (resetRef != null) {
    //                this.logger.info("Reset label " + label + " to version " + resetRef.getObjectId());
    //            }
    //            return resetRef;
    //        } catch (Exception ex) {
    //            String message = "Could not reset to remote for " + label + " (current ref=" + ref + "), remote: "
    //                + git.getRepository().getConfig().getString("remote", "origin", "url");
    //            warn(message, ex);
    //            return null;
    //        }
    //    }

    private Git createGitClient() throws IOException, GitAPIException {
        File lock = new File(getWorkingDirectory(), ".git/index.lock");
        if (lock.exists()) {
            // The only way this can happen is if another JVM (e.g. one that
            // crashed earlier) created the lock. We can attempt to recover by
            // wiping the slate clean.
            this.logger.info("Deleting stale JGit lock file at " + lock);
            lock.delete();
        }
        if (new File(getWorkingDirectory(), ".git").exists()) {
            return openGitRepository();
        } else {
            return copyRepository();
        }
    }

    // Synchronize here so that multiple requests don't all try and delete the
    // base dir
    // together (this is a once only operation, so it only holds things up on
    // the first
    // request).
    private synchronized Git copyRepository() throws IOException, GitAPIException {
        deleteBaseDirIfExists();
        getBasedir().mkdirs();
        Assert.state(getBasedir().exists(), "Could not create basedir: " + getBasedir());
        if (getUri().startsWith(FILE_URI_PREFIX)) {
            return copyFromLocalRepository();
        } else {
            return cloneToBasedir();
        }
    }

    private Git openGitRepository() throws IOException {
        Git git = this.gitFactory.getGitByOpen(getWorkingDirectory());
        return git;
    }

    private Git copyFromLocalRepository() throws IOException {
        Git git;
        File remote = new UrlResource(StringUtils.cleanPath(getUri())).getFile();
        Assert.state(remote.isDirectory(), "No directory at " + getUri());
        File gitDir = new File(remote, ".git");
        Assert.state(gitDir.exists(), "No .git at " + getUri());
        Assert.state(gitDir.isDirectory(), "No .git directory at " + getUri());
        git = this.gitFactory.getGitByOpen(remote);
        return git;
    }

    protected File getWorkingDirectory() {
        return this.basedir;
    }

    private Git cloneToBasedir() throws GitAPIException {
        CloneCommand clone = this.gitFactory.getCloneCommandByCloneRepository().setURI(getUri())
                                            .setDirectory(getBasedir());
        configureCommand(clone);
        try {
            return clone.call();
        } catch (GitAPIException e) {
            this.logger.warn("Error occured cloning to base directory.", e);
            deleteBaseDirIfExists();
            throw e;
        }
    }

    public File getBasedir() {
        return basedir;
    }

    private void deleteBaseDirIfExists() {
        if (getBasedir().exists()) {
            for (File file : getBasedir().listFiles()) {
                try {
                    FileUtils.delete(file, FileUtils.RECURSIVE);
                } catch (IOException e) {
                    throw new IllegalStateException("Failed to initialize base directory", e);
                }
            }
        }
    }

    protected File createBaseDir() {
        try {
            final Path basedir = Files.createTempDirectory("config-repo-");
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    try {
                        FileSystemUtils.deleteRecursively(basedir);
                    } catch (IOException e) {
                        logger.warn("Failed to delete temporary directory on exit: " + e);
                    }
                }
            });
            return basedir.toFile();
        } catch (IOException e) {
            throw new IllegalStateException("Cannot create temp dir", e);
        }
    }

    private void initialize() {
        if (!this.initialized) {
            this.initialized = true;
        }
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    private void configureCommand(TransportCommand<?, ?> command) {
        command.setTimeout(this.timeout);
        if (this.transportConfigCallback != null) {
            command.setTransportConfigCallback(this.transportConfigCallback);
        }
        CredentialsProvider credentialsProvider = getCredentialsProvider();
        if (credentialsProvider != null) {
            command.setCredentialsProvider(credentialsProvider);
        }
    }

    private CredentialsProvider getCredentialsProvider() {
        return this.gitCredentialsProviderFactory.createFor(this.getUri(), getUsername(), getPassword(),
                                                            "", isSkipSslValidation()
        );
    }

    private boolean isClean(Git git, String label) {
        StatusCommand status = git.status();
        try {
            BranchTrackingStatus trackingStatus = BranchTrackingStatus.of(git.getRepository(), label);
            boolean isBranchAhead = trackingStatus != null && trackingStatus.getAheadCount() > 0;
            return status.call().isClean() && !isBranchAhead;
        } catch (Exception e) {
            String message = "Could not execute status command on local repository. Cause: ("
                + e.getClass().getSimpleName() + ") " + e.getMessage();
            warn(message, e);
            return false;
        }
    }

    private void trackBranch(Git git, 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;
    }

    protected void warn(String message, Exception ex) {
        this.logger.warn(message);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Stacktrace for: " + message, ex);
        }
    }

    public long getLastRefresh() {
        return this.lastRefresh;
    }

    public void setLastRefresh(long lastRefresh) {
        this.lastRefresh = lastRefresh;
    }

    /**
     * Wraps the static method calls to {@link org.eclipse.jgit.api.Git} and {@link org.eclipse.jgit.api.CloneCommand}
     * allowing for easier unit testing.
     */
    public static class JGitFactory {

        private final boolean cloneSubmodules;

        public JGitFactory() {
            this(false);
        }

        public JGitFactory(boolean cloneSubmodules) {
            this.cloneSubmodules = cloneSubmodules;
        }

        public Git getGitByOpen(File file) throws IOException {
            Git git = Git.open(file);
            return git;
        }

        public CloneCommand getCloneCommandByCloneRepository() {
            CloneCommand command = Git.cloneRepository().setCloneSubmodules(cloneSubmodules);
            return command;
        }

    }
}

 

posted @ 2022-01-11 15:14  龘人上天  阅读(422)  评论(0编辑  收藏  举报