上篇博文介绍了JGit ,之后就开始做项目了。遇到的第一个问题是如何用JGit生成patch文件。
背景 我希望在项目中能够实现这样的功能:用户发送一个request,服务器就帮用户生成代码并生成一个commit到用户本地的git中,但是这不太可能,因为用户的环境并不是服务器的环境。进一步的方案是直接在服务器端clone git仓库(或是维持一份最新代码),服务器本地生成commit并push,这样做会有一些安全方面需要考虑的因素。我采用的是退一步的方案,即让服务器生成一个patch文件并上传到S3 ,以便用户稍后下载并apply到本地。
方案 api command 打开JGit的api包 一看,各种git命令应有尽有,如apply、cherry-pick等。但惟独没有format-patch命令。网上一搜,甚少有人有这样的需求或问题,只有这篇文章 比较靠谱,但是它介绍的侧重于diff而非生成patch。
Patch 还是得找找patch相关的代码。源代码搜遍也就这个Patch.java 应该是patch文件的JGit模型,但是读完后发现,它只能把patch文件映射成这个模型,并不能反向从模型序列化为patch文件。
DiffFormatter / DiffEntry DiffCommand其实上还是调用的DiffFormatter和DiffEntry,所以看看这俩是否能够支持什么样的参数,来生成patch文件呢?可惜还是无果。DiffFormatter的API也不太直观,不容易理解。但是它能够做一些diff commit这样的事情。
diff 走投无路之际,在git-format-patch 上看到,这个命令其实是用来生成用邮件发送 的patch文件。难怪patch文件的前几行看起来有From,有Subject什么的,也许它们不是必须的?那就可以试试把diff的结果当作patch直接写入文件。
动手时间 首先创建一个git环境,a、b、c三个文件用来测试改删增:1
2
3
4
5
6
7
8
9
10
11
12
mkdir -p /tmp/ggg
cd /tmp/ggg
git init
echo line a1 > a.txt
echo line a2 >> a.txt
echo line b1 > b.txt
git add .
git commit -m "first commit wiht a.txt and b.txt"
echo line a3 >> a.txt
echo line c1 > c.txt
rm b.txt
git diff
运行完成后就能看到diff文件的内容了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
diff --git a/a.txt b/a.txt
index 7a3d45f..c723fac 100644
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1,3 @@
line a1
line a2
+line a3
diff --git a/b.txt b/b.txt
deleted file mode 100644
index b45d9fa..0000000
--- a/b.txt
+++ /dev/null
@@ -1 +0,0 @@
-line b1
在程序中,如此这般运行git diff命令:1
2
Git git = Git.init().setDirectory(new File("/tmp/ggg" )).call();
git.diff().setOutputStream(System.out).call();
发现JGit的diff和Git的diff还是不太一样的。JGit的diff包含了新增文件的信息:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
diff --git a/a.txt b/a.txt
index 7a3d45f..c723fac 100644
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1,3 @@
line a1
line a2
+line a3
diff --git a/b.txt b/b.txt
deleted file mode 100644
index b45d9fa..0000000
--- a/b.txt
+++ /dev/null
@@ -1 +0,0 @@
-line b1
diff --git a/c.txt b/c.txt
new file mode 100644
index 0000000..8e37e08
--- /dev/null
+++ b/c.txt
@@ -0,0 +1 @@
+line c1
这就非常合适了。只要证明它能够被作为patch导入到git中即可。首先修改代码输出到文件:1
2
3
4
5
File patch = new File("/tmp/jgit.patch" );
Git git = Git.init().setDirectory(new File("/tmp/ggg" )).call();
try (OutputStream outputStream = new FileOutputStream(patch)) {
git.diff().setOutputStream(outputStream).call();
}
然后清空修改过的文件:1
2
3
4
cd /tmp/ggg
git checkout .
git clean -df
git status
现在尝试apply patch:1
2
git apply /tmp/jgit.patch
git status
果然成功了。在不考虑冲突的情况下,看起来这一招还是管用的。但是由于缺失了commit的信息,所以运行git am /tmp/jgit.patch
就会报错:Patch format detection failed. 有没有办法解决这个问题呢?当然了。我们现在知道了patch只不过是多了一些邮件信息罢了,那我们自己就可以生成。在try
内增加如下代码,模拟git format-patch
:1
2
3
4
5
outputStream.write("From: GGG <ggg@somewhere.com>\n" .getBytes());
outputStream.write("Date: Tue, 12 Sep 2017 20:16:10 +0800\n" .getBytes());
outputStream.write("Subject: [PATCH] ggg is not here. Turn left and ask JGit\n" .getBytes());
outputStream.write("\n---\n\n" .getBytes());
}
运行一下,然后尝试使用git am
:1
2
3
git am /tmp/jgit.patch
git log
git show HEAD
果然可以直接生成commit。Mission Complete!
patch & diff 其实Linux已经提供了一个patch
命令,无需git即可直接应用patch文件:1
2
3
git reset HEAD^ --hard
ls
patch < /tmp/jgit.patch
而且还支持回滚(git apply也支持):1
2
patch -R < /tmp/jgit.patch
ls
实际上patch文件一般是使用diff
命令来生成的:1
2
diff -u a.txt c.txt > diff.patch
cat diff.patch
这两个命令网上的教程不少,有兴趣的话可以自行搜索阅读。最后还是把环境恢复:1
rm -rf /tmp/ggg /tmp/jgit.patch