miauのブログ

はてなダイアリー「miauの避難所」をはてなブログに移行しました

msysGit の git stash や git rebase --onto でエラーの回避

Git で日本語のコミットログを残している場合、msysGit で git stash や git rebase --onto を実行すると、

usage: git update-ref [options] -d <refname> [<oldval>]
   or: git update-ref [options]    <refname> <newval> [<oldval>]

    -m <reason>           reason of the update
    -d                    deletes the reference
    --no-deref            update <refname> not the one it points to

Cannot save the current status

と言われて失敗しまうことがありました。この回避策を調べたので書いておきます。バージョンは git version 1.7.0.2.msysgit.0 です。

何が起きているか見てみる

どうも git stash や git rebase --onto が内部的に git update-ref を呼び出しており、そのコマンド引数が不正になっているみたいです。

git update-ref がどういう形で呼ばれているか知りたかったので、

で書いたプロセス起動のトレースを行いました。

(2011-09-26 追記)git 内部でどういうコマンドが呼ばれているか知りたいだけであれば、環境変数で GIT_TRACE=1 を設定しておけば詳細なコマンドが表示されるようです。(追記ここまで)

すると、

D:\Git\libexec\git-core\git.exe update-ref -m "WIP on master: 96ccd9c 螟画峩縲・ refs/stash 8a8d8c2ec1498bc690601d22f40db78e94a059ca

のような形でコマンドが呼ばれており、ここでエラーになっているらしいことがわかりました。

また、このログから、「git stash」を実行すると内部的に「sh.exe git-stash」のような処理が走ることがわかりました。Git\libexec\git-core\git-stash はただのシェルスクリプトで、上記の処理はこのスクリプト中の

	git update-ref -m "$stash_msg" $ref_stash $w_commit ||
		die "Cannot save the current status"

の部分に対応しているようです。"$stash_msg" の部分が "WIP on master: 96ccd9c 螟画峩縲・ のようになっており、末尾のダブルクォートが前の文字化けした文字列に食われてしまっていることがわかります。

ちなみに文字化けしている部分は直前のコミットログで、元は「変更。」と書いてありました。(検証用に短い文字列にしてますが、普段はもっとちゃんとしたログを書いてますよ…。)

この UTF-8 表現を Shift_JIS で解釈してコマンドが実行されるわけですが、「。」の UTF-8 表現は \xe3\x80\x82 で。末尾の \x82 は Shift_JIS の 1 バイト目で使われる文字列だから、次のダブルクォートと合わせて一文字、と解釈されているみたいです。

末尾が「。」になっているコメントは多いので、結構な確率で git stash できなかったりします。

i18n.logOutputEncoding を変更したら?
git config i18n.logOutputEncoding shift_jis

としている場合は、ログメッセージ $stash_msg が Shift_JIS として取得されるため、ダブルクォートが食われる問題は発生しません。ただし stash 時のメッセージが Shift_JIS で書かれることになりますので、i18n.commitEncoding が UTF-8 の環境だと、

Warning: commit message does not conform to UTF-8.
You may want to amend it after fixing the message, or set the config
variable i18n.commitencoding to the encoding your project uses.

のようなエラーになってしまい、気持ち悪いです。

i18n.commitEncoding も shift_jis にしてしまえば問題ないかもしれませんが、git commit -v することを考えると i18n.commitEncoding はソースコード文字コードUTF-8)と一致させたいので、このような設定は行わない方向で対応したいところです。

対策

git stash

残念ながら WindowsコマンドラインUTF-8 をうまく扱う方法は今のところ知られていませんので、上記の $stash_msg の末尾に空白を入れて回避することにします。

Git\libexec\git-core\git-stash の

	stash_msg="$*"

の行を

	stash_msg="$* "

にしたら、git stash はとりあえず動くようになりました。

git rebase --onto

git rebase --onto も git update-ref でエラーになっていましたが、これのエラーは git-rebase 中で起きているのではなく、git-am の中

	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||

で起きているようです。(どのスクリプトで発生しているかは、Process Monitor で Parent PID を追っていけばわかります。)

Git\libexec\git-core\git-am 中の

	FIRSTLINE=$(sed 1q "$dotest/final-commit")

の行を、git-stash と同様に

	FIRSTLINE=$(sed 1q "$dotest/final-commit")" "

こんな感じにすれば、動くようになりました。

すべてのコマンドを網羅できたわけではないですが、ひとまずよく使うコマンドが使えるようになって一安心です。もっと素直なやり方があるといいんですが・・・。

2011-10-15 追記: いちいち手で編集するのが面倒な場合は

cmd.exe を(UAC が有効な環境であれば管理者として)実行して、

cd %PROGRAMFILES%\Git\bin
perl -i.bak -pe "s{stash_msg=\x22\\x24\*\x22}{stash_msg=\x22\x24* \x22}" ../libexec/git-core/git-stash
perl -i.bak -pe "s{FIRSTLINE=\\x24\(sed 1q \x22\\x24dotest/final-commit\x22\)}{FIRSTLINE=\x24(sed 1q \x22\x24dotest/final-commit\x22)\x22 \x22}" ../libexec/git-core/git-am

とやれば置換できるはずです。確認はこんな感じで。

diff -u ../libexec/git-core/git-stash.bak ../libexec/git-core/git-stash
diff -u ../libexec/git-core/git-am.bak ../libexec/git-core/git-am

いちおう $ は \x24 にエスケープしているので bash でも動くとは思うんですけど、確認していません。

スクリプトを変更したくない場合は・・・

git stash については、git stash save hoge のように、message を明示的に指定すれば回避できるようです。

上記の対策を行う前は・・・

Linux 上でマウントして、そちら側で git stash 等を実行していました。

$ mount -t cifs //192.168.0.1/E$/Workspace/xxxxxx /media/xxxxxx -o username=xxxx
$ cd /media/xxxxxx
$ git stash

みたいな感じ。これがいいかげん面倒になってきたので、今回調査したのでした。