$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に否定などは使えなかった(要確認)