上篇博文介绍了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 | mkdir -p /tmp/ggg |
运行完成后就能看到diff文件的内容了:
1 | diff --git a/a.txt b/a.txt |
在程序中,如此这般运行git diff命令:
1 | Git git = Git.init().setDirectory(new File("/tmp/ggg")).call(); |
发现JGit的diff和Git的diff还是不太一样的。JGit的diff包含了新增文件的信息:
1 | diff --git a/a.txt b/a.txt |
这就非常合适了。只要证明它能够被作为patch导入到git中即可。首先修改代码输出到文件:
1 | File patch = new File("/tmp/jgit.patch"); |
然后清空修改过的文件:
1 | cd /tmp/ggg |
现在尝试apply patch:
1 | git apply /tmp/jgit.patch |
果然成功了。在不考虑冲突的情况下,看起来这一招还是管用的。但是由于缺失了commit的信息,所以运行git am /tmp/jgit.patch就会报错:*Patch format detection failed.*有没有办法解决这个问题呢?当然了。我们现在知道了patch只不过是多了一些邮件信息罢了,那我们自己就可以生成。在try内增加如下代码,模拟git format-patch:
1 | outputStream.write("From: GGG \n".getBytes()); |
运行一下,然后尝试使用git am:
1 | git am /tmp/jgit.patch |
果然可以直接生成commit。Mission Complete!
patch & diff
其实Linux已经提供了一个patch命令,无需git即可直接应用patch文件:
1 | git reset HEAD^ --hard |
这两个命令网上的教程不少,有兴趣的话可以自行搜索阅读。最后还是把环境恢复:
1 | rm -rf /tmp/ggg /tmp/jgit.patch |