📝 この記事は forge.workstyle.tech に掲載した記事の転載です。
シェルスクリプトやAIエージェントにgitの状態を確認させたとき、表示だけを見て逆の結論を出してしまうことがあります。シェルはコマンドを実行するための環境で、grepはファイルから文字列を探すコマンドです。私は使い捨てのリポジトリで、rev-parse、grep、stash、git statusの確認で起きることを再現しました。この記事では、出力だけに頼らず結果を確かめる方法を紹介します。
先に結論
gitの確認では、出力が空でないことや件数だけでは、対象の有無やコマンドの成功を判断できない場合があります。終了コードと実行されたコマンド、比較しているパスを確かめ、時刻を証拠にする場合はGIT_OPTIONAL_LOCKS=0を付けて状態を確認します。
rev-parseで対象の存在をどう確かめるか
git rev-parse(Gitの名前やパスを解決するコマンド)の出力が空でないことだけでは、指定したパスが存在するとは判定できません。次の例では、HEAD(現在のコミットを指す名前)に存在しないパスを指定しています。
$ v=$(git rev-parse "HEAD:存在しないpath" 2>/dev/null); echo "値=[$v] exit=$?"
値=[HEAD:存在しないpath] exit=128
解決できなかった引数が標準出力にそのまま出ています。終了コード(コマンドの成否を示す数値)は128ですが、変数vは空ではありません。そのため、[ -n "$v" ]だけで判定すると「在る」と読んでしまいます。
存在を確かめるには、出力の有無ではなくコマンドの終了コードを見ます。
git cat-file -e <rev>:<path>
記録した再現では、存在しない場合の終了コードは128、存在する場合は0でした。ツリーの内容を一覧にして確認するなら、git ls-treeを使います。大事なのは、何か出力されたかではなく、指定した対象を解決できたかです。
grepが0件やエラーに見えるとき何を調べるか
grepの件数が0に見えたりエラーになったりしたら、実際に何が呼ばれているかを確認します。ある環境では、grepが別のgrep実装を呼ぶシェル関数で上書きされていました。
$ grep -c alpha s.txt
error: unknown option '-G'
exit=1
$ command grep -c alpha s.txt
1
exit=0
ファイルにはalphaが1件あります。しかし最初の呼び出しはエラーになり、終了コードも1です。終了コード1を「0件」と読めば、中身があるのに見逃します。確認のためのコマンドが、確かめたい状態を隠してしまいます。
何が呼ばれるかは、次のコマンドで確かめられます。
type grep
シェル関数ではなく通常のコマンドを呼ぶには、command grepを使う方法があります。絶対パスで呼び出してもかまいません。件数を見る前に、実行したgrepが想定したものかを確認します。
stashの差を比べるとき何を数えるか
stash(作業内容を一時保存するGitの機能)を確かめるときは、比較する対象を言葉にします。stash@{0}はstashした時の作業ツリー(作業中のファイル一式)です。stash@{0}^2はその時のindex(ステージした内容)、stash@{0}^1はHEAD、stash@{0}^3は未追跡ファイルです。
ある比較では、ツリー全体に3ファイル・434行の差がありました。そこから「stashは控えにならない」と結論しました。しかし、その3ファイルは確認対象の20件に含まれていませんでした。
パスごとにファイルの内容を比較し直すと、20件の内訳は同一が15件、両方で削除が5件、食い違いは0件でした。対象の20件については、stashが完全な控えでした。
数字が違っていたのは、同じ対象を数えていなかったからです。差の大きさを見直す前に、比較対象の範囲を突き合わせます。「何と何の、どのパスを数えているか」を先に言葉にすると、数字の意味を取り違えにくくなります。
git statusの後に時刻が変わることはあるか
記録した環境では、読み取りだけのつもりで実行したgit statusがindexのmtime(ファイルの更新時刻)を書き換えました。
時刻が証拠になる場面では、通常のgit statusではなく次を使います。
GIT_OPTIONAL_LOCKS=0 git -C <path> status --porcelain
「状態を表示するコマンドだから、何も書き換えない」とは限りません。確認したあとに時刻を証拠として扱うなら、確認コマンド自身が時刻に影響しないかも考えます。
同じことで困ったら
gitやシェルの確認結果に迷ったら、次の順で確かめます。
- 何のパスやファイルを確認したいかを決める。
- 出力だけで決めず、終了コードも確認する。
-
grepのようなコマンドが関数で置き換わっていないか、type grepで調べる。 - 比較では、対象のパスと件数の範囲を揃える。
- 時刻を証拠にするなら、
GIT_OPTIONAL_LOCKS=0 git -C <path> status --porcelainで状態を確認する。