Gitのマージコミットから失われた2番目の親を復元する

公開日:2018-10-17
最終更新:2018-10-24
※この記事は外部サイト(https://qiita.com/tetsurom/items/ee89b80e6...)からのクロス投稿です

何らかの理由で2番目の親が記録されずマージコミットにならなかったマージを、マージコミットにする方法を考えます。

前提条件

  • マージをやり直さない
  • 親コミット以外、各コミット時点でのリポジトリの内容を変更しない
  • コミットのタイムスタンプを変更しない(Commiter Dateも)

図のコミットA5はコミットA4にB4をマージしたコミットとします。 何らかの理由でマージコミットにならなかったため、B4-A5の線が消えてしまいました[^1]。 これを元に戻していきます。

[^1]: 例えば、間違えてmerge --squashにしてしまったときや、git-svnでSubversionからの移行をして、mergeinfo属性がうまく取り込めなかったときなど。

方法1 rebase

rebase でなんとかするというのはすぐに思いつくと思います(なりません)。

git checkout A
git rebase -i A3
pick A4
exec git merge -s ours B4
fixup A5
pick A6
pick A7
  1. merge-s oursを指定して空のマージコミットを作ります。
  2. そこにA5を融合させることで目的のリビジョングラフを得ることができます。

しかし、この方法ではA5のコミットメッセージおよびタイムスタンプが失われてしまいます

方法2 replace

replace を使って、親コミットだけを変更します。

usage: git replace [-f] <object> <replacement>
   or: git replace [-f] --edit <object>
   or: git replace [-f] --graft <commit> [<parent>...]
   or: git replace -d <object>...
   or: git replace [--format=<format>] [-l [<pattern>]]

    -l, --list            list replace refs
    -d, --delete          delete replace refs
    -e, --edit            edit existing object
    -g, --graft           change a commit's parents
    -f, --force           replace the ref if it exists
    --raw                 do not pretty-print contents for --edit
    --format <format>     use this format

次のコマンドラインを使えば親コミットを変更できます。

git replace --graft <commit> [<parent>...]

そこで、次のようにコマンドを実行すると目的のリビジョングラフを得ることができます。

git checkout A
git replace --graft A5 A4 B4
git replace --graft A6 replace/A5
git replace --graft A7 replace/A6
git reset --hard replace/A7
git replace -d A5
git replace -d A6
git replace -d A7
  1. A5の親をA4,B4に変更します。これでマージコミットになります。
  2. A6の親を新しいA5に変更します。A5を置き換えたコミットはreplace/A5で参照できます。
  3. グラフがつながるように親を順次変更します。
  4. 最新のコミットまで置き換えたら、新しいコミットにリセットします。
  5. 最後に置き換えたコミットの参照を消します。
  6. 親コミット以外は何も変更せずに、目的のリビジョングラフを得ることができました。

この方法は rebase と違って作業フォルダで差分を適用するわけではないので、極めて高速です。

スクリプト化

コミットが大量にある場合、手動でコマンドを実行するのは大変です。

良く観察すると次の順に実行しても問題ないことがわかります。

git checkout A
git replace --graft A5 A4 B4

git replace --graft A6 replace/A5
git replace -d A5

git replace --graft A7 replace/A6
git replace -d A6

git reset --hard replace/A7
git replace -d A7

真ん中の部分は繰り返しなので、スクリプト化します(サンプルはPowerShellです)。

function rebase-by-replace($Commits, $InitialCommit)
{
    git replace --graft $Commits[0] $InitialCommit $Commits[0]
    for($i = 1; $i -lt $Commits.length; $i++)
    {
        git replace --graft $Commits[$i] "replace/$($Commits[$i-1])" $Commits[$i]
        git replace -d $Commits[$i-1]
    }

    git reset --hard "replace/$($Commits[$Commits.length-1][0])"
    git replace -d $Commits[$Commits.length-1][0]
}

このままだとマージコミットにできないので、とりあえずCSV形式で2番目の親も渡せるようにします。

function rebase-by-replace($Commits, $InitialCommit)
{
    for($i = 0; $i -lt $Commits.length; $i++)
    {
        $Commits[$i] = $Commits[$i] -split ','
    }

    git replace --graft $Commits[0][0] $InitialCommit $Commits[0][1]
    for($i = 1; $i -lt $Commits.length; $i++)
    {
        git replace --graft $Commits[$i][0] "replace/$($Commits[$i-1][0])" $Commits[$i][1]
        git replace -d $Commits[$i-1][0]
    }

    git reset --hard "replace/$($Commits[$Commits.length-1][0])"
    git replace -d $Commits[$Commits.length-1][0]
}

このスクリプトを使うと前述の操作は次のように実行できます。

git checkout -b new_A A4
rebase-by-replace @("A5,B4","A6","A7") A4
記事が少しでもいいなと思ったらクラップを送ってみよう!
18
+1
@tetsurom'の技術ブログ

よく一緒に読まれている記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる

技術ブログをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

技術ブログを開設する

Qrunchでアウトプットをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

Markdownで書ける

ログ機能でアウトプットを加速

デザインのカスタマイズが可能

技術ブログ開設

ここから先はアカウント(ブログ)開設が必要です

英数字4文字以上
.qrunch.io
英数字6文字以上
ログインする