8

Naive approach is find dir1 dir2 dir3 -type d -name .git | xargs -I {} dirname {} , but it's too slow for me, because I have a lot deep folder structures inside git repositories (at least I think that this is the reason). I've read about that I can use prune to prevent find to recurse into directories once it found something, but there's two things. I'm not sure how this works (I mean I don't understand what prune does although I've read man page) and the second it wouldn't work in my case, because it would prevent find to recurse into .git folder but not into all other folders.

So what I actually need is:

for all subdirectories check if they contain a .git folder and if it is then stop searching in this filesystem branch and report result. It would be perfect if this would also exclude any hidden directories from search.

6

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.

  • 2
    This of -prune this way: You start at the root of a tree you move down it and when a certain condition applies you cut of a whole subtree (like real "pruning"), so you won't look at any more nodes in this subtree. – phk Dec 31 '16 at 0:51
  • @phk oh, thanks. I seem to grasp it now. We searching directories -type d for which condition test -e ... is true and if it's true we execute actions -print -prune which means print it and cut subtree, right? – user1685095 Dec 31 '16 at 1:05
  • Yes, we cut the subtree of which it is the root. – phk Dec 31 '16 at 1:08
  • Quick one to use your solution to "update" all git repos: find . -type d -exec test -e '{}/.git' \; -print -prune | parallel cd "{}" \&\& git pull --rebase GNU parallel is a very handy replacement for xargs – Marcello Romani Sep 10 '18 at 8:31
2

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.)

  • if dir1 contains two directories dirx and diry that each contain a .git directory, this only reports dirx/.git – iruvar Dec 30 '16 at 20:15
  • @iruvar Ah OK, I misunderstood you in that case, I will try to redo the solution then. – phk Dec 30 '16 at 20:18
  • the issue with your new solution is this if dir1/.git exists, it still descends dir1/dirx, which, based on my reading of OP's requirement, is not desired – iruvar Dec 30 '16 at 20:55
  • @iruvar OK, added that as well. Any other ideas about what OP could have meant? ;-) – phk Dec 30 '16 at 21:39
  • @iruvar exactly – user1685095 Dec 30 '16 at 22:25
1

If you use locate, you could find directories with:

locate .git | grep "/.git$"

Result list is fast and further processing is easy, too.

1

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.

  • Note that .git can be a file as well - via git worktree – Steven Penny Oct 30 '18 at 17:16
  • Thanks @StevenPenny, I wasn't aware of that. I've now changed the -ds to -e. – Stéphane Chazelas Oct 30 '18 at 17:22
0

Use find ~/GIT-REPOSITORIES ( -exec test -d '{}'/.git \; ) -print -prune

time this to see the difference with and without prune.

This is based on solution in man page for find. You can edit out the cvs and svn if not required. man page content follows

find repo/ ( -exec test -d '{}'/.svn \; -or \ -exec test -d {}/.git \; -or -exec test -d {}/CVS \; ) \ -print -prune

Given the following directory of projects and their associated SCM administrative directories, perform an efficient search for the projects' roots: repo/project1/CVS repo/gnu/project2/.svn repo/gnu/project3/.svn repo/gnu/project3/src/.svn repo/project4/.git

In this example, -prune prevents unnecessary descent into directories that have already been discovered (for example we do not search project3/src because we already found project3/.svn), but ensures sibling directories (project2 and project3) are

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.