So finden Sie alle Git-Repositorys in bestimmten Ordnern (schnell)

9

Naiver Ansatz ist find dir1 dir2 dir3 -type d -name .git | xargs -I {} dirname {} , aber es ist zu langsam für mich, weil ich viele tiefe Ordnerstrukturen in Git-Repositories habe (zumindest denke ich, dass dies der Grund ist). Ich habe darüber gelesen, dass ich verhindern kann prune, dass Find in Verzeichnisse zurückkehrt, sobald es etwas gefunden hat, aber es gibt zwei Dinge. Ich bin mir nicht sicher, wie das funktioniert (ich meine, ich verstehe nicht, was prunefunktioniert, obwohl ich die Manpage gelesen habe) und das zweite würde in meinem Fall nicht funktionieren, weil es verhindern würde, dass findes in einen .gitOrdner zurückkehrt, aber nicht in alle andere Ordner.

Was ich also wirklich brauche, ist:

Überprüfen Sie für alle Unterverzeichnisse, ob sie einen .gitOrdner enthalten, und beenden Sie die Suche in diesem Dateisystemzweig und melden Sie das Ergebnis. Es wäre perfekt, wenn dies auch versteckte Verzeichnisse von der Suche ausschließen würde.

user1685095
quelle

Antworten:

7

Okay, ich bin mir immer noch nicht ganz sicher, wie das funktioniert, aber ich habe es getestet und es funktioniert.

.
├── a
│   ├── .git
│   └── a
│       └── .git
└── b
    └── .git

6 directories, 0 files

% find . -type d -exec test -e '{}/.git' ';' -print -prune
./a
./b

Ich freue mich darauf, dasselbe schneller zu machen.

user1685095
quelle
2
So -prunegeht's: Sie beginnen an der Wurzel eines Baumes und bewegen sich nach unten. Wenn eine bestimmte Bedingung zutrifft, schneiden Sie einen ganzen Teilbaum aus (wie echtes "Beschneiden"), sodass Sie keine Knoten mehr in diesem Teilbaum betrachten .
Phk
@phk oh, danke. Ich scheine es jetzt zu begreifen. Wir suchen in Verzeichnissen -type dnach der Bedingung test -e ..., und wenn dies der Fall ist, führen wir Aktionen aus -print -prune, dh drucken sie aus und schneiden den Teilbaum aus, oder?
user1685095
Ja, wir schneiden den Teilbaum, dessen Wurzel es ist.
Phk
Schnelle Verwendung Ihrer Lösung zum "Aktualisieren" aller Git-Repos: find . -type d -exec test -e '{}/.git' \; -print -prune | parallel cd "{}" \&\& git pull --rebaseGNU parallelist ein sehr praktischer Ersatz fürxargs
Marcello Romani
Sie erhalten keine Submodule, die auch Git-Repos sind. Möglicherweise möchten Sie sie durch rekursives Abrufen von Untermodulen abrufen, sobald Sie die von diesem Befehl zurückgegebene Root-Repos-Liste erhalten haben.
Hoijui
2

Mögliche Lösung

Für GNU findund andere Implementierungen, die Folgendes unterstützen -execdir:

find dir1 dir2 dir3 -type d -execdir test -d '.git' \; -print -prune

(siehe die Kommentare)

Zuvor besprochenes Zeug

Lösung, wenn das Beschneiden unten .gitausreicht

find dir1 dir2 dir3 -type d -path '*/.git' -print -prune | xargs -I {} dirname {}

Wenn -printf '%h'unterstützt wird (wie im Fall von GNUs find), brauchen wir nicht dirname:

find dir1 dir2 dir3 -type d -path '*/.git' -printf '%h\n' -prune

Sobald es auf einen Ordner .gitim aktuellen Pfad stößt, gibt es ihn aus und hört auf, weiter unten im Teilbaum zu suchen.

Lösung, wenn der gesamte Ordnerbaum entfernt werden soll, sobald a .gitgefunden wurde

Verwenden, -quitwenn Ihr es findunterstützt:

for d in dir1 dir2 dir3; do
  find "$d" -type d -name .git -print -quit
done | xargs -I {} dirname {}

(Nach diesem ausführlichen Beitrag von Stéphane wird Chazelas -quit in GNUs und FreeBSDs findsowie in NetBSDs als unterstützt -exit.)

Wieder mit -printf '%h'wenn unterstützt:

for d in dir1 dir2 dir3; do
  find "$d" -type d -name .git -printf '%h\n' -quit
done

Lösung zum Beschneiden auf derselben Ebene wie der .gitOrdner

Im Abschnitt "Mögliche Lösung" finden Sie die aktuelle Lösung für dieses spezielle Problem.

(Oh, und offensichtlich gehen die verwendeten Lösungen xargsdavon aus, dass die Pfade keine Zeilenumbrüche enthalten, da Sie sonst Null-Byte-Magie benötigen würden.)

phk
quelle
Wenn es dir1zwei Verzeichnisse enthält dirxund diryjedes ein .gitVerzeichnis enthält, meldet dies nur dirx/.git
iruvar
@iruvar Ah OK, ich habe dich in diesem Fall falsch verstanden, ich werde versuchen, die Lösung dann zu wiederholen.
Phk
Das Problem mit Ihrer neuen Lösung ist dir1/.git, dass sie, falls vorhanden, immer noch abfällt dir1/dirx, was nach meiner Lektüre der OP-Anforderungen nicht erwünscht ist
iruvar
@iruvar OK, fügte das auch hinzu. Irgendwelche anderen Ideen darüber, was OP hätte bedeuten können? ;-)
phk
@iruvar genau
user1685095
2

Idealerweise möchten Sie Verzeichnisbäume nach Verzeichnissen .gitdurchsuchen , die einen Eintrag enthalten, und die Suche weiter unten beenden (vorausgesetzt, Sie haben keine weiteren Git-Repos in Git-Repos).

Das Problem ist, dass bei finddieser Art der Überprüfung (dass ein Verzeichnis einen .gitEintrag enthält ) bei Standard ein Prozess erzeugt wird, der ein testDienstprogramm unter Verwendung des -execPrädikats ausführt , was weniger effizient ist als das Auflisten des Inhalts einiger Verzeichnisse.

Eine Ausnahme wäre, wenn Sie die findintegrierte boshShell verwenden (eine von @schily entwickelte POSIXified- Verzweigung der Bourne-Shell ), die ein -callPrädikat zum Auswerten von Code in der Shell enthält, ohne einen neuen sh-Interpreter erzeugen zu müssen:

#! /path/to/bosh
find . -name '.?*' -prune -o \
  -type d -call '[ -e "$1/.git" ]' {} \; -prune -print

Oder nutzen Sie 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' .

Länger, aber schneller als zsh's printf '%s\n' **/.git(:h)(das in alle nicht versteckten Verzeichnisse absteigt) oder GNUs find, find . -name '.?*' -prune -o -type d -exec test -e '{}/.git' \; -prune -printdie einen testBefehl in einem neuen Prozess für jedes nicht versteckte Verzeichnis ausführen.

Stéphane Chazelas
quelle
1
Beachten Sie, dass .gitdies auch eine Datei sein kann - viagit worktree
Steven Penny
1
Danke @StevenPenny, das war mir nicht bewusst. Ich habe jetzt das -ds in geändert -e.
Stéphane Chazelas
1

Wenn Sie find verwenden, können Sie Verzeichnisse finden mit:

locate .git | grep "/.git$"

Die Ergebnisliste ist schnell und die weitere Verarbeitung ist ebenfalls einfach.

Jarivaa
quelle
2
locate '*/.git'sollte genug sein.
Stéphane Chazelas
0

Verwenden

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

timedies, um den Unterschied mit und ohne zu sehen -prune.

Dies basiert auf einer Lösung in der man find. Sie können das CVSund, svnfalls nicht erforderlich, bearbeiten . Manpage-Inhalt folgt

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

Führen Sie anhand des folgenden Projektverzeichnisses und der zugehörigen SCM-Verwaltungsverzeichnisse eine effiziente Suche nach den Wurzeln der Projekte durch:

repo/project1/CVS
repo/gnu/project2/.svn
repo/gnu/project3/.svn
repo/gnu/project3/src/.svn
repo/project4/.git

In diesem Beispiel wird ein -pruneunnötiger Abstieg in bereits erkannte Verzeichnisse verhindert (z. B. suchen wir nicht project3/src, weil wir bereits gefunden haben project3/.svn), sondern es wird sichergestellt, dass Geschwisterverzeichnisse ( project2und project3) gefunden werden.

quiet_penguin
quelle