使用Git维护自定义修改:手动与自动合并

196 阅读4分钟

在软件开发过程中,维护自定义修改并将其合并到上游的新版本中是一个常见的需求。

但是因为自定义的修改可能不是加入主分支,相反是fork出来的,所以不能完全依赖于自动合并。

本文主要探讨了如何使用git命令行来进行最基本的合并操作,以及在这之前的环境准备。

仓库初始化

在进行操作之前需要进行环境的准备,首先就是需要创建一个git仓库,初始化最终的版本

$ mkdir ./git_test

$ cd ./git_test

$ git init
Initialized empty Git repository in /home/chenjingjue/CLionProjects/git_test/.git/

然后创建两个文件,分别是:

  • 可以进行自动合并的文件(file_auto_apply.c)

  • 不能进行自动合并的文件(file_rej_apply.c)

//file_auto_apply.c
#include <stdio.h>

void greet(void)
{
  printf("Hello");
}
//file_rej_apply.c
#include <stdio.h>

void farewell(void)
{
  printf("Goodbye");
}

然后进行初始化的提交

$ git add ./file_auto_apply.c ./file_rej_apply.c

$ git commit -m "Porject Init"

至此,仓库初始化完成

模拟自定义修改与主版本升级

模拟自定义修改

使用git checkout -b customchange基于master分支创建一个模拟自定义修改的分支。

分别修改file_auto_apply.c以及file_rej_apply.c到如下状态。

//file_auto_apply.c
#include <stdio.h>

void greet(void)
{
  printf("Hello");
  printf("fork change");  
}

//file_rej_apply.c
#include <stdio.h>

void farewell(void)
{
  printf("Goodbye");
  printf("fork change");
}

然后我们就可以进行提交,来模拟我们进行自定义提交。

git add ./file_auto_apply.c ./file_rej_apply.c
git commit -m "Fork Change Commit" 

模拟主版本升级

目前我们已经通过为版本A的git项目增加修改, 来使其在fork出来的分支里面变成了版本B, 但是现在我们需要模拟的是版本A进行了主版本的升级变为了版本C.

首先将分支切换回master分支。(git checkout master

然后分别修改file_auto_apply.c以及file_rej_apply.c到如下状态。

//file_auto_apply.c

#include <stdio.h>

void greet(void)
{
  printf("Hello");
}
//file_rej_apply.c

#include <stdio.h>

void farewell(void)
{
  printf("Farewell");
}

注意,在file_rej_apply.c里面, 我除了修改了版本字母还将Goodbye修改为了Farewell, 这在git中会导致版本冲突,无法进行自动合并,方便演示手动合并。

然后进行提交

git add ./file_auto_apply.c ./file_rej_apply.c
git commit -m "Version Main Change Commit" 

至此,使用git log查看主分支上面的日志,可以看到log上只存在AC两个版本。 而,如果使用git log查看custom change分支,可以看到log上只存在AB两个版本。

至此,环境准备已经完成,可以进行代码的合并了。

代码的合并

创建自定义修改的代码补丁(patch)

要进行代码的自动合并,首先要做的是将我们对于版本A的自定义修改变成一个patch, 然后通过git apply让git先进行自动的合并。

那么首先需要切换到customchange分支,然后使用git log查看提交的分支

$ git log
commit cbb9ae88c9eb47dc08d8b7173f861d8ac5bad841 (HEAD -> customchange)
Author: “Ch1ppy” <“chen737713@gmail.com”>
Date:   Fri Oct 4 01:26:48 2024 +0800

    Fork Change Commit

commit 46f3c18058aa5757ff6038631cea2c7522d8734e
Author: “Ch1ppy” <“chen737713@gmail.com”>
Date:   Fri Oct 4 01:23:03 2024 +0800

    Project Init 

commit后面跟的字符串就是提交的唯一标识符,我们可以通过这个字符串来标识我们需要进行的修改。

WARN: 需要注意的是每个不同git仓库版本号不一致,输入的时候需要查看自己的版本号 TIPS: 标识符号很长,但是可以不用输入全,只要仓库内部你输入的前几位可以代表唯一的版本就行


#git diff [old-commit] [new-commit] > change.patch

git diff d667ef 24c0a2 > change.patch

patch的内容应该类似于下文,如果使用vim等查看, 可以发现红色的就是被删除的,而绿色的就是新增的, 因为这里进行的修改是修改,所以同时有新增与删除。

diff --git a/file_auto_apply.c b/file_auto_apply.c
index 0e8d535..b7215d7 100644
--- a/file_auto_apply.c
+++ b/file_auto_apply.c
@@ -5,5 +5,6 @@
 void greet(void)
 {
   printf("Hello");
+  printf("fork change");
 }
 
diff --git a/file_rej_apply.c b/file_rej_apply.c
index 6a181aa..f1a7aa8 100644
--- a/file_rej_apply.c
+++ b/file_rej_apply.c
@@ -5,4 +5,5 @@
 void greet(void)
 {
   printf("Goodbye");
+  printf("fork change");
 }

至此,我们获取了用于自动合并的patch,可以开始进一步的合并。

根据patch进行自动合并

现在我们已经获取了分叉路径的patch, 下一步就是使用git checkout master切换到主路径, 然后使用git checkout -b fork_new创建一个基于新版本的fork分支来为新的主版本合并我们的fork修改。 接着使用git apply来进行合并。

但是,真的有这么简单么?不妨尝试一下。

$ git apply ./change.patch
error: patch failed: file_rej_apply.c:5
error: file_rej_apply.c: patch does not apply

因为git apply就是将修改后的patch文件内的修改合并到已有的分支上, 可以发现因为在file_rej_apply.c的文件中,我们的对同一行进行了修改, 导致git出现了冲突,无法进行自动合并,出现错误。

在这种情况下,我们需要让git apply除了自动合并之外, 对无法合并的文件进行手动合并。

手动合并的第一步是需要将出现冲突的文件列出来:

$ git apply ./change.patch --reject
Checking patch file_auto_apply.c...
Checking patch file_rej_apply.c...
error: while searching for:
void greet(void)
{
  printf("Goodbye");
}

error: patch failed: file_rej_apply.c:5
Applied patch file_auto_apply.c cleanly.
Applying patch file_rej_apply.c with 1 reject...
Rejected hunk #1.

可以看到当加上--reject之后,git会将可以合并的文件(./file_auto_apply.c)进行合并, 但是对于不能合并的文件,git会将出现冲突的地方在终端里面打印出来, 同时会生成.rej的文件,文件里面会以git diff的方式将未能apply的修改标识出来。

因为在错误很多的情况下,终端上面不方便看错误, 所以一般通过查看.rej文件来找到项目内的冲突文件。

# find file with .rej in target path
# find /path/to/directory -type f -name "*.rej" -printf "%p\n"
$ find ./ -type f -name "*.rej" -printf "%p\n"
./file_rej_apply.c.rej
diff a/file_rej_apply.c b/file_rej_apply.c  (rejected hunks)
@@ -5,4 +5,5 @@
 void greet(void)
 {
   printf("Goodbye");
+  printf("fork change");
 }

可以看到,在change.patch上需要需要增加的一行是新增, 但是上一行的GoodbyeFarewell导致了行冲突, 现在我们需要在主路线的基础上加上我们的自定义修改:

//file_auto_apply.c

#include <stdio.h>

void greet(void)
{
  printf("Farewell");
  printf("fork change");
}

然后将我们的修改作为一个新提交提交到我们的新分支, 我们就完成了自定义fork补丁合并到主分支的操作。

$ git add ./file_auto_apply.c file_rej_apply.c.rej
$ git commit -m "Merge Fork Change To New Master Verion"
[fork_new 2303fda] Merge Fork Change To New Master Verion
 2 files changed, 8 insertions(+)
 create mode 100644 file_rej_apply.c.rej
$ git log
commit 2303fdad3640cd9b9cf2977f9167890d56682b97 (HEAD -> fork_new)
Author: “Ch1ppy” <“chen737713@gmail.com”>
Date:   Fri Oct 4 02:26:44 2024 +0800

    Merge Fork Change To New Master Verion

commit 002e6db0fc19ac9672e5e4db1d8ff463b4886706 (main)
Author: “Ch1ppy” <“chen737713@gmail.com”>
Date:   Fri Oct 4 01:29:37 2024 +0800

    Version Main Change Commit

commit 46f3c18058aa5757ff6038631cea2c7522d8734e
Author: “Ch1ppy” <“chen737713@gmail.com”>
Date:   Fri Oct 4 01:23:03 2024 +0800

    Project Init

实施环境

  • Fedora Linux 40 (Workstation Edition) x86_64
  • git version 2.46.2