git rebase 和 git reset 之间的区别
Difference between git rebase and git reset
假设我有这个:
A - B - C - E - F [integration]
\
G - H - I [feature]
提交 I
后,我们通过集成重新设置基准:
git fetch origin
git rebase integration
所以现在我们有:
A - B - C - E - F [integration]
\
B - C - E - F - G - H - I [feature]
然后说我们将功能分支合并到集成中,然后我们有:
A - B - C - E - F - G - H - I [integration]
\
B - C - E - F - G - H - I [feature]
(我认为这是对的),但我看不出这与根本不进行变基有何不同?
基本上,我所了解和发现的是,如果您想删除提交历史记录,则只能执行变基。 Git 重置将更改参考文件中的参考历史记录。
我找到了关于堆栈溢出两者之间区别的很好的解释:here
我希望这有助于阐明在何种情况下哪个选项是工作的最佳工具。
你的画误导了你。
记住这些事情:
- 提交的 "true name" 是其哈希 ID。
- 每个提交都存储其父提交的哈希 ID,或者对于合并提交,存储其所有父提交的哈希 ID。
- 任何提交都不能更改,但提交可以复制到新的替换项。
如果有帮助,请将提交视为大而坚固的东西:例如,构成建筑物的砖块和横梁。 (就像超大乐高积木一样,每块积木都有一些连接器连接到其他积木,我们将积木连接在一起形成链条。这些连接是通过哈希 ID 实现的:它们来自子提交并指向父提交。 )
另一方面,Branch names 是非常轻的项目。它们就像便利贴,您可以在提交上贴上标签,然后撕下并贴在不同的提交上。
所以如果你有这个:
A <-B <-C <-E <-F <-- integration
\
G <-H <-I <-- feature
您无法获取第二张图片,因为现有提交 G
记录了 A
的哈希 ID。您不能 拥有一个以 F
作为父级的 G
。您还不应该 如果可能的话,不要绘制两次提交:提交是独一无二的,只有一个提交 G
.
git cherry-pick
是复制 提交
的基石
通常,我们发现自己处于这样一种情况,即我们有一个像 G
这样的提交,它是好的,但如果它有点不同,我们会更喜欢它。我们想要一个 类似于 G
的新副本,但其父级为 F
,并且具有与原始 [=14= 不同的源树快照] 也。让我们将新提交称为 G'
以将其与 G
区分开来,但提醒我们它有很多 像 G
。我们希望 F
和 G'
之间的 差异与 A
和 之间的差异相同G
,因此也考虑了由于提交 B-C-E-F
而需要的任何更改。所以我们想要的是一个看起来像这样的提交图:
G' <-- new-and-improved-feature
/
A--B--C--E--F <-- integration
\
G--H--I <-- feature
如果我们然后将提交 H
复制到 新的和改进的 H'
,并将 I
复制到 新的和改进的 I'
,我们得到这个:
G'-H'-I' <-- new-and-improved-feature
/
A--B--C--E--F <-- integration
\
G--H--I <-- feature
git reset
移动标签
git reset
的作用——好吧,它可以做的几件事之一,但这就是我们现在用它做的——是移动分支名称粘性标签。
有一张贴纸,上面写着feature
。该粘性标签现在已附加到提交 I
上。但我们只是使用了 git cherry-pick
三次,以 将 G-H-I
序列复制到 G'-H'-I'
序列,在我们新的和改进的设置中。
如果我们现在 Git 将标签 feature
从提交 I
上剥离,并将其粘贴到提交 I'
上,我们将得到:
G'-H'-I' <-- feature (HEAD), new-and-improved-feature
/
A--B--C--E--F <-- integration
\
G--H--I <-- ORIG_HEAD
为了实现这一目标,我们 运行:git checkout feature; git reset new-and-improved-feature
。
git reset
命令设置这个特殊名称 ORIG_HEAD
以记住 feature
曾经去过的地方。现在标签 feature
附加到提交 I'
,但是有一些方法可以找到 I
,包括这个 ORIG_HEAD
技巧。
(我们不再需要 "new and improved feature" 标签,所以我们现在可以删除它。)
请注意,没有提交更改。原始 G
仍在存储库中。 运行 git log ORIG_HEAD
,我们仍然可以看到它,至少在我们执行另一个使用 ORIG_HEAD
记住其他提交的 Git 命令之前是这样。我们将看到 I
,然后是 H
,然后是 G
。我们还可以使用 reflog for feature
来查找提交 G
、H
和 I
的哈希 ID。只要我们有哈希 ID 或哈希 ID 的名称,我们就可以找到提交。 (那些 reflog 条目最终会过期——它们有日期戳,一三个月后,Git 删除 reflog 条目。)
如果我们使用名称 feature
,但是,我们 找到新的副本 而不是原始提交。这使得 看起来 提交已经改变,只要我们不密切注意并注意到实际上,这些是 new 提交.
底线是这样的:在我们复制提交后,如果我们使用git reset
放弃原件以支持新的和改进的副本,我们只会看到新的和改进的副本,我们可以表现得像 提交已更改。提交没有改变,如果有人仔细观察,他们会发现我们的秘密,但是 如果其他人不知道原件,他们就不会发现这些是 廉价仿冒品 改良版。
git rebase
= cherry-pick 加重置
这让我们得出结论:git rebase
基本上是 git cherry-pick
一些提交集 之后是 git reset
。也就是说,我们从 复制 提交到新的和我们希望改进的版本开始;然后我们使用 git reset
试图欺骗每个人使用改进的提交来代替原始提交。
任何仍然拥有原件的人都不会被愚弄!如果其他人 - 其他 Git 存储库 - 仍然拥有原始提交,我们必须说服 他们 也切换到新的改进提交。但如果我们是唯一有权访问提交的人,我们只需要自欺欺人,这可能更容易。
假设我有这个:
A - B - C - E - F [integration]
\
G - H - I [feature]
提交 I
后,我们通过集成重新设置基准:
git fetch origin
git rebase integration
所以现在我们有:
A - B - C - E - F [integration]
\
B - C - E - F - G - H - I [feature]
然后说我们将功能分支合并到集成中,然后我们有:
A - B - C - E - F - G - H - I [integration]
\
B - C - E - F - G - H - I [feature]
(我认为这是对的),但我看不出这与根本不进行变基有何不同?
基本上,我所了解和发现的是,如果您想删除提交历史记录,则只能执行变基。 Git 重置将更改参考文件中的参考历史记录。
我找到了关于堆栈溢出两者之间区别的很好的解释:here
我希望这有助于阐明在何种情况下哪个选项是工作的最佳工具。
你的画误导了你。
记住这些事情:
- 提交的 "true name" 是其哈希 ID。
- 每个提交都存储其父提交的哈希 ID,或者对于合并提交,存储其所有父提交的哈希 ID。
- 任何提交都不能更改,但提交可以复制到新的替换项。
如果有帮助,请将提交视为大而坚固的东西:例如,构成建筑物的砖块和横梁。 (就像超大乐高积木一样,每块积木都有一些连接器连接到其他积木,我们将积木连接在一起形成链条。这些连接是通过哈希 ID 实现的:它们来自子提交并指向父提交。 )
另一方面,Branch names 是非常轻的项目。它们就像便利贴,您可以在提交上贴上标签,然后撕下并贴在不同的提交上。
所以如果你有这个:
A <-B <-C <-E <-F <-- integration
\
G <-H <-I <-- feature
您无法获取第二张图片,因为现有提交 G
记录了 A
的哈希 ID。您不能 拥有一个以 F
作为父级的 G
。您还不应该 如果可能的话,不要绘制两次提交:提交是独一无二的,只有一个提交 G
.
git cherry-pick
是复制 提交
的基石
通常,我们发现自己处于这样一种情况,即我们有一个像 G
这样的提交,它是好的,但如果它有点不同,我们会更喜欢它。我们想要一个 类似于 G
的新副本,但其父级为 F
,并且具有与原始 [=14= 不同的源树快照] 也。让我们将新提交称为 G'
以将其与 G
区分开来,但提醒我们它有很多 像 G
。我们希望 F
和 G'
之间的 差异与 A
和 之间的差异相同G
,因此也考虑了由于提交 B-C-E-F
而需要的任何更改。所以我们想要的是一个看起来像这样的提交图:
G' <-- new-and-improved-feature
/
A--B--C--E--F <-- integration
\
G--H--I <-- feature
如果我们然后将提交 H
复制到 新的和改进的 H'
,并将 I
复制到 新的和改进的 I'
,我们得到这个:
G'-H'-I' <-- new-and-improved-feature
/
A--B--C--E--F <-- integration
\
G--H--I <-- feature
git reset
移动标签
git reset
的作用——好吧,它可以做的几件事之一,但这就是我们现在用它做的——是移动分支名称粘性标签。
有一张贴纸,上面写着feature
。该粘性标签现在已附加到提交 I
上。但我们只是使用了 git cherry-pick
三次,以 将 G-H-I
序列复制到 G'-H'-I'
序列,在我们新的和改进的设置中。
如果我们现在 Git 将标签 feature
从提交 I
上剥离,并将其粘贴到提交 I'
上,我们将得到:
G'-H'-I' <-- feature (HEAD), new-and-improved-feature
/
A--B--C--E--F <-- integration
\
G--H--I <-- ORIG_HEAD
为了实现这一目标,我们 运行:git checkout feature; git reset new-and-improved-feature
。
git reset
命令设置这个特殊名称 ORIG_HEAD
以记住 feature
曾经去过的地方。现在标签 feature
附加到提交 I'
,但是有一些方法可以找到 I
,包括这个 ORIG_HEAD
技巧。
(我们不再需要 "new and improved feature" 标签,所以我们现在可以删除它。)
请注意,没有提交更改。原始 G
仍在存储库中。 运行 git log ORIG_HEAD
,我们仍然可以看到它,至少在我们执行另一个使用 ORIG_HEAD
记住其他提交的 Git 命令之前是这样。我们将看到 I
,然后是 H
,然后是 G
。我们还可以使用 reflog for feature
来查找提交 G
、H
和 I
的哈希 ID。只要我们有哈希 ID 或哈希 ID 的名称,我们就可以找到提交。 (那些 reflog 条目最终会过期——它们有日期戳,一三个月后,Git 删除 reflog 条目。)
如果我们使用名称 feature
,但是,我们 找到新的副本 而不是原始提交。这使得 看起来 提交已经改变,只要我们不密切注意并注意到实际上,这些是 new 提交.
底线是这样的:在我们复制提交后,如果我们使用git reset
放弃原件以支持新的和改进的副本,我们只会看到新的和改进的副本,我们可以表现得像 提交已更改。提交没有改变,如果有人仔细观察,他们会发现我们的秘密,但是 如果其他人不知道原件,他们就不会发现这些是 廉价仿冒品 改良版。
git rebase
= cherry-pick 加重置
这让我们得出结论:git rebase
基本上是 git cherry-pick
一些提交集 之后是 git reset
。也就是说,我们从 复制 提交到新的和我们希望改进的版本开始;然后我们使用 git reset
试图欺骗每个人使用改进的提交来代替原始提交。
任何仍然拥有原件的人都不会被愚弄!如果其他人 - 其他 Git 存储库 - 仍然拥有原始提交,我们必须说服 他们 也切换到新的改进提交。但如果我们是唯一有权访问提交的人,我们只需要自欺欺人,这可能更容易。