Recuperar o log de confirmação para uma linha específica em um arquivo?

503

Existe alguma maneira de o git fornecer um log de confirmação para apenas confirmações que tocavam uma linha específica de um arquivo?

Como git blame, mas git blamemostrará o ÚLTIMO commit que tocou em uma linha específica.

Eu realmente gostaria de obter um log semelhante, não a lista de confirmações para qualquer lugar do arquivo, mas apenas as confirmações que tocavam uma linha específica.

jrochkind
fonte
2
Veja também: Git culpa - commits anteriores
joeytwiddle

Respostas:

632

Veja também Git: descubra quais confirmações já tocaram em várias linhas .


Desde o Git 1.8.4 , git logé -Lnecessário visualizar a evolução de uma série de linhas.

Por exemplo, suponha que você observe a git blamesaída de. Aqui -L 150,+11significa "apenas olhe as linhas 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 você quer conhecer a história do que é agora a linha 155.

Então use git log. Aqui, -L 155,155:git-web--browse.shsignifica "rastrear a evolução das linhas 155 a 155 no arquivo chamado git-web--browse.sh".

$ git log --pretty=short -u -L 155,155:git-web--browse.sh
commit 81f42f11496b9117273939c98d270af273c8a463
Author: Giuseppe Bilotta <[email protected]>

    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 <[email protected]>

    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 <[email protected]>

    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)
Matt McClure
fonte
2
'git log --topo-order --graph -u -L 155,155: git-web--browse.sh' - isso gerou um erro fatal: 'nome de objeto inválido 155,155'. Versão Git: 1.8.3.2. Alguma sugestão?
BairDev 12/12
12
Atualize para o Git 1.8.4 ou posterior.
precisa
4
e se eu quiser saber "o histórico do que é a linha 155 no commitA" (em vez da linha 155 no HEAD). Posso simplesmente usar git log commitA-hash -L 155,155:file-name?
Ida
@Limlim, eu não tenho uma forte preferência.
Matt McClure
isso funciona bem, exceto se o arquivo foi movido / renomeado ... parece que --follow não gosta de ser combinado com argumentos de intervalo de linha.
9788 Mike Ellery
67

Você pode obter um conjunto de confirmações usando o picareta.

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

Isso fornecerá todos os commits que afetaram esse texto nesse arquivo. Se o arquivo foi renomeado em algum momento, você pode adicionar --follow-parent.

Se você deseja inspecionar os commits em cada uma dessas edições, pode canalizar o resultado para o git show:

git log ... | xargs -n 1 git show
Adam Dymitruk
fonte
5
Não tenho certeza de ver como isso ajuda. Se o texto foi afetado, a linha não é mais a mesma; portanto, a picareta mostrará apenas a alteração mais recente. Você teria que fazer git log -S'the previous version of the line'e assim por diante, exatamente como você terminaria git blame -L. E será muito mais lento do que git blame, já que ele precisa procurar o texto em qualquer lugar, não apenas no local indicado.
Cascabel
Você pode usar um regex para torná-lo mais aceitável. Atualmente, não há como "navegar pelas correções" no tempo sem alguns scripts elaborados. Espero que o gitk obtenha essa funcionalidade no modo de exibição de patches no futuro.
Adam Dymitruk 8/12/11
3
por que isso é chamado de picareta ? :)
Ciprian Tomoiagă
43

Tente usar o comando abaixo implementado no Git 1.8.4.

git log -u -L <upperLimit>,<lowerLimit>:<path_to_filename>

Então, no seu caso upperLimite lowerLimité o tocadoline_number

Mais informações - https://www.techpurohit.com/list-some-useful-git-commands

jitendrapurohit
fonte
2
Eu acho que --pretty=shorté ignorado ao usar -L. Corrija
Vic Seedoubleyew 1/17
14

Uma maneira extremamente fácil de fazer isso é usar vim-fugitive . Basta abrir o arquivo no vim, selecione as linhas que você está interessado em usar Ve digite

:Glog

Agora você pode usar :cnexte :cprevver todas as revisões do arquivo em que essa linha é modificada. A qualquer momento, insira :Gblamepara ver as informações de sha, autor e data.

Cory Klein
fonte
13

Simplificando a resposta de @ matt -

git blame -L14,15 -- <file_path>

Aqui você será culpado por algumas falas 14 to 15.

Como a -Lopção espera Rangecomo um parâmetro, não podemos obter um Blamepara uma única linha usando a -Lopção` .

Referência

Swaps
fonte
10

Não acredito que haja algo embutido para isso. Isso é complicado pelo fato de ser raro uma única linha mudar várias vezes sem que o restante do arquivo mude substancialmente também; portanto, você tenderá a acabar mudando bastante os números das linhas.

Se você tiver sorte o suficiente para que a linha sempre tenha alguma característica de identificação, por exemplo, uma atribuição a uma variável cujo nome nunca foi alterado, você pode usar a opção regex git blame -L. Por exemplo:

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

Mas isso só encontra a primeira correspondência para essa expressão regular; portanto, se você não tem uma boa maneira de corresponder à linha, não é muito útil.

Você poderia hackear alguma coisa, suponho. Não tenho tempo para escrever código agora, mas ... algo nesse sentido. Corra git blame -n -L $n,$n $file. O primeiro campo é o commit anterior tocado e o segundo campo é o número da linha nesse commit, pois ele pode ter sido alterado. Pegue-os e execute-o git blame -n $n,$n $commit^ $file, ou seja, a mesma coisa iniciando no commit antes da última vez que o arquivo foi alterado.

(Observe que isso falhará se a última confirmação que alterou a linha for uma confirmação de mesclagem. A principal maneira de isso acontecer se a linha for alterada como parte de uma resolução de conflito de mesclagem).

Edit: Eu deparei com esta postagem na lista de discussão de março de 2011 hoje, que menciona isso tige git guitem um recurso que o ajudará a fazer isso. Parece que o recurso foi considerado, mas não concluído, para o próprio git.

Cascabel
fonte
6

Isso exigirá git blameque todas as revisões significativas mostrem a linha $LINEdo arquivo $FILE:

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

Como sempre, a culpa mostra o número da revisão no início de cada linha. Você pode acrescentar

| sort | uniq -c

para obter resultados agregados, algo como uma lista de confirmações que mudaram essa linha. (Não é bem assim, se apenas o código foi movido, isso pode mostrar o mesmo ID de confirmação duas vezes para diferentes conteúdos da linha. Para uma análise mais detalhada, você teria que fazer uma comparação atrasada dos git blameresultados para confirmações adjacentes. )

krlmlr
fonte
Mais uma vez, acho que isso não funciona, porque não rastreia a localização anterior dessa linha. Portanto, se uma linha foi adicionada há 2 commits atrás, você estaria olhando para outra linha
Vic Seedoubleyew 1/17/17
6

Aqui está uma solução que define um alias do git, para que você possa usá-lo assim:

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

Exemplo de saída:

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

Você pode definir o alias no seu .gitconfig ou simplesmente executar o seguinte comando

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

Esta é uma linha única feia, então aqui está uma função bash equivalente ofuscada:

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
}

A solução de picareta ( git log --pickaxe-regex -S'REGEX ' ) fornecerá apenas adições / exclusões de linha, e não as outras alterações da linha que contêm a expressão regular.

Uma limitação desta solução é que a culpa retorna apenas a 1ª correspondência do REGEX; portanto, se existirem várias correspondências, a recursão poderá "saltar" para seguir outra linha. Certifique-se de verificar a saída do histórico completo para identificar esses "saltos" e, em seguida, corrija o REGEX para ignorar as linhas de parasitas.

Finalmente, aqui está uma versão alternativa que executa o git show em cada commit para obter o 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
Lucas Cimon
fonte
2

Você pode misturar git blamee git logcomandos para recuperar o resumo de cada confirmação no comando git blame e anexá-los. Algo como o seguinte script bash + awk. Ele anexa o resumo da confirmação como comentário de código embutido.

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);
 }'

Em uma linha:

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); }'
cserpell
fonte
2

No meu caso, o número da linha mudou muito ao longo do tempo. Eu também estava no git 1.8.3, que não suporta regex no "git blame -L". (RHEL7 ainda tem 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>"

Obviamente, isso pode ser transformado em um script ou uma função.

sastorsl
fonte