mkjeldsen
mkjeldsen

Reputation: 2180

Git: list filtered paths from root in subdirectory

git diff can list all file differences between two revisions:

$ git diff --name-only master
foo.txt
bar/bar.txt
bar/baz.qux

It accepts an optional <path> filter in the form of a glob pattern that names paths to include:

$ git diff --name-only master -- '*.txt'
foo.txt
bar/bar.txt

<path> is relative to the working directory:

$ cd bar/
$ git diff --name-only master --    '*.txt' # no output
$ git diff --name-only master -- '../*.txt'
foo.txt
bar/bar.txt

Is there a way to invoke git diff to achieve the effect of

$ cd bar/
$ git diff --name-only master | grep '\.txt$'
foo.txt
bar/bar.txt

without relying on external tools? A <path> with a prefix of / does not seem to work.

Upvotes: 1

Views: 55

Answers (3)

phd
phd

Reputation: 94483

$ cd bar/
$ git diff --name-only master -- $(git rev-parse --show-cdup)/'*.txt'
foo.txt
bar/bar.txt

See https://git-scm.com/docs/git-rev-parse#Documentation/git-rev-parse.txt---show-cdup

Upvotes: 2

torek
torek

Reputation: 488193

The arguments to git diff are what Git calls a pathspec, which is defined in the gitglossary. Here is a snippet from the (rather long) description:

A pathspec that begins with a colon : has special meaning. In the short form, the leading colon : is followed by zero or more "magic signature" letters (which optionally is terminated by another colon :), and the remainder is the pattern to match against the path. The "magic signature" consists of ASCII symbols that are neither alphanumeric, glob, regex special characters nor colon. The optional colon that terminates the "magic signature" can be omitted if the pattern begins with a character that does not belong to "magic signature" symbol set and is not a colon.

In the long form, the leading colon : is followed by an open parenthesis (, a comma-separated list of zero or more "magic words", and a close parentheses ), and the remainder is the pattern to match against the path. ...

top

    The magic word top (magic signature: /) makes the pattern match from the root of the working tree, even when you are running the command from inside a subdirectory.

Hence we can use :(top) or :/:. Here's the same setup as in your example:

$ git diff --name-only
bar/bar.txt
bar/baz.qux
foo.txt

(these are all modified)

$ cd bar
$ git diff --name-only
bar/bar.txt
bar/baz.qux
foo.txt

(all still listed, even in subdirectory, when there is no pathspec)

$ git diff --name-only -- '*.txt'
bar/bar.txt

(only one listed due to being in subdirectory plus pathspec). Now we use the pathspec specifiers:

$ git diff --name-only -- ':(top)*.txt'
bar/bar.txt
foo.txt
$ git diff --name-only -- ':/:*.txt'
bar/bar.txt
foo.txt

(:(top) or :/: as a prefix gets everything listed)

See the documentation for the remaining words, which are fairly powerful tools.

Upvotes: 3

mkjeldsen
mkjeldsen

Reputation: 2180

Arguably, one option is to change to the repository root, which can be safely done in a sub-shell, before executing the basic command:

$ cd bar/
$ ( cd $(git rev-parse --show-toplevel) &&
    git diff --name-only master -- '*.txt' )
foo.txt
bar/bar.txt

This introduces no new external tools—the shell provides sub-shells and cd, and Git is given—but it doesn't technically satisfy the requirement of no external tools.

Upvotes: 2

Related Questions