git - 通过变基从旧提交中删除分阶段的更改
问题描述
我不小心通过交互式变基(编辑-> --amend
)将暂存文件添加到过去的提交(HEAD~10)中。
有没有一种简单的方法可以撤消此操作?
解决方案
有没有一种简单的方法可以撤消此操作?
这部分取决于您走多远,部分取决于您如何定义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
点返回到 commitG
或G'
,无论它刚才命名的那个 - 并且该提交在HEAD
reflog 中,在 position 中{1}
,所以:
git reset --hard HEAD@{1}
会让我们回到合适的画面。提交G"
保留在存储库中,而它可以通过HEAD
reflog 找到,然后最终真正过期并且一旦git gc
解决就消失了。
但是,如果您已经在 top 上构建了新的提交, G"
事情就会变得更加困难。从理论上讲,您可以找出重置HEAD
到的位置,然后进行任何可能需要的挑选,以复制复制到 top 的提交G"
(如果有的话)。但在这一点上,完全中止变基可能是最容易的。
2就地,我的意思是变基的目标--onto
是与参数命名的相同提交upstream
,它告诉 Git 哪些提交不要复制。也就是说,git rev-parse HEAD~10
将为我们提供--onto
未指定的目标,因此默认upstream
为HEAD~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~1
to 查看 commitG'
和topic.alt
to 查看 commit H'
。我们可以使用git restore
从这些特定提交中提取特定文件,或git show
将提交视为差异,或git cherry-pick
,或任何我们喜欢的东西:整个 Git 工具套件都在那里,我们有一个我们可以记住的名称,而无需输入看起来随机的哈希 ID。
请注意,如果您愿意,可以使用标签名称而不是分支名称。git push
除了您自己的个人喜好和您个人的跑步方式(如果您经常git push --tags
,您可能希望避免使用标签名称)之外,没有特别的理由偏爱其中一个。
推荐阅读
- php - 如何通过类似查询对mysql结果进行排序?
- node.js - 盖茨比构建失败。无法构建“夏普”
- haskell - 免费 Monad 与显式传递函数
- angular - 角度 7:过滤后的数据不发送到子组件
- javascript - 滚动到元素总是重置视图,直到页面刷新
- node.js - 使用 npm 在 Windows 10 上安装 React 时出现问题
- php - 在 Codeigniter 上的 Controller 中 return(exit) (提前终止)(使用 Twig 模板引擎)
- google-chrome - 如何修复 Chrome 无限重定向?
- javascript - 如何在 JavaScript 中检查“typeof”的类型?
- c# - 按多个变量排序列表