Come grep (ricerca) codice commesso nella cronologia di Git


1435

Ho eliminato un file o un codice in un file qualche volta in passato. Posso grep nel contenuto (non nei messaggi di commit)?

Una soluzione molto scadente è grep il registro:

git log -p | grep <pattern>

Tuttavia, ciò non restituisce immediatamente l'hash di commit. Ho giocato con git grepinutilmente.


2
Questi post sul blog di Junio ​​C Hamano (manutentore di git) potrebbero essere interessanti per te: * Lo strumento di tracciamento dei contenuti per eccellenza di Linus (sulla ricerca di picconi, ad esempio git log -Se la colpa) * [Divertimento con "git log --grep"] [2] (ricerca di messaggi di commit ) * [Divertimento con "git grep"] [3] [2]: gitster.livejournal.com/30195.html [3]: gitster.livejournal.com/27674.html
Jakub Narębski


la risposta da un possibile duplicato funziona effettivamente: stackoverflow.com/a/1340245/492
CAD bloke

problema con questo è che non dà alcun contesto al cambiamento .. cioè chi / quando
Sonic Soul

Risposte:


1890

Per cercare contenuti di commit (ovvero, righe di origine effettive, anziché messaggi di commit e simili), devi fare:

git grep <regexp> $(git rev-list --all)

git rev-list --all | xargs git grep <expression> funzionerà se si verifica un errore "Elenco argomenti troppo lungo".

Se si desidera limitare la ricerca a qualche sottostruttura (ad esempio "lib / util"), sarà necessario passarla al rev-listsottocomando e grepanche:

git grep <regexp> $(git rev-list --all -- lib/util) -- lib/util

Questo verrà visualizzato in modo rapido attraverso tutto il testo di commit per regexp.

Il motivo del passaggio del percorso in entrambi i comandi è perché rev-listrestituirà l'elenco delle revisioni in cui sono lib/utilavvenute tutte le modifiche , ma è anche necessario passare in grepmodo che esegua solo la ricerca lib/util.

Immagina solo il seguente scenario: greppotrebbe trovare lo stesso <regexp>su altri file contenuti nella stessa revisione restituiti da rev-list(anche se su quella revisione non sono state apportate modifiche a quel file).

Ecco alcuni altri modi utili per cercare la tua fonte:

Cerca albero di lavoro per il testo corrispondente all'espressione regolare regexp:

git grep <regexp>

Cerca nella struttura di lavoro le righe di testo corrispondenti all'espressione regolare regexp1 o regexp2:

git grep -e <regexp1> [--or] -e <regexp2>

Cerca nella struttura di lavoro le righe di testo corrispondenti all'espressione regolare regexp1 e regexp2, riportando solo i percorsi dei file:

git grep -l -e <regexp1> --and -e <regexp2>

Cerca l'albero di lavoro per i file che hanno righe di testo corrispondenti all'espressione regolare regexp1 e righe di testo corrispondenti all'espressione regolare regexp2:

git grep -l --all-match -e <regexp1> -e <regexp2>

Cerca l'albero di lavoro per le righe modificate del modello di corrispondenza del testo:

git diff --unified=0 | grep <pattern>

Cerca in tutte le revisioni il testo corrispondente all'espressione regolare regexp:

git grep <regexp> $(git rev-list --all)

Cerca in tutte le revisioni tra rev1 e rev2 il testo corrispondente all'espressione regolare regexp:

git grep <regexp> $(git rev-list <rev1>..<rev2>)

61
Grazie, funziona benissimo! È triste però che sia necessario "$ (git rev-list --all)" e che non sia necessario un comodo interruttore per specificare la ricerca nell'intera cronologia di un ramo.
Ortwin Gentz,

3
Eccellente. +1. Il GitBook aggiunge alcuni dettagli ( book.git-scm.com/4_finding_with_git_grep.html ) e Junio ​​C Hamano illustra alcuni dei tuoi punti: gitster.livejournal.com/27674.html
VonC,

18
Sfortunatamente, non riesco a farlo con msysgit-1.7.4. E mi dice sh.exe": /bin/git: Bad file number. La risposta di VonC funziona anche con msysgit.
Devo dire il

4
Se viene visualizzato l'errore "Impossibile leggere l'albero" quando si richiama la cronologia di git grep con rev-list, potrebbe essere necessario ripulire le cose. Prova git gco controlla: stackoverflow.com/questions/1507463/…
Anthony Panozzo,

8
Sì, questo sembra fallire anche su Windows, ahimè.
mlissner,

552

Dovresti usare l' opzione piccone ( -S) di git log.

Per cercare Foo:

git log -SFoo -- path_containing_change
git log -SFoo --since=2009.1.1 --until=2010.1.1 -- path_containing_change

Vedi la cronologia di Git - trova la linea persa per parola chiave per ulteriori informazioni.


Come ha commentato Jakub Narębski :

  • questo cerca le differenze che introducono o rimuovono un'istanza di<string> . Di solito significa "revisioni in cui hai aggiunto o rimosso la riga con 'Foo'".

  • l' --pickaxe-regexopzione consente di utilizzare regex POSIX esteso invece di cercare una stringa. Esempio (da git log):git log -S"frotz\(nitfol" --pickaxe-regex


Come ha commentato Rob , questa ricerca fa distinzione tra maiuscole e minuscole: ha aperto una domanda di follow-up su come cercare senza distinzione tra maiuscole e minuscole.


3
Grazie, non ero a conoscenza di questa opzione. Sembra che questa sia la soluzione migliore se sei interessato ai messaggi di commit e la soluzione di Jeet è più appropriata se hai bisogno del comportamento grep UNIX tradizionale della corrispondenza della linea pura.
Ortwin Gentz,

@Ortwin: d'accordo (e ho votato a favore della soluzione scelta). la parte git logdella tua domanda mi ha confuso;)
VonC,

12
Combinalo con il -pflag per generare anche il diff.
Sander,

C'è un modo per escludere tutte le directory che corrispondono a modelli specifici usando git log -S?
BakaKuna,

3
@Anentropic ti occorrerebbero le --branches --allopzioni per cercare tutto il repository.
VonC,

249

Il mio modo preferito per farlo è con l git log' -Gopzione (aggiunta nella versione 1.7.4).

-G<regex>
       Look for differences whose added or removed line matches the given <regex>.

C'è una sottile differenza tra il modo in cui le opzioni -Ge -Sdeterminano se un commit corrisponde:

  • L' -Sopzione conta essenzialmente il numero di volte in cui la ricerca corrisponde a un file prima e dopo un commit. Il commit viene visualizzato nel registro se i conteggi prima e dopo sono diversi. Ad esempio, questo non mostrerà i commit in cui è stata spostata una linea corrispondente alla tua ricerca.
  • Con l' -Gopzione, il commit viene visualizzato nel registro se la ricerca corrisponde a una riga aggiunta, rimossa o modificata.

Prendi questo commit come esempio:

diff --git a/test b/test
index dddc242..60a8ba6 100644
--- a/test
+++ b/test
@@ -1 +1 @@
-hello hello
+hello goodbye hello

Poiché il numero di volte in cui "ciao" appare nel file è lo stesso prima e dopo questo commit, non corrisponderà all'utilizzo -Shello. Tuttavia, poiché è stata apportata una modifica a una corrispondenza di riga hello, il commit verrà mostrato utilizzando -Ghello.


2
Esiste un modo per mostrare il contesto di modifica corrispondente nell'output del registro git?
Thilo-Alexander Ginkel,

13
@ Thilo-AlexanderGinkel - Di solito aggiungo solo l' -popzione per mostrare un diff per ogni commit. Quindi quando il registro viene aperto nel mio cercapersone, cerco qualunque cosa stia cercando. Se il tuo cercapersone è lesse tu git log -Ghello -p, puoi digitare /hello, premere Entere utilizzare ne Nper trovare le occorrenze successive / precedenti di "ciao".
Tyler Holien,

Ho riscontrato un problema interessante con -GRegex: se la riga di comando utilizza UTF-8 e il file che stai guardando utilizza una codifica ISO-Latina (8 bit), .*non riesce. Ad esempio, ho una modifica Vierter Entwurf-> Fünfter Entwurf, e mentre 'V.*ter Entwurf'produce una corrispondenza, 'F.*ter Entwurf'no.
U. Windl,

51

Se vuoi sfogliare le modifiche al codice (vedi cosa è stato effettivamente cambiato con la parola data nell'intera cronologia) vai in patchmodalità - Ho trovato una combinazione molto utile di fare:

git log -p
# Hit '/' for search mode.
# Type in the word you are searching.
# If the first search is not relevant, hit 'n' for next (like in Vim ;) )

11
La soluzione accettata non funziona per me né per il log git -S. Questo ha fatto!
rodvlopes,

29

git log può essere un modo più efficace di cercare testo tra tutti i rami, specialmente se ci sono molte corrispondenze e si desidera prima vedere le modifiche (rilevanti) più recenti.

git log -p --all -S 'search string'
git log -p --all -G 'match regular expression'

L'elenco di questi comandi di log consente di aggiungere o rimuovere la stringa / regex di ricerca specificata, (generalmente) prima più recente. L' -popzione fa sì che il diff rilevante sia mostrato dove il pattern è stato aggiunto o rimosso, in modo da poterlo vedere nel contesto.

Dopo aver trovato un commit pertinente che aggiunge il testo che stavi cercando (ad esempio 8beeff00d), trova i rami che contengono il commit:

git branch -a --contains 8beeff00d

Ciao, queste linee non sembrano funzionare affatto. Il mio comando è> git log -p --all -S 'stringa pubblica DOB {get; impostato; } = string.Empty; ' e ogni volta che provo a eseguirlo ottengo> fatal: argomento ambiguo 'stringa': revisione sconosciuta o percorso non nella struttura di lavoro. > Usa '-' per separare i percorsi dalle revisioni, in questo modo:> 'git <comando> [<revision> ...] - [<file> ...]'
user216652

@ user216652 Per qualche ragione le 'virgolette non raggruppano la stringa di ricerca come un singolo argomento. Invece, 'publicè l'argomento -Se tratta il resto come argomenti separati. Non sono sicuro in quale ambiente stai eseguendo, ma quel contesto sarebbe necessario per aiutare a risolvere i problemi. Suggerirei di aprire una domanda StackOverflow separata, se necessario, per aiutarti a risolvere i problemi, con tutto il contesto di come il tuo comando git viene inviato alla shell. Mi sembra che venga inviato tramite qualche altro comando? I commenti qui non sono il posto giusto per capirlo.
Edward Anderson,

26

Ho preso la risposta di Jeet e l'ho adattata a Windows (grazie a questa risposta ):

FOR /F %x IN ('"git rev-list --all"') DO @git grep <regex> %x > out.txt

Si noti che per me, per qualche motivo, il commit effettivo che ha eliminato questa regex non è apparso nell'output del comando, ma piuttosto un commit prima di esso.


2
+1 - e se vuoi evitare di premere "q" dopo ogni individuazione, aggiungi --no-pageril comando git alla fine
cgp

2
Inoltre, vorrei notare che l'aggiunta a un file di testo ha l'ulteriore vantaggio di visualizzare effettivamente il testo corrispondente. (aggiungi a un file di testo usando >>results.txtper quelli che non sono esperti nelle tubazioni di Windows ...
CG

1
E ho pensato che la sintassi di bash fosse brutta :)
smido

23

Cerca in qualsiasi revisione, qualsiasi file :

git rev-list --all | xargs git grep <regexp>

Cerca solo in alcuni file, ad esempio file XML:

git rev-list --all | xargs -I{} git grep <regexp> {} -- "*.xml"

Le righe dei risultati dovrebbero apparire così: 6988bec26b1503d45eb0b2e8a4364afb87dde7af: bla.xml: testo della riga trovata ...

È quindi possibile ottenere ulteriori informazioni come autore, data e diff utilizzando git show:

git show 6988bec26b1503d45eb0b2e8a4364afb87dde7af

11

Per semplicità, suggerirei di usare la GUI: gitk - Il browser del repository Git . È abbastanza flessibile

  1. Per cercare il codice:

    Inserisci qui la descrizione dell'immagine
  2. Per cercare i file:

    Inserisci qui la descrizione dell'immagine
  3. Naturalmente, supporta anche le espressioni regolari:

    Inserisci qui la descrizione dell'immagine

E puoi navigare attraverso i risultati usando le frecce su / giù.


6

Per chiunque cerchi di farlo in Sourcetree , non esiste alcun comando diretto nell'interfaccia utente (a partire dalla versione 1.6.21.0). Tuttavia, è possibile utilizzare i comandi specificati nella risposta accettata aprendo la finestra Terminale (pulsante disponibile nella barra degli strumenti principale) e copiandoli / incollandoli.

Nota: la vista Ricerca di Sourcetree può eseguire parzialmente la ricerca di testo per te. Premi Ctrl+ 3per accedere alla vista Cerca (o fai clic sulla scheda Cerca disponibile in basso). Dall'estrema destra, imposta Tipo di ricerca su Modifiche file, quindi digita la stringa che desideri cercare. Questo metodo presenta le seguenti limitazioni rispetto al comando precedente:

  1. Sourcetree mostra solo i commit che contengono la parola di ricerca in uno dei file modificati. Trovare il file esatto che contiene il testo di ricerca è di nuovo un'attività manuale.
  2. RegEx non è supportato.

4

Ogni volta che mi trovo al tuo posto, utilizzo la seguente riga di comando:

git log -S "<words/phrases i am trying to find>" --all --oneline  --graph

Spiegazione:

  1. git log- Devo scrivere di più qui; mostra i registri in ordine cronologico.
  2. -S "<words/phrases i am trying to find>" - Mostra tutti quei commit Git in cui qualsiasi file (aggiunto / modificato / cancellato) ha le parole / frasi che sto cercando di trovare senza i simboli '<>'.
  3. --all - Per imporre e cercare in tutti i rami.
  4. --oneline - Comprime il registro Git in una riga.
  5. --graph - Crea il grafico dei commit ordinati cronologicamente.

1
"Ogni volta che mi trovo al tuo posto, sento il bisogno di usare git!"
Sebi,

1
Questa è un'ottima risposta!
Alf Eaton,

@AlfEaton piacere mio!
surajs1n

2

La risposta di Jeet funziona in PowerShell.

git grep -n <regex> $(git rev-list --all)

Di seguito vengono visualizzati tutti i file, in qualsiasi commit, che contengono un password.

# Store intermediate result
$result = git grep -n "password" $(git rev-list --all)

# Display unique file names
$result | select -unique { $_ -replace "(^.*?:)|(:.*)", "" }

1

Quindi stai provando a sfogliare le versioni precedenti del codice cercando di vedere dove esiste l'ultima cosa?

Se lo facessi, probabilmente userei git bisect . Usando bisect, puoi specificare una versione valida conosciuta, una versione non nota nota e un semplice script che controlla se la versione è buona o cattiva (in questo caso un grep per vedere se il codice che stai cercando è presente ). L'esecuzione di questo troverà quando il codice è stato rimosso.


2
Sì, ma il tuo "test" può essere uno script che richiede il codice e restituisce "true" se il codice esiste e "false" in caso contrario.
Rob Di Marco,

2
Bene, se il codice fosse cattivo nella revisione 10, diventasse buono nella revisione 11 e diventasse nuovamente cattivo nella revisione 15 ...
Paolo

2
Sono d'accordo con Paolo. La ricerca binaria è appropriata solo per i valori "ordinati". Nel caso di git bisect, ciò significa che tutte le revisioni "buone" vengono prima di tutte le revisioni "cattive", a partire dal punto di riferimento, ma tale ipotesi non può essere fatta quando si cerca un codice transitorio. Questa soluzione potrebbe funzionare in alcuni casi, ma non è una buona soluzione per scopi generici.
Kent,

Penso che questo sia altamente inefficiente in quanto l'intero albero viene verificato più volte per bisect.
U. Windl,

0

Scenario: hai eseguito una ripulitura del codice utilizzando il tuo IDE. Problema: l'IDE ha ripulito più di quanto dovrebbe e ora il codice non viene compilato (risorse mancanti, ecc.)

Soluzione:

git grep --cached "text_to_find"

Troverà il file in cui è stato modificato "text_to_find".

Ora puoi annullare questa modifica e compilare il tuo codice.


0
git rev-list --all | xargs -n 5 git grep EXPRESSION

è una modifica della soluzione di Jeet , quindi mostra i risultati mentre cerca e non solo alla fine (che può richiedere molto tempo in un grande repository).


-1

Nel mio caso avevo bisogno di cercare un breve commit e, purtroppo, le soluzioni elencate non funzionavano.

Sono riuscito a farlo con (sostituire il token REGEX ):

for commit in $(git rev-list --all --abbrev-commit)
do
    if [[ $commit =~ __REGEX__ ]]; then 
        git --no-pager show -s --format='%h %an - %s' $commit
    fi
done
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.