JGit 在java中操作Git仓库

jgit是eclipse基金会下属的一个java项目,用于在java环境下实现对git仓库的操作,包括的基本的clone pull push remote commit add等操作。当然底层的实现逻辑还是依赖了操作系统中已安装的Git软件。

在jgit中存在一个抽象类FS实现了基本的和Git程序的交互,派生出三个实现类FS_POSIX FS_Win32 FS_Win32_Cygwin分别应对三种不同的git环境。其中FS_Win32 是比较常见的环境,此类实现了runInShell方法,从中可以明确的知道jgit是直接通过sh调用了git。windows版本的git其实是linux移植版本,算不上纯正windows,自带了mingw64环境用于模拟linux环境。sh.exe就是这个环境下的shell。

 
	public ProcessBuilder runInShell(String cmd, String[] args) {
		List argv = new ArrayList<>(4 + args.length);
		argv.add("sh.exe"); //$NON-NLS-1$
		argv.add("-c"); //$NON-NLS-1$
		argv.add(cmd + " \"$@\""); //$NON-NLS-1$
		argv.add(cmd);
		argv.addAll(Arrays.asList(args));
		ProcessBuilder proc = new ProcessBuilder();
		proc.command(argv);
		return proc;
	}

在jgit中,存在最核心的三个组件:Git类,Repository类。Git类中包含了push commit之类的常见git操作,而Repository则实现了仓库的初始化和基本的管理功能。
Git类的实例都会持有一个Repository实例。

Repository类的初始化

针对一个git仓库,我们一般会有三种方式获得
1.新建一个空仓库

 
 Git git = Git.init().setDirectory(localPath).call()

2.加载一个已存在的仓库

 
Repository repository = builder.setGitDir(repoDir)
                .readEnvironment() // scan environment GIT_* variables
                .findGitDir() // scan up the file system tree
                .build()

3.从远程仓库下载

 
Git result = Git.cloneRepository()
                .setURI(REMOTE_URL)
                .setDirectory(localPath)
                .call()

接下来贴一个完整的git工具类 GitRepository

 
package cn.bobmao.pro.logic.utils;

import cn.bobmao.pro.logic.config.Const;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
import org.eclipse.jgit.transport.OpenSshConfig;
import org.eclipse.jgit.transport.SshTransport;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
import org.jetbrains.annotations.NotNull;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;

// 实现了Closeable接口,使用的时候记得卸载try()中 仓库使用完毕或者代码出错后必须close 
public class GitRepository implements Closeable {
    private Git targetRepo;
    private Git sourceRepo;
    private TransportConfigCallback targetSessionFactory;
    
    // 1. 下载或者更新sourceRepo
    // 2. 下载或者更新targetRepo 然后删掉targetRepo里的东西 把sourceRepo里的东西复制到targetRepo 
    public void init() throws IOException, GitAPIException, URISyntaxException {
        // TransportConfigCallback这个东西 是因为使用ssh连接服务器 存在一个key不受信任的问题
        // 具体搜索关键词StrictHostKeyChecking 这里只是封装了构建代码
        TransportConfigCallback sourceSessionFactory = getTransportConfigCallback(Const.getGitPrivateKey());
        targetSessionFactory = getTransportConfigCallback(Const.getGitTargetPrivateKey());
        sourceRepo = cloneRepo(Const.getSourcePath(), Const.getRemoteSourceUrl(), sourceSessionFactory);
        targetRepo = cloneRepo(Const.getTargetPath(), Const.getRemoteTargetUrl(), targetSessionFactory);
        // 清空生成的代码
        FileUtils.deleteDirectory(new File(Const.getTargetPath() + File.separator + Const.getProjectDirName()));
        // 复制模板代码到生成代码目录
        FileUtils.copyDirectory(new File(Const.getSourcePath() + File.separator + Const.getProjectDirName()),
                new File(Const.getTargetPath() + File.separator + Const.getProjectDirName()));
    }

    @NotNull
    private TransportConfigCallback getTransportConfigCallback(String sshKey) {
        JschConfigSessionFactory jschConfigSessionFactory = new JschConfigSessionFactory() {
            @Override
            protected void configure(OpenSshConfig.Host host, Session session) {
                session.setConfig("StrictHostKeyChecking", "no");
            }

            @Override
            protected JSch createDefaultJSch(FS fs) throws JSchException {
                JSch defaultJSch = super.createDefaultJSch(fs);
                // 配置 ssh key
                defaultJSch.addIdentity(sshKey);
                return defaultJSch;
            }
        };
        return transport -> {
            SshTransport sshTransport = (SshTransport) transport;
            sshTransport.setSshSessionFactory(jschConfigSessionFactory);
        };
    }

    public void commitAndPush(String version) throws GitAPIException {
        commitAll();
        tag(version);
        push();
    }

    /**
     * 初始化代码仓库
     */
    public Git cloneRepo(String targetPath, String url, TransportConfigCallback sshSessionFactory)
            throws GitAPIException, IOException, URISyntaxException {
        File file = new File(targetPath);
        Git git;
        if (file.exists()) {
            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            Repository repository = builder.setGitDir(new File(targetPath + File.separator + ".git"))
                    .readEnvironment()
                    .findGitDir()
                    .build();
            git = new Git(repository);

            git.remoteSetUrl().setRemoteUri(new URIish(url)).setRemoteName("origin").call();
            git.pull().setTransportConfigCallback(sshSessionFactory).call();
        } else {
            git = Git.cloneRepository()
                    .setURI(url)
                    .setBranch("dev")
                    .setDirectory(file)
                    .setTransportConfigCallback(sshSessionFactory).call();
        }
        return git;
    }

    public void commitAll() throws GitAPIException {
        targetRepo.add().addFilepattern(".").call();
        targetRepo.commit()
                .setAll(true)
                .setMessage("Generate Code")
                .call();
    }

    public void push() throws GitAPIException {
        // 推送代码
        targetRepo.push().setTransportConfigCallback(targetSessionFactory).call();
        // 推送tag
        targetRepo.push().setTransportConfigCallback(targetSessionFactory).setPushTags().call();
    }

    public void tag(String version) throws GitAPIException {
        targetRepo.tag().setName(version).call();
    }

    @Override
    public void close() {
        if (sourceRepo != null) {
            // 如果不close 下次操作会直接报错 因为仓库已被占用
            sourceRepo.close();
        }
        if (targetRepo != null) {
            targetRepo.close();
        }
    }
}

大部分的jgit功能可以使用在cookbook上查找到
https://github.com/centic9/jgit-cookbook