我在一个大型团队中工作,最近我们从TFS转到了Git。
我们已经有一些工作被覆盖的问题,并且相信git pull --rebase
是我们想要做的,因此与其直接 merge ,不如直接 merge 它在本地分支上重播 pull 出的提交。
但是,我们一直在阅读黄金法则,但仍不确定是否可以使用rebase。
我们的理解是,当您使用重定基向远程分支进行推送时,黄金法则就会发挥作用。从https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372
You have probably came across that rule, maybe phrased differently. For those who haven’t, this rule is quite simple. Never, NEVER, NEVER, rebase a shared branch. By shared branch I mean a branch that exists on the distant repository and that other people on your team could pull.
所以这很糟糕:
git pull --rebase
与远程git pull --rebase
与远程可以:
pull origin support --rebase
pull origin feature-a --rebase
--rebase
,并 merge 到主本质上,我们不想在每次推送时都使用
--rebase
,对吗?
最佳答案
实际上,没有任何一种简单,通用的规则在任何情况下都适用于每个人。 “永远不要让任何东西 rebase 础”是一个简单的规则,确实可以,但是并不适合所有人。有一个更复杂的规则始终有效:只要拥有原始提交的每个人都愿意切换到新副本,Rebase就可以了。
描述
您以及与您一起工作的每个人所需要知道的是,Git对待 promise 非常宝贵,并且努力工作以确保您永远不会丢失任何东西。但是分支名称的重要性要小得多。分支名称充当指针,指向特定的提交。分支名称使Git可以找到提交,但重要的是提交。当您使用git rebase
时,您是在告诉Git将一些现有(旧)提交复制到一些新提交中。新的将获得新的“真实名称”(请参见下文)。将旧的提交复制到新的提交有很多原因,包括没有提交可以更改的事实。制作完成后,它(通常)是永久性的且不可更改。
您可以做一件事使提交似乎消失:您可以完全停止使用它。一旦没有人使用它很长一段时间,并且它没有名称,也找不到它,它的确会消失。但是提交是很宝贵的,因此这需要一段时间。 (这背后的实际机制是reflog,您可以在其他地方阅读更多信息。)
一个简单的过程
现在,提交具有丑陋的哈希ID作为其“真实名称”。这些名称很难使用:例如d35688db19c9ea97e9e2ce751dc7b47aee21636b
与4d7268b888d7bb6d675340ec676e4239739d0f6d
的相对关系是什么?谁知道,它们只是个丑陋的数字,似乎是随机的。这些是Git真正关心的名称,而不是我们仅人类使用的名称:我们使用诸如master
或v2.14
之类的名称。
这就是分支(和标签)名称的来源。Git让我们选择一个分支名称,例如master
,用于一段时间内标识d35688db19c9ea97e9e2ce751dc7b47aee21636b
。最终,我们向master分支添加了一个新的提交,并且新的提交获得了一个新的,又大又丑陋的真实名称-Git更改了master
以将其映射到新名称。这是一个简单的示例,在一个仅以三个提交开始的存储库中:
A <-B <-C <--master
名称
master
“指向” commit C
(无论如何C
代表c0193fa8...
-不管怎么说,它是一个非常丑陋的哈希ID)。分支名称保留当前的分支提示哈希ID,作为Git自动执行的服务。我们之所以说“指向”,是因为Git将在名称之后跟随ID来获取提交。 Commit C
依次指向(存储)commit B
的哈希ID,commit B
点指向commit A
。由于A
提交是有史以来的第一个提交,因此它不会指向任何地方:它是一个根提交。这就是Git知道在哪里停下来的方法。要立即添加新的提交,请在存储库工作树中工作,编辑文件
git add
和git commit
。 Git现在编写一个新的提交(我们将其称为D
),它指向C
,并将名称master
更改为指向您的最新提交:A--B--C--D <-- master
(内部箭头始终指向后方,更新的箭头指向较旧的箭头,因此没有真正的必要继续绘制它们。很难在更复杂的图中绘制它们。)
merge
只要您所做的唯一一件事就是添加新的提交,就可以很好地工作,因为Git是围绕添加新的提交的整个想法而构建的。如您的示例所示,如果Charles和Taylor都向某个分支添加了新的提交,然后其中一个或任何第三方去获取您的新提交,则Git能够添加您的提交及其提交一起。如果添加的内容很简单:
A--B--C--D--E--F <-- master
然后名称
master
只会随着向右添加新提交而继续向右移动。如果添加是并行进行的,则Git必须添加“merge 提交”以记录两个较新的提交。比如说,查尔斯添加了E
,泰勒添加了F
,两者的工作速度都不比另一个快,所以我们得到了这一点: E
/
A--B--C--D
\
F
现在我们有一个问题:
master
只能指向一个提交。如果选择E
,则无法处理F
。如果我们选择F
,则会丢失E
。 merge 工作流通过在E
和F
之上添加一个新的提交G
来处理此问题,该提交指向E
和F
: E
/ \
A--B--C--D G <-- master
\ /
F
这很好用,但是随着时间的流逝,您会得到很多 merge 气泡,这些气泡仅用于记录人们并行工作的事实。尽管这是该项目的正确,真实的历史记录,但它充满了无用的细节。这是重新部署的地方。让我们看一下它是如何工作的。
rebase
Rebase复制了一些提交,然后(至少在理想情况下)复制了您和其他拥有原始提交的所有人,都停止了使用原始提交。相反,您和其他所有人都使用了 Shiny 的新提交。
例如,假设我们有:
E <-- origin/master
/
A--B--C--D
\
F <-- master
在Taylor的存储库中,提交
E
的名称为origin/master
,提交F
的名称为master
。 (请记住,Taylor制作了F
。)Taylor是唯一拥有
F
的程序员。Taylor现在可以选择-这是一个选择,而不是必须-将提交
F
复制到更新的,更漂亮的F'
,至少有一个区别:F'
将指向的不是D
,而是指向E
。(Taylor的新
F'
应该至少有另一个区别:他也应该接受Charles在E
中所做的任何工作。)如果泰勒要跑:
git rebase origin/master
他的Git会找到要复制的提交列表,该列表只会列出
F
。然后它将F
复制到F'
之上构建的E
: F' <-- [temporary]
/
E <-- origin/master
/
A--B--C--D
\
F <-- master
现在到了棘手的部分:Taylor的Git将移动Taylor的
master
指向复制的提交,从而放弃原始的F
。结果是相同的图,但名称已移动: F' <-- master
/
E <-- origin/master
/
A--B--C--D
\
F [abandoned]
由于
F
现在已被放弃-它没有明显的名称-Git停止显示它。提交仍然存在,因为(大多数)提交是永久的并且(完全)不变,但是最终它将消失,因为泰勒是唯一拥有提交的人,并且他更改了master
的名称,以便master
不再找到它。从master
开始,他的Git依次找到F'
,E
,D
,C
,等等—它再也看不到原始的F
了。如果我们停止完全绘制
F
,而将新提交的git push
F'
停止,以便其他人现在都可以看到,我们得到:A--B--C--D--E--F' <-- master, origin/master
(我们已经更新了
origin/master
,因为我们推送了F'
,所以现在origin
的master
也指向F'
,泰勒的Git知道了这一点,并相应地调整了他的origin/master
)。猴子 Spanner
但是现在假设泰勒在 rebase 之前先推送
F
。假设他在分支F
上而不是在feature
上创建master
,因此他在重新设置基准之前就拥有了它: E <-- origin/master
/
A--B--C--D <-- master
\
F <-- feature
通过将
F
推送到feature
上的origin
,Taylor在另一个Git中创建一个分支origin
。 Taylor自己的Git通过命名为origin/feature
(也指向F
)来记住这一点: E <-- origin/master
/
A--B--C--D <-- master
\
F <-- feature, origin/feature
现在,如果Taylor执行与以前相同的 rebase 操作,他将获得与以前相同的
F'
,但是这次有一个F
的名称: F' <-- feature
/
E <-- origin/master
/
A--B--C--D <-- master
\
F <-- origin/feature
由于位于
origin
的另一个Git存储库具有指向feature
的名称F
,因此其他任何人(例如Charles)都可以选择commit F
。现在,请记住,提交是很宝贵的。分支名称可以更改,但是提交是永久且不变的,因此,如果Charles具有
F
,而Taylor推送F'
,则Charles仍然具有commit F
。如果Charles知道自己在做什么,也知道Taylor在做什么,那么通过移动任何允许他访问
F
的分支名称,Charles可以让Git忘记F
。一旦Charles放弃了F
,而Taylor放弃了F
,而其他所有拥有F
的人都放弃了F
,那么-直到那时,F
最终才能真正消失。因此,使用
git rebase
的规则实际上是这样的:只要拥有原始文档的每个人都愿意切换到 Shiny 的新提交,Rebase就可以了。如果您(或泰勒)是唯一拥有原件的人,那会容易得多。如果您已将那些原始提交复制到其他存储库中,那么会有更多的人不得不告诉他们的Git切换。要 merge 还是要 rebase ?就是那个问题
merge 更容易,因为 merge 意味着“添加 merge 的提交”。您可能需要在 merge 操作(动词 merge )中做一些工作,但是一旦完成,它就完成了; Git进行了一个 merge 提交,该提交不仅记录了一个先前的提交,而且还记录了两个提交,并且新的提交被添加到其中,每个Git都会自动知道该如何处理。
但这会使您的历史变得无用。调整基础是更多的工作-可能复制许多提交,并让拥有旧提交的每个人都切换到新提交。在此过程中有更多出错的机会。更糟糕的是,每个复制步骤实际上是另一个 merge 即动词。不过,一旦完成所有操作,您将拥有光鲜,外观清晰的历史,就好像您刚开始时就确切知道要去的地方,并且从未做过任何错误的举动。看到发生的事情要容易得多,或者更确切地说,是您希望人们认为发生的事情,这与实际发生的事情不同。
由您(和每个人)决定要做什么。有时重新定价既容易又好。有时候,这是艰难而美好的。有时,这太难了,或者不是一个好主意,因为它掩盖了确实或可能会造成影响的太多真实历史。您必须立即做出判断:将来会不会很重要?如果现在改头换面,明天,下个月或明年工作会更容易吗?还是会更困难?
关于git - 了解git rebase和 “golden rule”,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46656240/