java git_用 JGit 通过 Java 来操作 Git
JGit 是一個由 Eclipse 基金會開發、用于操作 git 的純 Java 庫。它本身也是 Eclispe 的一部分,實際上 Eclipse 的插件 EGit 便是基于 JGit 的。如果你像我這樣有使用代碼來操作 git 的需求,那就準備好擁抱 JGit 吧。目前來看別的競品沒它靠譜。
文章目錄
JGit 是一個由 Eclipse 基金會開發、用于操作 git 的純 Java 庫。它本身也是 Eclispe 的一部分,實際上 Eclipse 的插件 EGit 便是基于 JGit 的。如果你像我這樣有使用代碼來操作 git 的需求,那就準備好擁抱 JGit 吧。目前來看別的競品沒它靠譜。
概念
從用戶指南的概念一節中可以看到,JGit 的基本概念如下:Git 對象(Git Objects):就是 git 的對象。它們在 git 中用 SHA-1 來表示。在 JGit 中用AnyObjectId和ObjectId表示。而它又包含了四種類型:二進制大對象(blob):文件數據
樹(tree):指向其它的 tree 和 blob
提交(commit):指向某一棵 tree
標簽(tag):把一個 commit 標記為一個標簽
引用(Ref):對某一個 git 對象的引用。
倉庫(Repository):顧名思義,就是用于存儲所有 git 對象和 Ref 的倉庫。
RevWalk:該類用于從 commit 的關系圖(graph)中遍歷 commit。晦澀難懂?看到范例就清楚了。
RevCommit:表示一個 git 的 commit
RevTag:表示一個 git 的 tag
RevTree:表示一個 git 的 tree
TreeWalk:類似 RevWalk,但是用于遍歷一棵 tree
準備環境
讓我們從一個最典型的用例開始吧。首先在/tmp/jgit/repo中創建一個 git 倉庫:mkdir -p /tmp/jgit/repocd /tmp/jgit/repogit init --bare
再創建一個 clone 該倉庫的客戶端:cd /tmp/jgit/git clone repo clientcd client
輸入git status應該能夠看到 Initial commit,這樣環境就沒有問題了。然后提交一個文件,給倉庫里來點庫存:echo hello > hello.txtgit add hello.txtgit commit -m "hello"
git push
動手
獲取倉庫
動手時間。新建 Maven 工程,往 pom.xml 中增加 dependency,最后的 pom.xml 看起來就像這樣:<?xml version="1.0" encoding="UTF-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
org.ggg.jgit
helloJgit
1.0-SNAPSHOT
org.eclipse.jgit
org.eclipse.jgit
4.8.0.201706111038-r
讓我們先嘗試 clone 一下這個倉庫。因為 client 分為已經存在以及重新 clone 的兩種,所以我們在 src/main/java 中新增一個RepositoryProvider接口,用兩種不同實現以示區分:public interface RepositoryProvider {
Repository get() throws Exception;
}
并實現之:public class RepositoryProviderCloneImpl implements RepositoryProvider {
private String repoPath;
private String clientPath;
public RepositoryProviderCloneImpl(String repoPath, String clientPath) {
this.repoPath = repoPath;
this.clientPath = clientPath;
}
@Override
public Repository get() throws Exception {
File client = new File(clientPath);
client.mkdir();
try (Git result = Git.cloneRepository()
.setURI(repoPath)
.setDirectory(client)
.call()) {
return result.getRepository();
}
}
}
新增一個HelloJGit主程序類:public class HelloJGit {
private static RepositoryProvider repoProvider = new RepositoryProviderCloneImpl("/tmp/jgit/repo", "/tmp/jgit/clientJava");
public static void main(String[] args) throws Exception {
try (Git git = new Git(repoProvider.get())) {
git.pull().call();
}
}
}
直接運行HelloJGit的main函數,ls /tmp/jgit/應該就能看到新 clone 出來的clientJava文件夾了。cd /tmp/jgit/clientJavalsgit status
我們當然不希望總是在使用的時候才重新 clone 一個倉庫,因為當倉庫很大的時候可能會非常耗時。讓我們在client中再提交一個 commit:echo hello2 > hello2.txtgit add hello2.txtgit commit -m "hello again"git push
然后嘗試直接從剛剛 clone 下來的 clientJava 中創建 Repository:public class RepositoryProviderExistingClientImpl implements RepositoryProvider {
private String clientPath;
public RepositoryProviderExistingClientImpl(String clientPath) {
this.clientPath = clientPath;
}
@Override
public Repository get() throws Exception {
try (Repository repo = new FileRepository(clientPath)) {
return repo;
}
}
}
然后把HelloJGit的repoProvider實例替換為RepositoryProviderExistingClientImpl:private static RepositoryProvider repoProvider = new RepositoryProviderExistingClientImpl("/tmp/jgit/clientJava/.git");
注意這次的路徑中需要加上.git才行。再次運行HelloJGit的main函數,便可以通過ls /tmp/jgit/clientJava看到新提交的hello2.txt文件了。
常用操作
接下來嘗試git add、git commit和git push這幾個最常用的命令。讓我們往clientJava中添加一個hello3.txt文件并提交。如下修改HelloJGit:public static void main(String[] args) throws Exception {
try (Repository repo = repoProvider.get();
Git git = new Git(repo)) {
createFileFromGitRoot(repo, "hello3.txt", "hello3");
git.add()
.addFilepattern("hello3.txt")
.call();
git.commit()
.setMessage("hello3")
.call();
git.push()
.call();
}
}
private static void createFileFromGitRoot(Repository repo, String filename, String content) throws FileNotFoundException {
File hello3 = new File(repo.getDirectory().getParent(), filename);
try (PrintWriter out = new PrintWriter(hello3)) {
out.println(content);
}
}
雖然操作多了,但是有了Repository和Git對象之后,看起來它們的實現都非常直觀。運行main函數之后,可以到client文件夾中校驗一下:cd /tmp/jgit/clientgit pullcat hello3.txtgit log
在我的機器上運行git log,可以得到:commit 7841b8b80a77918f2ec45bcedb934e2723b16b5c (HEAD -> master, origin/master),以及另外兩個 commit。有興趣的讀者們可以自行嘗試其它的 git 命令。
其它對象
雖然上面兩小節的內容對于普通需求來說已經大致上夠用了,但是在概念一節中介紹到的其它概念,如 Git 對象、引用等還沒有出場呢。我們再新建一個WalkJGit的類,在main函數中編寫如下代碼:try (Repository repo = repoProvider.get()) {
Ref ref = repo.getAllRefs().get(Constants.HEAD);
ObjectId objectId = ref.getObjectId();
System.out.println(objectId);
}
這回,Ref和ObjectId都出現了。在我的機器上,運行以上程序打印出來了 AnyObjectId[7841b8b80a77918f2ec45bcedb934e2723b16b5c]。我們可以看到,取得HEAD的Ref,其ObjectId其實就是在client文件夾中運行git log之后結果。除了HEAD以外,repo.getAllRefs()返回的Map實例中還有refs/heads/master和refs/remotes/origin/master,在目前的情況下,它們的ObjectId完全相同。那么如何獲取其它的 commit 呢?那就是RevWalk出場的時候。把main函數中的內容替換為如下代碼:try (Repository repo = repoProvider.get()) {
Ref ref = repo.getAllRefs().get(Constants.HEAD);
try (RevWalk revWalk = new RevWalk(repo)) {
RevCommit lastCommit = revWalk.parseCommit(ref.getObjectId());
revWalk.markStart(lastCommit);
revWalk.forEach(System.out::println);
}
}
可以看到RevWalk本身是實現了Iterable接口的。通過對該對象進行循環,就可以獲取所有的 commit 的RevCommit對象。可以到client文件夾確認一下,這些 SHA-1 字符串應該跟剛才git log命令的結果相同。RevCommit對象本身含有這個 commit 的所有信息,所以可以如下打印出來:revWalk.forEach(c -> {
System.out.println("commit " + c.getName());
System.out.printf("Author: %s n", c.getAuthorIdent().getName(), c.getAuthorIdent().getEmailAddress());
System.out.println("Date: " + LocalDateTime.ofEpochSecond(c.getCommitTime(), 0, ZoneOffset.UTC));
System.out.println("t" + c.getShortMessage() + "n");});
這樣看起來是不是很有git log的感覺呢?需要注意的是,RevWalk線程不安全,并且像Stream那樣,只能使用一次。如果想要再來一次,就需要重新創建RevWalk對象或是調用其reset方法(還得重新markStart!)。
要想看到每個 commit 中有什么內容,那就需要用到TreeWalk了,它的思路和RevWalk類似。嘗試如下代碼:for (RevCommit commit : revWalk) {
System.out.println("ncommit: " + commit.getName());
try (TreeWalk treeWalk = new TreeWalk(repo)) {
treeWalk.addTree(commit.getTree());
treeWalk.setRecursive(true);
while (treeWalk.next()) {
System.out.println("filename: " + treeWalk.getPathString());
ObjectId objectId = treeWalk.getObjectId(0);
ObjectLoader loader = repo.open(objectId);
loader.copyTo(System.out);
}
}
}
這樣便可以顯示倉庫在每個 commit 時候的狀態了。如果需要 diff,那么還將需要用到DiffEntry等類,本文就不再贅述了,有興趣的讀者可以參考這個類。
最后將環境還原:rm -rf /tmp/jgit
參考資料
這個代碼庫里有很全面的、基本可以直接用于生產環境的范例。 JGit 的源碼和用戶指南。
總結
以上是生活随笔為你收集整理的java git_用 JGit 通过 Java 来操作 Git的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无水硫酸钠的用途(无水硫酸钠和硫酸钠的区
- 下一篇: 如何选购客厅吸顶灯哪个牌子好