git - 是否可以在不更改当前提交中的文件的情况下修改父提交?

标签 git rebase git-rebase

在某些情况下,为了代码审查或更清晰的版本历史,我想将现有的提交分成两部分。但是,在拆分提交时,您会发现出现某些不一致的情况,或者您必须修改新的父提交以维护构建。但是,我往往不得不做大量的手动工作来跟踪/维护我编写的最终代码。

是否可以在不修改最终提交的树哈希的情况下运行 rebase 。

例如我们有 3 个提交,ABC: 这是提交 A

# test_file.py
# this is commit A
does_stuff()

这是一个新的提交 B

# test_file.py
# this is commit B
does_stuff()

这是一个新的提交 C

# test_file.py
# this is commit C
does_stuff()

原始树的样子

... -- C -- A

但是我们想将A“拆分”为两个提交

... -- C -- B -- A

当我们使用交互式 rebase 创建 B 时,A 的注释也会更新以显示 # this is commit B(假设除了这个文件之外,还有其他一些更改)。但是,我们希望保持评论不变。注意:A 将由于具有不同的父级而具有不同的整体哈希,但其树哈希应与之前相同。

最佳答案

不可能修改任何 提交。相反,您将一些现有的提交集复制到一组新的提交中。这实际上是个好消息,因为这意味着原始提交仍然可用

When we create B using an interactive rebase, A's comment will also get updated to show # this is commit B (assuming there's a few other changes other than just this one file).

在您展示的具体示例中,它不应该:您应该遇到 merge 冲突。

当然,对于其他情况,您是对的。

However, we want to keep the comment as is. Note: A will have a different overall hash due to having a different parent, but its tree hash should remain identical to before.

请记住,您开始于:

... -- C -- A

我会画成:

...--C--A   <-- branchname (HEAD)

表示一些名为 branchname 的现有分支要提交的点 C , 和 HEAD附加到 A .

然后你跑了git rebase -i <hash-of-C>或类似的。这为您提供了待办事项列表,您可以选择“编辑”A .现在开始:

  • 将 HEAD 分离到 rebase 的目标:

           A   <-- branchname
          /
    ...--C   <-- HEAD
    
  • 复制提交 A (在这种情况下,使用精确复制/快进,以便它重新使用 A 本身;如果你喜欢 --no-ff,你可以禁用它,尽管最终它没有区别):

           A   <-- HEAD, branchname
          /
    ...--C
    

    或:

           A   <-- branchname
          /
    ...--C--A'   <-- HEAD
    

    (使用 --no-ff 强制复制)。

此时您将进行一些更改并运行 git addgit commit --amend将当前提交推开并制作HEAD指向新提交 B , 其 parent 是 C .假设您没有使用 --no-ff ;结果是:

       A   <-- branchname
      /
...--C--B   <-- HEAD

(如果你确实使用了 --no-ff ,还有一个额外的 A' 没有名字卡在外面;它会在大约一个月内被垃圾收集。然后我们必须调用下一个副本 A" 来告诉它们分开,所以我们假设您没有使用 --no-ff 。)

现在您想从提交 A 中获取文件 ,以及来自提交 A 的提交消息,并进行新的提交。自 branchname仍然指向原始提交 A ,就这样做:

$ git checkout branchname -- .     # assumes you're at the top level of your repo
$ git commit -C branchname         # or -c if you want to edit it again

现在你有:

       A   <-- branchname
      /
...--C--B--A'  <-- HEAD

此时你用 git rebase --continue 完成了 rebase .由于没有提交要复制——你已经完成了最后一次提交的复制,A , 就 rebase 而言——这是 rebase 的最后一步,即将分支名称从原始提交链中剥离并将其移动到指向与 HEAD 相同的提交。 , 同时重新附加 HEAD :

       A   <-- ORIG_HEAD
      /
...--C--B--A'  <-- branchname (HEAD)

作为副作用,rebase 设置了 ORIG_HEAD记住在哪里branchname用于指向,因此很容易确保一切正常工作并最终达到所需状态:

git diff ORIG_HEAD HEAD

如果那是错误的,你可以 git reset --hard ORIG_HEAD ,导致:

       A   <-- branchname (HEAD)
      /
...--C--B--A'  <-- ORIG_HEAD

注意其他命令,包括git reset , 设置 ORIG_HEAD (这就是他们在这里交换的原因)。最终这两个提交中的一个将被完全放弃,除了 reflog 条目,当这些条目到期时,无法访问的提交将真正消失。此类提交的默认到期时间为 30 天。

关于git - 是否可以在不更改当前提交中的文件的情况下修改父提交?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51071162/

相关文章:

git - git rebase冲突中找不到 `HEAD`?

git - 你如何重命名 git 中的文件夹并使用交互式 rebase stash 它?

Git 将一个分支 rebase 到另一个分支之上

git - 并行使用 cygwin-git 和 git for windows

GIT Rebase 一个协作的分支?

java - 带有 git 的 Intellij - 内容仅在行分隔符上有所不同

git - Pro Git 书是否向后提供了 git-rebase 的语法?

GIT——从提交中排除/忽略文件

Git submodule.$name.url 覆盖不起作用

git - 从 git/GitHub 的历史记录中删除文件夹及其内容