Git rebase 删除了另一个 PR 添加的行
Git rebase removed lines added by another PR
我 运行 遇到了一个 st运行ge 问题,我想了解发生了什么:
我们有一个 develop
b运行ch,我和同事基于一个新特征 b运行ch 来自:feature-a
和 feature-b
.
在他的 b运行ch (feature-a
) 中,他向 foo.js
添加了行(例如:第 100-120 行)。
在我的 b运行ch (feature-b
) 中,我删除了 foo.js
中的行(比如:第 90-110 行)。
他的功能被合并到 develop
之后,我将我的 b运行ch 重新设置为 develop
。
没有合并冲突,但是当一切都说完了,他的台词就没有了。
我怀疑发生的事情是:
- 第 100-120 行已从
feature-a
添加到 develop
- 我的作品(删除 90-110)在顶部重播
- 90-110 现在实际上是 90-130,因为增加了 20 行
- 这些行已在
feature-b
中删除
我实际上期望 git 足够聪明,不会这样做并保留 "new" 行。这不是正确的假设吗?
有什么好的方法可以检查发生了什么吗?
你是对的,根据你的描述,你应该在变基过程中遇到合并冲突。要找出你是否没有以及为什么不,你可以重复变基,也许将其分解为 commit-by-commit 个副本。
运行 git rebase
运行 要么是一系列重复的 git cherry-pick
操作,要么是一个大的 git am --3way
应用由git format-patch
。无论哪种方式,这 复制 原始提交到更新的 supposedly-better,提交。
请注意,所有提交都是 read-only。这意味着没有任何东西可以改变现有的提交。你现有的提交,pre-rebasing,没有被 rebase 影响!诀窍是找到 原始提交。由于没有明显的名称可以找到它们,Git 最终(默认情况下 30 天后)删除 它们。
画出你的起点
让我们看一个典型的例子,绘制一些提交,其哈希ID由您的各个分支名称记录。我们不知道提交的哈希 ID,但它们太笨重了,所以我们只使用大写字母:
...--D--E <-- master
\
F--G <-- develop
|\
| H--I <-- feature-a
\
J--K <-- feature-b (HEAD)
在这里,您在 feature-b
上并且有两个提交 J
和 K
是您的工作所独有的。你的同事在 feature-a
上,也有两个提交。你们都共享提交 G
作为你们开始的基础; commit G
位于分支 develop
以及您的两个功能分支上。 CommitG
具体是此时develop
的tip commit。提交 F
也在所有三个分支上; F
指向(记录)E
的哈希 ID,这是 master
的尖端。 E
指向 D
,依此类推一直到存储库中的第一个提交。此时提交 E
及更早版本在所有四个分支上。
假设他在提交 I
中将这些行添加到 foo.js
,因此快照 H
没有这些行,而快照 I
有。他的功能现在已获得批准,因此某些存储库中的某人具有非常相似的分支名称并指向相同的提交,执行以下两个命令:
git checkout develop
git merge feature-a # or maybe git merge --no-ff feature-a
第一个将他们的 HEAD
附加到他们的名字 develop
,这样合并结果将被添加或保存在那里。如果指定了 --no-ff
或需要真正的合并,则第二个执行真正的合并,或者在可能且不被禁止的情况下执行 fast-forward 操作。为简单起见,我假设 fast-forward,结果是:
...--D--E <-- master
\
F--G
\
H--I <-- develop (HEAD), feature-a
(我把你的提交完全放在这里,假设它们没有被推送;它不会影响结果。)
在他们的存储库中。如果你现在 运行 git fetch origin
获得 他们的 状态,你最终会在你自己的存储库中得到这个:
...--D--E <-- master, origin/master
\
F--G <-- develop
|\
| H--I <-- origin/develop, feature-a, origin/feature-a
\
J--K <-- feature-b (HEAD)
请注意,您的 origin/*
名称是 他们的 名称的副本,更改为 remote-tracking 名称而不是分支名称。与您的分支名称一样,这些 remote-tracking 名称指向 tip 提交。您的 git fetch
步骤这样做是为了确保您自己的分支名称不受影响。
观看变基发生
您现在可以 运行 git rebase origin/develop
或 git rebase origin/feature-a
甚至(在这种情况下)git rebase feature-a
,因为我们关心的只是选择正确的 提交。这将:
- 枚举所有可从
HEAD
访问但无法从目标提交访问的 (non-merge) 提交:即 J
和 K
;
- 检查目标提交:那是
I
;
- cherry-pick 或以其他方式复制步骤 1 中列举的提交;
- 移动分支名称。
要复制提交,Git 通过将提交与其父提交进行比较,实质上将其转换为一组更改。因此对于 J
,Git 将 G
中的快照与 J
中的快照进行比较。无论您在那里更改了什么,Git 都会对当前提交执行相同的操作,然后进行新的提交。让我们调用新提交 J'
:
...--D--E <-- master, origin/master
\
F--G <-- develop
|\
| H--I <-- origin/develop, feature-a, origin/feature-a
| \
| J' <-- temporary HEAD
\
J--K <-- feature-b
Git 通过比较 J
与 K
并对 J'
:
应用相同的更改,重复 K
的副本
...--D--E <-- master, origin/master
\
F--G <-- develop
|\
| H--I <-- origin/develop, feature-a, origin/feature-a
| \
| J'-K' <-- temporary HEAD
\
J--K <-- feature-b
现在所有的提交都被复制了,rebase 完成了 "peeling the label" feature-b
提交 K
并使其指向提交 K'
而不是:
...--D--E <-- master, origin/master
\
F--G <-- develop
|\
| H--I <-- origin/develop, feature-a, origin/feature-a
| \
| J'-K' <-- feature-b (HEAD)
\
J--K [abandoned]
现在您的提交 K
没有 name,您很难找到它。
出了点问题,请确定是什么原因并找出原因
你的一个提交删除了一些行。您认为复制该提交删除了 太多 行。现在您可以看到每个提交是如何被复制的,您可以:
查找原始提交的哈希 ID。
这可能有点棘手。他们的名字 feature-b
不再指向他们。它指向副本。不过有几种选择:
ORIG_HEAD
: git rebase
设置此名称指向原始最终提交(K
在我们的绘图中五)。但是,此名称也被其他操作获取 re-used,因此它可能不再指向您的原始链。你可以去看看。
Reflogs:每个分支都有一个 reflog,HEAD
也有一个。 运行 git reflog
将向您展示 HEAD
的那个; 运行ning git reflog feature-b
将向您展示 feature-b
的那个。 reflog 会记住分支名称 用于指向 的哪个提交,并且至少会记住 30 天。查看您的 feature-b
的 reflog 以查找您在 之前 rebase 复制它们的提交。
重复复制操作,这样您就可以看到发生了什么。
现在您已经有了原始提交并且知道了您的分支名称过去指向的位置(使用它们的各种 reflog),您可以找到所有原始提交,它们仍处于原始状态。
然后你可以git checkout
(作为分离的 HEAD)在出错之前提交正确的提交,然后使用 git cherry-pick
或 git format-patch -1 <hash> --stdout | git am --3way
复制它,就像 git rebase
做了。
虽然这应该只是做与以前相同(错误)的事情,但您现在有足够的详细信息来提出更具体的问题:为什么将此提交复制到此特定快照上会导致此特定损坏?
我 运行 遇到了一个 st运行ge 问题,我想了解发生了什么:
我们有一个 develop
b运行ch,我和同事基于一个新特征 b运行ch 来自:feature-a
和 feature-b
.
在他的 b运行ch (feature-a
) 中,他向 foo.js
添加了行(例如:第 100-120 行)。
在我的 b运行ch (feature-b
) 中,我删除了 foo.js
中的行(比如:第 90-110 行)。
他的功能被合并到 develop
之后,我将我的 b运行ch 重新设置为 develop
。
没有合并冲突,但是当一切都说完了,他的台词就没有了。
我怀疑发生的事情是:
- 第 100-120 行已从
feature-a
添加到develop
- 我的作品(删除 90-110)在顶部重播
- 90-110 现在实际上是 90-130,因为增加了 20 行
- 这些行已在
feature-b
中删除
我实际上期望 git 足够聪明,不会这样做并保留 "new" 行。这不是正确的假设吗?
有什么好的方法可以检查发生了什么吗?
你是对的,根据你的描述,你应该在变基过程中遇到合并冲突。要找出你是否没有以及为什么不,你可以重复变基,也许将其分解为 commit-by-commit 个副本。
运行 git rebase
运行 要么是一系列重复的 git cherry-pick
操作,要么是一个大的 git am --3way
应用由git format-patch
。无论哪种方式,这 复制 原始提交到更新的 supposedly-better,提交。
请注意,所有提交都是 read-only。这意味着没有任何东西可以改变现有的提交。你现有的提交,pre-rebasing,没有被 rebase 影响!诀窍是找到 原始提交。由于没有明显的名称可以找到它们,Git 最终(默认情况下 30 天后)删除 它们。
画出你的起点
让我们看一个典型的例子,绘制一些提交,其哈希ID由您的各个分支名称记录。我们不知道提交的哈希 ID,但它们太笨重了,所以我们只使用大写字母:
...--D--E <-- master
\
F--G <-- develop
|\
| H--I <-- feature-a
\
J--K <-- feature-b (HEAD)
在这里,您在 feature-b
上并且有两个提交 J
和 K
是您的工作所独有的。你的同事在 feature-a
上,也有两个提交。你们都共享提交 G
作为你们开始的基础; commit G
位于分支 develop
以及您的两个功能分支上。 CommitG
具体是此时develop
的tip commit。提交 F
也在所有三个分支上; F
指向(记录)E
的哈希 ID,这是 master
的尖端。 E
指向 D
,依此类推一直到存储库中的第一个提交。此时提交 E
及更早版本在所有四个分支上。
假设他在提交 I
中将这些行添加到 foo.js
,因此快照 H
没有这些行,而快照 I
有。他的功能现在已获得批准,因此某些存储库中的某人具有非常相似的分支名称并指向相同的提交,执行以下两个命令:
git checkout develop
git merge feature-a # or maybe git merge --no-ff feature-a
第一个将他们的 HEAD
附加到他们的名字 develop
,这样合并结果将被添加或保存在那里。如果指定了 --no-ff
或需要真正的合并,则第二个执行真正的合并,或者在可能且不被禁止的情况下执行 fast-forward 操作。为简单起见,我假设 fast-forward,结果是:
...--D--E <-- master
\
F--G
\
H--I <-- develop (HEAD), feature-a
(我把你的提交完全放在这里,假设它们没有被推送;它不会影响结果。)
在他们的存储库中。如果你现在 运行 git fetch origin
获得 他们的 状态,你最终会在你自己的存储库中得到这个:
...--D--E <-- master, origin/master
\
F--G <-- develop
|\
| H--I <-- origin/develop, feature-a, origin/feature-a
\
J--K <-- feature-b (HEAD)
请注意,您的 origin/*
名称是 他们的 名称的副本,更改为 remote-tracking 名称而不是分支名称。与您的分支名称一样,这些 remote-tracking 名称指向 tip 提交。您的 git fetch
步骤这样做是为了确保您自己的分支名称不受影响。
观看变基发生
您现在可以 运行 git rebase origin/develop
或 git rebase origin/feature-a
甚至(在这种情况下)git rebase feature-a
,因为我们关心的只是选择正确的 提交。这将:
- 枚举所有可从
HEAD
访问但无法从目标提交访问的 (non-merge) 提交:即J
和K
; - 检查目标提交:那是
I
; - cherry-pick 或以其他方式复制步骤 1 中列举的提交;
- 移动分支名称。
要复制提交,Git 通过将提交与其父提交进行比较,实质上将其转换为一组更改。因此对于 J
,Git 将 G
中的快照与 J
中的快照进行比较。无论您在那里更改了什么,Git 都会对当前提交执行相同的操作,然后进行新的提交。让我们调用新提交 J'
:
...--D--E <-- master, origin/master
\
F--G <-- develop
|\
| H--I <-- origin/develop, feature-a, origin/feature-a
| \
| J' <-- temporary HEAD
\
J--K <-- feature-b
Git 通过比较 J
与 K
并对 J'
:
K
的副本
...--D--E <-- master, origin/master
\
F--G <-- develop
|\
| H--I <-- origin/develop, feature-a, origin/feature-a
| \
| J'-K' <-- temporary HEAD
\
J--K <-- feature-b
现在所有的提交都被复制了,rebase 完成了 "peeling the label" feature-b
提交 K
并使其指向提交 K'
而不是:
...--D--E <-- master, origin/master
\
F--G <-- develop
|\
| H--I <-- origin/develop, feature-a, origin/feature-a
| \
| J'-K' <-- feature-b (HEAD)
\
J--K [abandoned]
现在您的提交 K
没有 name,您很难找到它。
出了点问题,请确定是什么原因并找出原因
你的一个提交删除了一些行。您认为复制该提交删除了 太多 行。现在您可以看到每个提交是如何被复制的,您可以:
查找原始提交的哈希 ID。
这可能有点棘手。他们的名字
feature-b
不再指向他们。它指向副本。不过有几种选择:ORIG_HEAD
:git rebase
设置此名称指向原始最终提交(K
在我们的绘图中五)。但是,此名称也被其他操作获取 re-used,因此它可能不再指向您的原始链。你可以去看看。Reflogs:每个分支都有一个 reflog,
HEAD
也有一个。 运行git reflog
将向您展示HEAD
的那个; 运行ninggit reflog feature-b
将向您展示feature-b
的那个。 reflog 会记住分支名称 用于指向 的哪个提交,并且至少会记住 30 天。查看您的feature-b
的 reflog 以查找您在 之前 rebase 复制它们的提交。
重复复制操作,这样您就可以看到发生了什么。
现在您已经有了原始提交并且知道了您的分支名称过去指向的位置(使用它们的各种 reflog),您可以找到所有原始提交,它们仍处于原始状态。
然后你可以
git checkout
(作为分离的 HEAD)在出错之前提交正确的提交,然后使用git cherry-pick
或git format-patch -1 <hash> --stdout | git am --3way
复制它,就像git rebase
做了。
虽然这应该只是做与以前相同(错误)的事情,但您现在有足够的详细信息来提出更具体的问题:为什么将此提交复制到此特定快照上会导致此特定损坏?