1

简单记录下日常用到的git patch相关用法。

1. git format-patch/am

生成patch

针对指定的commit range生成patch,默认对每一个commit分别单独生成patch文件。patch文件按照commit的先后顺序从1开始编号。patch文件会生成到当前目录下。

生成的patch例子
image.png

. 生成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:
image.png

patch格式说明:
其格式为 @@ -[起始行号],[修改前的行数] +[起始行号],[修改后的行数]

修改前的行数为上下文和'-'部分的总行数,修改后的行数为上下文和'+'部分的总行数。其中上下文都需要空格开头,包括空白行。

修改后的patch:
这里加了一行comments,此时需要同步update patch的行数修改信息。

image.png


黑暗森林
12 声望2 粉丝

弱小和无知不是生存的障碍,傲慢才是!