将git父指针设置为其他父


220

如果我过去曾经提交过一次指向一个父母的提交,但是我想更改它指向的父母,那我该怎么做呢?

Answers:


404

使用git rebase。这是Git中的通用“获取提交并将其放到其他父级(基础)上”命令。

但是,有些事情要知道:

  1. 由于提交SHA涉及其父级,因此,当您更改给定提交的父级时,其SHA也会更改-在开发领域中,紧随其后(比更新更早)的所有提交的SHA也会更改。

  2. 如果您正在与其他人一起工作,并且已经将有问题的提交公开推到了他们将其拉到的位置,则修改提交可能是Bad Idea™。这是由于#1所致,因此,其他用户的存储库在尝试找出由于SHA不再匹配“相同”提交而与他们的SHA匹配而发生的情况时,会引起混乱。(有关详细信息,请参见链接的手册页的“从UPSTREAM REBASE恢复”部分。)

就是说,如果您当前正在某个分支上,并且有一些提交要移到新的父级,那么它将看起来像这样:

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

这将移动一切 <old-parent>在当前分支坐在上面<new-parent>来代替。


我正在使用git rebase --on来更改父母,但看来我最终只完成了一次提交。我对此提出了一个问题:stackoverflow.com/questions/19181665/…–
Eduardo Mello

7
啊哈,那--onto是用来干什么的!即使阅读此答案,文档也始终对我完全晦涩!
Daniel C. Sobral

6
这行代码:git rebase --onto <new-parent> <old-parent>告诉我有关如何使用rebase --onto到目前为止我已阅读的所有其他问题和文档的所有内容。
jpmc26 2015年

3
对我来说,此命令应显示为:rebase this commit on that onegit rebase --on <new-parent> <commit>。在这里使用老父母对我没有任何意义。
Samir Aguiar

1
@kopranb当您要丢弃从头部到远程头部的某些路径时,旧的父级很重要。您所描述的情况由处理git rebase <new-parent>
clacke

35

如果事实证明您需要避免重新建立后续提交(例如,因为历史记录重写将是站不住脚的),则可以使用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_OBJECTSrefs/replace/*refs/heads/*

如果您不喜欢提交查询(上面用sed完成),则可以使用更高级别的命令来创建替换提交:

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

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


然后,如何将更改推送到现有存储库?它似乎在本地工作,但之后git push不会再执行任何操作
Baptiste Wicht 2014年

2
@BaptisteWicht:替换项存储在单独的引用集中。您将需要安排推送(并获取)refs/replace/引用层次结构。如果只需要执行一次,则可以git push yourremote 'refs/replace/*'在源存储库和git fetch yourremote 'refs/replace/*:refs/replace/*'目标存储库中进行。如果需要多次执行,则可以将这些refspecs添加到remote.yourremote.push配置变量(在源存储库中)和remote.yourremote.fetch配置变量(在目标存储库中)。
克里斯·约翰森

3
一种更简单的方法是使用git replace --grafts。不确定何时添加,但其整个目的是创建一个与提交B相同但具有指定父级的替换。
保罗·瓦格兰

32

请注意,在Git中更改提交要求还必须更改其后的所有提交。如果您发布了历史的这一部分,则不建议这样做,并且可能有人将其工作建立在变更之前的历史上。

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

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

注意 !!!该解决方案与rebase解决方案的不同之处在于,它git rebase可以对更改进行 rebase /移植,而基于嫁接的解决方案将仅按原样重新提交,而不考虑旧父母与新父母之间的差异!


3
据我了解(从git.wiki.kernel.org/index.php/GraftPoint),git replace已经取代了git 嫁接(假设您有git 1.6.5或更高版本)。
亚历山大·伯德

4
@ Thr4wn:大体上是正确的,但是如果您想重写历史记录,则可以使用嫁接+ git-filter-branch(或交互式变基,或快速导出+例如reposurgeon)。如果您希望/需要保留历史记录,那么git-replace远远优于嫁接。
2011年

@JakubNarębski,您能解释一下重写保留历史的含义吗?为什么我不能使用git-replace+ git-filter-branch?在我的测试中,filter-branch似乎尊重替换,重新树了整棵树。
cdunn2001

1
@ cdunn2001:git filter-branch重写历史记录,尊重嫁接和替换(但是在重写的历史记录中,替换将无意义);推送将导致不方便的更改。git replace创建就地可转让替代品;推送将快速进行,但是您需要推送refs/replace以转移替换内容以更正历史记录。HTH
JakubNarębski2013年

23

为了澄清以上答案,并毫不客气地插入我自己的脚本:

这取决于您是要“变基”还是“变基”。根据Amber的建议,rebasediff周围移动。正如雅库布(Jakub)克里斯(Chris)所建议的那样,一个父母在整个树的快照周围移动。如果您想复习,我建议使用而不是手动进行工作。git reparent

比较方式

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

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

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


1
+1我已经尝试过该reparent脚本;它工作得很漂亮;强烈推荐。我还建议创建一个新branch标签,并checkout在调用之前创建到该分支(因为分支标签将移动)。
Rhubbarb 2015年

还值得注意的是:(1)原始节点保持不变,并且(2)新节点不继承原始节点的子节点。
Rhubbarb 2015年

0

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

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

有关更多信息,请参见文档中的 “重新移植历史记录”部分

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.