团队协作中如何避免Git冲突?

56

我们是一些在同一项目上工作的开发人员,使用git进行项目管理。如果两个或更多的人同时编辑同一个文件,就会出现难以处理的git冲突,有时候一个开发人员所做的更改在这种情况下会丢失。

在团队中他们如何使用git?为了避免git冲突应该采取什么样的合适流程呢?

提前感谢您的回答。

8个回答

64
在深入了解工作流程之前,在重建通信过程之前花费时间或金钱之前,请向您的团队提出三个问题:
  1. 我们是否执行空格约定?
  2. 如果我们使用IDE,它们是否都使用相同的格式和关键字设置?例如,Eclipse可以自动finalize parameters
  3. 我们是否生成文本构建工件?例如,我们是否缩小JS,从.sass.scss文件生成CSS规则,或者即时构建XML配置?我们会进行检查吗?
经过多年在集中式工作流程中生成冲突并帮助他人解决冲突后,我认为这三件事是我们集体冲突痛苦的主要原因。

The causes of merge conflicts

在上面的图片中,“!”切片表示合并冲突是合法的。大多数糟糕的合并都来自于懒惰的空格约定或过度进取的 IDE(或偶尔,过度重构的开发人员)。在进行其他任何操作之前,请标准化您的 IDE 设置和空格约定。然后要求每个人在他们的本地仓库中执行此命令:
# Enable the repository's stock pre-commit hook
mv .git/hooks/pre-commit.sample .git/hooks/pre-commit

这个 pre-commit 钩子将在每次执行 git commit 时进行一系列检查,包括一个版本的 git diff --check 命令,该命令检查您提交的更改是否引入了空格错误。如果有,则提交将被拒绝。如果您真的需要绕过它,可以发出 git commit --no-verify 命令,但不要这样做:空格错误就像版本控制中未爆炸的弹药一样。它们是等待发生合并冲突的问题。

如果您想清理空格、重构文件以改善其缩进或以其他方式进行大量纯格式更改,请在隔离的提交中执行此操作,并警告团队先检入他们的冲突工作。

如果您进行代码审查,请确保这些是每个审阅者首先问的问题:“这个变更集是否触及了任何它不应该触及的内容?它是否清理了任何格式?它是否不必要地重构了某个方法?”如果是这样,审核失败。或者,如果您无法这样做,请确保这些更改与逻辑更改足够隔离(即在不同的提交中),以使您的历史记录有用。

Stylesheet语言、RequireJS和js minifiers也会引起冲突,如果它们生成的目标被检入。这些技术创建的文件是构建工件。你不会检入一个WAR存档文件;不要检入SASS编译的CSS文件。或者,如果你觉得必须检入,请使用.gitattributes文件让git将它们视为二进制文件。
如果完成所有这些操作后仍然有合并冲突,请实施工作流程改进。Gary Fixler的回答绝对正确:合法的合并冲突是因为团队不能或不会很好地沟通其项目范围所致。只需确保不是强制执行的格式规则先出了问题。

1
我带着嘲讽的心情来到这个问题,但你用你的优秀回答赢得了我! - Tim Keating
我在考虑定期自动合并到主分支的“克隆”中。如果有“太多”或“太大”的合并冲突,则发送电子邮件给相关人员解决,或在某个仪表板中提到。这样,人们更有可能尽早解决合并冲突。这种做法可行吗? - MasterJoe

18

说实话,正确的工作流程需要良好的沟通和管理。团队成员不应该经常在同一件事情上工作,并引起冲突。每日站立会议和一个注意每个团队成员正在做什么 - 至少是大致了解 - 的细心经理,在许多情况下都可以限制这种情况。

当然,这取决于产品的确切性质,但这更多是组织问题而不是git问题。事实上,冲突通常被视为“好”的事情,因为它们能够让人们离开他们的桌子,或者互相打电话谈论为什么会有冲突,以及今后该怎么办。这是一个机会来确定一个人应该拥有一个区域、一组文件,或至少是讨论对该部分变更的联系点。

除了制定前期计划外,没有办法避免git冲突,但是这在任何版本控制系统中都是同样的问题。每当两个人更改代码的同一部分时,你必须找出谁胜利了。这就是为什么将目光投向版本控制器寻求解决方案并不真正合适,而要看看团队的方法和实践。两辆车无法同时通过十字路口,但很难发明可以互相穿过的汽车,因此我们发明了停止标志和交通信号控制系统。这是一个类似的问题。我们真的无法使对同一事物做两个更改不发生冲突,因此我们必须控制如何处理文件。

你可以考虑使用允许在git中锁定的前端之一,但我并不完全同意这个概念,除了非可合并的文件类型之外。我认为最好找出更好的团队工作流程,同时利用这个机会来真正擅长合并文件。我就是这样做的,现在几个月后,冲突对我来说已经不是令人沮丧的事情了。


14

避免冲突的唯一方法是:不要让不同的人同时编辑同一个文件。基本上,每个文件都有一个负责进行所有编辑并可以将所有权转移给其他人的所有者。文件的所有权可以基于特定的功能/分支或日常传递,只要所有权清楚。

如果您发现无法为每个文件指定一个所有者,则:

  • 您需要将文件拆分为较小的文件,以便可以分配给一个所有者
  • 绝对要求解决GIT冲突(所有编辑者聚在一起解决各个冲突)。
  • 使用良好的多合并工具来可视化然后解决冲突。

多合并工具比文本编辑器好在哪里?你会推荐哪一个? - MasterJoe

7

订购。

还有一些其他的事情可以帮助你。如果我将它们分开发布,那么它会更清晰。

插入新内容的位置将有助于确定是否创建冲突。

想象一下员工姓名列表

Andy, 
Oliver, 
Ivan,

然后 Brad 和 Patrick 加入,他们的名字被添加到列表中。 你添加 Brad,我添加 Patrick。我们将两个名字添加到列表底部,然后使用 git 合并我们的列表。对于 Git 用户来说,结果将是熟悉的 :-

Merge branch 'Patrick' into Brad

Conflicts:
    names.txt

@@@ -1,4 -1,4 +1,8 @@@
  Andy, 
  Oliver, 
  Ivan,
  <<<<<<< HEAD
 +Brad,
  =======
+ Patrick,
  >>>>>>> Patrick

现在假设我们将相同的操作应用于列表,但对其进行了简单的按字母顺序排序。当我们合并两个分支时,结果会更加令人满意 :-

Andy,
Ivan,
Oliver,

在git中添加您的名称,然后与另一个人的更改合并,以添加另一个姓名。

Auto-merging names.txt
Merge made by the 'recursive' strategy.
 names.txt | 1 +
 1 file changed, 1 insertion(+)

我们得到了

Andy,
Brad,
Ivan,
Oliver,
Patrick,

由于我们不知道谁会加入公司,因此我们随机地添加到列表中,并通过在随机位置插入来减少文件位置冲突。


这个方法有效是因为Brad和Patrick不是按字母顺序相邻的,他们之间有Ivan。如果你加上Patrick,我加上Oliver,我们就会遇到同样的问题。我希望能找到一个百分之百解决这个问题的方法。 - snips-n-snails

5

使用追加块起始符

在像这样的软件中...

function chess() 
{
    while (!end_of_game)
    {
        make_move()

以左花括号开始的代码块容易被与只包含单个左花括号的代码混淆。若将代码修改为以下格式,将花括号放在前一行...

function chess() {
    while (!end_of_game) {
        make_move()

我个人不太喜欢,但Git却喜欢,在Git中有很多看起来相似的行,并且容易被误认为是同一行,因此Git更容易像我们一样感知编辑,从而使任何冲突更容易解决。

关于块结束符号的注释

使用注释使相似的行区分开来。

如果你写很多JavaScript和JSON代码,你可能会有很多类似以下的行。

            }
        }
    }
)

如果你添加注释,那么它们就可以变得清晰可辨。
            }
        }
    }
) // end of weekdays()

并且

            }
        }
    }
) // end of weekend()

不再看起来像git。这可以帮助git更好地理解您的更改。如果您添加了一些内容,例如

function a()
{
    ...
} // end of a()

Git更可能将其视为变化的单位,而不认为您添加了类似以下内容的东西

}

function a()
{
    ...

在其他函数的末尾之前插入代码,即使这样做不能完全避免冲突,如果git能够合理地呈现您的更改(即我们在脑海中所看到的方式),那么您或许能够更轻松地解决冲突。对于函数的描述性标题、它们所接受的参数等,将进一步帮助防止git混淆相邻函数的内容。


2
为了减少版本控制中的冲突,而不用担心谁在编辑什么,您只需要进行较小的更改并逐步提交/推送它们。将问题分解成足够小的部分,以便您可以快速修改文件并将其推回主干。您的分支存在时间越短,合并冲突的可能性就越小。
即使如此,在某个时刻,两个人会无意中同时编辑同一个文件。当发生这种情况时,显而易见的问题是:
“如何消除git冲突的痛苦?”
答案是您应该经常拉取主干并将您的分支重新基于它进行变基,这样您就可以尽早地注意到冲突,而且冲突还很小。此时,两位开发人员应该坐在一起,冷静地讨论最佳处理方式,同时记忆中仍然新鲜的更改。
与试图在紧急发布期限前恐慌地解决长期存在的大型分支上的冲突相比,这种方法有所不同,此时开发人员难以记得一段时间前所有更改背后的思想。

1

使用UTF-8而非UTF-16的文本文件。

许多版本的Git(据我所知,包括我使用过的每个版本)会将UTF-16文件视为二进制文件,因为典型的UTF-16文本文件中存在许多零字节。

一旦Git认为一个文件是二进制文件,即使它被更改,它也可能继续将该文件视为二进制文件。这意味着版本控制是通过存储完整版本的文件而不是它们之间的差异来完成的,因此版本控制系统的某些优势将会丧失。(编辑:不。我最近测试了一下,将一个UTF-16文件更改为UTF-8,提交它,编辑它并再次提交 - 一旦原始文件和编辑后的文件都是UTF-8,它就开始将更改视为文本更改。)

大多数现代编辑器都能识别文件的字符编码和行结束样式,并以相同的格式保存文件。一些编辑器(例如Babelpad)允许您选择是否将文件保存为UTF-8或UTF-16,并选择是否带有字节顺序标记等。

如果您要版本控制的文件(i)是UTF-16格式,且(ii)在UTF-8下同样有效——例如现代编译器的源程序——那么考虑将其转换为UTF-8。
如果Git认为您的源文本在第一次提交之前是二进制文件,请查看它,并查看是否值得在编辑器中加载并以Git可识别的不同格式保存。
(注意。Linux文件默认为UTF-8。某些Windows程序习惯于创建UTF-16。因此,最有可能出现问题的是Windows用户。还要注意,在文件的第一次提交之前,您需要纠正这个问题,否则Git会认为它是一个二进制文件!)

enter image description here

使用 Git rerere

重复使用记录的解决方案!

https://git-scm.com/book/en/v2/Git-Tools-Rerere


1

在推送更改后通知同事

减少冲突痛苦的另一种方法是在推送更改后通知同事。这样他们就可以拉取您的更改并在那里解决一些冲突。任何冲突可能是您最近的更改与他们正在处理的内容之间的冲突,这两者都还新鲜在他们的脑海中。

如果人们在完成大型开发之前不从主分支拉取更改,然后与许多人在同一区域进行的更改发生冲突,那么解决起来将更加困难。

使用合并工具

enter image description here

Git的目的之一是版本控制。其他程序专门用于合并文件和解决冲突。如果您配置了一个与Git一起使用的合并工具,它可以自动解决许多Git认为是冲突的问题,或者至少为您提供一个非常好的猜测,以供检查,并且如果看起来可以或者您信任该工具,则可以简单地保持不变。
这样就会减少需要智能决策解决的真正冲突。

使用较小的文件

我在mycontrollers.js底部添加了一个新的控制器,您在yourcontrollers.js底部添加了一个新的控制器:没有问题。
我们都在allcontrollers.js底部添加了一个新的控制器:冲突。
(但请记住有关按字母顺序排序的建议。以M开头的myNewController()可能会放在文件的中间,而以Y开头的yourNewController()可能会放在同一文件的末尾,同样也不会冲突。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接