Kann ich mit Git nach passenden Dateinamen in einem Repository suchen?

75

Sagen Sie einfach, ich habe eine Datei: "HelloWorld.pm" in mehreren Unterverzeichnissen innerhalb eines Git-Repositorys.

Ich möchte einen Befehl ausgeben, um die vollständigen Pfade aller Dateien zu finden, die mit "HelloWorld.pm" übereinstimmen:

Zum Beispiel:

/path/to/repository/HelloWorld.pm
/path/to/repository/but/much/deeper/down/HelloWorld.pm
/path/to/repository/please/dont/make/me/search/through/the/lot/HelloWorld.pm

Wie kann ich Git verwenden, um alle vollständigen Pfade, die einem bestimmten Dateinamen entsprechen, effizient zu finden?

Mir ist klar, dass ich dies mit dem Linux / Unix-Befehl find tun kann, aber ich hatte gehofft, nicht alle Unterverzeichnisse nach Instanzen des Dateinamens durchsuchen zu müssen.

Neuling Git
quelle

Antworten:

112

git ls-filesSie erhalten eine Liste aller Dateien im aktuellen Status des Repositorys (Cache oder Index). Sie können ein Muster übergeben, um Dateien zu erhalten, die diesem Muster entsprechen.

git ls-files HelloWorld.pm '**/HelloWorld.pm'

Wenn Sie eine Reihe von Dateien finden und deren Inhalt durchsuchen möchten, können Sie dies tun mit git grep:

git grep some-string -- HelloWorld.pm '**/HelloWorld.pm'
Brian Campbell
quelle
ls-Dateien können auch ein Muster annehmen.
Josh Lee
1
Denken Sie daran, '** / HelloWorld.pm' anstelle von '* / HelloWorld.pm' zu verwenden, um eine beliebige Tiefe des Repositorys nach Übereinstimmungen zu durchsuchen. Das Beispiel des OP enthält Dateien auf verschiedenen Ebenen.
John Rix
8
'git ls-files' listet keine Dateien im Repository auf. Es listet Dateinamen im Index (Staging-Bereich) oder im Arbeitsbaum auf. Es ist völlig normal, dass sich ein Dateiname irgendwo im Repository befindet, aber nicht im Index oder im Arbeitsbaum. Der Dateiname befindet sich möglicherweise in einem anderen Zweig als dem, den Sie gerade ausgecheckt haben. Die Antwort von @GregHewgill sollte hier als korrekter angesehen werden.
Steveve
1
(Das 5-minütige Kommentarbearbeitungsfenster wurde verpasst ...) Die Antworten von Uwe Geuder und Dean Hall erweitern im Wesentlichen Gregs, indem sie alle Zweige und Tags durchlaufen und den Fall von Dateien behandeln, die in anderen Zweigen benannt sind (oder die gelöscht wurden). .
Steveve
1
Beachten Sie, dass HelloWorld.pm nicht im Stammverzeichnis Ihres Projekts gefunden wird. In diesem Fall müssen Sie verwendengit ls-files 'HelloWorld.pm' '*/HelloWorld.pm'
Chris Maes
44

Hmm, die ursprüngliche Frage betraf das Repository. Ein Repository enthält mehr als 1 Commit (zumindest im allgemeinen Fall), aber die vor der Suche gegebenen Antworten durchsuchen nur ein Commit.

Da ich keine Antwort finden konnte, die wirklich den gesamten Commit-Verlauf durchsucht, habe ich ein schnelles Brute-Force-Skript geschrieben, das (fast) alle Commits berücksichtigt.

#! /bin/sh
tmpdir=$(mktemp -td git-find.XXXX)
trap "rm -r $tmpdir" EXIT INT TERM

allrevs=$(git rev-list --all)
# well, nearly all revs, we could still check the log if we have
# dangling commits and we could include the index to be perfect...

for rev in $allrevs
do
  git ls-tree --full-tree -r $rev >$tmpdir/$rev 
done

cd $tmpdir
grep $1 * 

Vielleicht gibt es einen eleganteren Weg.

Bitte beachten Sie die triviale Art und Weise, wie der Parameter an grep übergeben wird, damit er mit Teilen des Dateinamens übereinstimmt. Wenn dies nicht erwünscht ist, verankern Sie Ihren Suchausdruck und / oder fügen Sie geeignete Grep-Optionen hinzu.

Für tiefe Historien könnte die Ausgabe zu verrauscht sein. Ich dachte an ein Skript, das eine Liste von Revisionen in einen Bereich konvertiert, wie das Gegenteil von dem, was Git Rev-List tun kann. Aber bisher ist es ein Gedanke geblieben.

Uwe Geuder
quelle
Tolles Drehbuch. Ich konnte es jedoch nicht verwenden, da mein Git-Repo so groß ist, dass das Skript meine Festplatte überflutet hat :(
Arne Böckmann
@ ArneBöckmann Verschieben Sie einfach den Befehl grep in die letzte Schleife und entfernen Sie nach jedem grep alles.
Uwe Geuder
9
Ihr Code kann zu einem Einzeiler gemacht werden : git rev-list --all | xargs -I '{}' git ls-tree --full-tree -r '{}' | grep '.*HelloWorld\.pm$'. Dies löst auch das Problem der Festplattenüberflutung.
Subhacom
@subhacom Ihr Oneliner sollte die akzeptierte Antwort sein
Kochfelder
23

Versuchen:

git ls-tree -r HEAD | grep HelloWorld.pm
Greg Hewgill
quelle
1
Oder unter Windows:git ls-tree -r HEAD | findstr HelloWorld.pm
John Rix
man git ls-treezeigt, dass dies -rbedeutet "In Unterbäume zurückgreifen". Ich weiß nicht was das bedeutet. Können Sie bitte erklären, was dies bedeutet?
Gabriel Staples
@JohnRix, zuletzt habe ich überprüft, ob Sie das von Git für Windows bereitgestellte Terminal verwenden , das ich unter Windows sehr empfehle . Es unterstützt allgemeine Linux-Befehle wie Piping to grep, Ausführen von Bash-Skripten usw., sodass diese Antwort einwandfrei funktionieren sollte wie es ist. Probieren Sie es aus und lassen Sie es mich wissen. Ich habe Windows für Ubuntu vor ein paar Jahren komplett über Bord geworfen.
Gabriel Staples
@GabrielStaples, zu Recht oder zu Unrecht, ich bin ein bisschen verrückt, wenn es um alternative Terminals in Windows geht (möglicherweise teilweise, weil CygWin vor vielen Jahren gebräunt hat), und bleibe eher beim kleinsten gemeinsamen Nenner immer für mich verfügbar sein. (Andererseits steht die Veröffentlichung von WSL 2 unter Windows 10 unmittelbar bevor, und Berichten zufolge wird es sehr effizient funktionieren. Vielleicht verabschiede ich mich endlich von der alten Windows-Eingabeaufforderung!)
John Rix
Übrigens -rsollte der Befehl ls-tree veranlassen, Unterverzeichnisse im Repository zu durchsuchen.
John Rix
9
git ls-files | grep -i HelloWorld.pm

Das grep -i macht die Groß- und Kleinschreibung von grep unabhängig.

Stier
quelle
Ich denke, das ist mit Sicherheit die beste Antwort. Siehe meine Kommentare unter der am besten bewerteten Antwort: stackoverflow.com/questions/277546/…
Gabriel Staples
4

[Ich gebe zu, es ist ein bisschen Kommentarmissbrauch, aber ich kann noch keinen Kommentar abgeben und dachte, ich würde die Antwort von @ uwe-geuder verbessern.]

#!/bin/bash
#
#

# I'm using a fixed string here, not a regular expression, but you can easily
# use a regular expression by altering the call to grep below.
name="$1"

# Verify usage.
if [[ -z "$name" ]]
then
    echo "Usage: $(basename "$0") <file name>" 1>&2
    exit 100
fi  

# Search all revisions; get unique results.
while IFS= read rev
do
    # Find $name in $rev's tree and only use its path.
    grep -F -- "$name" \
        <(git ls-tree --full-tree -r "$rev" | awk '{ print $4 }')
done < \
    <(git rev-list --all) \
    | sort -u

Nochmals +1 an @ uwe-geuder für eine großartige Antwort.

Wenn Sie sich für die BASH selbst interessieren:

Sofern Ihnen die Wortaufteilung in einer for-Schleife nicht garantiert ist (wie bei Verwendung eines Arrays wie diesem :) for item in "${array[@]}", empfehle ich dringend, while IFS= read var ; do ... ; done < <(command)die Befehlsausgabe zu verwenden, wenn die Schleife durch Zeilenumbrüche getrennt ist (oder read -d''wenn die Ausgabe durch die Zeile getrennt ist) Nullzeichenfolge $'\0'). Obwohl git rev-list --allgarantiert 40-Byte-Hexadezimalzeichenfolgen (ohne Leerzeichen) verwendet werden, gehe ich nie gerne Risiken ein. Ich kann den Befehl jetzt einfach von git rev-list --alljedem Befehl ändern, der Zeilen erzeugt

Ich empfehle auch die Verwendung integrierter BASH-Mechanismen zum Einfügen von Eingaben und Filtern von Ausgaben anstelle von temporären Dateien.

Dean Hall
quelle
Ich bin mir nicht sicher, warum so viel Prozesssubstitution verwendet wird, wenn Sie einfach weiterleiten können:git rev-list --all | while read rev; do; git ls-tree --full-tree -r $rev | cut -c54- | fgrep -- "$name"; done | sort -u
Simon Buchan
Skript-Echos-Datei, aber nicht welche Revision es gefunden wurde. Nützlich, um auch zu wiederholen, um $revzu zeigen, in welchen Revisionen es gefunden wurde.
LB2
2

Das Skript von Uwe Geuder (@ uwe-geuder) ist großartig, aber es ist wirklich nicht nötig, jede der ls-tree-Ausgaben ungefiltert in einem eigenen Verzeichnis abzulegen.

Viel schneller und mit weniger Speicherplatz: Führen Sie das grep für die Ausgabe aus und speichern Sie es dann, wie in dieser Übersicht gezeigt

Dirkjot
quelle
Das Wesentliche kann sich ändern, und es ist aus Bequemlichkeitsgründen besser, das Code-Snippet in Ihre Antwort aufzunehmen, insbesondere wenn es kurz ist. Ich empfehle Ihnen, das Code-Snippet aus dem Kern in Ihre Antwort zu kopieren. Lassen Sie einfach den Link zum Kern, um ihn als Quelle zu nennen, falls Sie den Kern jemals aktualisieren, aber nicht diese Antwort.
Gabriel Staples
Jetzt, wo ich mir Ihr Skript genauer ansehe, sehe ich, dass dies tatsächlich sehr nützlich ist. Ihre Antwort benötigt jedoch 1) einen Titel: # How to find a long-lost file by searching all commitsund 2) den Code aus dem Kern, der direkt in diese Antwort eingefügt wurde.
Gabriel Staples