2008-11-10 6 views
51

Basta dire che ho un file: "HelloWorld.pm" in più sottodirectory all'interno di un repository Git.Posso usare Git per cercare nomi di file corrispondenti in un repository?

Vorrei inviare un comando per trovare i percorsi completi di tutti i file corrispondenti "HelloWorld.pm":

Ad esempio:

/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 

Come posso usare Git per trovare in modo efficiente tutti i i percorsi completi che corrispondono a un determinato nome file?

Mi rendo conto che posso farlo con il comando di ricerca Linux/Unix ma speravo di evitare la scansione di tutte le sottodirectory cercando le istanze del nome file.

risposta

19

Prova:

git ls-tree -r HEAD | grep HelloWorld.pm 
+1

O su Windows: 'git ls-tree -r HEAD | findstr HelloWorld.pm' –

79

git ls-files vi darà una lista di tutti i file nel repository. È possibile passare un modello per ottenere file corrispondenti a tale modello.

git ls-files '*/HelloWorld.pm' 

Se volete trovare una serie di file e grep attraverso il loro contenuto, è possibile farlo con git grep:

git grep some-string -- '*/HelloWorld.pm' 
+0

I ls-files possono anche avere uno schema. –

+0

@jleedev Ah, giusto. Aggiornato la mia risposta per semplificarlo e risolvere un problema con il modello in 'git grep'. –

+0

(Annoyingly, si chiama un [pathspec] (http://www.kernel.org/pub/software/scm/git/docs/gitglossary.html#def_pathspec) in gitglossary (7), ma quel termine non è usato in modo coerente altrove.) –

38

Hmm, la domanda iniziale era di circa il repository. Un repository contiene più di 1 commit (nel caso generale almeno), ma le risposte date prima di cercare solo attraverso un commit.

Perché non sono riuscito a trovare una risposta che cerchi effettivamente l'intera cronologia dei commit Ho scritto un rapido script di forza bruta git-find-by-name che prende (quasi) tutti i commit in considerazione.

#! /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 * 

Forse c'è un modo più elegante.

Si noti il ​​modo in cui il parametro viene passato in grep, quindi corrisponderà alle parti del nome file. Se ciò non è desiderato, ancorare l'espressione di ricerca e/o aggiungere le opzioni di grep appropriate.

Per le cronologie profonde l'output potrebbe essere troppo rumoroso, ho pensato a uno script che converte un elenco di revisioni in un intervallo, come l'opposto di ciò che git rev-list può fare. Ma finora è rimasto un pensiero.

+1

Grazie mille per questa grande risposta. Mi ha salvato la giornata. Vorrei poterti invogliare di più. –

+0

Grande sceneggiatura. Tuttavia non ero in grado di usarlo perché il mio repository git è così grande che lo script ha invaso il mio hard disk :( –

+0

@ ArneBöckmann Basta spostare il comando grep nell'ultimo ciclo e rimuovere tutto dopo ogni grep –

3

[E 'un po' di abusi, lo ammetto, ma non posso commentare ancora e ho pensato di migliorare la risposta di @ Uwe-Geuder.]

#!/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 

Anche in questo caso, uno a @ Uwe-Geuder per una grande risposta.

Se siete interessati alla BASH sé:

A meno che la garanzia della parola-splitting in un ciclo for (come quando si usa un array come questo: for item in "${array[@]}"), mi raccomando usando while IFS= read var ; do ... ; done < <(command) quando l'output del comando su cui stai eseguendo il ciclo è separato da una nuova riga (o read -d'' quando l'output è separato dalla stringa nulla $'\0'). Mentre lo git rev-list --all è garantito per utilizzare stringhe esadecimali da 40 byte (senza spazi), non mi piace mai rischiare.Ora posso facilmente modificare il comando da git rev-list --all a qualsiasi comando che produce linee

Inoltre, consiglio di utilizzare i meccanismi BASH incorporati per iniettare input e filtrare l'output anziché i file temporanei.

+0

Non sono sicuro del motivo per cui viene utilizzata una tale sostituzione di processo, quando si può semplicemente eseguire il pipe: 'git rev-list --all | while read rev; do; git ls-tree --full-tree -r $ rev | cut -c54- | fgrep - "$ name"; done | sort -u' –

7
git ls-files | grep -i HelloWorld.pm 

grep -i rende non sensibile la distinzione tra maiuscole e minuscole.

0

La sceneggiatura di Uwe Geuder (@ uwe-geuder) è ottima, ma non è necessario eseguire il dump di tutti gli output di ls-tree nella propria directory, non filtrata.

Molto più veloce e con meno di stoccaggio: Eseguire il grep sull'uscita e poi conservarlo, come mostrato in questo gist

Problemi correlati