git parentポインターを別の親に設定する


220

過去に1つの親を指すコミットがあるが、それが指す親を変更したい場合、どうすればよいですか?

回答:


404

を使用しgit rebaseます。これは、Gitの一般的な「コミットを取得して、別の親(ベース)にコミットする」コマンドです。

ただし、いくつか知っておくべきこと:

  1. コミットSHAにはその親が含まれるため、特定のコミットの親を変更すると、そのSHAが変更されます- 開発ラインで後に続くすべてのコミットのSHAも変更されます。

  2. 他の人と共同作業をしていて、問題のコミットをプルした場所にすでに公開している場合、コミットの変更はおそらくBad Idea™です。これは#1が原因であり、したがって、SHAが「同じ」コミットでSHAと一致しないために何が起こったかを理解しようとすると、他のユーザーのリポジトリが遭遇する混乱が生じます。(詳細については、リンクされたmanページの「UPSREAM REBASEからの回復」セクションを参照してください。)

そうは言っても、現在ブランチを使用していて、新しい親に移動したいコミットがいくつかある場合は、次のようになります。

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

これにより、現在のブランチの後の すべて<old-parent><new-parent>代わりに上に移動します。


私はgit rebase --ontoを使用して親を変更していますが、最終的には1つのコミットしか行われないようです。私はそれに質問をしました:stackoverflow.com/questions/19181665/…–
Eduardo Mello

7
ああ、それがそのために--onto使われているのです!この回答を読んだ後で、ドキュメントは常に完全に不明瞭でした!
ダニエルC.ソブラル2015

6
この行:私がこれまでに読んだ他のすべての質問とドキュメントが失敗したことgit rebase --onto <new-parent> <old-parent>を使用する方法についてすべて私に言っrebase --ontoた。
jpmc26 2015年

3
私にとって、このコマンドは次のようになります:rebase this commit on that one、またはgit rebase --on <new-parent> <commit>。ここで古い親を使用しても意味がありません。
Samir Aguiar

1
@kopranb頭からリモートヘッドへのパスの一部を破棄する場合は、古い親が重要です。説明しているケースはによって処理されgit rebase <new-parent>ます。
2016

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/*

commit-munging(上記の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/ref階層をプッシュ(およびフェッチ)するように調整する必要があります。一度だけ実行する必要がある場合git push yourremote 'refs/replace/*'は、ソースリポジトリとgit fetch yourremote 'refs/replace/*:refs/replace/*'宛先リポジトリで実行できます。複数回行う必要がある場合は、代わりにそれらのrefspecを構成remote.yourremote.push変数(ソースリポジトリ内)および構成remote.yourremote.fetch変数(宛先リポジトリ内)に追加できます。
Chris Johnsen

3
これを行う簡単な方法は、を使用することgit replace --graftsです。これがいつ追加されたかはわかりませんが、その目的は、コミットBと同じであるが指定された親を持つ置換を作成することです。
ポールワグランド

32

Gitでコミットを変更するには、それに続くすべてのコミットも変更する必要があることに注意してください。歴史のこの部分を公開した場合、これはお勧めできません。誰かが変更前の歴史に基づいて作品を作成している可能性があります。

代替ソリューションgit rebaseで述べた琥珀の応答が使用される移植片メカニズム(Gitリポジトリの定義がで移植片見るGitの用語集との文書.git/info/grafts内のファイルのGitリポジトリレイアウトコミットの変更親へのドキュメント)、それはいくつかの歴史ビューアで正しいことをしたことを確認し(gitkgit log --graphなど)、次にgit filter-branch(そのマンページの「例」セクションで説明されているように)使用して永続化します(その後、graftを削除し、オプションで、によってバックアップされた元の参照を削除するgit filter-branchか、リポジトリを再クローンします)。

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

注意 !!!このソリューションは、変更をリベース/移植するという点でリベースソリューションとは異なりますが、移植ベースのソリューション、古い親と新しい親の違いを考慮せずに、コミットをそのまま親に変更します。git rebase


3
私が理解しているところ(git.wiki.kernel.org/index.php/GraftPointから)は、git replacegitグラフトに取って代わっています(git 1.6.5以降を使用している場合)。
アレクサンダーバード

4
@ Thr4wn:ほぼ真実ですが、履歴書き換えたい場合は、grafts + git-filter-branch(またはインタラクティブなリベース、または高速エクスポート+たとえばreposurgeon)がその方法です。履歴保持する必要がある場合は、Git-replaceが移植片よりもはるかに優れています。
JakubNarębski、2011年

JakubNarębski@、あなたは何を意味を説明できリライト保存の歴史?git-replace+ を使用できないのはなぜgit-filter-branchですか?私のテストでfilter-branchは、ツリー全体をリベースして、置換を尊重しているように見えました。
cdunn2001 2013年

1
@ cdunn2001:git filter-branch移植片と置換を尊重して、履歴を書き換えます(ただし、書き換えられた履歴では、置換は無効です)。pushはfasf-forward変更になります。git replaceインプレースの転送可能な置換を作成します。プッシュは早送りされますが、refs/replace履歴を修正するには、置換を転送するためにプッシュする必要があります。HTH
JakubNarębski2013

23

上記の答えを明確にし、恥ずかしくないように私自身のスクリプトをプラグインするには:

これは、「リベース」するか「リペアレント」するかによって異なります。Amberによって提案されたリベースは差分を移動します。JakubChrisが示唆するようにはツリー全体のスナップショットを移動します。親を変更したい場合は、手動で作業するのではなく、使用することをお勧めします。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

確かに、@ Jakubの回答は、数時間前、私がOPとまったく同じことを試みていたときに役に立ちました。
ただし、git replace --graft現在は移植に関するより簡単な解決策です。また、その解決策の主な問題は、フィルターブランチによって、HEADのブランチにマージされなかったすべてのブランチが失われることでした。その後、git filter-repo完全かつ完璧に仕事をしました。

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

Fore more info:チェックアウトのセクションの「再移植履歴」のドキュメント

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.