$git --version
git version 2.19.1
tl;dr
- 
git grepやgit ls-filesなどでのパスの絞り方にはpathspecという共通仕様がある - 
:に続けてmagic word/signatureを使うことができ、ディレクトリの除外など高度な検索ができる 
# リポジトリルート上のファイルでtestという文字列を含むものを検索
$git grep -w test ':(top)*' ':(top,exclude)*/*'
きっかけ
@laiso さんの「git-grepで特定のディレクトリを除外する」が便利でよく使うのだが、
git grep word ':!exclude/' .という記法がなかなか覚えられず、都度記事を見てしまう。
なぜ:!で除外を表せるのかを理解しないとダメだと思い、仕様を調べてみた。
glob(7)の仕様?
$git grep --help
helpで仕様を確認してみる。optionがずらりと並ぶが、引数の最後に[<pathspec>…]とある
(--はoptionではないことの明示なのでpathspecが最後の引数ならなくても良い)
If given, limit the search to paths matching at least one pattern. Both leading paths match and glob(7) patterns are supported.
とある。
前半にglob(7)をサポートと書かれているので、今度はglobの仕様を追ってみる。
# if __linux__
$man 7 glob
# elif __APPLE__
$man n glob
- 
?は任意の一文字 - 
*は任意の文字列(空文字も含む) - 
[a,z]はaかz、[a-z]ならaからzの中の一文字- 
[^abc]はa,b,c以外の一文字(先頭にブラケットで否定) 
 - 
 
$git grep 'word' .
index.js:2:word
src/ab/index.js:2:word
src/ac/index.js:2:word
src/index.js:2:word
$git grep 'word' src/a[a,b]*
src/ab/index.js:2:word
$git grep 'word' src/a?
src/ab/index.js:2:word
src/ac/index.js:2:word
といった記法が使えることがわかった。しかし、肝心の文字列否定は仕様にない。
gitglossary
実は先ほどのpathspecの仕様の説明文には下記の続きがある。
For more details about the syntax, see the pathspec entry in gitglossary[7].
自分が初めて調べた時、gitのversionが2.14.0で、そのhelpにはこの一文はなかった
しかし2.14.3以降で、上記の記述が追加されていた(web検索のおかげで発見)
詳細な仕様を確認してみる
$git --help gitglossary
pathspec要点
- 
git ls-files,git ls-tree,git add,git grep,git diff,git checkoutなどで共通の仕様 - 
/はディレクトリを表し、末尾につけば配下も対象となる - 
fnmatch(3)に従う- ex, 
git grep 'word' 'src/*.js'はsrc/index.js,src/ab/index.js,src/ac/index.jsにマッチ - pathspecはクオートで囲まないと意図通りに動かないので注意
 
 - ex, 
 
後半が本題
- 
:始まりでmagic word(または一文字のmagic signature)を続けると、下記ルールを適用できる - magic word(long form)の場合は
:(magic_word_1,magic_word_2)pathspecと表現 
| magic signature | magic word | 効果 | 
|---|---|---|
/ | 
top | 通常カレントディレクトリ以下が検索対象だがルートディレクトリから検索 | 
| literal | 
*,?を文字列として扱う | 
|
| icase | 大文字小文字区別せず検索 | |
| glob | 
**で階層任意で検索できる(literalとは互換性がない) | 
|
| attr | attributeを利用: gitattributes | |
!,^
 | 
exclude | 除外 | 
$git ls-files
index.js
src/ab/index.js
src/ac/index.js
src/index.js
$git ls-files 'INDEX.JS'
$git ls-files ':(icase)INDEX.JS'
index.js
# **はコロン無しでも使えたりするが、
$git ls-files **/index.js
src/ab/index.js
src/ac/index.js
src/index.js
# その場合は**/のスラッシュがディレクトリを表すため、ディレクトリルートが検索対象にならない。magic wordを入れれば全てが対象となる
$git ls-files ':(glob)**/index.js'
index.js
src/ab/index.js
src/ac/index.js
src/index.js
$cd src
$git ls-files 'index.js'
index.js
$git ls-files ':/index.js'
../index.js
$git ls-files ':(top,glob)**/index?js'
../index.js
ab/index.js
ac/index.js
index.js
結論: ディレクトリ除外はgit pathspecの独自仕様だった
$git ls-files ':^src/ab' '**/index.js'
src/ac/index.js
src/index.js
Note: 1系だと使えない可能性あり
会社のレガシー環境だとgit 1.7.1を使っているが、pathspecに否定などは使えなかった(要確認)