设置git父指针指向不同的父指针

如果我在过去有一个指向一个父结点的提交,但我想改变它所指向的父结点,我该怎么做呢?

115421 次浏览

使用git rebase。这是Git中通用的“获取提交并将其/它们放到不同的父(基)上”命令。

然而,有些事情是需要知道的:

  1. 由于提交SHA涉及到它们的父节点,当你改变一个给定提交的父节点时,它的SHA也会改变——在开发线中紧随其后(比它更晚)的all提交的SHA也会改变。

  2. 如果您正在与其他人一起工作,并且您已经将所讨论的提交公开到他们所拉取的位置,那么修改提交可能是一个坏主意™。这是由于#1造成的,因此当其他用户的存储库试图弄清楚由于您的sha不再匹配他们的“相同”提交而发生了什么时,将会遇到混乱。(详细信息请参见链接手册页的“从UPSTREAM REBASE中恢复”部分

也就是说,如果你目前在一个分支上有一些提交,你想要移动到一个新的父节点,它看起来像这样:

git rebase --onto <new-parent> <old-parent>

这将把当前分支中的所有 <old-parent>移到<new-parent>之上。

注意,在Git中更改一个提交要求后面的所有提交也必须更改。如果你已经发表了这部分历史,那么这是不鼓励的,而且有些人可能已经把他们的工作建立在变革之前的历史上

琥珀色的反应中提到的git rebase的替代解决方案是使用移植机制(参见Git术语表中Git移植的定义和Git存储库布局文档中.git/info/grafts文件的文档)来更改提交的父级,用一些历史查看器(gitkgit log --graph等)检查它是否正确,然后使用.git/info/grafts0(如“示例”中所述)。(然后删除graft,并可选地删除由git filter-branch或reclone repository备份的原始引用):

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

这个解决方案是不同于rebase解决方案,因为git rebase将重基/移植<强> < / >强变化,而基于嫁接的解决方案将简单地重新提交<强> < /强>,而不考虑旧父级和新父级之间的差异!

如果结果证明你需要避免重设后续提交的基(例如,因为重写历史将是站不住脚的),那么你可以使用< em > git替换< / em >(在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.

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

默认情况下替换是激活的,但是可以通过将--no-replace-objects选项设置为git(在命令名之前)或设置GIT_NO_REPLACE_OBJECTS环境变量来关闭替换。替换可以通过推refs/replace/*来共享(除了正常的refs/heads/*之外)。

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

git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -

最大的区别是,如果B是合并提交,这个序列不会传播额外的父节点。

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

这取决于你是想“rebase”还是“reparent”。变基,正如琥珀色的所建议的,围绕差别移动。相应,如Jakub克里斯所示,在整棵树的快照周围移动。如果你想重做,我建议使用git reparent而不是手动做这项工作。

比较

假设你有左边的图片,你想让它看起来像右边的图片:

                   C'
/
A---B---C        A---B---C

重基和重父都将生成相同的图像,但C'的定义不同。对于git rebase --onto A BC'将不包含由B引入的任何更改。对于git reparent -p AC'将与C相同(除了B将不在历史记录中)。

@Jakub的回答确实在几个小时前帮助了我,当时我正在尝试与op完全相同的事情 然而,对于移植,git replace --graft 现在是更简单的解决方案。此外,该解决方案的一个主要问题是过滤器分支使我失去了没有合并到HEAD分支中的每个分支。然后,git filter-repo完美无缺地完成了这项工作
$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force

我曾经问过类似的问题,所以完整的答案可以在在这里中找到。


查看更多信息:签出“重新嫁接历史”栏目;在文档