【git】基于JGit通过ssh-url拉取指定commit-id的代码

发布时间 2023-06-25 17:32:06作者: hsjjj

实现

1️⃣ pom依赖:

<dependency>
      <groupId>org.eclipse.jgit</groupId>
      <artifactId>org.eclipse.jgit</artifactId>
    <version>6.6.0.202305301015-r</version>
</dependency>
<dependency>
    <groupId>org.eclipse.jgit</groupId>
    <artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
    <version>6.5.0.202303070854-r</version>
</dependency>

2️⃣ JschConfigSessionFactory配置:

JSch与JGit

JSch 是一个纯 Java 实现的 SSH2 协议库,它提供了在 Java 程序中进行 SSH 通信的功能。JSch 允许你在 Java 应用程序中使用 SSH 协议进行远程命令执行、文件传输和端口转发等操作。

JGit 是一个用于访问 Git 版本控制系统的 Java 库,它提供了在 Java 应用程序中与 Git 仓库进行交互的功能。JGit 是 Eclipse Foundation 的一个项目,旨在为 Java 开发者提供一个功能齐全且易于集成的 Git 客户端库。

JschConfigSessionFactory 是基于 JSch 的一个辅助类,用于在 JGit 中配置 SSH 会话工厂。

配置
SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() {
    @Override
    protected void configure(OpenSshConfig.Host host, Session session) {
        // 设置 SSH 客户端在连接远程服务器时不进行严格的主机密钥检查
        session.setConfig("StrictHostKeyChecking", "no");
    }

    @Override
    protected JSch createDefaultJSch(FS fs) throws JSchException {
        JSch jSch = super.createDefaultJSch(fs);
        // 添加私钥文件用于身份验证
        jSch.addIdentity(privateKey);
        return jSch;
    }
};
JschConfigSessionFactory工作流程

JGit中,首先createSession()方法被调用,方法内部调用getJSch()获取JSch实例,再调用JSch实例的getSession()方法获取Session对象。
getJSch()方法被调用时,createDefaultJSch()方法于内部被调用。
原始的createDefaultJSch()方法中,configureJSch()方法会被调用,但原始的configureJSch()方法内部为空。
createSession()方法中会调用configure()方法。

主机密钥检查

主机密钥检查(Host Key Checking)是在 SSH 连接过程中进行的一种安全机制,用于验证远程主机的身份和防止中间人攻击。

在 SSH 连接过程中,当客户端(例如使用 JSch 库的应用程序)首次连接到一个远程主机时,远程主机会返回自己的公钥。为了确保连接的远程主机是预期的主机而不是中间人,客户端会对远程主机的公钥进行验证。

主机密钥检查的过程如下:

  1. 客户端收到远程主机的公钥。

  2. 客户端会将远程主机的公钥与本地存储的已知主机密钥进行比较。

  • 如果已知主机密钥列表中存在与远程主机公钥匹配的密钥,表示该远程主机的身份是可信的,连接将继续进行。
  • 如果已知主机密钥列表中不存在匹配的密钥,表示该远程主机的身份是不可信的或是首次连接的主机,客户端会触发主机密钥检查流程。
  1. 主机密钥检查的具体行为取决于 SSH 客户端的配置:
  • 如果客户端的配置指定了接受新的主机密钥,客户端将自动接受远程主机的公钥,并将其添加到已知主机密钥列表中。这种配置一般用于自动化脚本等场景。
  • 如果客户端的配置要求用户手动确认新的主机密钥,客户端会提示用户确认该主机的公钥指纹是否可信。用户需要进行手动确认才能继续连接。
  • 如果客户端的配置指定了拒绝新的主机密钥,连接将被终止,并抛出异常或给出相应的错误提示。

3️⃣ 指定一不存在或为空的目录

Path tempDirectory = Paths.get("d:/tmp");

4️⃣ 基于JGit拉代码

try (Git git = Git.cloneRepository()
        .setURI(sshUrl)
        // 用于在回调中将自定义的session工厂配置到JGit中
        .setTransportConfigCallback(transport -> {
            SshTransport sshTransport = (SshTransport) transport;
            sshTransport.setSshSessionFactory(sshSessionFactory);
        })
        .setDirectory(new File(String.valueOf(tempDirectory)))
        // 禁用自动`checkout`,使得工作目录下不会出现克隆的文件,便于后续拉取指定id的代码
        .setNoCheckout(true)
        .call()) {
    // 解析commit-id
    ObjectId commitObjectId = git.getRepository().resolve(commitId);
    // 
    git.checkout().setName(commitObjectId.getName()).call();
}

? 完整代码


public interface CloneGithubRepository {
    public static String privateKey = "C:\\Users\\UserName\\.ssh\\id_ecdsa";
    public static String sshUrl = "git@github.com:username/repository_name.git";
    public static String commitId = "68d18ccbf5605f16a15f8fb66c21a8b6776c6d8c";
    public static void main(String[] args) throws GitAPIException, IOException {
        // Set SSH credentials and authentication method
        SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() {
            @Override
            protected void configure(OpenSshConfig.Host host, Session session) {
                session.setConfig("StrictHostKeyChecking", "ask");
            }

            @Override
            protected JSch createDefaultJSch(FS fs) throws JSchException {
                JSch jSch = super.createDefaultJSch(fs);
                jSch.addIdentity(privateKey);
                return jSch;
            }
        };
        Path tempDirectory = Paths.get("d:/tmp");
        // Clone or fetch a repository
        try (Git git = Git.cloneRepository()
                .setURI(sshUrl)
                .setTransportConfigCallback(transport -> {
                    SshTransport sshTransport = (SshTransport) transport;
                    sshTransport.setSshSessionFactory(sshSessionFactory);
                })
                .setDirectory(new File(String.valueOf(tempDirectory)))
                .setNoCheckout(true)
                .call()) {
            ObjectId commitObjectId = git.getRepository().resolve(commitId);
            git.checkout().setName(commitObjectId.getName()).call();
        }
        System.out.println(tempDirectory);
    }
}

Bug清单

(1) Caused by: com.jcraft.jsch.JSchException: invalid privatekey: [B@6591f517

原因:ssh免密格式OPENSSH不支持
重新生成:ssh-keygen -t rsa -m PEM

  • ssh-keygen -t rsa
    • 这个命令生成的 RSA 密钥对使用的是默认的密钥格式(OpenSSH 格式)。
  • ssh-keygen -t rsa -m PEM
    • 这个命令生成的 RSA 密钥对使用的是 PEM 格式(Privacy-Enhanced Mail)。不同之处在于,生成的密钥使用了 PEM 编码。
      PEM 格式是一种基于 Base64 编码的文本格式,常用于表示密钥、证书和其他加密相关数据。PEM 格式的密钥可以在许多软件和系统中使用,包括一些 Web 服务器、证书颁发机构等。

(2)

Caused by: org.eclipse.jgit.errors.NoRemoteRepositoryException: git@github.com:zjujhs/blog-frontend.git:
ERROR: You're using an RSA key with SHA-1, which is no longer allowed. 
Please use a newer client or a different key type.

原因:2022年3月15日起,github不再支持SHA-1的加密方式。
重新生成:ssh-keygen -t ecdsa -m PEM

ssh-keygen指令选项概述

-t keytype:指定要生成的密钥类型,如 rsa、dsa、ecdsa 或 ed25519。

  • 示例:ssh-keygen -t rsa

-b bits:指定生成密钥时使用的位数。

  • 示例:ssh-keygen -b 4096

-C comment:为生成的密钥添加注释。

  • 示例:ssh-keygen -C "My Key"

-f filename:指定生成的密钥文件的名称和路径。

  • 示例:ssh-keygen -f /path/to/mykey

-N passphrase:设置生成的密钥的密码短语(passphrase),用于加密私钥。

  • 示例:ssh-keygen -N "mypassword"
  • 在 SSH 中,passphrase(密码短语)是为了增加私钥的安全性而设置的一种额外保护层。当你生成或使用一个使用密码短语保护的私钥时,每次使用该私钥时都需要提供密码短语才能解锁私钥。

-p:更改私钥的密码短语(passphrase)。

  • 示例:ssh-keygen -p -f /path/to/mykey

-q:静默模式,减少命令输出。

  • 示例:ssh-keygen -q

-i:导入其他格式的密钥文件并转换为 SSH 格式。

  • 示例:ssh-keygen -i -f /path/to/otherkey

-e:将密钥文件转换为其他格式。

  • 示例:ssh-keygen -e -f /path/to/mykey

-R hostname:从 known_hosts 文件中删除指定主机的记录。

  • 示例:ssh-keygen -R example.com

-m :用于指定生成的密钥文件的格式或导入/导出密钥时的编码格式

  • -m PEM:指定生成的密钥文件使用 PEM 格式编码。这是一种常见的编码格式,适用于多种应用场景。
  • -m PKCS8:指定生成的私钥文件使用 PKCS#8 格式编码。这种格式常用于导入和导出私钥,特别是与其他工具和系统的集成。
  • -m RFC4716:指定生成的密钥文件使用 RFC 4716 格式编码。该格式与 OpenSSH 兼容,可以用于与其他 SSH 实现进行交互。
  • -m SSH2:指定生成的密钥文件使用 SSH2 格式编码。这是 OpenSSH 的早期格式,不常用于现代应用,仅用于特定的兼容性需求。
  • -m FINGERPRINT:显示密钥的指纹信息。