LoginSignup
10
4

More than 3 years have passed since last update.

git pathspecを使った高度なgrep

Last updated at Posted at 2018-11-04
$git --version
git version 2.19.1

tl;dr

  • git grepgit 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が最後の引数ならなくても良い)

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はクオートで囲まないと意図通りに動かないので注意

後半が本題

  • :始まりで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に否定などは使えなかった(要確認)

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4