• 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命令还会生成包括邮件头的完整邮件信息,列出提交作者,提交日期以及与该变更相关的提交日志信息

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

    1. git init
    2. echo abcd > file1
    3. git add file1
    4. git commit -m "commit file1"
    5. echo abcde > file2
    6. git add file2
    7. git commit -m "commit file2"
    8. echo abcdef > file3
    9. git add file3
    10. git commit -m "commit file3"
    11. echo abcdefg > file4
    12. git add file4
    13. git commit -m "commit file4"

    此时会存在几次提交:

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

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

    1. $ git format-patch -1
    2. 0001-commit-file4.patch
    3. $ git format-patch -2
    4. 0001-commit-file3.patch
    5. 0002-commit-file4.patch
    6. $ git format-patch -3
    7. 0001-commit-file2.patch
    8. 0002-commit-file3.patch
    9. 0003-commit-file4.patch
    10. $ git format-patch -4
    11. 0001-commit-file1.patch
    12. 0002-commit-file2.patch
    13. 0003-commit-file3.patch
    14. 0004-commit-file4.patch

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

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

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

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

    1. $ cat 0001-commit-file3.patch
    2. From 43f9a45cc872f7bda33f1b0ab5422b3e861318af Mon Sep 17 00:00:00 2001
    3. From: wood_glb
    4. Date: Sun, 21 Aug 2022 11:30:05 +0800
    5. Subject: [PATCH 1/2] commit file3
    6. ---
    7. file3 | 1 +
    8. 1 file changed, 1 insertion(+)
    9. create mode 100644 file3
    10. diff --git a/file3 b/file3
    11. new file mode 100644
    12. index 0000000..0373d93
    13. --- /dev/null
    14. +++ b/file3
    15. @@ -0,0 +1 @@
    16. +abcdef
    17. --
    18. 2.31.1.windows.1
    19. $ cat 0002-commit-file4.patch
    20. From 00bf3a31efe83ea41f129881ee0dcac006653c4c Mon Sep 17 00:00:00 2001
    21. From: wood_glb
    22. Date: Sun, 21 Aug 2022 11:30:09 +0800
    23. Subject: [PATCH 2/2] commit file4
    24. ---
    25. file4 | 1 +
    26. 1 file changed, 1 insertion(+)
    27. create mode 100644 file4
    28. diff --git a/file4 b/file4
    29. new file mode 100644
    30. index 0000000..b5b9ef6
    31. --- /dev/null
    32. +++ b/file4
    33. @@ -0,0 +1 @@
    34. +abcdefg
    35. --
    36. 2.31.1.windows.1

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

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

    1. git checkout -b alt master~2
    2. echo AAA > file5
    3. git add file5
    4. git commit -m "commit file5"
    5. echo BBB > file6
    6. git add file6
    7. git commit -m "commit file6"
    8. echo CCC > file7
    9. git add file7
    10. git commit -m "commit file7"

    此时的版本库变为:

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

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

    1. git checkout master
    2. git merge alt
    3. echo DDD > file8
    4. git add file8
    5. git commit -m "commit file8"

    此时的版本库变为:

    1. $ git log --graph --pretty=oneline --abbrev-commit --all
    2. * 41fbc84 (HEAD -> master) commit file8
    3. * e66c0fc Merge branch 'alt'
    4. |\
    5. | * b3a44b4 (alt) commit file7
    6. | * 42afe97 commit file6
    7. | * 691bb0b commit file5
    8. * | 00bf3a3 commit file4
    9. * | 43f9a45 commit file3
    10. |/
    11. * aef04b2 commit file2
    12. * 4b0b17c commit file1

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

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

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

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

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

    1. $ git format-patch master~5
    2. 0001-commit-file2.patch
    3. 0002-commit-file3.patch
    4. 0003-commit-file4.patch
    5. 0004-commit-file5.patch
    6. 0005-commit-file6.patch
    7. 0006-commit-file7.patch
    8. 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命令有很多配置选项,都记录在用户手册文档中。可以使用如下命令设置相关属性:

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

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

    应用补丁

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

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

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

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

    1. $ git format-patch --root master
    2. 0001-commit-file1.patch
    3. 0002-commit-file2.patch
    4. 0003-commit-file3.patch
    5. 0004-commit-file4.patch
    6. 0005-commit-file5.patch
    7. 0006-commit-file6.patch
    8. 0007-commit-file7.patch
    9. 0008-commit-file8.patch

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

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

    然后执行如下代码:

    1. $ git am 0001-commit-file1.patch
    2. applying to an empty history
    3. Applying: commit file1
    4. $ git am 0002-commit-file2.patch
    5. Applying: commit file2
    6. $ git am 0003-commit-file3.patch
    7. Applying: commit file3
    8. $ git am 0004-commit-file4.patch
    9. Applying: commit file4
    10. $ git am 0005-commit-file5.patch
    11. Applying: commit file5
    12. $ git am 0006-commit-file6.patch
    13. Applying: commit file6
    14. $ git am 0007-commit-file7.patch
    15. Applying: commit file7
    16. $ git am 0008-commit-file8.patch
    17. Applying: commit file8
    18. $ ls
    19. 0001-commit-file1.patch 0005-commit-file5.patch file1 file5
    20. 0002-commit-file2.patch 0006-commit-file6.patch file2 file6
    21. 0003-commit-file3.patch 0007-commit-file7.patch file3 file7
    22. 0004-commit-file4.patch 0008-commit-file8.patch file4 file8

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

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

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

  • 相关阅读:
    cadence SPB17.4 - allegro - modify shape
    使用 EasyCV Mask2Former 轻松实现图像分割
    [datawhale202211]跨模态神经搜索实践:环境配置
    盘点3种Python网络爬虫过程中的中文乱码的处理方法
    3.1-分类-概率生成模型
    详细介绍c++中的类
    LOAM误差函数、代价函数的雅克比矩阵详细推导,点到线和点到面误差函数求导
    MYSQL——毫秒值和日期类型数据的转换,DATE_SUB的用法
    除烟超猛的油烟机,还有智慧内核加持,云米AI烟灶套装体验
    【Go 基础篇】Go语言运算符解析:探索数学与逻辑的奥秘与运用
  • 原文地址:https://blog.csdn.net/SAKURASANN/article/details/126448621