Git中合并,存储和重新绑定之间的概念差异是什么?

Sye*_*iom 4 git version-control merge rebase git-stash

我一直在主分支上使用Merging.但是最近在我的情况下进行功能开发,合并对于项目历史来说似乎很复杂.我遇到了Rebasing,它解决了我的问题.在解决问题的同时,我也遇到了变相的黄金法则.

我有时也使用Stashing,但是我觉得同样的东西也可以通过合并来实现.

虽然我使用这些命令,但我觉得如果有人可以解释关于这三个命令的概念上的突出事实/规则,它将帮助我更清楚地理解.谢谢.

Sch*_*ern 8

假设你有这个存储库.A,B和C是提交.master是在C.

A - B - C [master]
Run Code Online (Sandbox Code Playgroud)

你做了一个分支feature.它指向C.

A - B - C [master]
          [feature]
Run Code Online (Sandbox Code Playgroud)

你做既一些工作masterfeature.

A - B - C - D - E - F [master]
         \
          G - H - I [feature]
Run Code Online (Sandbox Code Playgroud)

您想要feature使用更改进行更新master.你可以合并masterfeature,导致合并提交J.

A - B - C - D - E - F [master]
         \           \
          G - H - I - J [feature]
Run Code Online (Sandbox Code Playgroud)

如果你这样做了足够多的时间,事情开始变得混乱.

A - B - C - D - E - F - K - L - O - P - Q [master]
         \           \       \       \ 
          G - H - I - J - M - N - Q - R - S [feature]
Run Code Online (Sandbox Code Playgroud)

这可能看起来很简单,但那是因为我已经这样画了.Git历史是一个图形(在计算机科学意义上)并没有任何东西说它必须像那样绘制.并且没有任何明确说明,例如,提交M是分支功能的一部分.你必须从图中找出答案,有时可能会变得混乱.

当你决定你完成和合并featuremaster,事情变得复杂.

A - B - C - D - E - F - K - L - O - P - Q - T [master]
         \           \       \       \     /
          G - H - I - J - M - N - Q - R - S [feature]
Run Code Online (Sandbox Code Playgroud)

现在很难说M原本是其中的一部分feature.再一次,我选择了一种很好的方法来绘制它,但Git并不一定知道这样做.M是主人和特征的祖先.这使得难以解释历史并弄清楚在哪个分支中做了什么.它还可能导致不必要的合并冲突.


让我们重新开始吧.

A - B - C - D - E - F [master]
         \
          G - H - I [feature]
Run Code Online (Sandbox Code Playgroud)

将分支重新分支到另一个分支在概念上就像将分支移动到另一个分支的顶端.重订featuremaster是这样的:

                      G1 - H1 - I1 [feature]
                     /
A - B - C - D - E - F [master]
         \
          G - H - I
Run Code Online (Sandbox Code Playgroud)

每个提交都在feature重播之上master.就像你在C和G之间取得差异,将它应用于F,然后调用G1.然后G和H之间的差异应用于G1,即H1.等等.

没有合并提交.就像你一直在编写feature分支 master一样.这样可以保持一个漂亮,干净,线性的历史记录,而不会出现没有告诉任何内容的合并提交.

请注意,旧功能分支仍然存在.只是没有任何东西指向它,它最终将被垃圾收集.这是为了告诉你rebase不会重写历史 ; 相反,rebase创造了新的历史,然后我们假装它一直都是这样.这有两个重要原因:

首先,如果你搞砸了一个rebase,旧的分支仍然存在.您可以使用git reflog或使用它ORIG_HEAD.

其次,最重要的是,rebase会产生新的提交ID.Git中的所有内容都通过ID工作.这就是为什么,如果你修改共享分支,它会引入复杂性.

关于变基与合并的说法还有很多,所以我会把它留在这里:

  • 要更新分支,请使用rebase.这避免了混乱的中间合并.
  • 完成一个分支......
    • 使用更新它rebase.
    • 然后使用merge --no-ff强制创建合并提交.
    • 然后删除功能分支,再也不要使用它.

您希望在历史记录中看到的最终结果是"功能泡沫".

                      G1 - H1 - I1
                     /            \
A - B - C - D - E - F ------------ J [master]
Run Code Online (Sandbox Code Playgroud)

这使历史保持线性,同时仍然为代码考古学家提供了G1,H1和I1作为分支的一部分完成的重要背景,应该一起检查.


存储是完全不同的.它基本上是一个存储补丁的特殊分支.

有时候你处于中间状态并且还没有准备好承诺,但你需要做一些其他的工作.您可以将其放在补丁文件中git diff > some.patch,重置您的工作目录,执行其他工作,提交它,然后应用some.patch.或者你可以git stash save,以后git stash pop.


bcm*_*cfc 1

隐藏是非常不同的,因为它本质上是为以后保留您的更改。如果您正处于某件事的中间并且必须跳到其他事情并切换分支,那么这很有用。

合并和变基最终实现相同的目标 - 将更改合并到一个分支中。

  • 变基提供了可以说是“更干净”的历史,并且冲突得到内联解决。
  • 通过合并,冲突在合并提交中得到解决。
  • 重新基址分支完成后仍会合并回主分支。

不同之处在于,有效地解决内联冲突会使冲突仿佛从未发生过,因为您正在再次编辑文件以合并导致冲突的更改。

当您稍后回顾更改时,这可能很有用。

正如您所提到的,需要注意的是,重写历史使得在该特定分支上与其他人合作变得困难或不可能。


当查看 gitg 等工具的图表或视觉表示时,来自长期存在的功能分支的一系列合并可能很难理解。

通过变基,可以更轻松地直观地跟踪更改。当您使用 git-bisect 等工具来查找错误的原始提交时,它也可以提供帮助,因为分支更容易遍历。

选择哪个取决于几个因素。

就我个人而言,如果我在一个我独自开发的短期功能分支上,我会经常进行变基和变基。否则就是合并。


在某些情况下,如果您从错误的点开始分支,您可能别无选择重新设置基准。例如,您可能已经检查了某个特定功能并开始从事其他工作。在它所基于的功能之前可能需要合并和部署其他东西。此时,将更改重新调整到不同的分支将使您能够独立于其他功能发布该功能。


当您将重新基址的分支合并回主分支时,即使历史记录是线性的,这仍然是合并。您可能会在 git 中看到一条消息,告诉您它进行了“快进”合并。这意味着它只是将引用“master”移至新位置。您可以告诉 git 不要这样做,并使用--no-ffgit merge 命令上的标志创建一个合并提交。

线性历史:

* HEAD, master, your_branch: your last commit
|
*
|
*
|
*
|
*
|
* previous master: where master was when you rebased
Run Code Online (Sandbox Code Playgroud)

使用 no-ff 合并提交重新调整历史记录:

* HEAD: merge branch your_branch into master
| \
|  * your_branch the last commit in your branch
|  |
|  * 
|  |
|  *
|  |
|  *
|/
* previous master: the starting point of your branch
Run Code Online (Sandbox Code Playgroud)