What goes on/what's going on:2019年07月23日分

2019/07/23(Tue)

[SCM] git-subtreeの落とし穴

以前はcontribだったけど現在は本体に取り込まれたgit-subtree、こいつは3rd partyなソースの管理に便利なんだけどrebaseが絡むとややこしいのよな。毎回忘れるのでメモ残しておく。

まずgit subtree addした後、それより以前のcommitに間違いを発見してrebaseを実行するようなケース、簡単な図にすると

	A (master) edit master 1
	|
	B (master) subtree add onto master
	|
	C (master) edit master 2

という履歴があった場合、Aを改変すべく

$ git rebase -i A

を実行すると、なぜかBの時点で実行した

$ git subtree add --prefix=foo --squash remote master

の--prefixが最適用の際に無視されてしまい、ディレクトリ構成が変になるわAのソースが上書きされCの差分適用でconflictが発生するので、お前は全身の穴から血を吹いて死ぬ。

例えばワイのオレオレN6ではOpenSSLをsubtree化してるのだけど、subtree add以前のcommitからrebaseを実行するとこうなる。

$ git checkout tnozaki-openssl
$ git rebase a25e71bd8d516744e119cf08eaf89ed9c3a11ec2
CONFLICT (add/add): Merge conflict in tools/Makefile
Auto-merging tools/Makefile
CONFLICT (add/add): Merge conflict in .gitignore
Auto-merging .gitignore
error: could not apply 44cfd356c... Squashed 'crypto/external/bsd/openssl/dist/' content from commit 76599d516c
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 44cfd356c... Squashed 'crypto/external/bsd/openssl/dist/' content from commit 76599d516c

前述の通りsubtree addで指定した--prefix=src/crypto/external/bsd/openssl/distが無視されてしまい

と本来はprefix以下に置かれるべきファイルがrepository rootに存在するファイルを上書きしてしまい、conflictが発生してしまうのだ。

わざわざ実装読みたくねえし原因調べる気もないのでこれが正解かは知らんけど、git rebaseに-r/--rebase-mergesオプションをつけて実行してmerge commit情報を残すといいみたい。

$ git rebase --rebase-merges a25e71bd8d516744e119cf08eaf89ed9c3a11ec2
Rebasing (2/59)
Checking out files: 100% (2278/2278), done.
Rebasing (5/59)
Checking out files: 100% (2199/2199), done.
Checking out files: 100% (2278/2278), done.
Checking out files: 100% (569/569), done.
Checking out files: 100% (24/24), done.
Successfully rebased and updated refs/heads/tnozaki-openssl.

はい--prefixが維持されてsubtree addが適用されるので正常にrebaseできまんた。

そんでお次、今度は

というケース、これも言葉じゃ伝わりにくいので図にすると

	A (master) edit master 1
	|\
	| B (branch) edit branch 1
	| |
	| C (branch) subtree add onto branch
	| |
	| D (branch) edit branch 2
	|
	E (master) edit master 2

という状態から

$ git rebase master

を実行して

	A (master) edit master 1
	|
	E (master) edit master 2
	 \
	  B (branch) edit branch 1
	  |
	  C (branch) subtree add onto branch
	  |
	  D (branch) edit branch 2

としたい場合やね、でもこれもまたエラーになってしまう。

さっきと同様にオレオレN6でやってみた場合の失敗ログはこちら。

$ git rebase master
First, rewinding head to replay your work on top of it...
Generating patches: 100% (54/54), done.
Applying: ENHANCEMENT: temporary remove crypto/external/bsd/openssl/dist for git subtree.
Applying: Squashed 'crypto/external/bsd/openssl/dist/' content from commit 76599d516c
Using index info to reconstruct a base tree...
.git/rebase-apply/patch:1285: trailing whitespace.
     the certificate can be used for (if anything). Set valid_flags field
.git/rebase-apply/patch:1328: trailing whitespace.
  *) Initial experimental support for explicitly trusted non-root CAs.
.git/rebase-apply/patch:1379: trailing whitespace.
  *) New ctrls to retrieve supported signature algorithms and
.git/rebase-apply/patch:1584: trailing whitespace.

.git/rebase-apply/patch:1754: trailing whitespace.
  *) Fix for TLS record tampering bug. A carefully crafted invalid
warning: squelched 7650 whitespace errors
warning: 7655 lines add whitespace errors.
Falling back to patching base and 3-way merge...
CONFLICT (add/add): Merge conflict in tools/Makefile
Auto-merging tools/Makefile
CONFLICT (add/add): Merge conflict in .gitignore
Auto-merging .gitignore
error: Failed to merge in the changes.
Patch failed at 0002 Squashed 'crypto/external/bsd/openssl/dist/' content from commit 76599d516c
hint: Use 'git am --show-current-patch' to see the failed patch
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

やっぱり.gitignoreとtools/Makefileのconflictなので、subtree addの--prefixが失われたことが原因と思われる。

これもやっぱり-r/--rebase-mergesオプションをつければおk。

$ git rebase --rebase-merges master
Checking out files: 100% (2278/2278), done.
Checking out files: 100% (2199/2199), done.
Checking out files: 100% (2278/2278), done.
Checking out files: 100% (569/569), done.
Checking out files: 100% (3/3), done.
Checking out files: 100% (11/11), done.
Successfully rebased and updated refs/heads/tnozaki-openssl.

しかしBitbucketの運営元である暮らし安心 Altassianのブログには

あなたは sub-tree の含まれるリポジトリをどうやってリベースしていますか? この Stack Overflow に関する議論から分かることは、銀の弾丸は存在しないということです。

有効な手順は、手動でリベースを行うために単純に rebase --interactive を実行し、git subtree add のコミットを削除して rebase --continue を実行し、リベースが完了したら git subtree add コマンドを再実行することだと思います。

などと書いてある、Stackoverflowがソースってのがアレやけどまぁワイより何十倍もgitに詳しいだろうから、--rebase-mergesではダメなケースもあるのかもしれない。 つーか古いバージョンの話をしているのだろうか…

そして最後にgit subtree addする際には必ず--squashを忘れないこと、でないとrebaseをする時に

という落とし穴があるのだ。

結論、やっぱりgit捨てたい。