首页 > 解决方案 > 通过变基从旧提交中删除分阶段的更改

问题描述

我不小心通过交互式变基(编辑-> --amend)将暂存文件添加到过去的提交(HEAD~10)中。

有没有一种简单的方法可以撤消此操作?

标签: git

解决方案


有没有一种简单的方法可以撤消此操作?

这部分取决于您走多远,部分取决于您如何定义simple

关于所有rebase 操作,无论是否交互,最重要的是要了解和记住它们涉及复制提交。请参阅我对如何在变基时处理与给定策略的特定合并冲突的回答?

停止或撤消变基

如果一个 rebase 出错了,而你还在它中间,你可以用git rebase --abort. 由于 rebase 过程不能(因此没有)改变任何原始提交,而且它还没有完成,没有什么真正改变--abort只是清理了 Git 的索引和你的工作树,让你回到一个干净的状态从你开始变基之前开始。1

如果你已经完成了 rebase,然后意识到它出错了,并且没有进行任何新的提交(并且没有使用 autostash,所以你的状态现在是“干净的”),你可以使用当前分支的 reflog , 或特殊名称ORIG_HEAD, 让它看起来像你从未启动过变基:

git reset --hard ORIG_HEAD

例如。


1我在上面链接的其他答案中没有提到,但值得考虑的是 autostash 选项如何用于 rebase。中止应该作为清理的一部分自动弹出自动存储​​,这意味着您处于与开始时相同的状态(干净或脏)。如果你没有开启 autostash,除非状态是干净的,否则 rebase 将不允许你启动。“干净”和“脏”不是 Git 中定义最好的术语,但至少在 rebase 中它们往往相当明显。


撤消一份已制作的副本

您的文字描述有点粗略:

通过交互式变基(编辑-> --amend

但让我们假设命令序列是:

git rebase -i HEAD~10
<replace at least one `pick` in the instruction sheet with `edit`>
<write instruction sheet and exit editor>
<rebase begins, copies some number of commits, and then stops
 after copying the one marked edit>

此时,您处于分离 HEAD 模式,已复制了一些提交。因为这是一个就地变基,2 Git 可能使用了一些捷径来保留原始提交,所以图片可能是:

...--C--D   (HEAD~10)
         \
          E--F--G--H--I-...-N   <-- branch (HEAD)

当我们开始时,并且:

...--C--D--E--F--G   <-- HEAD
                  \
                   H--I-...-N   <-- branch

当 Git 停止编辑时。或者,也许我们做了一些事情(例如更改作者姓名或使用--no-ff变基选项),以便我们拥有:

...--C--D--E'-F'-G'  <-- HEAD
         \
          E--F--G--H--I-...-N   <-- branch

无论哪种方式,这并不是真正重要的,它只是我们绘制图片时要注意的事情。

无论如何,此时我们在 Git 的索引(又名“暂存区”)和我们的工作树中拥有来自 commit 的所有文件,这些文件G现在可能也以 commit 的形式存在,也可能不存在G'。然后编辑一些工作树文件并git add在文件上运行,然后git commit --amend. 这样做是生成另一个 commit 副本G,这次使用更新的索引文件:

                G"  <-- HEAD
               /
...--C--D--E--F--G
                  \
                   H--I-...-N   <-- branch

或者:

                G"   <-- HEAD
               /
...--C--D--E'-F'-G'
         \
          E--F--G--H--I-...-N   <-- branch

可能存在的事实G'就是我称之为的原因G":如果没有 的G'副本G,则称之为G"无害(除了可能对我们的心理健康有害,但如果我们担心这一点,我们会使用 Git 吗? )。

如果这是一个错误,并且您想摆脱G",那么只需将HEAD点返回到 commitGG',无论它刚才命名的那个 - 并且该提交在HEADreflog 中,在 position 中{1},所以:

git reset --hard HEAD@{1}

会让我们回到合适的画面。提交G"保留在存储库中,而它可以通过HEADreflog 找到,然后最终真正过期并且一旦git gc解决就消失了。

但是,如果您已经在 top 上构建了新的提交 G"事情就会变得更加困难。从理论上讲,您可以找出重置HEAD到的位置,然后进行任何可能需要的挑选,以复制复制到 top 的提交G"(如果有的话)。但在这一点上,完全中止变基可能是最容易的。


2就地,我的意思是变基的目标--onto是与参数命名的相同提交upstream,它告诉 Git 哪些提交不要复制。也就是说,git rev-parse HEAD~10将为我们提供--onto未指定的目标,因此默认upstreamHEAD~10. 因此,第一个复制的提交是HEAD~10,这意味着我们已经复制了 commit HEAD~9,例如,它在 之后HEAD~10。但它已经来了HEAD~10: 所以我们没有改变提交的位置。并且新快照与旧快照匹配,因此我们对提交的内容没有任何更改。唯一可能改变的是提交的其余元数据:作者、提交者等。如果所有这些都可以保持不变,Git 可以采取捷径并重新使用原始提交。


中止变基,同时保留到目前为止所做的工作的副本

假设我们开始一个变基——甚至可能不是就地交互式变基;也许我们正在做一些类似 rebase 的topic事情main

          G--H--I   <-- topic (HEAD)
         /
...--E--F--J--K--L   <-- main

最终目标是:

          G--H--I   [abandoned]
         /
...--E--F--J--K--L   <-- main
                  \
                   G'-H'-I'  <-- topic (HEAD)

事情可能一开始还不错,但是在我们努力复制H到之后H',我们到达了I-to-I'复制(由于 复制edit,或者由于冲突而复制后停止)并决定,虽然一半左右到目前为止我们所做的工作都是可用的,重新开始会更好。但我们想保存我们目前所拥有的:

          G--H--I   <-- topic
         /
...--E--F--J--K--L   <-- main
                  \
                   G'-H'  <-- HEAD

要做的简单的事情是立即在这里创建一个新分支。例如:

git branch topic.alt

如果存在合并冲突,并且我们懒得修复它们但出于某种原因想要保存它们,我们可以git add无论如何都可以冲突的文件并提交以添加提交I'(我们可以在创建之前或之后执行此操作,topic.alt但如果我们这样做它在我们需要强行topic.alt向前拖动之后git branch -f topic.alt,所以最好在创建之前执行它topic.alt)。然后我们运行:

git rebase --abort

我们回到:

          G--H--I   <-- topic (HEAD)
         /
...--E--F--J--K--L   <-- main
                  \
                   G'-H'  <-- topic.alt

I'酌情添加)。现在,我们的提交有了一个分支名称:我们计划作为新的和改进的替代品,但由于某种原因变得太复杂了。但这也意味着我们可以重新启动git rebase,而这一次,我们可以使用topic.alt~1to 查看 commitG'topic.altto 查看 commit H'。我们可以使用git restore从这些特定提交中提取特定文件,或git show将提交视为差异,或git cherry-pick,或任何我们喜欢的东西:整个 Git 工具套件都在那里,我们有一个我们可以记住的名称,而无需输入看起来随机的哈希 ID。

请注意,如果您愿意,可以使用标签名称而不是分支名称。git push除了您自己的个人喜好和您个人的跑步方式(如果您经常git push --tags,您可能希望避免使用标签名称)之外,没有特别的理由偏爱其中一个。


推荐阅读