Skip to content

Git - 冲突处理

当 Git 无法自动合并来自不同 commit(提交)或 branch(分支)的更改时,就会发生合并冲突(Merge conflicts)。这种情况通常发生在两个开发者修改了同一文件中的同一行代码,或者一个开发者删除了另一个开发者修改过的文件。本节演示了一个常见的冲突场景及其解决方法。

假设我们的项目有一个文件 src/str_ops.c,其中包含一个函数 w_strlen(一个宽字符字符串长度函数)。Jerry 正在一个特性分支 wchar_enhancements 上工作,而 Tom 正在 main 分支上工作。两人都决定重命名 w_strlen。

Jerry 在 wchar_enhancements 分支上的更改

Section titled “Jerry 在 wchar_enhancements 分支上的更改”

Jerry 将 w_strlen 重命名为 my_wstrlen。

[jerry@devbox project]$ git checkout -b wchar_enhancements
Switched to a new branch 'wchar_enhancements'
[jerry@devbox project]$ sed -i 's/w_strlen/my_wstrlen/g' src/str_ops.c # 示例修改
[jerry@devbox project]$ git diff src/str_ops.c

Diff 输出(简化):

diff --git a/src/str_ops.c b/src/str_ops.c
--- a/src/str_ops.c
+++ b/src/str_ops.c
@@ -10,7 +10,7 @@
#include <wchar.h>
-size_t w_strlen(const wchar_t *s)
+size_t my_wstrlen(const wchar_t *s)
{
const wchar_t *p = s;
// ...

Jerry 提交(commit)并推送(push)他的分支:

[jerry@devbox project]$ git add src/str_ops.c
[jerry@devbox project]$ git commit -m "Rename w_strlen to my_wstrlen on feature branch"
[wchar_enhancements 3789fe8] Rename w_strlen to my_wstrlen on feature branch
1 file changed, 1 insertion(+), 1 deletion(-)
[jerry@devbox project]$ git push origin wchar_enhancements

与此同时,Tom 在 main 分支上工作,将同一个 w_strlen 函数重命名为 my_wc_strlen 并添加了一个注释。

[tom@devbox project]$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
[tom@devbox project]$ sed -i 's/w_strlen/my_wc_strlen/g' src/str_ops.c # 示例修改
[tom@devbox project]$ # (Tom 还在函数上方添加了一个注释)
[tom@devbox project]$ git diff src/str_ops.c

Diff 输出(简化):

diff --git a/src/str_ops.c b/src/str_ops.c
index c2f1477..f1f7e4e 100644
--- a/src/str_ops.c
+++ b/src/str_ops.c
@@ -10,7 +10,8 @@
#include <wchar.h>
-size_t w_strlen(const wchar_t *s)
+/* 自定义的宽字符 strlen 函数 */
+size_t my_wc_strlen(const wchar_t *s)
{
const wchar_t *p = s;
// ...

Tom 提交并推送他的更改到 main:

[tom@devbox project]$ git add src/str_ops.c
[tom@devbox project]$ git commit -m "Rename w_strlen to my_wc_strlen and add comment"
[main ad4b530] Rename w_strlen to my_wc_strlen and add comment
1 file changed, 2 insertions(+), 1 deletion(-)
[tom@devbox project]$ git push origin main

现在,假设 Jerry(仍然在他的 wchar_enhancements 分支上)在 src/str_ops.c 中添加了另一个函数 my_wstrchr,然后提交并推送到他的 wchar_enhancements 分支。目前,这个更改与之前的重命名是独立的。

[jerry@devbox project]$ # (Jerry 在 wchar_enhancements 分支上的 src/str_ops.c 中添加 my_wstrchr)
[jerry@devbox project]$ git add src/str_ops.c
[jerry@devbox project]$ git commit -m "Add my_wstrchr function"
[wchar_enhancements 9d201a9] Add my_wstrchr function
1 file changed, 10 insertions(+)
[jerry@devbox project]$ git push origin wchar_enhancements

后来,Tom 决定将 Jerry 的 wchar_enhancements 分支合并到 main 中,以集成新功能。他首先拉取最新的更改:

[tom@devbox project]$ git checkout main
[tom@devbox project]$ git fetch origin

现在 Tom 尝试将 origin/wchar_enhancements 合并到他本地的 main 分支:

[tom@devbox project]$ git merge origin/wchar_enhancements

这个命令很可能会导致冲突,因为两个分支以不同的方式修改了 w_strlen 函数名及其附近的行:

Auto-merging src/str_ops.c
CONFLICT (content): Merge conflict in src/str_ops.c
Automatic merge failed; fix conflicts and then commit the result.

Git 已经修改了 src/str_ops.c 文件,并加入了冲突标记。Tom 打开文件,会看到类似这样的内容:

#include <wchar.h>
<<<<<<< HEAD
/* 自定义的宽字符 strlen 函数 */
size_t my_wc_strlen(const wchar_t *s)
=======
wchar_t *my_wstrchr(wchar_t *ws, wchar_t wc)
{
// ... my_wstrchr 的实现 ...
}
size_t my_wstrlen(const wchar_t *s)
>>>>>>> origin/wchar_enhancements
{
const wchar_t *p = s;
// ...

标记解释:

  • <<<<<<< HEAD: 标记当前分支(main)的冲突更改的开始。
  • =======: 分隔来自 main 分支和传入分支的更改。
  • >>>>>>> origin/wchar_enhancements: 标记来自 origin/wchar_enhancements 分支的冲突更改的结束。

Tom 需要手动编辑此部分来解决冲突。他决定:

  1. 保留 Jerry 的函数名 my_wstrlen。
  2. 保留他自己的注释 /* Custom wide character strlen function */。
  3. 保留 Jerry 新添加的 my_wstrchr 函数。

编辑后,src/str_ops.c 中已解决冲突的部分看起来像这样:

#include <wchar.h>
wchar_t *my_wstrchr(wchar_t *ws, wchar_t wc)
{
// ... my_wstrchr 的实现 ...
}
/* 自定义的宽字符 strlen 函数 */
size_t my_wstrlen(const wchar_t *s)
{
const wchar_t *p = s;
// ...

然后 Tom 暂存(stage)已解决的文件,并提交(commit)合并结果:

[tom@devbox project]$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/str_ops.c
no changes added to commit (use "git add" and/or "git commit -a")
[tom@devbox project]$ git add src/str_ops.c
[tom@devbox project]$ git commit
# Git 会打开一个编辑器,其中包含预填充的合并提交消息,例如:
# Merge remote-tracking branch 'origin/wchar_enhancements'
#
# Conflicts:
# src/str_ops.c
#
# Tom 保存并关闭编辑器。

合并提交输出:

[main 6b1ac36] Merge remote-tracking branch 'origin/wchar_enhancements'

Tom 成功解决了冲突并合并了更改。他本地的 main 分支现在包含了集成后的工作。他现在可以将这些更改推送到远程的 main 分支:

[tom@devbox project]$ git push origin main

处理冲突是 Git 协作工作流中至关重要的技能。使用可视化合并工具(visual merge tool)也有助于解决复杂的冲突。有关更多信息,请参考 Git 关于解决合并冲突的文档。