git submodule を100倍便利にするpost-commit hook

追記[2011/11/20]

  • submodule のコミット書き換えは上手く動いていないので、該当箇所を取り消し線で削除

100倍は釣りです。
深夜にgit helpを読みながら構築した黒魔術の構築式がこちらです。

git submoduleについて

gitで外部リポジトリを扱うコマンドです。
詳しくはgit submodule --help、もしくは参考ページを読んでください。

これは何?

親プロジェクトのGitリポジトリで変更をコミットした時、追加している全てのサブモジュールでも同時にコミットを行います。
コミットメッセージは親プロジェクトのコミットのコミットメッセージとsha-1になり、下記のようになります。

commit 5cd4716a7bb8b38557e11886256b878d004bd82e
Author: sinsoku <sinsoku.listy[at]gmail.com>
Date:   Thu Oct 27 02:28:27 2011 +0900

    親プロジェクトのコミットメッセージ
    
    This related commit ec7fa14290d651c02b27b6d151d7aac86b3ba2d4

注) 同時に行うのはコミットだけなので、submoduleのpushは手動です。

post-commit.for_submodule

コードはGitHubに置いてあります。
post-commitにリネーム&実行権限を追加して、使用したいリポジトリの.git/hooksにコピーして下さい。

sinsoku/dotfiles - GitHub post-commit.for_submodule.sample

Ubuntu 11.04で動作確認しており、Windowsでの動作は未検証です。

  • post-commit.for_submodule.sample (c877bb5)
#!/bin/sh
#
# An example hook script that is called after a successful
# commit is made.
#
# To enable this hook, rename this file to "post-commit".

export supermsg="`git log -n 1 --format="%B" HEAD`"
export pre_submsg="`git log -n 1 --format="%B%nThis related commit %H" HEAD`"
git submodule foreach 'if [ -n "`git status -s`" ]; then git add -A; git commit -m "${pre_submsg}"; fi'

submodules=`cat .gitmodules | grep "\[submodule" | awk 'match($0,/\"[-0-9a-zA-Z_\.\/]*\"/) { print substr($0,RSTART+1,RLENGTH-2) }'`
for m in $submodules; do
if [ -n "`git status -s $m`" ]; then
git add $m
  fi
done

treeobj=`git write-tree`
commitobj=`git commit-tree ${treeobj} -p HEAD^ <<EOF
${supermsg}
EOF`
git reset ${commitobj}

export fix_submsg="`git log -n 1 --format="%B%nThis related commit %H" HEAD`"
git submodule foreach 'if [ "`git log -n 1 --format="%B"`" = "${pre_submsg}" ]; then git commit --amend -m "${fix_submsg}"; fi'

hookの解説

簡単にpost-commit.for_submodule の作り方の説明。

post-commit

  • Pro Gitから引用
コミットプロセスが全て完了した後に、post-commitフックが実行されます。パラメータは必要無く、git log -1
HEADを実行することで直前のコミットを簡単に取り出すことができます。一般的にこのスクリプトは何かしらの通
知といった目的に使用されます。

実行タイミングとしては、ユーザがコミットメッセージを作成し終わった時になります。
後述しますが、post-commit.for_submodule は親コミットを書き換える必要があるため、post-commitにしてあります。

post-commit-hook.for_submodule がやっている事

post-commit-hook.for_submodule は下記のような事をやってます。

  • 親プロジェクトでコミットする。(この後にフックが起動する)
  • 親プロジェクトのコミットメッセージを取得する。
  • 各サブモジュールで変更をコミットする。
  • 親プロジェクトのコミットを書き換え、サブモジュールのsha-1の変更をコミットに含める。
  • 各サブモジュールのコミットを書き換え、親プロジェクトの書き換え後のsha-1をコミットメッセージに追加する。

一回のcommit-hookで、最低でも2回コミットが書き換わるお得なhookです。

post-commit-hook での注意点

git commit

post-commit-hookの中で、git commit を実行すると無限ループに陥るため、使えません。*1
そこで、コミットしたい場合は、git write-treeとgit commit-treeを使ってコミットオブジェクトを作る必要があります。

git submodule foreach

git submodule foreach の中でbashの変数を使う場合、exportにしておく必要があります。

*1:git submodule foreach内のgit commitは別リポジトリになるので、大丈夫です