git Patch

认识 GIT DIFF
git diff 命令我们用得太多了,默认返回工作区和暂存区的文件详细差异。
什么叫详细差异?
工作区与暂存区的每一行数据变更都能够在这个命令中看到,多用于提交代码前的再次审阅,避免出错。
当然,git diff 不仅能提供工作区和暂存区的文件对比,可以说能提供任意文件差异,如下:



git diff filepath 工作区与暂存区某文件对比
git diff HEAD filepath 工作区与 HEAD(当前工作分支) 比较,HEAD 也可替换为任意一次 commitID
git diff –staged 或 –cached filepath 暂存区与 HEAD 比较
git diff branchName filepath 当前分支的文件与 branchName 分支的文件进行比较
git diff commitId filepath 与某一次提交进行比较
git diff commitID1 commitID2 两次 commit 提交间文件对比

生成 PATCH 补丁
patch 补丁即为根据 git diff 生成的文本内容文件,最简单的生成方法为 git diff > test.patch。
其中 test。patch 文件记录了你本次的所有修改,若由于你忘记 git stash,或者强行 git reset –hard HEAD,这个文件将是最后的救命稻草。
GIT APPLY
基本使用方法为 git apply patch,根据 patch 文件内的信息,在现有文件中添加或删除内容。
这里添加或删除的操作,和手动修改并无多大区别,即修改内容还是在工作区,不会提交到暂存区。之后的操作,就任君发挥了。
示例
我们预先生成一个文本文件 text,其内容如下并提交 commit,工作区,暂存区皆无内容:
origin context line1
origin context line2
复制代码将其修改为:
origin context line1
origin context
add line
复制代码使用 git diff > test.patch 生成 patch 补丁,当然你可以把这个文件放至任何目录下,打开其内容如下:
diff –git a/test b/test
index ce2f4b3..ae27ef5 100644
— a/test
+++ b/test
@@ -1,2 +1,3 @@
origin context line1
-origin context line2
+origin context
+add line
复制代码然后我们使用 git checkout . 将文件重置为最初状态,text 文件恢复为
origin context line1
origin context line2
复制代码使用 git apply test.patch,将变更内容再次写入,查看 text 文件,又变回了
origin context line1
origin context
add line
复制代码示例为求简洁特别简单,也可以使用上文 git diff 保存各种对比内容,从而恢复不同的文件内容。
另外需要多说一句的是,git apply 是一个事务性操作的命令,若有冲突修改会被全部放弃,加参数 –reject 即可。
应用
前面说了那么多,会有人反驳了,这里我用其它命令诸如 git stash,git rebase 比你这个强大多了,这条命令很鸡肋没什么用啊~
下面,用两个鲜明的实例,证明这一命令的方便之处



使用 patch 补丁下载 MR 内容
这个方法来源于部门同事,这是个有趣的人,经常会给我带来各种奇思妙想,这里我也就借花献佛了。(为了获取这个 idea 能够撰文,偷偷塞给他 10 块钱获取了授权)
经常使用 gitlab 的同学大家都知道,当他人提交 MR 到主库时,你只能做代码审阅,无法在 MR 未合并前直接下载更改后的文件内容验证代码是否正确。
当文件更改内容过多,或者是前端项目你特别想看看代码运行起来的页面效果时,备感无力。
当然,若是仅凭代码就能脑补其在浏览器中的显示效果的人,当我没说……
这里关键的地方在于,如何获取这个 MR 与主库现有文件的对比 patch 补丁,然而 gitlab 早已看透了你的一切小心思~



点击 Email Patches,就可以直接下载 patch 文件,然后通过上文的 git apply 直接将修改内容下载至本地,然后愉快的 yarn start 或者其它命令跑起你的项目查看效果啦,so good!
多人协作
程序员的工作大家都是知道的,周末也会偶尔加个班修个紧急 bug 啥的,若你正放假坐着火车上,吃着火锅唱着歌,一个电话打过来,紧急 bug,速度修复,真是要了个命呐~
而这时候屋漏偏逢连阴雨,VPN 也连不上公司内网,啥倒霉事全让你占尽了,这可咋整?!
要么打电话请同事帮你改,二十八个文件都需要改两三句代码,一个一个说吧…… 半天过去了
或者整个修改的文件全发过去,信号差传得又慢,还不如打电话说,可急死我了……
这时候,废什么话,git diff 打个 patch 补丁传过去呗,一个 bug 修改的内容通常极少,不到 1KB……
到这里也看出来了,patch 补丁最大的优势在于内容小,能够只记录你的修改内容而非全文件,通过 git apply 简直不能更好用~
补充
这里还有两个类似命令:git format-patch commitA commitB 和 git am。
前者会将一次 commit 的所有信息也带上,如作者,时间,节点描述等,再配合后者将 patch 内容应用到当前分支。
与 git apply 的不同点是,这里补丁不再只在工作区,你会发现当前分支多 commitA 和 commitB 之间的提交记录。
生成patch:
git format-patch -M master
生成指定patch,0163bed3bf59ae74c36cc5138b4c24f1556d8304是commit id,-1是指从当前id开始,向下提交次数,包含此次且计数从1开始。
也就是说,我想要打出0163bed3bf59ae74c36cc5138b4c24f1556d8304当前的patch,则:
git format-patch 0163bed3bf59ae74c36cc5138b4c24f1556d8304 -1
想要打出0163bed3bf59ae74c36cc5138b4c24f1556d8304和它之前的一次提交的patch,则:
git format-patch 0163bed3bf59ae74c36cc5138b4c24f1556d8304 -2
生成diff:
git diff (id1) (id2) –binary –(path) > 目标文件路径
比如要生成frameworks/base/下的diff,保存到~/gittest/下的f_b.diff:(注意:旧的id1在前)
git diff 206b47c132a80870c06d87c69a548bbfeebecd2d b5ce3e4ebe9503e370d734cecc12482bca023fdf –binary – frameworks/base/ > ~/gittest/f_b.diff



打入 patch / diff:
git apply xxx.patch
git apply xxx.diff
检查 patch / diff:
git apply –check xxx.patch
git apply –check xxx.diff
若git和需要打patch的文件不在一个目录:(git在framework下,patch要打入frameworks/base/下)
git apply –check –directory=base/ xxx.patch
git apply –directory=base/ xxx.patch
** git am 后面会说到,以及生产patch和打入patch的一些命令参数**



我们创建一个文件夹,git init一下,模拟diff / patch源环境
$ mkdir gittest
$ git init
然后创建一个空文件 test,然后首次提交
$ touch test
$ git add .
$ git commit -m “test init”
在里面加入11111,add,commit(add 11111)一次;
在里面加入22222,add,commit(add 22222)一次;

在里面加入55555,add,commit(add 55555)一次;
一共提交5次,可以看到提交了5次。
gitk
生成patch / diff 文件(我们单独建一个文件夹来存patch和diff –> ~/patch/patch/):
在工作中,二者选一个就可以,看自己的需求。
patch相对于diff,多了提交记录,也就是说可以原封不动的把他人commit内容写上去,但是操作比diff麻烦一些



生成patch:
我们记录一下最新的commit id : 21ebfb1ef6a0a9b56d46036c036e8377b56b2da5,有5次提交。
$ git format-patch 21ebfb1ef6a0a9b56d46036c036e8377b56b2da5 -5
git patch



生成了包含当前id和之前的4个id的patch,一共5个,命名也有规律000X+commit的内容.patch
我们把这5个移到~/patch/patch/中。



生成diff:
我们记录要生成diff的区间,!!包上不包下!!,
就是说我要生成 11111~55555的diff
id1 是 test init的id;1cf68afcf3e089a349c8ee534dc3ff44d11a6624
id2是add 55555的id。21ebfb1ef6a0a9b56d46036c036e8377b56b2da5



$ git diff 1cf68afcf3e089a349c8ee534dc3ff44d11a6624 21ebfb1ef6a0a9b56d46036c036e8377b56b2da5 –binary – . > ~/patch/patch/test.diff



diff 多次提交可以是一个文件,但是patch不行,因为它里面有commit记录!



打 patch / diff 补丁(无冲突):
我们在当前目录创建一个需要打补丁的文件夹(gittest1),里面也有一个空的test文件。
然后我们把patch/diff打入gittest1/test。
执行最开始的1、2即可,改一下文件夹名称就ok



gittest1
打patch(不包含commit内容)
检查patch是否可用,没显示文字,就说明可用,且无冲突;
git apply –check ~/patch/patch/0001-add-11111.patch
一般检查一个就可以。
打入patch,可以批量,也可以单个。
git apply ~/patch/patch/*.patch
肯定会成功,因为没有冲突。我们查看一下
git diff
git diff



5个patch都打上去了,接着就可以 git add / commit提交了。但是这样没有commit记录。
打patch(包含commit内容 git am) git checkout . 撤销一下
检查patch是否可用,没显示文字,就说明可用,且无冲突;
git apply –check ~/patch/patch/0001-add-11111.patch
打入patch,可以批量,也可以单个。
** git am ~/patch/patch/*.patch **
肯定会成功,因为没有冲突。我们查看一下



gitk
gitk



我们发现已经自动commit,不用add、commit,可以直接push。



**打diff ** git reset –hard 4c6eb312e94214a5f34fa3f119382ace647b1b3c 撤销一下
检查diff是否可用,没显示文字,就说明可用,且无冲突;
git apply –check ~/patch/patch/test.diff
打入diff;
git apply ~/patch/patch/test.diff
肯定会成功,因为没有冲突。我们查看一下
git diff
git diff



接着就可以 git add / commit提交了。但是这样没有commit记录。
打 patch / diff 补丁(有冲突):
我们还原gittest1/test的初始状态,然后修改一下test文件,写入00000,,然后add、commit。



add 00000
打patch
检查patch是否可用,没显示文字,就说明可用,且无冲突;
git apply –check ~/patch/patch/0001-add-11111.patch
error: 打补丁失败:test:0
error: test:补丁未应用
说明是可以用,但是有冲突。



打入patch,可以批量,也可以单个。
因为在check的时候知道已经有冲突了,就不好用apply来打patch,如果一定要用的话,建议一个一个apply,所以很麻烦,不如用diff。
在这里要用到 git am



git am ~/patch/patch/*.patch



正应用:add 11111
error: 打补丁失败:test:0
error: test:补丁未应用
补丁失败于 0001 add 11111
失败的补丁文件副本位于:
/home/deshui/**/log/gittest1/.git/rebase-apply/patch
当您解决了此问题后,执行 “git am –continue”。
如果您想跳过此补丁,则执行 “git am –skip”。
要恢复原分支并停止打补丁,执行 “git am –abort”。
这行话告诉你,patch冲突了,但是有三个选项 git am –continue、git am –skip、git am –abort
这时候不要动!不要动!不要动!



  1. 解决冲突。

    我们 gedit test,在里面手动加入11111,因为我们加入了00000,导致错行,所以接下来的4个patch都会有冲突。




  2. 提交记录。
    git add test
    git am –continue



正应用:add 11111
正应用:add 22222
error: 打补丁失败:test:1
error: test:补丁未应用
补丁失败于 0002 add 22222
失败的补丁文件副本位于:
~/gittest1/.git/rebase-apply/patch
当您解决了此问题后,执行 “git am –continue”。
如果您想跳过此补丁,则执行 “git am –skip”。
要恢复原分支并停止打补丁,执行 “git am –abort”。
我们gitk一下,发现已经commit已经有add 11111的记录了!
** 同上操作,一直修改到44444 **
git add test
git am –continue



正应用:add 44444
正应用:add 55555
这样就完整的解决冲突,保留commit了。



git am finish
打diff ** git reset –hard 0050cda7f22df985d79b9b98da9bfc282ea10ef1 撤销到add 00000
检查diff是否可用,肯定有冲突。
git apply –check ~/patch/patch/test.diff
error: 打补丁失败:test:0
error: test:补丁未应用
打入diff;
git apply –reject –ignore-whitespace ~/patch/patch/test.diff 2>&1 | tee ~/patch/patch/testdiff.log
这里reject是生成一个.rej的文件,是一个差异文件。
ignore-whitespace是忽略多余的空格。
2>&1是值错误信息
tee 错误信息输出到控制台
~/patch/patch/testdiff.log 错误信息保存在这个文件里
**
**输出log***
检查补丁 test…
error: 当查询:
error: 打补丁失败:test:0
应用 test 个补丁,其中 1 个被拒绝…
拒绝第 #1 个片段。
**输出log***
这时候我们发现,文件夹多了一个test.rej的文件,打开它。
diff a/test b/test (rejected hunks)
@@ -0,0 +1,5 @@
+11111
+22222
+33333
+44444
+55555
这个告诉我们,要在test中加入的信息,我们打开test补上就可以了。手动!
然后add / commit 就可以了。


Category web