Git - como faço para ver o histórico de alterações de um método / função?

91

Então, encontrei a dúvida sobre como visualizar o histórico de alterações de um arquivo, mas o histórico de alterações desse arquivo em particular é enorme e estou realmente interessado apenas nas alterações de um método específico. Então, seria possível ver o histórico de alterações apenas para esse método específico?

Eu sei que isso exigiria git para analisar o código e que a análise seria diferente para diferentes linguagens, mas as declarações de método / função parecem muito semelhantes na maioria das linguagens, então pensei que talvez alguém tenha implementado esse recurso.

A linguagem que estou trabalhando atualmente é Objective-C e o SCM que estou usando é git, mas eu estaria interessado em saber se este recurso existe para qualquer SCM / linguagem.

Erik B
fonte
1
Eu vi essa funcionalidade na proposta Git GSoG.
Vi.
É esta a proposta de que você estava falando? lists-archives.org/git/…
Erik B
@lpapp a pergunta aqui foi feita 10 meses antes, a outra pergunta deve ser marcada como ingênua desta (se é que são ingênuos).
Dirty-flow
2
@lpapp Essas são duas questões completamente diferentes. Você poderia escrever um script que resolva o nome de uma função em um intervalo de linhas e, em seguida, usar a técnica dessa pergunta para obter o histórico dessas linhas, mas por si só não responde a essa pergunta.
Erik B,

Respostas:

97

Versões recentes de git logaprenderam uma forma especial do -Lparâmetro:

-L: <funcname>: <file>

Rastreie a evolução do intervalo de linha dado por "<start>,<end>"(ou o nome da função regex <funcname>) dentro do <file>. Você não pode fornecer nenhum limitador de pathspec. No momento, isso está limitado a uma caminhada a partir de uma única revisão, ou seja, você só pode dar zero ou um argumento de revisão positivo. Você pode especificar esta opção mais de uma vez.
...
If “:<funcname>”é fornecido no lugar de <start>e <end>, é uma expressão regular que denota o intervalo da primeira linha funcname que corresponde <funcname>, até a próxima linha funcname. “:<funcname>”pesquisa a partir do final do -Lintervalo anterior , se houver, caso contrário, a partir do início do arquivo. “^:<funcname>”pesquisa desde o início do arquivo.

Em outras palavras: se você pedir ao Git git log -L :myfunction:path/to/myfile.c, ele agora imprimirá o histórico de alterações daquela função.

eckes
fonte
15
isso pode funcionar para o objetivo-c fora da caixa, mas se você estiver fazendo isso para outras linguagens (por exemplo, Python, Ruby etc.), pode ser necessário adicionar a configuração apropriada dentro de um arquivo .gitattributes para que o git reconheça a função / método declarações nesse idioma. Para python, use * .py diff = python, para ruby, use * .rb diff = ruby
samaspin
1
Como o git rastreia a função?
nn0p
@ nn0p Presumo ter um conhecimento de sintaxe de várias linguagens e, portanto, saber isolar uma função e rastrear suas alterações.
JasonGenX
4
Estendendo o comentário de @samaspin, para outros idiomas você pode consultar a documentação aqui: git-scm.com/docs/gitattributes#_generating_diff_text
edhgoose
Isso não funciona para linguagens como Scala e Java e até mesmo C, que usam chaves aninhadas (expressões regulares não podem lidar com isso em geral), e mesmo a forma inicial e final não funciona quando a função é movida no arquivo e modificado no mesmo commit.
Robin Green
16

Usar git gui blameé difícil de usar em scripts e, embora git log -Ge git log --pickaxepossa mostrar a você quando a definição do método apareceu ou desapareceu, não encontrei nenhuma maneira de fazê-los listar todas as alterações feitas no corpo do seu método.

No entanto, você pode usar gitattributese a textconvpropriedade para reunir uma solução que faça exatamente isso. Embora esses recursos tenham sido originalmente planejados para ajudá-lo a trabalhar com arquivos binários, eles funcionam tão bem aqui.

A chave é fazer com que o Git remova do arquivo todas as linhas, exceto aquelas em que você está interessado, antes de fazer qualquer operação diff. Em seguida git log, git diffetc. verá apenas a área na qual você está interessado.

Aqui está o esboço do que faço em outro idioma; você pode ajustá-lo para suas próprias necessidades.

  • Escreva um script de shell curto (ou outro programa) que receba um argumento - o nome de um arquivo de origem - e produza apenas a parte interessante desse arquivo (ou nada se nada disso for interessante). Por exemplo, você pode usar sedo seguinte:

    #!/bin/sh
    sed -n -e '/^int my_func(/,/^}/ p' "$1"
  • Defina um textconvfiltro Git para seu novo script. (Consulte a gitattributespágina do manual para obter mais detalhes.) O nome do filtro e a localização do comando podem ser o que você quiser.

    $ git config diff.my_filter.textconv /path/to/my_script
  • Diga ao Git para usar esse filtro antes de calcular as diferenças para o arquivo em questão.

    $ echo "my_file diff=my_filter" >> .gitattributes
  • Agora, se você usar -G.(observe o .) para listar todos os commits que produzem mudanças visíveis quando seu filtro é aplicado, você terá exatamente aqueles commits nos quais está interessado. Quaisquer outras opções que usam as rotinas diff do Git, como --patch, também obter essa visão restrita.

    $ git log -G. --patch my_file
  • Voilà!

Uma melhoria útil que você pode querer fazer é fazer com que o script do filtro tome um nome de método como seu primeiro argumento (e o arquivo como seu segundo). Isso permite que você especifique um novo método de interesse apenas chamando git config, em vez de ter que editar seu script. Por exemplo, você pode dizer:

$ git config diff.my_filter.textconv "/path/to/my_command other_func"

Claro, o script de filtro pode fazer o que você quiser, aceitar mais argumentos ou o que quer que seja: há muita flexibilidade além do que mostrei aqui.

Paul Whittaker
fonte
1
O argumento take mais de um não funcionou para mim, mas colocar o nome da função no trabalho duro funciona bem e isso é realmente incrível!
qwertzguy
Brilhante, embora eu me pergunte como é (in) conveniente alternar entre vários métodos diferentes. Além disso, você conhece um programa que pode extrair uma função semelhante ao C inteira?
nafg
12

O git log tem uma opção '-G' que pode ser usada para encontrar todas as diferenças.

-G Procure por diferenças cuja linha adicionada ou removida corresponda à fornecida <regex>.

Basta dar a ele um regex adequado do nome da função de seu interesse. Por exemplo,

$ git log --oneline -G'^int commit_tree'
40d52ff make commit_tree a library function
81b50f3 Move 'builtin-*' into a 'builtin/' subdirectory
7b9c0a6 git-commit-tree: make it usable from other builtins
lht
fonte
5
Eu não executei o comando, mas me parece que este comando mostraria apenas os commits que tocam a linha que corresponde ao regex, que não é todo o método / função.
Erik B,
se você quiser mais contexto, pode substituir --onelinepor-p
lfender6445
3
Mas e se uma mudança foi feita em 20 linhas no método?
nafg
+1. Para mim, a resposta principal funcionou, mas mostrou apenas o commit mais recente. Possivelmente devido a rebases ou à movimentação da função algumas vezes, não tenho certeza. Ao pesquisar a linha de código real em vez da função em que ele estava (embora uma linha única), essa resposta me permitiu localizar facilmente o commit que estava procurando. Felizmente, geralmente escrevo mensagens de commit úteis!
Dave S
11

A coisa mais próxima que você pode fazer é determinar a posição de sua função no arquivo (por exemplo, digamos que sua função i_am_buggyesteja nas linhas 241-263 de foo/bar.c) e, em seguida, execute algo no sentido de:

git log -p -L 200,300:foo/bar.c

Isso abrirá menos (ou um pager equivalente). Agora você pode digitar /i_am_buggy(ou o equivalente no seu pager) e começar a percorrer as alterações.

Isso pode até funcionar, dependendo do estilo do seu código:

git log -p -L /int i_am_buggy\(/,+30:foo/bar.c

Isso limita a pesquisa desde o primeiro acerto dessa regex (de preferência, sua declaração de função) a trinta linhas depois disso. O argumento final também pode ser uma regexp, embora detectar isso com regexp's seja uma proposição mais duvidosa.

badp
fonte
Doce! Para sua informação, isso é novo no Git v1.8.4. (Acho que devo atualizar.) Embora uma solução mais precisa fosse boa ... como se alguém criasse a resposta de Paul Whittaker.
Greg Price de
@GregPrice Aparentemente, as bordas da pesquisa podem ser até mesmo expressões regulares , então você pode ter pelo menos um ponto de início mais ou menos preciso.
badp de
Oh, uau. Na verdade: em vez de escrever sua própria regexp, você pode apenas dizer -L ":int myfunc:foo/bar.c"e limitar à função com esse nome. Isso é fantástico - obrigado pelo ponteiro! Agora, se apenas a detecção da função fosse um pouco mais confiável ...
Greg Price,
3

A maneira correta é usar git log -L :function:path/to/fileconforme explicado na resposta eckes .

Mas, além disso, se sua função for muito longa, você pode querer ver apenas as mudanças que vários commits introduziram, não todas as linhas de função, incluídas não modificadas, para cada commit que talvez toque apenas uma dessas linhas. Como um normal difffaz.

Normalmente git logpode ver as diferenças com -p, mas não funciona com -L. Então você tem que grep git log -Lmostrar apenas linhas envolvidas e cabeçalhos de commits / arquivos para contextualizá-los. O truque aqui é combinar apenas as linhas coloridas do terminal, adicionando --colorswitch, com uma regex. Finalmente:

git log -L :function:path/to/file --color | grep --color=never -E -e "^(^[\[[0-9;]*[a-zA-Z])+" -3

Observe que ^[deve ser real, literal ^[. Pode introduzi-los pressionando ^ V ^ [em bash, que é Ctrl+ V, Ctrl+ [. Referência aqui .

Também a última -3opção, permite imprimir 3 linhas do contexto de saída, antes e depois de cada linha correspondida. Você pode querer ajustá-lo às suas necessidades.

Hazzard17
fonte
2

git blame mostra quem alterou pela última vez cada linha do arquivo; você pode especificar as linhas a serem examinadas de modo a evitar que o histórico de linhas fora de sua função.

Vai
fonte
4
com git gui blamevocê pode navegar pelas revisões mais antigas.
Vi.
0
  1. Mostrar histórico de função git log -L :<funcname>:<file>como mostrado na resposta do eckes e no documento git

    Se não mostrar nada, consulte Definindo um cabeçalho de hunk personalizado para adicionar algo semelhante *.java diff=javaao .gitattributes arquivo para oferecer suporte ao seu idioma.

  2. Mostrar histórico de funções entre commits com git log commit1..commit2 -L :functionName:filePath

  3. Mostrar histórico de função sobrecarregada (pode haver muitas funções com o mesmo nome, mas com parâmetros diferentes) com git log -L :sum\(double:filepath

Kris Roofe
fonte