Setting git parent pointer to a different parent

dou*_*gvk 208 git

If I have a commit in the past that points to one parent, but I want to change the parent that it points to, how would I go about doing that?

Amb*_*ber 376

git rebase.它是Git中的通用"take commit(s)and plop it/them on a different parent(base)"命令.

但是要注意一些事情:

  1. 由于提交SHA涉及其父项,因此当您更改给定提交的父项时,其SHA将更改 - 在开发行中将更改其后的所有提交(比它更新)的SHA也将更改.

  2. 如果您正在与其他人合作,并且您已经将提交的问题公开推送到他们撤回的地方,那么修改提交可能是一个坏主意™.这是由于#1,因此在尝试弄清楚由于您的SHA不再与"相同"提交相匹配时发生的事情时,其他用户的存储库将会遇到混淆.(有关详细信息,请参阅链接手册页中的"从上游重新恢复"部分.)

也就是说,如果你目前在一个分支上有一些你希望转移到新父级的提交,它看起来像这样:

git rebase --onto <new-parent> <old-parent>
Run Code Online (Sandbox Code Playgroud)

这会将当前分支之后 <old-parent>的所有内容移动到顶部<new-parent>.

  • 啊哈,所以这就是`--onto`用的!在阅读这个答案后,文件对我来说一直都是模糊的,甚至*! (7认同)
  • 这一行:`git rebase --onto <new-parent> <old-parent>`告诉我关于如何使用`rebase --onto`的一切,我到目前为止读过的所有其他问题和文档都没有. (6认同)
  • 对我而言,该命令应为:`在该提交基础上重新提交,或`git rebase --on &lt;new-parent&gt; &lt;commit&gt;`。在这里使用老父母对我没有任何意义。 (3认同)

Chr*_*sen 35

如果事实证明你需要避免重新定义后续提交(例如,因为历史重写是站不住脚的),那么你可以使用git替换(在Git 1.6.5及更高版本中可用).

# …---o---A---o---o---…
#
# …---o---B---b---b---…
#
# We want to transplant B to be "on top of" A.
# The tree of descendants from B (and A) can be arbitrarily complex.

replace_first_parent() {
    old_parent=$(git rev-parse --verify "${1}^1") || return 1
    new_parent=$(git rev-parse --verify "${2}^0") || return 2
    new_commit=$(
      git cat-file commit "$1" |
      sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
      git hash-object -t commit -w --stdin
    ) || return 3
    git replace "$1" "$new_commit"
}
replace_first_parent B A

# …---o---A---o---o---…
#          \
#           C---b---b---…
#
# C is the replacement for B.
Run Code Online (Sandbox Code Playgroud)

在建立了上述替换后,对对象B的任何请求都将实际返回对象C.除了第一个父项(相同的父项(第一个除外),同一个树之外,C的内容与B的内容完全相同)相同的提交消息).

默认情况下,替换处于活动状态,但可以通过使用git--no-replace-objects选项(在命令名之前)或通过设置环境变量来启用替换.可以通过推送(除正常之外)共享替换.GIT_NO_REPLACE_OBJECTSrefs/replace/*refs/heads/*

如果您不喜欢commit-munging(使用上面的sed完成),那么您可以使用更高级别的命令创建替换提交:

git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
Run Code Online (Sandbox Code Playgroud)

最大的区别在于,如果B是合并提交,则此序列不会传播其他父项.

  • @BaptisteWicht:替换存储在一组单独的引用中.你需要安排推送(和获取)`refs/replace /`ref层次结构.如果你只需要做一次,那么你可以在源代码库中执行`git push yourremote'refs/replace/*'`,并且`git fetch yourremote'refs/replace/*:refs/replace/*'`in目的地存储库.如果你需要多次这样做,那么你可以将这些refspecs添加到`remote.yourremote.push`配置变量(在源存储库中)和`remote.yourremote.fetch`配置变量(在目标存储库中) . (2认同)
  • 一个更简单的方法是使用``git replace --grafts``。不确定何时添加,但是其整个目的是创建一个与commit _B_相同但具有指定父级的替换。 (2认同)

Jak*_*ski 30

请注意,更改Git中的提交要求必须更改其后的所有提交.如果您已经发布了历史的这一部分,那么就不鼓励这样做了.有人可能已经在改变之前建立了他们的历史工作.

替代解决方案git rebase中提到琥珀的反应是使用移植机制(见Git中移植的定义Git的词汇和文档.git/info/grafts文件在Git仓库布局文档)改变提交父,检查它做正确的事情与一些浏览器的历史(gitk,git log --graph等等,然后使用git filter-branch(如其联机帮助页的"示例"部分所述)使其永久化(然后删除移植物,并选择删除备份git filter-branch或重新定义存储库的原始引用):

echo "$commit-id $graft-id" >> .git/info/grafts
git filter-branch $graft-id..HEAD

注意 !!!该解决方案是从底垫的解决方案不同的是git rebase将重订/移植的变化,而移植物为基础的解决方案只会重新设置父级提交原样,完全没有考虑到老上级和新的父之间的差异!

  • @ Thr4wn:非常正确,但如果你想**重写历史**那么移植+ git-filter-branch(或交互式rebase,或快速导出+例如reposurgeon)就是这样做的方法.如果你想/需要**保存历史**,那么git-replace远远优于移植物. (4认同)
  • 根据我的理解(来自https://git.wiki.kernel.org/index.php/GraftPoint),`git replace`取代了git grafts(假设你有git 1.6.5或更高版本). (2认同)

Mar*_*ato 22

为了澄清上述答案并无耻地插入我自己的脚本:

这取决于你是否要"重新"或"重新".一个底垫中,通过所建议的琥珀,到处移动的diff.一个重新设置父级,所建议的Jakub克里斯,四处移动快照整个树.如果你想重新表达,我建议使用git reparent而不是手动完成工作.

对照

假设您在左侧有图片,并且您希望它看起来像右侧的图片:

                   C'
                  /
A---B---C        A---B---C
Run Code Online (Sandbox Code Playgroud)

重新定位和重新定位将产生相同的图片,但定义C'不同.随着git rebase --onto A B,C'将不包含任何引入的变化B.与git reparent -p A,C'将是相同的C(除了B不会在历史中).


Teo*_*oro 7

当然,几个小时前,当我尝试与OP完全相同的事情时,@Jakub的回答对我有所帮助。
然而,git replace --graft 现在是关于移植的更简单的解决方案。另外,该解决方案的一个主要问题是过滤器分支使我失去了未合并到 HEAD 分支中的每个分支。然后,git filter-repo就完美无缺地完成了工作。

$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force
Run Code Online (Sandbox Code Playgroud)

我前段时间提出过这样的问题,所以完整的答案可以在这里找到。


了解更多信息:查看文档中的“重新移植历史记录”部分