将 git parent 指针设置为不同的父级

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3810348/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 09:10:08  来源:igfitidea点击:

Setting git parent pointer to a different parent

git

提问by dougvk

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?

如果我过去有一个指向一个父级的提交,但我想更改它指向的父级,我将如何去做?

回答by Amber

Using git rebase. It's the generic "take commit(s) and plop it/them on a different parent (base)" command in Git.

使用git rebase. 它是 Git 中通用的“进行提交并将其/它们放在不同的父(基)上”命令。

Some things to know, however:

但是,有些事情需要了解:

  1. Since commit SHAs involve their parents, when you change the parent of a given commit, its SHA will change - as will the SHAs of allcommits which come after it (more recent than it) in the line of development.

  2. If you're working with other people, and you've already pushed the commit in question public to where they've pulled it, modifying the commit is probably a Bad Idea?. This is due to #1, and thus the resulting confusion the other users' repositories will encounter when trying to figure out what happened due to your SHAs no longer matching theirs for the "same" commits. (See the "RECOVERING FROM UPSTREAM REBASE" section of the linked man page for details.)

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

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

That said, if you're currently on a branch with some commits that you want to move to a new parent, it'd look something like this:

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

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

That will move everything after<old-parent>in the current branch to sit on top of <new-parent>instead.

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

回答by Chris Johnsen

If it turns out that you need to avoid rebasing the subsequent commits (e.g. because a history rewrite would be untenable), then you can use the git replace(available in Git 1.6.5 and later).

如果事实证明您需要避免对后续提交进行重新设置(例如,因为历史重写是站不住脚的),那么您可以使用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") || return 1
    new_parent=$(git rev-parse --verify "^0") || return 2
    new_commit=$(
      git cat-file commit "" |
      sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
      git hash-object -t commit -w --stdin
    ) || return 3
    git replace "" "$new_commit"
}
replace_first_parent B A

# …---o---A---o---o---…
#          \
#           C---b---b---…
#
# C is the replacement for B.

With the above replacement established, any requests for the object B will actually return the object C. The contents of C are exactly the same as the contents of B except for the first parent (same parents (except for the first), same tree, same commit message).

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

Replacements are active by default, but can be turned of by using the --no-replace-objectsoption to git(before the command name) or by setting the GIT_NO_REPLACE_OBJECTSenvironment variable. Replacements can be shared by pushing refs/replace/*(in addition to the normal refs/heads/*) .

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

If you do not like the commit-munging (done with sedabove), then you could create your replacement commit using higher level commands:

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

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

The big difference is that this sequence does not propagate the additional parents if B is a merge commit.

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

回答by Jakub Nar?bski

Note that changing a commit in Git requires that all commits that follow it alse have to be changed. This is discouraged if you have published this part of history, and somebody might have build their work on history that it was before change.

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

Alternate solution to git rebasementioned in Amber's responseis to use graftsmechanism (see definition of Git grafts in Git Glossaryand documentation of .git/info/graftsfile in Git Repository Layoutdocumentation) to change parent of a commit, check that it did correct thing with some history viewer (gitk, git log --graph, etc.) and then use git filter-branch(as described in "Examples" section of its manpage) to make it permanent (and then remove graft, and optionally remove the original refs backed up by git filter-branch, or reclone repository):

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

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

NOTE !!!This solution is different from rebase solutionin that git rebasewould rebase / transplant changes, while grafts-based solution would simply reparent commits as is, not taking into account differences between old parent and new parent!

笔记 !!!此解决方案与 rebase 解决方案的不同之处在于,它git rebase会 rebase / 移植更改,而基于移植的解决方案只会按原样重新提交提交,而不考虑旧父级和新父级之间的差异!

回答by Mark Lodato

To clarify the above answers and shamelessly plug my own script:

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

It depends on whether you want to "rebase" or "reparent". A rebase, as suggested by Amber, moves around diffs. A reparent, as suggested by Jakuband Chris, moves around snapshotsof the whole tree. If you want to reparent, I suggest using git reparentrather than doing the work manually.

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

Comparison

比较

Suppose you have the picture on the left and you want it to look like the picture on the right:

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

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

Both rebasing and reparenting will produce the same picture, but the definition of C'differs. With git rebase --onto A B, C'will not contain any changes introduced by B. With git reparent -p A, C'will be identical to C(except that Bwill not be in the history).

rebase 和 reparenting 都会产生相同的图片,但定义C'不同。随着git rebase --onto A BC'将不包含由 引入的任何更改B。与git reparent -p A,C'将相同C(除了B不会在历史记录中)。