将git父指针设置为其他父级

时间:2010-09-28 06:52:52

标签: git

如果我过去曾提交过指向父母一方的提交,但我想更改它指向的父级,我该如何做呢?

5 个答案:

答案 0 :(得分:355)

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

但有些事情要知道:

  1. 由于提交SHA涉及其父项,因此当您更改给定提交的父项时,其SHA将更改 - 所有提交的SHA将更改为它(更新近于它在发展方面。

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

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

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

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

答案 1 :(得分:34)

如果事实证明您需要避免重新设置后续提交(例如,因为历史记录重写是站不住脚的),那么您可以使用git replace(在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的内容完全相同)相同的树,相同的提交消息)。

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

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

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

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

答案 2 :(得分:30)

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

Amber's response中提及的git rebase的替代解决方案是使用移植机制(请参阅Git Glossary中的Git移植定义和.git/info/grafts的文档Git Repository Layout文档中的文件)来更改提交的父级,检查它是否与某个历史记录查看器(gitkgit log --graph等)一致,然后使用 {{ 3}} (如其联机帮助页的“示例”部分所述)使其永久化(然后删除移植,并可选择删除由git filter-branch备份的原始引用,或重新定义存储库):< / p>

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

注意!!! 此解决方案<与rebase解决方案不同,因为git rebase会重新/移植 更改 ,而基于移植的解决方案只是简单地重新提交 提交 ,而不考虑旧父级和新父级之间的差异!

答案 3 :(得分:22)

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

这取决于你是否想要“变基”或“重新表现”。 Amber建议的 rebase 会移动 diffs JakubChris建议的 reparent 会移动整个树的快照。如果你想重新表达,我建议使用git reparent而不是手动完成工作。

比较

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

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

重新定位和重新定位都会生成相同的图片,但C'的定义不同。使用git rebase --onto A B时,C'不会包含B引入的任何更改。使用git reparent -p A时,C'将与C相同(除了B不在历史记录中)。

答案 4 :(得分:3)

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

$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force

我前段时间已经提出过类似的问题,因此可以在here处找到完整的答案。


更多信息,请查看docs

中的“重新移植历史记录”部分