我一直在主分支上大量使用Merging。但是最近在我的情况下进行功能开发,合并对于项目历史来说似乎很复杂。我遇到了Rebasing,它解决了我的问题。在解决问题时我也遇到了the golden rule of rebasing。
我有时也使用Stashing,但是我觉得合并也可以达到同样的目的。
虽然我使用这些命令,但我觉得如果有人可以解释关于这三个命令的概念上的突出事实/规则,它将帮助我更清楚地理解。感谢。
答案 0 :(得分:7)
让我们说你有这个存储库。 A,B和C是提交。 master
在C。
A - B - C [master]
你创建了一个名为feature
的分支。它指向C。
A - B - C [master]
[feature]
您对master
和feature
进行了一些工作。
A - B - C - D - E - F [master]
\
G - H - I [feature]
您希望使用feature
的更改来更新master
。您可以将master
合并到feature
,从而产生合并提交J。
A - B - C - D - E - F [master]
\ \
G - H - I - J [feature]
如果你这么做的话,事情就会变得混乱。
A - B - C - D - E - F - K - L - O - P - Q [master]
\ \ \ \
G - H - I - J - M - N - Q - R - S [feature]
这可能看起来很简单,但那是因为我已经这样画了。 Git的历史记录是a graph (in the computer science sense),并且没有任何内容表明它必须像这样绘制。而显式所说的并不存在,例如,提交M是分支功能的一部分。你必须从图中找出答案,有时可能会变得混乱。
当您决定完成并将feature
合并到master
时,事情会变得混乱。
A - B - C - D - E - F - K - L - O - P - Q - T [master]
\ \ \ \ /
G - H - I - J - M - N - Q - R - S [feature]
现在很难说M原本属于feature
。再一次,我选择了一种很好的方法来绘制它,但Git并不一定知道这样做。 M是主人和特征的祖先。这使得难以解释历史并弄清楚在哪个分支中做了什么。它还可能导致不必要的合并冲突。
让我们重新开始并改变。
A - B - C - D - E - F [master]
\
G - H - I [feature]
将分支重新分支到另一个分支上概念就像将分支移动到另一个分支的顶端一样。将feature
重新定位到master
上是这样的:
G1 - H1 - I1 [feature]
/
A - B - C - D - E - F [master]
\
G - H - I
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]
这使历史保持线性,同时仍然为代码考古学家提供了G1,H1和I1作为分支的一部分完成的重要背景,应该一起检查。
Stashing是完全不同的东西。它基本上是一个存储补丁的特殊分支。
有时候你处于某种中间并且还没准备好提交,但你需要做一些其他的工作。您可以将其放在包含git diff > some.patch
的补丁文件中,重置您的工作目录,执行其他工作,提交它,然后应用some.patch
。或者您可以git stash save
以及之后git stash pop
。
答案 1 :(得分:2)
Git中的Merging,Stashing和Rebasing之间的概念差异是什么?
我认为你的意思是"用于"的这三个功能是什么,而不是实际的技术差异 - 你似乎知道。
合并意味着您选择两个分支并最终得到一个包含原始分支中所有内容的分支。这意味着合并在概念上是分支的逆。您主要使用它来删除"分支 - 将其合并回原来的位置。合并表示您不打算向该分支添加更多提交。
重新定位意味着您可以更改现有分支的来源。从概念上讲,这意味着您将分支保留为自己的开发路径,您只需模拟您在其他时间点创建它。
这在理论上有用的原因是,对于大量分支(例如,功能分支)而言,您并不关心创建它们的确切时间点。你关心的是他们是一个三角洲"到master
,无论master
现在是什么。因此,您希望始终将它们从当前 master
分支出来,只要master
更改,就可以将它们重新定位到它上面。
这些概念意味着何时使用哪个概念非常明显。如果你保持分支(作为它自己的实体,即因为你继续在它上面开发),然后变基。如果你想让分支消失(在概念上)并且永远不想为它添加anoter提交,那么合并。
这也意味着它们都不比另一个好或坏。它们都是工具,两者都是用来使用的。两者都可以被滥用;如果不加思索地使用它们,它们都会对您的存储库造成严重破坏。
Stashing与合并或重新定位完全无关。把它想象成你为自己做的本地事情;你快速扫除你所做的任何改变,做其他事情,然后回想起以前的状态。它在概念上类似于在其他地方克隆存储库的干净副本,在那里工作,然后返回到以前的存储库。
答案 2 :(得分:1)
存储是非常不同的,因为它基本上为您的后期留出了您的更改。如果你处于某个中间并且你必须跳到其他东西并切换分支,这很有用。
合并和变基最终实现了同样的目标 - 将变更合并到一个分支中。
不同之处在于,有效地解决内联冲突会使冲突永远不会发生,因为您再次编辑文件以合并导致冲突的更改。
当您稍后回顾这些更改时,这可能很有用。
您提到的警告是,重写历史记录使得与该特定分支上的其他人协作变得困难或不可能。
在查看gitg等工具中的图形或可视化表示时,很难从一个长期的功能分支中进行一系列合并。
使用rebase,可以更直观地跟踪更改。当你使用像git-bisect这样的工具来查找bug的原始提交时,它也会有所帮助,因为分支更容易遍历。
选择要去哪里取决于一些事情。
就个人而言,如果我是一个我独自工作的短暂功能分支,我会经常变换和变基。否则它就是合并。
如果您从错误的角度开始分支,那么您可能无法重新定价。例如,您可能已签出特定功能并开始处理其他内容。在它所基于的功能之前,可能需要合并和部署其他东西。此时,将更改重新定位到不同的分支将允许您使该功能独立于另一个发布。
当你将一个重新分支的分支合并回master时,即使历史是线性的,它仍然是一个合并。您可能会在git中看到一条消息,告诉您它执行了“快进”合并。这意味着它只是将引用“master”移动到新位置。您可以告诉git不要这样做,并且无论如何都要使用git merge命令上的--no-ff
标志创建合并提交。
线性历史:
* HEAD, master, your_branch: your last commit
|
*
|
*
|
*
|
*
|
* previous master: where master was when you rebased
使用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
答案 3 :(得分:1)
简而言之,让我们用图表来说明差异。 假设你的git日志如下所示,
facebook
A---B---C dev
/
D---E---F---G main
,在合并分支dev到main之后,主分支上还有一个提交H
git merge dev
A---B---C dev merge
/ \
D---E---F---G---H main
,所有提交(A,B和C)都在main上重放,提交历史记录看起来像线性
git rebase main
D---E---F---G---H---A`---B`---C` main rebase
,当您修改文件时,假设您要切换分支但不想提交修改后的文件。所以可以存储它,在完成其他工作后,您可以切换回您正在使用的分支,并使用git stash
,以便工作目录为您准备修改过的文件。