チェックアウトせずにマージ

dev-xxxブランチにいるときに、masterブランチに移動しないままでdev-xxxブランチをmasterにmergeできたら

http://hibari.2ch.net/test/read.cgi/tech/1284467898/810

低レベル操作によって、ワーキングディレクトリを一切無視して任意のインデクスに対してマージしてくれるコマンドが欲しい。

http://hibari.2ch.net/test/read.cgi/tech/1284467898/813

GIT_INDEX_FILE でどうにかなりそうな気がしたので実験してみたメモ。

マージする準備

最初のコミット。a.txtb.txtを追加。

$ git init
$ echo a > a.txt
$ echo b > b.txt
$ git add a.txt b.txt
$ git commit -m 'initial'
[master (root-commit) e1f7440] initial
 2 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 a.txt
 create mode 100644 b.txt

こうなった

$ git whatchanged --oneline
e1f7440 initial
:000000 100644 0000000... 7898192... A  a.txt
:000000 100644 0000000... 6178079... A  b.txt

topicブランチを作って、進める

$ git checkout -b topic --track master
Branch topic set up to track local branch master.
Switched to a new branch 'topic'

$ echo 'BB(topic)' >> b.txt
$ git commit -a -m 'BB(topic)'
[topic ce78e31] BB(topic)
 1 files changed, 1 insertions(+), 0 deletions(-)

1つ進んだ

$ git whatchanged --oneline topic
ce78e31 BB(topic)
:100644 100644 6178079... a86f970... M  b.txt
e1f7440 initial
:000000 100644 0000000... 7898192... A  a.txt
:000000 100644 0000000... 6178079... A  b.txt

topicmasterより1つ進んでいる

$ git branch -v
  master e1f7440 initial
* topic  ce78e31 [ahead 1] BB(topic)

masterをチェックアウトせずに、masterにtopicをマージ

topicをチェックアウトした状態のまま、そしてインデックスも保持したまま、mastertopicをマージする。

  • 方針
    • masterをインデックスに読み込んでtopicをマージし、結果のコミットを作ってmasterを進める
    • Fast-Forwardはとりあえず気にしない
    • マージ後もそのまま作業を続けたいので、現在のインデックスの内容はそのまま残す
      • GIT_INDEX_FILEで別のインデックスを使う

インデックスが保全されることを確認したいので、ちょっといじっておく

$ echo 'あばばばばwwwwwwwwwwww' >> b.txt
$ git add b.txt

編集したb.txtがインデックスに登録された

$ git status
# On branch topic
# Your branch is ahead of 'master' by 1 commit.
#
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   b.txt
#
$ git diff --cached
diff --git a/b.txt b/b.txt
index a86f970..cb6733f 100644
--- a/b.txt
+++ b/b.txt
@@ -1,2 +1,3 @@
 b
 BB(topic)
+あばばばばwwwwwwwwwwww

この状態を維持することにする。

GIT_INDEX_FILEで別のインデックスを使う

現在のインデックスの状態を表示

$ git ls-files -s
100644 78981922613b2afb6025042ff6bd878ac1994e85 0       a.txt
100644 cb6733f4cdef9366bbbaeef9c8a5af40a3f03b5f 0       b.txt

b.txtのblobを見るとさっきの変更が反映されているのが分かる

$ git cat-file blob cb6733f4cdef9366bbbaeef9c8a5af40a3f03b5f
b
BB(topic)
あばばばばwwwwwwwwwwww

.index_tmpをインデックスファイルに指定して、masterの内容を読み込んでみる

$ GIT_INDEX_FILE=.index_tmp git read-tree master

.index_tmpが出来ていた

$ ls -1a
.
..
.git
.index_tmp
a.txt
b.txt

通常のインデックスは.git/indexなので、GIT_INDEX_FILE=.git/index_altとかにすれば良かったかも。

.index_tmpインデックスの状態を表示

$ GIT_INDEX_FILE=.index_tmp git ls-files -s
100644 78981922613b2afb6025042ff6bd878ac1994e85 0       a.txt
100644 61780798228d17af2d34fce4cfbdf35556832472 0       b.txt

b.txtのblobを見るとmasterの内容なのが分かる

$ git cat-file blob 61780798228d17af2d34fce4cfbdf35556832472
b

別々に操作出来ていることが確認できた。

インデックス上でマージ

.index_tmpのほうで、mastertopicをマージしてみる

$ GIT_INDEX_FILE=.index_tmp git read-tree -m -i master topic

マージ後の内容

$ GIT_INDEX_FILE=.index_tmp git ls-files -s
100644 78981922613b2afb6025042ff6bd878ac1994e85 0       a.txt
100644 a86f970145550ca53098e9bed4fcdd61a935b6f4 0       b.txt

b.txtの内容をそれぞれ確認してみる。

通常のインデックスのb.txt

$ git cat-file blob cb6733f4cdef9366bbbaeef9c8a5af40a3f03b5f
b
BB(topic)
あばばばばwwwwwwwwwwww

保持したい内容のままになっている

マージ後のインデックスのb.txt

$ git cat-file blob a86f970145550ca53098e9bed4fcdd61a935b6f4
b
BB(topic)

topicブランチの内容になっている。マージできたっぽい。

Low-levelコマンドでマージコミットを作る。

$ echo 'Merge! branch topic into master!' | git commit-tree `GIT_INDEX_FILE=.index_tmp git write-tree` -p master -p topic
f59b2bb25340f8677c3b2842db81c2e9a8250781

コミットオブジェクトできた

$ git log --oneline --graph f59b2bb25340f8677c3b2842db81c2e9a8250781
*   f59b2bb Merge! branch topic into master!
|\
| * ce78e31 BB(topic)
|/
* e1f7440 initial

Low-levelコマンドでmasterを更新

$ git update-ref refs/heads/master f59b2bb25340f8677c3b2842db81c2e9a8250781

できたー

$ git branch -v
  master f59b2bb Merge! branch topic into master!
* topic  ce78e31 [behind 1] BB(topic)

通常のインデックスはそのまま変化なし

$ git status
# On branch topic
# Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
#
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   b.txt
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       .index_tmp
$ git diff --cached
diff --git a/b.txt b/b.txt
index a86f970..cb6733f 100644
--- a/b.txt
+++ b/b.txt
@@ -1,2 +1,3 @@
 b
 BB(topic)
+あばばばばwwwwwwwwwwww

問題点

ファイル単位で更新がぶつかるとアウト

git merge-baseで3-Wayにしたりとか、Low-levelコマンドをくししてがんばればどうにかなるけど…

もっと本格的にコンフリクトするとアウト

人間が関与しないとダメなやつは無理ですねこれ。

ツール化したらどうなるか

  • コンフリクトしない自信がある場合は良いかも知れない
  • コンフリクトしたらしょうがなくチェックアウトしてマージするようにする方向なら良いかもしれない

個人的にはマージする時はstashしてチェックアウトすればいいかなーと思う。。。