简单记录下日常用到的git patch相关用法。
1. git format-patch/am
生成patch
针对指定的commit range生成patch,默认对每一个commit分别单独生成patch文件。patch文件按照commit的先后顺序从1开始编号。patch文件会生成到当前目录下。
生成的patch例子
. 生成ec06d35b到HEAD的patch文件(不包含ec06d35b)
git format-patch ec06d35b
. 将patch group写入一个文件
git format-patch ec06d35b --stdout > mypatch.patch
. 生成距离HEAD最近的n个patch
git format-patch -2
. 生成A-B之间的patch
git format-patch R1..R2
git format-patch R1..HEAD
apply patch
apply patch成功后会自动commit,并且保留原来commit的comments,submittor等信息。
但是commit id会新生成(目前我也不知道有什么方法能保留commit id)
git am 0001-some-patch
. git am出错,取消patch
git am --abort
. [坑1]
如下所示,某分支mybranch上小王和小明都基于commit A开发。小王本地先push了push了commit B,C。小明本地commit D,然后push到remote。
# 小王
# commit B,C
git push
# 小明
git commit -m "description for commit D"
git pull
git push
------A---B---C---D---E---
\ /
\---D----/
小明执行的git pull,实际相当于git fetch加git merge,即将remote上的update拿过来,merge到本地,因此会产生一个commit E,对应merge(如果有conflict,解决后,也会包含resolve conflict的修改)。
小明的commit D是基于A的,即commit D中上下文都是基于A的。
如果在通过git format-patch/am来将小王和小明的修改patch到某个repo。
[问题]
git format-patch生成的patch group中,只会包含B,C,D,但是E不会被包含。而git am时,实际过程为按照commit先后,逐个apply patch,也就是B,C,D的顺序来apply。问题是D的上下文是A,不包含C,D。因此可能出现由于上下文不一致导致D apply失败。
[补救措施]
git am失败后,会报告出错patch的位置。可以参照当前实际文件的上下文编辑patch文件,然后再git am --abort后继续git am。重复上面步骤,直到所有的问题解决。
* 修改patch可以参考下面的patch格式说明
# 生成单一patch文件
git format-patch A --stdout > mypatch.patch
# apply patch
git am mypatch.patch
# 按照实际上下文,手动修改patch文件
git am --abort
# apply 修改后的patch
git am mypatch.patch
# 重复上述apply patch过程,直到成功
[更好的实践]
pull的时候追加--rebase选项,这样小明的commit D1的base变成了merge B,C之后。通过git format-patch生成的patch的上下文是和实际match的,git am apply patch的时候,不会出现上述问题。而且不会产生E,git log也更简洁。
# 小王
# 小明
git commit -m "description for commit D"
git pull --rebase
git push
------A---B---C-----------D1----
\ /
\---B---C---D1---/
[坑2]
上面是在同一branch上,在两个不同branch上时,也存在此类问题。
如下所示,master上小王基于A开发。mybranch上也基于A开发。master上小王先push了commit B,C到remote。小明在mybranch上本地commit D,然后push到remote。小明切换到master,merge mybranch的修改。
# 小王
git checkout master
# add some changes B,C
git push
# 小明
git checkout mybranch
# add some changes
git commit -m "description for commit D"
git pull
git push
# 小明在master上merge mybranch的修改
git checkout master
git merge mybranch
master------A---B---C---D---E---
\ /
mybranch \---D----/
[问题]
merge到master上的D的上下文,仍旧不包含BC。在git format-patch/am时可能出错。
[更好的实践]
mybranch merge到master前,先在mybranch上rebase master。
master------A---B---C---
\
mybranch \---D---
git checkout master
git pull
### rebase master
git checkout mybranch
git rebase master
# 如果出现conflict,resolve后
git add .
git rebase --continue
# rebase master后, mybranch上rebase了master上的update
master------A---B---C---
\
mybranch \---B--C---D1---
# master上 merge mybranch
git checkout master
git merge mybranch
master------A---B---C-------D1---
\ /
mybranch \---B--C---D1/
[坑3]
git format-patch等命令生成的patch,默认修改的上下文为3行。当遇到文件结尾,有可能上下文不够3行,通常patch中会自动增加'\ No newline at end of file',表示到达文件尾。git am来apply patch的时候也正常。如下面例子所示
@@ -11,4 +11,6 @@ int abcd(char *mystr,int len);
int dosomething1();
int dosomething2();
+int dosomething3();
+int dosomething4();
#endif
\ No newline at end of file
但是git format-patch存在一个bug,上述情况下,有时候不会添加'\ No newline at end of file',此时git am会报错,原因可能是检查上下文的行数不够。可以手动修改patch文件,手动添加'\ No newline at end of file'。
如下例子:
@@ -11,4 +11,6 @@ int abcd(char *mystr,int len);
int dosomething1();
int dosomething2();
+int dosomething3();
+int dosomething4();
#endif
===> 手动修改为下面即可:
@@ -11,4 +11,6 @@ int abcd(char *mystr,int len);
int dosomething1();
int dosomething2();
+int dosomething3();
+int dosomething4();
#endif
\ No newline at end of file
2. git diff/git apply
生成patch
A..B,生成A到B之间的patch(不包含A),A是较早的submit id
git diff ec06d35b..3280c7bb > 3280c7bb_somepatch.patch
apply patch
check patch,不实际apply:
git apply --check 3280c7bb_somepatch.patch
apply patch:
不会自动像git am一样自动commit,需要手动commit,原来的committor和comment也不能保留。
git apply 3280c7bb_somepatch.patch
3. git diff/patch
git apply 对patch上下文检查比较严格,如果apply 失败,可以尝试用patch命令
apply patch
检查patch,不实际apply:
patch -p1 --dry-run < 3280c7bb_somepatch.patch
apply patch:
patch -p1 --dry-run < 3280c7bb_somepatch.patch
4. git cherry-pick
从一个branch,cherry-pick到另一个branch
从branch1 获取某次commit的commit_id
切换到branch2上进行cherry-pick,会自动commit,只需要push即可
git cherry-pick [commit_id]
git log
git push
出错的话
git cherry-pick --abort
5. 手动修改patch
有时候patch apply遇到问题,可以根据当前上下文,手动修改patch再进行apply。也可以删除有问题的hunk,patch成功后再手动修改(如结合git commit --amend...)
下面是一个修改patch的例子:
修改前的patch:
patch格式说明:
其格式为 @@ -[起始行号],[修改前的行数] +[起始行号],[修改后的行数]
修改前的行数为上下文和'-'部分的总行数,修改后的行数为上下文和'+'部分的总行数。其中上下文都需要空格开头,包括空白行。
修改后的patch:
这里加了一行comments,此时需要同步update patch的行数修改信息。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。