git-sh-setupが見付からない

$ git pull -h
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git-pull: line 12: git-sh-setup: No such file or directory

元旦早々こんな問題が発生。

実は前にも見たけどなんとなくスルーしてしまった。今回、ちょっと気になったので調べてみると、どうも端末エミュレータによって発生したりしなかったりするらしい。実際Teriminal.appでは問題なくて、iTerm2だと上のようになる。いったい何が違うのん?

(あ、ちなみに新しめのiTerm2なら修正済みという話を見掛けました。よって、ここからの追求は多分あんまり意味がないです。また、以下の現象がよそで起きている現象と同じかどうか分かりません。どうして古いiTerm2を使い続けているのかというと、新しいiTerm2だとMacUIM-tutcode/tcodeが動かないという別の問題がありまして……。AquaSKKもだっけ? まあ、新しいiTerm2にpatch適用するのをめんどくさがっているだけなのですが。参考: iTerm2 + AquaSKK)

さて。エラーが起きているgit-pullの11行目はこんな記述だった。

. git-sh-setup

これがない、と。でもlsするとある。そもそもTerminal.appなら動いている。少し検索してみるとpopenがうんぬん、環境変数がうんぬんという意見が出てきた。なるほど「.」はPATHに従う。

ひとまず確認をと思い、10行目にecho $PATHを埋めてgit pull -hを実行してみた。まずは問題のないTerminal.appから。

$ git pull -h
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/local/heroku/bin:/Users/akira/bin:/usr/local/bin:/usr/local/sbin:/Users/akira/.rbenv/shims:/Users/akira/.rbenv/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
usage: git pull [-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff] [--[no-]rebase|--rebase=preserve] [-s strategy]... [<fetch-options>] <repo> <head>...
[...]

そして困ったことになっているiTerm2。

$ git pull -h
/usr/bin:/bin:/usr/sbin:/sbin
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git-pull: line 12: git-sh-setup: No such file or directory

おー、そうですか。いや、そうですよね。ちなみにこのときのPATHはがどうなっているのかなと見てみると、いずれの環境でも以下の通りだった。

/usr/local/heroku/bin:/Users/akira/bin:/usr/local/bin:/usr/local/sbin:/Users/akira/.rbenv/shims:/Users/akira/.rbenv/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

と、ここで気付く。環境変数を表示させるのにenvを使ったのだがiTerm2での結果がおかしい。

$ env | grep PATH
PATH=/usr/local/heroku/bin:/Users/akira/bin:/usr/local/bin:/usr/local/sbin:/Users/akira/.rbenv/shims:/Users/akira/.rbenv/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
PATH=/usr/bin:/bin:/usr/sbin:/sbin

何これ? psでも見てみる。(3222はiTerm2上で動いているシェル。)

$ ps -wwwE -o args -p 3222
ARGS
-zsh PATH=/usr/bin:/bin:/usr/sbin:/sbin USER=akira PATH=/usr/bin:/bin:/usr/sbin:/sbin LOGNAME=akira SSH_AUTH_SOCK=/private/tmp/...(略)...

えーと、PATHの内容が違うのはプロセス起動時のものということで、それはともかくとして二つのPATHがあるのを確認できる。ちなみにTerminal.appのほうはやはり問題ない。(2875はTerminal.app上で動いているシェル。)

$ ps -wwwE -o args -p 2875
ARGS
-zsh TMPDIR=/var/folders/...(略)... LOGNAME=akira USER=akira PATH=/usr/bin:/bin

ただ、よく分からないのは、gitから起動されるgit-pullプロセスの環境変数にgit用のパスが加わっていないこと。このパスを加えているのはgitなはずで、fork()/exec()するのもgitのはず。つまり、git-pullを見付けられないって話ならまだ分かるのだけど、一段とんでgit-sh-setupなのがどういうことなのか。

また、iTerm2上で別プロセスを起動して、そこからgit pull -hするとちゃんと思く。どういうこと?

$ ruby -e 'system "git pull -h"'
usage: git pull [-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff] [--[no-]rebase|--rebase=preserve] [-s strategy]... [<fetch-options>] <repo> <head>...
[...]

ではgit-pullの環境変数はどうなっているか。10行目にenv | grep PATHをいれてみる。

$ git pull -h
PATH=/usr/bin:/bin:/usr/sbin:/sbin
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git-pull: line 12: git-sh-setup: No such file or directory

おや一つしかない。念のためpsでも確認。10行目をps -wwwE -o args -p $$に変更する。

$ git pull -h
ARGS
/bin/sh /Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git-pull -h PATH=/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/local/heroku/bin:/Users/akira/bin:/usr/local/bin:/usr/local/sbin:/Users/akira/.rbenv/shims:/Users/akira/.rbenv/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin USER=akira PATH=/usr/bin:/bin:/usr/sbin:/sbin LOGNAME=akira SSH_AUTH_SOCK=/private/tmp/...(略)...
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git-pull: line 12: git-sh-setup: No such file or directory

ここから、git-pullの起動時には二つのPATHがあったことをうかがえる。そして一つはgit用パスを含み、もう一つは含まない。含まないほうはデフォルトのパスのようでもある。git-pullプロセス中でPATHが一つになっているのは/bin/shによる初期化処理か何かによるものだと推測できる。

ここでちょっと実験。二つあるPATHのどっちが効いてるのん?

まず、おさらい。iTerm2上での二つのPATHを確認する。

$ env | grep PATH
PATH=/usr/local/heroku/bin:/Users/akira/bin:/usr/local/bin:/usr/local/sbin:/Users/akira/.rbenv/shims:/Users/akira/.rbenv/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
PATH=/usr/bin:/bin:/usr/sbin:/sbin

二つめのPATHの内容は一つめにも含まれるので、一つめのPATHをちょっといじる。

$ PATH=/usr/local/bin

二つのPATHがどうなったか確認する。

$ env |grep PATH
zsh: command not found: env
zsh: command not found: grep

おっと。

まあ、このミスですでに分かったような部分もあるがもう改めて。

$ /usr/bin/env |/usr/bin/grep PATH
PATH=/usr/local/bin
PATH=/usr/bin:/bin:/usr/sbin:/sbin

二つめのPATHにしかないenvgrepは実行できないのをうっかり確認してしまったが、一つめのPATH、つまり/usr/local/binにしかないコマンドは実行できることも確認しておく。

$ brew -h
Example usage:
  brew [info | home | options ] [FORMULA...]
[...]

つまり一つめのPATHが効いている。一つめか二つめかというのは表示上の順番でもあるから、どっちがどうというのはおいておいて、ともかくどちらか一方だけが有効だと分かる。となるとこういうことだろうか。

  • iTerm2上のシェル - 二つのPATHを持つ
  • gitプロセス - 二つのPATHを持ち、そのうち一つにgit用パスを追加する
  • git-pullプロセス - 二つのPATHを与えられて起動するが、本体のシェルスクリプト実行時には一つだけが残されもう一つは消えている

git-pullプロセス上ではデフォルトの簡素なPATHが残っているようで、つまり、このためにgit-sh-setupを見付けられないということだ。

一応、回避方法はgitに二つのPATHを渡さないことだろうか。幸いというかなんというか、iTerm2上で動くシェル自身は、デフォルトの簡素なパスではなくて自分で設定したそれなりのパスが有効になっている。(そのせいで今回の問題に気付かなかったという面がある。) よって、どこか適当なところにgitのwrapperを置いておけばよい。

$ cat ~/bin/git
#!/bin/sh
exec /usr/bin/git "$@"

まあ、こういうの作ると忘れたころに弊害をくらうものなのだけど。というわけで、冒頭に書いた通り、iTerm2を更新するなどするのがまっとうなんだろう。