[译] 保护咱们的 Git Repos,马上中止“狐步舞”

狐步舞舞者

舞者们正准备跳狐步舞。html

首先,什么是“狐步舞”式的合并?

“狐步舞”式的合并是 git commit 的一个特别很差的具体顺序。如同在户外看到的“狐步舞”,这种commits序列像这个样子:前端

“狐步舞”式的合并

但在公开场合不多会见到“狐步舞”。它们隐藏在树冠之间,树枝之间。我称它们“狐步舞”式,是由于他们交叉的样子,他们看起来像同步舞蹈的舞步顺序:react

狐步示意图

还有一些人也提到“狐步舞”式的合并,但它们历来没有直接说出它的名字。例如,Junio C. Hamano 的博客有有趣的 --first-parent,还有有趣的非快进方式(Non-Fast-Forward)。David Lowe 的 nestoria.com 有关于保持一致的线性历史记录的文章。此外还有告诉你要避免使用 git pull,而是使用 git pull –rebase。为何?主要是为了不通常的合并和提交时的错误,此外还能够避免出现该死的“狐步舞”式的提交。linux

“狐步舞”式的合并真的很很差吗?是的。android

水母

它们显然不如僧帽水母那样糟糕。可是“狐步舞”式的合并也是很差的,你不但愿你的 git 仓库里有它们的身影。ios

“狐步舞”式的合并为何很差?

“狐步舞”式的合并很差,由于它会改变 origin/master 分支的“第一父级”的地位。git

合并提交记录的父级是有序的。第一个父级是 HEAD。第二个父级是用 git merge 命令提交的。github

你能够像下面这样想:后端

git checkout 1st-parent
git merge 2nd-parent
复制代码

若是你是 octopus 的说客:bash

git merge 2nd-parent 3rd-parent 4th-parent ... 8th-parent etc...
复制代码

这意味着父级的记录就像它听起来同样。当你提交新的代码的时候,忽略第一个父级之外的父级,从而获得一个新的代码记录。对于常规的 commit(非 merge),第一个父级是惟一的父级,而且对于 merge 来讲,它是你在输入 git merge 时所产生的记录。这种父级概念是直接植入到 Git 里的,而且在不少命令行中都有所体现,例如,git log –-first-parent

“狐步舞”式的合并问题在于,它使得 origin/master 由第一父级变成了第二父级。

除了 Git 在评估提交是否有资格进行 fast-forward 时,Git 并不关心父级的前后次序。

固然你很不但愿这样。你不但愿“狐步舞”式的合并经过 fast-forward 的方式更新你的 origin/master,使得 origin/master 第一父级的地位不稳定。

看一下当“狐步舞”式的合并被 push 上去的时候会发生什么:

“狐步舞”式的合并被 push

可使用手指从 origin/master 开始沿着图形往下,在每一个分叉的地方选择左边的分支,从而知道当前的第一父级的变动历史。

问题是,最初的第一个父级提交次序(从 origin/master 开始)是这样的:

B, A.

可是当“狐步舞”式的合并被 push 以后,父级的次序变成这样了:

D, C, A.

这时,B 节点已从 origin/master 第一父级中消失,事实上,B在它的第二父级上。固然,不会有任何资料的丢失,而且 B 节点仍然是 origin/master 的一部分。

可是,这样父级节点就会有错综复杂的关系。你是否知道,tilda 符号(例如 ~N)指定从第 N 个提交的节点到第一个父节点间的路径?

你有没有想要看看你的分支上的每一个提交记录之间的差别,可是使用 git log -p 显然会漏掉一些信息,使用 git log -p -m 能获取更多的信息吗?

尝试使用 git log -p -m –first-parent 吧。

你想过要还原一个合并的分支吗?那你须要为 git revert 提供 -m parent-number 选项,这时候你就很不但愿本身提供的 parent-number 是错的。

和我一块儿工做的人,大多数都将第一个父级做为真正的 master 分支。有意识或无心识地,人们将 git log –first-parent origin/master 视为重要事物的顺序。 至于任何其余合并进来的分支?嗯,你应该知道他们会怎么说:

topic

可是“狐步舞”式的合并把这些都混在了一块儿。请考虑下面的例子,其中 origin/master 分支的一系列的重要提交信息,与你本身的稍微不那么重要的提交并行:

topic escaped

如今,你终于准备把你的工做并入到 master 中。你输入 git pull,或者可能你在一个主题分支上使用 git merge master 命令。那这样发生了什么?一个“狐步舞”式的合并就这么出现了。

topic escaped b

一切都没有什么大问题,除了当你键入 git push,让你的远程仓库接受它时,你的历史记录看起来像这样:

topic escaped c

topic escaped lego

对于已经混入了“狐步舞”式的合并的 git 项目应该怎么作?

啥招都没有,随它们去吧。除非你重写 master 分支的历史而惹怒其余人,那么就去这么疯吧。

事实上,不要这样作。

如何防止将来“狐步舞”式的合并出如今个人 git 项目中?

这有几个方法。我最喜欢的的方式是下面的四步:

  1. 为你的团队安装 Atlassian Bitbucket 服务器。

  2. 安装我为 Bitbucket 服务器写的插件,名字叫“Bit Booster Commit Graph and More”。 你能够在下面的连接中找到他们:marketplace.atlassian.com/plugins/com…marketplace.atlassian.com/plugins/com…

  3. 在你全部项目中,都点击 “Protect First Parent Hook” 上的 “Enabled” 按钮,也就是“启用”按钮:

hook enabled

  1. 你能够在试用许可结束前无偿使用31天。(感受它好用的话,能够在试用期后进行购买)。

这是我最喜欢的方式,由于它杜绝了“狐步舞”的出现。每当有一个“狐步舞”式的合并被阻挡时,它会打印一只牛:

$ git commit -m 'my commit'
$ git pull
$ git push

remote:  _____________________________________________
remote: /                                             \
remote: | Moo! Your bit-booster license has expired!  |
remote: \                                             /
remote:  ---------------------------------------------
remote:         \   ^__^
remote:          \  (oo)\_______
remote:             (__)\       )\/\
remote:                 ||----w |
remote:                 ||     ||
remote:
remote: *** PUSH REJECTED BY Protect-First-Parent HOOK ***
remote:
remote: Merge [da75830d94f5] is not allowed. *Current* master
remote: must appear in the 'first-parent' position of the
remote: subsequent commit.
复制代码

还有其余的方法。你能够禁止直接向 master 分支进行推送,并保证不在 fast-forward 的状况下合并 pull-requests。或者培训你的员工使用 git pull –rebase 命令,而且永远不要使用 git merge master。而且一旦你培训完你的员工,就不要再招聘其余员工了。

若是你能够直接访问远程仓库,则能够设置 pre-receive hook。 如下的 bash 脚本能够帮助你开始这项设置:

#/bin/bash

# Copyright (c) 2016 G. Sylvie Davies. http://bit-booster.com/
# Copyright (c) 2016 torek. http://stackoverflow.com/users/1256452/torek
# License: MIT license. https://opensource.org/licenses/MIT
while read oldrev newrev refname
do
if [ "$refname" = "refs/heads/master" ]; then
   MATCH=`git log --first-parent --pretty='%H %P' $oldrev..$newrev |
     grep $oldrev |
     awk '{ print \$2 }'`

   if [ "$oldrev" = "$MATCH" ]; then
     exit 0
   else
     echo "*** PUSH REJECTED! FOXTROT MERGE BLOCKED!!! ***"
     exit 1
   fi
fi
done
复制代码

我不当心建立了一个“狐步舞”式的合并,但我尚未 push 上去。我该怎么解决?

假设你安装了预先接收的钩子,而且阻止你“狐步舞”式的合并。你下一步作什么?你有三种可能的补救办法:

  1. 普通的 rebase

使用 `rebase` 进行补救

  1. 撤销你以前的合并,使你的 origin/master 分支成为第一父级:

撤销 merge

  1. 在“狐步舞”式的合并后建立第二个合并并提交,以恢复 origin/master 的第一父级的地位。

补救

但请不要使用上面的第三种方法,由于最后的结果被称为“僧帽水母”式的合并,这种合并甚至比“狐步舞”式的合并更糟糕。

总结

在最后,其实“狐步舞”式的合并也像其余的合并那样。两个(或多个)提交到一块儿融合成一个新的记录节点。就你的代码库而言,没有任何区别。不管 commit A 合并到 commit B 中仍是反过来 commit B 合并到 commit A,从代码的角度来看最终结果是相同的。

可是,当涉及到你的仓库的历史记录时,以及有效地使用 git 工具集时,“狐步舞”式的合并会有必定的破坏性。经过设置相应的策略来防止其出现,可使你仓库的历史记录更加清晰明了,并减小了须要记住的 git 命令选项的范围。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOSReact前端后端产品设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划

相关文章
相关标签/搜索