Recuperare il registro di commit per una riga specifica in un file?


503

C'è un modo per ottenere git che ti dia un registro di commit solo per i commit che hanno toccato una determinata linea in un file?

Come git blame, ma git blameti mostrerà l'ULTIMO commit che ha toccato una linea particolare.

Mi piacerebbe davvero ottenere un registro simile di, non l'elenco di commit in qualsiasi parte del file, ma solo i commit che hanno toccato una determinata riga.


2
Vedi anche: Git
Blame

Risposte:


632

Vedi anche Git: scopri quali commit hanno mai toccato una serie di linee .


Da Git 1.8.4 , git logdeve -Lvedere l'evoluzione di una gamma di linee.

Ad esempio, supponiamo di guardare git blamel'output di. Qui -L 150,+11significa "guarda solo le linee da 150 a 150 + 11":

$ git blame -L 150,+11 -- git-web--browse.sh
a180055a git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:36 +0100 150)            die "The browser $browser is not
a180055a git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:36 +0100 151)    fi
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 152) fi
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 153) 
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 154) case "$browser" in
81f42f11 git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:38 +0100 155) firefox|iceweasel|seamonkey|iceape)
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 156)    # Check version because firefox < 2.0 do
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 157)    vers=$(expr "$($browser_path -version)" 
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 158)    NEWTAB='-new-tab'
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 159)    test "$vers" -lt 2 && NEWTAB=''
a0685a4f git-web--browse.sh (Dmitry Potapov   2008-02-09 23:22:22 -0800 160)    "$browser_path" $NEWTAB "$@" &

E vuoi conoscere la storia di quella che ora è la linea 155.

Quindi utilizzare git log. Qui, -L 155,155:git-web--browse.shsignifica "traccia l'evoluzione delle linee da 155 a 155 nel file denominato git-web--browse.sh".

$ git log --pretty=short -u -L 155,155:git-web--browse.sh
commit 81f42f11496b9117273939c98d270af273c8a463
Author: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>

    web--browse: support opera, seamonkey and elinks

diff --git a/git-web--browse.sh b/git-web--browse.sh
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
@@ -143,1 +143,1 @@
-firefox|iceweasel)
+firefox|iceweasel|seamonkey|iceape)

commit a180055a47c6793eaaba6289f623cff32644215b
Author: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>

    web--browse: coding style

diff --git a/git-web--browse.sh b/git-web--browse.sh
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
@@ -142,1 +142,1 @@
-    firefox|iceweasel)
+firefox|iceweasel)

commit 5884f1fe96b33d9666a78e660042b1e3e5f9f4d9
Author: Christian Couder <chriscool@tuxfamily.org>

    Rename 'git-help--browse.sh' to 'git-web--browse.sh'.

diff --git a/git-web--browse.sh b/git-web--browse.sh
--- /dev/null
+++ b/git-web--browse.sh
@@ -0,0 +127,1 @@
+    firefox|iceweasel)

2
'git log --topo-order --graph -u -L 155.155: git-web--browse.sh' - questo ha dato un errore fatale: 'nome oggetto non valido 155.155'. Versione Git: 1.8.3.2. Eventuali suggerimenti?
BairDev,

12
Esegui l'upgrade a Git 1.8.4 o successivo.
Matt McClure,

4
e se volessi sapere "la storia di ciò che è la riga 155 in commitA" (invece della riga 155 in HEAD). Posso semplicemente usare git log commitA-hash -L 155,155:file-name?
Ida,

@Flimm, non ho una forte preferenza.
Matt McClure,

funziona bene, tranne se il file è stato spostato / rinominato ... sembra che a --follow non piaccia essere combinato con gli argomenti dell'intervallo di linee.
Mike Ellery,

67

Puoi ottenere una serie di commit usando pick-ax.

git log -S'the line from your file' -- path/to/your/file.txt

Questo ti darà tutti i commit che hanno influenzato quel testo in quel file. Se il file è stato rinominato ad un certo punto, puoi aggiungere --follow-parent.

Se desideri ispezionare i commit in ciascuna di queste modifiche, puoi reindirizzare quel risultato per git show:

git log ... | xargs -n 1 git show

5
Non sono sicuro di vedere come questo aiuta. Se il testo è stato interessato, la riga non è più la stessa, quindi piccone ti mostrerà solo la modifica più recente. Dovresti quindi fare git log -S'the previous version of the line'e così via, esattamente come faresti per finire git blame -L. E sarà molto più lento di git blame, dal momento che deve cercare il testo ovunque, non solo in un determinato posto.
Cascabel,

Puoi usare una regex per renderla più accettabile. Attualmente non c'è modo di "navigare tra le patch" indietro nel tempo senza alcuni script elaborati. Spero che Gitk ottenga questa funzionalità nella sua vista patch in futuro.
Adam Dymitruk l'

3
perché questo si chiama pick-ax ? :)
Ciprian Tomoiagă,


14

Un modo estremamente semplice per farlo è usare vim-fuggitivo . Basta aprire il file in vim, selezionare le righe che si desidera utilizzare V, quindi inserire

:Glog

Ora puoi usare :cnexte :cprevvedere tutte le revisioni del file in cui viene modificata quella linea. In qualsiasi momento, entra :Gblameper vedere le informazioni su sha, autore e data.


13

Semplificando la risposta di @ matt -

git blame -L14,15 -- <file_path>

Qui avrai la colpa di una battuta 14 to 15.

Dato che l' -Lopzione si aspetta Rangecome parametro, non possiamo ottenere a Blameper una singola riga usando l' -Lopzione` .

Riferimento


10

Non credo che ci sia qualcosa di incorporato per questo. È complicato dal fatto che è raro che una singola riga cambi più volte senza che anche il resto del file cambi sostanzialmente, quindi tenderai a finire con i numeri di riga che cambiano molto.

Se sei abbastanza fortunato che la linea abbia sempre delle caratteristiche identificative, ad esempio un'assegnazione a una variabile il cui nome non è mai cambiato, puoi usare la scelta regex per git blame -L. Per esempio:

git blame -L '/variable_name *= */',+1

Ma questo trova solo il primo corrispondenza per quella regex, quindi se non hai un buon modo di abbinare la linea, non è troppo utile.

Potresti hackerare qualcosa, suppongo. Non ho tempo per scrivere il codice proprio ora, ma ... qualcosa del genere. Corri git blame -n -L $n,$n $file. Il primo campo è toccato dal commit precedente e il secondo campo è il numero di riga in quel commit, poiché avrebbe potuto essere modificato. Prendi quelli ed esegui git blame -n $n,$n $commit^ $file, ovvero la stessa cosa a partire dal commit prima dell'ultima modifica del file.

(Nota che questo fallirà se l'ultimo commit che ha cambiato la linea è stato un commit di unione. Il modo principale questo potrebbe accadere se la linea fosse cambiata come parte di una risoluzione del conflitto di unione.)

Modifica: Mi sono imbattuto in questo post della mailing list di marzo 2011 oggi, che lo menziona tige git guiha una funzione che ti aiuterà a farlo. Sembra che la funzionalità sia stata considerata, ma non finita, per Git stesso.


6

Ciò richiederà git blameche ogni revisione significativa mostri la riga $LINEdel file $FILE:

git log --format=format:%H $FILE | xargs -L 1 git blame $FILE -L $LINE,$LINE

Come al solito, la colpa mostra il numero di revisione all'inizio di ogni riga. Puoi aggiungere

| sort | uniq -c

per ottenere risultati aggregati, qualcosa come un elenco di commit che ha cambiato questa riga. (Non del tutto, se solo il codice è stato spostato, questo potrebbe mostrare lo stesso ID commit due volte per diversi contenuti della riga. Per un'analisi più dettagliata dovresti fare un confronto ritardato dei git blamerisultati per i commit adiacenti. Qualcuno? )


Ancora una volta penso che questo non funzioni, perché non tiene traccia della posizione precedente di quella linea. Quindi, se una riga è stata aggiunta 2 commit fa, ne
guarderesti

6

Ecco una soluzione che definisce un alias git, quindi potrai usarlo in questo modo:

git rblame -M -n -L '/REGEX/,+1' FILE

Esempio di output:

00000000 18 (Not Committed Yet 2013-08-19 13:04:52 +0000 728) fooREGEXbar
15227b97 18 (User1 2013-07-11 18:51:26 +0000 728) fooREGEX
1748695d 23 (User2 2013-03-19 21:09:09 +0000 741) REGEXbar

È possibile definire l'alias in .gitconfig o semplicemente eseguire il comando seguente

git config alias.rblame !sh -c 'while line=$(git blame "$@" $commit 2>/dev/null); do commit=${line:0:8}^; [ 00000000^ == $commit ] && commit=$(git rev-parse HEAD); echo $line; done' dumb_param

Questo è un brutto one-liner, quindi ecco una funzione bash equivalente off-offuscata:

git-rblame () {
    local commit line
    while line=$(git blame "$@" $commit 2>/dev/null); do
        commit="${line:0:8}^"
        if [ "00000000^" == "$commit" ]; then
            commit=$(git rev-parse HEAD)
        fi
        echo $line
    done
}

La soluzione piccone ( git log --pickaxe-regex -S'REGEX ' ) fornirà solo aggiunte / eliminazioni di riga, non le altre alterazioni della riga contenente l'espressione regolare.

Una limitazione di questa soluzione è quella colpa git restituisce solo la prima corrispondenza REGEX, quindi se esistono più corrispondenze la ricorsione può "saltare" per seguire un'altra linea. Assicurati di controllare l'output della cronologia completa per individuare quei "salti" e quindi correggere il tuo REGEX per ignorare le linee del parassita.

Infine, ecco una versione alternativa che esegue git show su ogni commit per ottenere il diff completo:

git config alias.rblameshow !sh -c 'while line=$(git blame "$@" $commit 2>/dev/null); do commit=${line:0:8}^; [ 00000000^ == $commit ] && commit=$(git rev-parse HEAD); git show $commit; done' dumb_param

2

È possibile mescolare git blamee git logcomandi per recuperare il riepilogo di ciascun commit nel comando git blame e aggiungerli. Qualcosa come il seguente script bash + awk. Aggiunge il riepilogo del commit come commento di codice in linea.

git blame FILE_NAME | awk -F" " \
'{
   commit = substr($0, 0, 8);
   if (!a[commit]) {
     query = "git log --oneline -n 1 " commit " --";
     (query | getline a[commit]);
   }
   print $0 "  // " substr(a[commit], 9);
 }'

In una riga:

git blame FILE_NAME | awk -F" " '{ commit = substr($0, 0, 8); if (!a[commit]) { query = "git log --oneline -n 1 " commit " --"; (query | getline a[commit]); } print $0 "  // " substr(a[commit], 9); }'

2

Nel mio caso il numero di riga è cambiato molto nel tempo. Ero anche su git 1.8.3 che non supporta regex in "git blame -L". (RHEL7 ha ancora 1.8.3)

myfile=haproxy.cfg
git rev-list HEAD -- $myfile | while read i
do
    git diff -U0 ${i}^ $i $myfile | sed "s/^/$i /"
done | grep "<sometext>"

Oneliner:

myfile=<myfile> ; git rev-list HEAD -- $myfile | while read i; do     git diff -U0 ${i}^ $i $myfile | sed "s/^/$i /"; done | grep "<sometext>"

Questo può ovviamente essere trasformato in uno script o in una funzione.

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.