How to find all git repositories within given folders (fast)
Okay, I still don't totally sure how this works, but I've tested it and it works.
.
├── a
│ ├── .git
│ └── a
│ └── .git
└── b
└── .git
6 directories, 0 files
% find . -type d -exec test -e '{}/.git' ';' -print -prune
./a
./b
I'm looking forward into making the same faster.
Possible Solution
For GNU find
and other implementations that support -execdir
:
find dir1 dir2 dir3 -type d -execdir test -d '.git' \; -print -prune
(see the comments)
Previously discussed stuff
Solution if pruning below .git
is enough
find dir1 dir2 dir3 -type d -path '*/.git' -print -prune | xargs -I {} dirname {}
If -printf '%h'
is supported (as in the case of GNU's find
) we don't need dirname
:
find dir1 dir2 dir3 -type d -path '*/.git' -printf '%h\n' -prune
Once it comes across a folder .git
in the current path it will output it and then stop looking further down the subtree.
Solution if the whole folder tree should be pruned once a .git
is found
Using -quit
if your find
supports it:
for d in dir1 dir2 dir3; do
find "$d" -type d -name .git -print -quit
done | xargs -I {} dirname {}
(According to this detailed post by Stéphane Chazelas -quit
is supported in GNU's and FreeBSD's find
and in NetBSD as -exit
.)
Again with -printf '%h'
if supported:
for d in dir1 dir2 dir3; do
find "$d" -type d -name .git -printf '%h\n' -quit
done
Solution for pruning at the same level as where the .git
folder is
See the "Possible Solution" part for the current solution for this particular problem.
(Oh and obviously the solutions using xargs
assume there are no newlines in the paths, otherwise you would need null-byte magic.)
Ideally, you'd want to crawl directory trees for directories that contain a .git
entry and stop searching further down those (assuming you don't have further git repos inside git repos).
The problem is that with standard find
, doing this kind of check (that a directory contains a .git
entry) involves spawning a process that executes a test
utility using the -exec
predicate, which is going to be less efficient than listing the content of a few directories.
An exception would be if you use the find
builtin of the bosh
shell (a POSIXified fork of the Bourne shell developed by @schily) which has a -call
predicate to evaluate code in the shell without having to spawn a new sh interpreter:
#! /path/to/bosh
find . -name '.?*' -prune -o \
-type d -call '[ -e "$1/.git" ]' {} \; -prune -print
Or use perl
's File::Find
:
perl -MFile::Find -le '
sub wanted {
if (/^\../) {$File::Find::prune = 1; return}
if (-d && -e "$_/.git") {
print $File::Find::name; $File::Find::prune = 1
}
}; find \&wanted, @ARGV' .
Longer, but faster than zsh
's printf '%s\n' **/.git(:h)
(which descends into all non-hidden directories), or GNU find
's find . -name '.?*' -prune -o -type d -exec test -e '{}/.git' \; -prune -print
which runs one test
command in a new process for each non-hidden directory.