在软件开发过程中,维护自定义修改并将其合并到上游的新版本中是一个常见的需求。
但是因为自定义的修改可能不是加入主分支,相反是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上只存在A,C两个版本。
而,如果使用git log查看custom change分支,可以看到log上只存在A,B两个版本。
至此,环境准备已经完成,可以进行代码的合并了。
代码的合并
创建自定义修改的代码补丁(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上需要需要增加的一行是新增,
但是上一行的Goodbye和Farewell导致了行冲突,
现在我们需要在主路线的基础上加上我们的自定义修改:
//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