我们是一些在同一个项目上工作的开发人员,我们在项目中使用 git。如果我们两个或更多人碰巧在同一个文件上工作,我们会收到难以处理的 git 冲突,有时当这些冲突发生时,一个开发人员所做的更改会丢失。
他们如何在团队中使用 git?为了避免 git 冲突,正确的流程应该是什么?
提前致谢。
我们是一些在同一个项目上工作的开发人员,我们在项目中使用 git。如果我们两个或更多人碰巧在同一个文件上工作,我们会收到难以处理的 git 冲突,有时当这些冲突发生时,一个开发人员所做的更改会丢失。
他们如何在团队中使用 git?为了避免 git 冲突,正确的流程应该是什么?
提前致谢。
在您深入研究您的工作流程之前,在您花费一点时间或一美元来重建您的沟通流程之前,请向您的团队询问三个问题:
.sass
还是.scss
即时构建 xml 配置?我们检查他们吗?经过多年在集中式工作流程中产生冲突并帮助他人解决它们,我认为这三件事导致了我们绝大多数集体冲突的痛苦:
在上图中,“!” slice 表示合法的合并冲突。绝大多数糟糕的合并来自懒惰的空白约定或过于激进的 IDE(或偶尔,过度重构的开发人员)。在您做任何其他事情之前,请标准化您的 IDE 设置和空白约定。然后让每个人在他们的本地存储库中发出这个命令:
# Enable the repository's stock pre-commit hook
mv .git/hooks/pre-commit.sample .git/hooks/pre-commit
每次您发出 时,该预提交挂钩都会进行一系列检查git commit
,包括一个版本的git diff --check
, 检查您提交的更改是否引入空白错误的命令。如果这样做,提交将被拒绝。如果你真的需要绕过它,你可以发出git commit --no-verify
,但不要:空白错误就像版本控制的未爆炸条例。它们是等待发生的合并冲突。
如果您想清理空白、重构文件以改进其缩进,或者以其他方式进行大量纯格式更改,请在独立提交中进行,并警告团队首先检查他们正在进行的冲突工作。
如果您进行代码审查,请让每个审查者首先问这些问题:“这个更改集是否触及了它不应该触及的任何东西?它是否清理了任何格式?它是否不必要地重构了一个方法?” 如果是这样,则无法通过审核。或者,如果您不能,请确保这些更改与逻辑更改(即在不同的提交中)充分隔离,以使您的历史记录有用。
样式表语言、RequireJS 和 js 缩小器也会导致冲突,如果它们生成的目标被签入。这些技术创建的文件是构建工件。您不会签入 WAR 档案;不要签入 SASS 编译的 CSS 文件。或者,如果您觉得必须,使用.gitattributes
文件让 git 将它们视为二进制文件。
如果在完成所有这些事情之后,您仍然存在合并冲突,请改进工作流程。Gary Fixler 的回答是绝对正确的:合法的合并冲突的出现是因为团队不能或不能很好地沟通他们的项目范围。只要确保它没有执行不良的格式化规则首先。
好吧,老实说,正确的工作流程涉及良好的沟通和管理。团队成员不应该经常在同一件事上工作并引起冲突。诸如每日站会之类的事情,以及一个细心的经理,他知道团队中的每个成员都在做什么——至少在一般情况下——将在许多情况下大大限制这一点。
当然,这取决于产品的确切性质,但这更多是组织问题而不是 git 问题。事实上,冲突通常被视为“好事”,因为它们让人们从办公桌前站起来,或者互相打电话讨论为什么会发生冲突,以及接下来应该怎么做。这是一个确定一个人应该拥有一个区域或一组文件的机会,或者至少是讨论该部分更改的联系人。
除了预先制定计划之外,没有办法避免 git 冲突,但这在任何版本控制系统中都是相同的问题。每当两个人更改同一段代码时,您都必须弄清楚谁赢了。这就是为什么向版本管理器寻求解决方案并不合适,而是向团队的方法和实践寻求帮助。两辆车不能同时通过一个十字路口,但是要发明可以互相通过的汽车是非常困难的,所以我们发明了停车标志和交通信号灯的控制系统。这是一个类似的问题。我们不能真正对同一事物进行两次不冲突的更改,因此我们必须控制我们处理文件的方式。
您可以考虑允许锁定 git 的前端之一,但除了不可合并的文件类型之外,我并不真正同意这个概念。我认为最好找出更好的团队工作流程,同时利用这个机会来真正擅长合并文件。我这样做了,现在经过几个月的努力,冲突对我来说并不是一件令人沮丧的事情。
只有一种方法可以避免冲突:不要让不同的人同时编辑同一个文件。本质上,每个文件都有一个负责所有编辑的所有者,并且可以将所有权转让给另一个文件。只要所有权明确,文件的所有权就可以根据特定的功能/分支或日常传递。
如果您发现不能为每个文件分配一个所有者,则:
您还可以做一些其他的事情,这也可能会有所帮助。如果我单独发布它们会更清楚。
在哪里插入新事物将有助于确定您是否会产生冲突。
想象一下员工姓名列表
Andy,
Oliver,
Ivan,
然后布拉德和帕特里克加入,他们的名字被添加到列表中。你加布拉德,我加帕特里克。我们都将名称添加到列表的底部,然后使用 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,
由于我们不知道接下来谁将加入公司,因此我们有效地随机添加到列表中,并且通过在随机位置插入,文件中位置冲突的可能性较小。
在这样的软件中...
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 将相邻函数的内容混淆在一起。
为了减少版本控制中的冲突数量而不用担心谁在编辑什么,您只需要进行较小的更改并逐步提交/推送它们。将问题分成足够小的部分,以便您可以快速修改文件并将它们推回主干。您的分支寿命越短,合并冲突的可能性就越小。
即便如此,在某些时候,两个人会同时编辑同一个文件,这不是他们自己的过错。发生这种情况时显而易见的问题是:
“我怎样才能摆脱解决 git 冲突的痛苦?”
答案是你应该经常拉动主干并将你的分支重新定位到它上面,你会尽早注意到冲突,而它们仍然很小。在这一点上,开发人员应该坐在一起,冷静地讨论最好的方法来进行,而这些变化在他们脑海中是新鲜的。
将这种方法与在发布截止日期前的恐慌中试图解决巨大的长期分支上的冲突进行对比,当时开发人员很难记住他们前一段时间所做的所有更改背后的想法。
减少冲突痛苦的另一种方法是在您推动更改时通知同事。它允许他们提取您的更改并在那里解决一些冲突。任何冲突都可能发生在您最近的变化、您的脑海中的新鲜事物和他们正在做的、他们头脑中的新鲜事物之间。
如果人们在完成大型开发之前不从主分支中提取更改,然后与同一区域的许多人所做的更改发生冲突,那么解决起来将更加困难。
Git 的目的之一是版本控制。其他程序专门用于合并文件和解决冲突。如果您将合并工具配置为与 git 一起使用,那么它可以自动解决许多 git 认为是冲突的问题,或者至少做出一个很好的猜测供您检查,如果它看起来不错,或者您信任该工具,则只需保持不变.
这样就减少了需要明智决策来解决的真正冲突。
我在 mycontrollers.js 的底部添加了一个新控制器,您在 yourcontrollers.js 的底部添加了一个新控制器:没问题。
我们都在allcontrollers.js的底部添加了一个新控制器:冲突。
(但是请记住关于按字母顺序排序的建议。以 M 开头的 myNewController() 可能位于文件中间,以 Y 开头的 yourNewController() 可能位于同一文件的末尾,同样没有冲突。)
由于典型的 UTF-16 文本文件中存在许多零字节,许多版本的 Git(即,据我所知,我使用的每个版本)都可以将 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 认为它有一个二进制文件之前!)
重复使用记录的分辨率!