19

Using git list-files gives me the directories and the files tracked within. Is there a command like:

git list-directories

or something similar that lists only the tracked non-empty non-recursive directory names?

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
tmaric
  • 5,347
  • 4
  • 42
  • 75
  • 2
    Probably not. Git tracks files, not directories. You might be able to somehow derive that information from the `list-files` command though and define a git alias that does what you want. – Ajedi32 Nov 27 '13 at 16:12
  • @Ajedi32 Thanks, then I'll do that. – tmaric Nov 27 '13 at 16:13
  • Cool. Once you figure it out be sure to post an answer here for the benefit of anyone else who might have the same question in the future. – Ajedi32 Nov 27 '13 at 16:16

4 Answers4

25

git ls-files | xargs -n 1 dirname | uniq

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
CDub
  • 13,146
  • 4
  • 51
  • 68
  • I'd do a sed, `...|sed s,/[^/]*$,,|uniq`, avoiding `xargs -n 1` seems like a good rule of thumb to me. – jthill Nov 27 '13 at 16:30
  • 1
    @CDub : This gives out the subdirectories as well. Still useful though. – tmaric Nov 27 '13 at 16:31
  • 1
    Why wouldn't you want subdirectories? – CDub Nov 27 '13 at 16:32
  • 1
    @CDub: I'm tracking directories that define simulation test cases. There are a lot of subdirectories. When the parameters I'm using to set up the tests differ strongly in numbers and values, I create another test and start tracking it with git, I don't have it defined as another version of the same directory in that case. Once the algorithm is up and running I need to decide which tests to keep, so I need to do some cleaning up: so I only want to list the top level directories to see if I am tracking all of them, or which ones are not tracked. – tmaric Nov 27 '13 at 16:57
  • I believe that this misses directories that have no files, just other directories. Here is a method that considers them as well: https://stackoverflow.com/a/54162211/895245 – Ciro Santilli OurBigBook.com Jan 12 '19 at 17:39
  • this won't work if file names contain newlines. See [Why *not* parse `ls` (and what to do instead)?](https://unix.stackexchange.com/q/128985/44425) – phuclv Jun 11 '20 at 07:35
2
git ls-tree -rt HEAD:./ | awk '{if ($2 == "tree") print $4;}'

If the files may contain spaces, we would have to play around with: Using awk to print all columns from the nth to the last which is not very fun.

-r makes it recurse, and -t makes it print trees when recursing, which is turned off by default.

I was unable to use ls-tree as mentioned at: https://stackoverflow.com/a/20247815/895245 because it is hard to deal with directories that have no files, just other directories.

This method also shows empty trees.

Tested on Git 2.19.0.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
0

The following command (leveraging git ls-files) returns all paths of directories directly containing committed or staged files.

This command can be executed irrespective of the 'current working directory' it is executed in (i.e., it can be executed in any subdirectory of this git repository, yet will always return paths including those from other subdirectories (instead of only returning paths of only the current subdirectories(that being a characteristic/limitation/caveat/gotcha I noticed with other answers))).

The returned paths are relative to this git repository's root directory.

git ls-files --full-name $(git rev-parse --show-toplevel) | xargs -n 1 dirname | sort --version-sort | uniq | grep --invert-match '^\.$'

Explanation:

  • git ls-files --full-name ...: return file paths relative to "starting point".
  • ... $(git rev-parse --show-toplevel): set the "starting point" to this git repository's root directory. This is important if you want to have all paths, and not only paths scoped to the current working directory this command is executed in.
  • xargs -n 1 dirname: The previous pipe step has returned committed and staged files. This pipe step returns their containing directory.
  • sort --version-sort: by default, sort sorts strings with dots (e.g. .config/foo) unexpectedly. The --version-sort option results in a more logical sorting.
  • uniq: filter away duplicate entries.
  • grep --invert-match '^\.$': previous pipe steps created a line entry ".", signifying that there exists at least one committed or staged file directly in this git repository's root directory. This "." entry may or may not be desirable. When not desired, this grep statement filters it away.

Example git repository (located within /home/someuser/git/testrepository/):

# committed files (listed with "git ls-files /home/someuser/git/testrepository/")
.config/foo.conf
.config/very/nested/deep.txt
README.md
a-directory/a1-file.txt
a-directory/a2-file.txt
z-directory/z-file.txt
z-directory/z-subdirectory/z-subfile.txt

Running aforementioned command from any contained (sub-)directory, e.g. from /home/someuser/git/testrepository/.config/very/:

$ cd /home/someuser/git/testrepository/.config/very/
$ git ls-files --full-name $(git rev-parse --show-toplevel) | xargs -n 1 dirname | sort --version-sort | uniq | grep --invert-match '^\.$'
.config
.config/very/nested
a-directory
z-directory
z-directory/z-subdirectory

Notice:

  • .config/very is not present in the returned list. This is because there doesn't exist any file committed or staged directly in .config/very/.
  • .config/very/nested is present in the returned list as it (directly) contains the committed file .config/very/nested/deep.txt.

Also returning paths from directories which don't have any committed files directly in them (but which contain subdirectories with committed files)

The following command (leveraging git ls-tree) will return all paths leading towards committed files, including intermediate directories. Different to the previous command, it will not include paths to directories which only contain staged-and-never-committed files:

git ls-tree -rt HEAD --name-only --full-tree | xargs -n 1 dirname | sort --version-sort | uniq | grep --invert-match '^\.$'

This is the output for the previously given example git repository:

$ cd /home/someuser/git/testrepository/.config/very/
$ git ls-tree -rt HEAD --name-only --full-tree | xargs -n 1 dirname | sort --version-sort | uniq | grep --invert-match '^\.$'
.config
.config/very
.config/very/nested
a-directory
z-directory
z-directory/z-subdirectory

Notice:

  • With this command, .config/very is present in the returned list despite that directory not having a committed file directly in it. It is included because it leads towards a committed file (.config/very/nested/deep.txt).
Abdull
  • 26,371
  • 26
  • 130
  • 172
0

How about the following solution:

git ls-files | sed -e '/^[^/]*$/d' -e 's|/[^/]*$||' | sort -u
Brad Bell
  • 21
  • 3