Git版本控制管理——补丁

Git实现了自己的传输协议用于版本库间交换数据。出于效率方面的考虑(为了节约时间和空间),Git传输协议会进行握手,以确定原版本库中的哪些提交不在目标版本库中,最终传输提交的二进制压缩形式。接收的版本库会给新的提交合并到其本地历史记录中,同时添加到提交图中,并按需要更新分支和标签。

之前提到,要交换提交并保持分布式版本库同步,可以使用Git原生协议和HTTP协议。但在很多情况下使用这些协议是不可行的,Git还支持补丁操作,通常通过email来进行数据交换。

Git实现三条特定的命令帮助交换补丁:

  • git format-patch:会生成email形式的补丁
  • git send-email:会通过简单邮件传输协议(SMTP)来发送一个Git补丁
  • git am:会应用邮件消息中的补丁

为什么要使用补丁

其理由为:

  • 某些情况下,Git原生协议和HTTP协议都不能用于在版本库间交换数据,比如企业内部防火墙
  • 同行评审

生成补丁

git format-patch命令会以邮件消息的形式生成一个补丁。其会为你指定的每次提交都创建一封邮件。

Git的diff机制是git format-patch命令的核心,但是其与git diff还有区别:

  • git diff会生成一个整合了所有选中提交差异的补丁,而git format-patch则会为每个选中的提交生成一条邮件消息
  • git diff不会生成邮件头,而除了生成实际的diff内容外,git format-patch命令还会生成包括邮件头的完整邮件信息,列出提交作者,提交日期以及与该变更相关的提交日志信息

这里看个例子,首先使用下面的代码初始化一个版本库,并提交几次修改:

git init
echo abcd > file1
git add file1
git commit -m "commit file1"
echo abcde > file2
git add file2
git commit -m "commit file2"
echo abcdef > file3
git add file3
git commit -m "commit file3"
echo abcdefg > file4
git add file4
git commit -m "commit file4"

此时会存在几次提交:

$ git show-branch --more=4 master
[master] commit file4
[master^] commit file3
[master~2] commit file2
[master~3] commit file1

从上面来看,已经存在了4次提交。然后为最近n次提交生成补丁:

$ git format-patch -1
0001-commit-file4.patch

$ git format-patch -2
0001-commit-file3.patch
0002-commit-file4.patch

$ git format-patch -3
0001-commit-file2.patch
0002-commit-file3.patch
0003-commit-file4.patch

$ git format-patch -4
0001-commit-file1.patch
0002-commit-file2.patch
0003-commit-file3.patch
0004-commit-file4.patch

默认情况下,Git会为每个补丁生成单独的文件,用一序列数字加上提交日志信息为其命名。

也可以使用一个提交范围来指定把哪些提交格式化为补丁。在上边的打印中,可以看到file4的版本号为master,file3的版本号为master^,file2的版本号为master~2,执行下边的指令:

$ git format-patch master~2..master
0001-commit-file3.patch
0002-commit-file4.patch

从上面来看,虽然file2到file4一共存在3次提交,但是只有两条邮件信息:

$ cat 0001-commit-file3.patch
From 43f9a45cc872f7bda33f1b0ab5422b3e861318af Mon Sep 17 00:00:00 2001
From: wood_glb <wood_glb@git.com>
Date: Sun, 21 Aug 2022 11:30:05 +0800
Subject: [PATCH 1/2] commit file3

---
 file3 | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 file3

diff --git a/file3 b/file3
new file mode 100644
index 0000000..0373d93
--- /dev/null
+++ b/file3
@@ -0,0 +1 @@
+abcdef
--
2.31.1.windows.1

$ cat 0002-commit-file4.patch
From 00bf3a31efe83ea41f129881ee0dcac006653c4c Mon Sep 17 00:00:00 2001
From: wood_glb <wood_glb@git.com>
Date: Sun, 21 Aug 2022 11:30:09 +0800
Subject: [PATCH 2/2] commit file4

---
 file4 | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 file4

diff --git a/file4 b/file4
new file mode 100644
index 0000000..b5b9ef6
--- /dev/null
+++ b/file4
@@ -0,0 +1 @@
+abcdefg
--
2.31.1.windows.1

上面的打印形式和git diff的形式很相似,也就是每个邮件信息其实是两次提交间的diff信息。

这里再master~2处添加一个新的分支alt,并执行如下代码:

git checkout -b alt master~2

echo AAA > file5
git add file5
git commit -m "commit file5"
echo BBB > file6
git add file6
git commit -m "commit file6"
echo CCC > file7
git add file7
git commit -m "commit file7"

此时的版本库变为:

$ git log --graph --pretty=oneline --abbrev-commit --all
* b3a44b4 (HEAD -> alt) commit file7
* 42afe97 commit file6
* 691bb0b commit file5
| * 00bf3a3 (master) commit file4
| * 43f9a45 commit file3
|/
* aef04b2 commit file2
* 4b0b17c commit file1

然后合并分支alt到master,并在合并基础上添加新修改:

git checkout master
git merge alt
echo DDD > file8
git add file8
git commit -m "commit file8"

此时的版本库变为:

$ git log --graph --pretty=oneline --abbrev-commit --all
* 41fbc84 (HEAD -> master) commit file8
*   e66c0fc Merge branch 'alt'
|\
| * b3a44b4 (alt) commit file7
| * 42afe97 commit file6
| * 691bb0b commit file5
* | 00bf3a3 commit file4
* | 43f9a45 commit file3
|/
* aef04b2 commit file2
* 4b0b17c commit file1

此时再次执行之前的git format-patch命令:

$ git format-patch master~2..master
0001-commit-file5.patch
0002-commit-file6.patch
0003-commit-file7.patch
0004-commit-file8.patch

可以看出,此时还存在file5,file6和file7的变更。

而如果范围没有指定重点,则默认为HEAD,但其具体的打印内容可能会和想象的结果不同。

补丁是通过git format-patch命令按照拓扑顺序创建的,对于一个给定的提交,所有父提交的补丁会先于该提交的补丁生成和发出。这里可以看一下上面例子:

$ git format-patch master~5
0001-commit-file2.patch
0002-commit-file3.patch
0003-commit-file4.patch
0004-commit-file5.patch
0005-commit-file6.patch
0006-commit-file7.patch
0007-commit-file8.patch

上面例子中,file1是范围中的第一个提交,而且没有使用--root选项,因此没有针对file1的补丁,而合并分支alt是合并提交,也不生成补丁,因此才有了上面的生成顺序。

邮递补丁

生成补丁后,就是要将之发送到另一个开发人员或开发人员列表进行代码审查,最终目标是希望其能够被另一个开发人员接收或上游仓库管理者接收,以应用到另一个版本库中。

格式化的补丁一般是要通过电子邮件发送的,可以直接导入本地用户的电子邮件用户代理(MUA),或者使用Git的git send-email命令。

如果要将一个已生成的补丁文件发送给另一个开发人员,有以下方式可以选择:

  • 执行git send-email命令,将邮件程序直接指向补丁文件
  • 将补丁文件包含在邮件中

使用git send-email的示例为:

$ git send-email -to example@gmail.com 0001-commit-file2.patch

不过在使用该命令之前需要知道本地SMTP服务器机器端口号。

git send-email命令有很多配置选项,都记录在用户手册文档中。可以使用如下命令设置相关属性:

git config --global sendemail.smtpserver smtp.my-isp.com
git config --global sendemail.smtpserverport 465

这里不做示例,详细可查该命令的帮助文档。

应用补丁

Git有两条基础命令来应用补丁。高层命令git am的一部分是由底层命令git apply实现的。

git apply命令接受git diff风格的输出,然后将其应用到当前工作目录的文件中。而由于diff格式只包含逐行的编辑而没有其它信息,因此不能够用于提交,也不能记录版本库中的变更,因此,当git aply结束时,工作目录下的文件就留在修改的状态。

与之不同的是,无论是在发送前还是发送后,只要通过git format-patch格式化后的补丁,都包含额外的信息以记录版本库中的提交。

这里看个示例,首先是执行下面的命令生成全部的补丁:

$ git format-patch --root master
0001-commit-file1.patch
0002-commit-file2.patch
0003-commit-file3.patch
0004-commit-file4.patch
0005-commit-file5.patch
0006-commit-file6.patch
0007-commit-file7.patch
0008-commit-file8.patch

再初始化新的版本库,然后将上面生成的补丁拷贝到新版本库的目录下:

$ git init
Initialized empty Git repository in C:/Users/wood/Desktop/GIT/tmp_file/.git/

然后执行如下代码:

$ git am 0001-commit-file1.patch
applying to an empty history
Applying: commit file1

$ git am 0002-commit-file2.patch
Applying: commit file2

$ git am 0003-commit-file3.patch
Applying: commit file3

$ git am 0004-commit-file4.patch
Applying: commit file4

$ git am 0005-commit-file5.patch
Applying: commit file5

$ git am 0006-commit-file6.patch
Applying: commit file6

$ git am 0007-commit-file7.patch
Applying: commit file7

$ git am 0008-commit-file8.patch
Applying: commit file8

$ ls
0001-commit-file1.patch  0005-commit-file5.patch  file1  file5
0002-commit-file2.patch  0006-commit-file6.patch  file2  file6
0003-commit-file3.patch  0007-commit-file7.patch  file3  file7
0004-commit-file4.patch  0008-commit-file8.patch  file4  file8

从上面结果来看,确实是将对应的补丁应用到了新的版本库中。

但上面的命令在某些情况下执行并不是总是能够顺序执行的,尤其是当出现冲突的情况下。上面的例子中,各个提交都是基于新的文件,各个修改之间并没有产生冲突,因此顺序应用patch可能并不会有问题。而在文件修改产生冲突,并且还涉及到多个分支的补丁,由于其可能存在的拓扑结构,应用patch可能需要用户更加小心谨慎才不会出现错误。

而要清理一个失败的的git am,并恢复原始分支,可以简单地执行git am --abort。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值