Como grep (pesquisar) código confirmado no histórico do Git

1435

Eu apaguei um arquivo ou algum código em um arquivo em algum momento no passado. Posso grep no conteúdo (não nas mensagens de confirmação)?

Uma solução muito ruim é grep o log:

git log -p | grep <pattern>

No entanto, isso não retorna o hash de confirmação imediatamente. Eu brinquei git grepsem sucesso.

Ortwin Gentz
fonte
2
Essas postagens de blog de Junio ​​C Hamano (mantenedor do git) podem ser interessantes para você: * A melhor ferramenta de rastreamento de conteúdo do Linus (sobre busca de picareta, ie git log -Sculpa) * [Diversão com "git log --grep"] [2] (pesquisando mensagens de confirmação ) * [Diversão com "git grep"] [3] [2]: gitster.livejournal.com/30195.html [3]: gitster.livejournal.com/27674.html
Jakub Narębski
resposta de possível duplicado realmente funciona: stackoverflow.com/a/1340245/492
CAD bloke
problema com isso é que ele não dá nenhum contexto para a mudança .. ou seja, quem / quando
Sonic Soul

Respostas:

1890

Para procurar conteúdo de confirmação (ou seja, linhas reais de origem, em vez de confirmar mensagens e similares), você precisa:

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

git rev-list --all | xargs git grep <expression> funcionará se você tiver um erro "A lista de argumentos é muito longa".

Se você deseja limitar a pesquisa a alguma subárvore (por exemplo, "lib / util"), será necessário passar isso para o rev-listsubcomando e greptambém:

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

Isso irá verificar todo o seu texto de confirmação para regexp.

O motivo para passar o caminho nos dois comandos é porque rev-listretornará a lista de revisões onde todas as alterações lib/utilocorreram, mas você também precisa passar para grepque ele só procure lib/util.

Imagine o seguinte cenário: greppode encontrar o mesmo <regexp>em outros arquivos contidos na mesma revisão retornada por rev-list(mesmo se não houver nenhuma alteração nesse arquivo nessa revisão).

Aqui estão algumas outras maneiras úteis de pesquisar sua fonte:

Pesquise na árvore de trabalho o texto que corresponde à expressão regular regexp:

git grep <regexp>

Pesquise na árvore de trabalho as linhas de texto que correspondem à expressão regular regexp1 ou regexp2:

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

Pesquise na árvore de trabalho as linhas de texto que correspondem à expressão regular regexp1 e regexp2, relatando apenas os caminhos do arquivo:

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

Pesquise na árvore de trabalho arquivos que tenham linhas de texto que correspondem à expressão regular regexp1 e linhas de texto que correspondem à expressão regular regexp2:

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

Pesquise na árvore de trabalho as linhas alteradas do padrão de correspondência de texto:

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

Pesquise em todas as revisões o texto correspondente à expressão regular regexp:

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

Pesquise em todas as revisões entre rev1 e rev2 o texto que corresponde à expressão regular regexp:

git grep <regexp> $(git rev-list <rev1>..<rev2>)
Jeet
fonte
61
Obrigado, funciona muito bem! É triste, porém, que "$ (git rev-list --all)" seja necessário e nenhuma opção conveniente para especificar a pesquisa em todo o histórico de uma ramificação.
Ortwin Gentz ​​28/05
3
Excelente. +1. O GitBook adiciona alguns detalhes ( book.git-scm.com/4_finding_with_git_grep.html ) e Junio ​​C Hamano ilustra alguns de seus pontos: gitster.livejournal.com/27674.html
VonC
18
Infelizmente, não consigo fazer isso com o msysgit-1.7.4. Isso me diz sh.exe": /bin/git: Bad file number. A resposta do VonC também funciona com o msysgit.
Eckes
4
Se você receber um erro "incapaz de ler a árvore" ao invocar o histórico do git grep com rev-list, pode ser necessário limpar as coisas. Tente git gcou faça o check-out: stackoverflow.com/questions/1507463/…
Anthony Panozzo 28/10
8
Sim, isso parece falhar no Windows também, infelizmente.
mlissner
552

Você deve usar a opção pickaxe ( -S) de git log.

Para procurar Foo:

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

Veja o histórico do Git - encontre linhas perdidas por palavra - chave para obter mais.


Como Jakub Narębski comentou:

  • isso procura diferenças que introduzem ou removem uma instância de<string> . Geralmente significa "revisões nas quais você adicionou ou remove linhas com 'Foo'".

  • a --pickaxe-regexopção permite usar o regex POSIX estendido em vez de procurar uma sequência. Exemplo (de git log):git log -S"frotz\(nitfol" --pickaxe-regex


Como Rob comentou, essa pesquisa diferencia maiúsculas de minúsculas - ele abriu uma pergunta de acompanhamento sobre como pesquisar sem distinção entre maiúsculas e minúsculas.

VonC
fonte
3
Obrigado, eu não estava ciente desta opção. Parece que esta é a melhor solução se você estiver interessado nas mensagens de confirmação e a solução do Jeet é mais apropriada se você precisar do tradicional comportamento de grep do UNIX da correspondência pura de linha.
Ortwin Gentz ​​28/05
@ Ortwin: concordou (e eu votei na solução escolhida). o git logpouco na sua pergunta me confundiu;) #
2828 VonC
12
Combine-o com a -pbandeira para também gerar o diff.
Sander
Existe alguma maneira de excluir todos os diretórios que correspondem a padrões específicos usando o git log -S?
BakaKuna
3
@ Anentropic, você precisaria das --branches --allopções para procurar o repo all.
VonC
249

Minha maneira favorita de fazer isso é com git loga -Gopção (adicionada na versão 1.7.4).

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

Há uma diferença sutil entre a maneira como as opções -Ge -Sdeterminam se um commit corresponde:

  • A -Sopção basicamente conta o número de vezes que sua pesquisa corresponde a um arquivo antes e depois de uma confirmação. A confirmação é mostrada no log se as contagens antes e depois forem diferentes. Por exemplo, isso não mostra confirmações para onde uma linha correspondente à sua pesquisa foi movida.
  • Com a -Gopção, a confirmação é mostrada no log se a sua pesquisa corresponder a qualquer linha que foi adicionada, removida ou alterada.

Tome esse commit como um exemplo:

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

Como o número de vezes que "olá" aparece no arquivo é o mesmo antes e depois dessa confirmação, ele não coincide com o uso -Shello. No entanto, como houve uma alteração em uma correspondência de linha hello, a confirmação será mostrada usando -Ghello.

Tyler Holien
fonte
2
Existe uma maneira de mostrar o contexto de alteração correspondente na saída do log do git?
Thilo-Alexander Ginkel 07/07
13
@ Thilo-AlexanderGinkel - eu normalmente apenas adiciono a -popção para mostrar um diff para cada commit. Então, quando o log é aberto no meu pager, procuro o que estou procurando. Se o seu pager é lesse você git log -Ghello -p, você pode digitar /hello, pressionar Entere usar ne Npara encontrar as ocorrências seguintes / anteriores de "olá".
Tyler Holien
Eu encontrei um problema interessante com o -GRegex: se a linha de comando usa UTF-8 e o arquivo que você está visualizando usa alguma codificação ISO-Latin (8 bits), .*falha. Por exemplo, eu tenho uma alteração Vierter Entwurf-> Fünfter Entwurfe, embora 'V.*ter Entwurf'produza uma correspondência, 'F.*ter Entwurf'não.
U. Windl
51

Se você deseja procurar alterações de código (veja o que realmente foi alterado com a palavra especificada em toda a história), vá para o patchmodo - Encontrei uma combinação muito útil de fazer:

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 ;) )
Bartek Skwira
fonte
11
A solução aceita não funcionou para mim nem o git log -S. Este fez!
Rodvlopes
29

git log pode ser uma maneira mais eficaz de pesquisar texto em todos os ramos, especialmente se houver muitas correspondências, e você desejar ver alterações mais recentes (relevantes) primeiro.

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

Esses comandos de log listam confirmações que adicionam ou removem a sequência / regex de pesquisa especificada, (geralmente) mais recentes primeiro. A -popção faz com que o diff relevante seja mostrado onde o padrão foi adicionado ou removido, para que você possa vê-lo em contexto.

Depois de encontrar um commit relevante que adiciona o texto que você estava procurando (por exemplo, 8beeff00d), encontre os ramos que contêm o commit:

git branch -a --contains 8beeff00d
Edward Anderson
fonte
Oi, essas linhas parecem não funcionar. Meu comando é> git log -p --all -S 'string pública DOB {get; conjunto; } = string.Empty; ' e toda vez que tento executá-lo, recebo> fatal: argumento ambíguo 'string': revisão desconhecida ou caminho que não está na árvore de trabalho. > Use '-' para separar caminhos das revisões, desta forma:> 'git <comando> [<revisão> ...] - [<arquivo> ...]'
user216652 18/02
@ user216652 Por algum motivo, as 'aspas não estão agrupando sua sequência de pesquisa como um único argumento. Em vez disso, 'publicé o argumento -Se trata o resto como argumentos separados. Não sei em que ambiente você está executando, mas esse contexto seria necessário para ajudar a solucionar problemas. Sugiro que você abra uma pergunta StackOverflow separada, se necessário, para ajudá-lo a solucionar problemas, com todo o contexto de como seu comando git está sendo enviado para o shell. Parece-me que está sendo enviado através de algum outro comando? Os comentários aqui não são o lugar certo para descobrir isso.
Edward Anderson
26

Peguei a resposta de Jeet e a adaptei ao Windows (graças a esta resposta ):

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

Observe que, para mim, por algum motivo, o commit real que excluiu esse regex não apareceu na saída do comando, mas um commit anterior a ele.

ripper234
fonte
2
+1 - e se você quiser evitar pressionar "q" após cada localização, adicione --no-pagerao comando git no final
cgp 28/03
2
Além disso, eu observaria que anexar a um arquivo de texto tem a vantagem adicional de realmente exibir o texto correspondente. (anexado a um arquivo de texto usando >>results.txtpara aqueles que não são versados ​​na tubulação do Windows ... #
22412
1
E eu pensei sintaxe de bash é feio :)
smido
23

Pesquise em qualquer revisão, qualquer arquivo :

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

Procure apenas em alguns arquivos, por exemplo, arquivos XML:

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

As linhas de resultado devem ter a seguinte aparência: 6988bec26b1503d45eb0b2e8a4364afb87dde7af: bla.xml: texto da linha encontrada ...

Você pode obter mais informações como autor, data e diff usando git show:

git show 6988bec26b1503d45eb0b2e8a4364afb87dde7af
Christophe Roussy
fonte
11

Para simplificar, sugiro o uso da GUI: gitk - O navegador do repositório Git . É bem flexível

  1. Para pesquisar código:

    Digite a descrição da imagem aqui
  2. Para pesquisar arquivos:

    Digite a descrição da imagem aqui
  3. Obviamente, ele também suporta expressões regulares:

    Digite a descrição da imagem aqui

E você pode navegar pelos resultados usando as setas para cima / para baixo.

watashiSHUN
fonte
6

Para qualquer outra pessoa que tente fazer isso no Sourcetree , não há comando direto na interface do usuário para ele (a partir da versão 1.6.21.0). No entanto, você pode usar os comandos especificados na resposta aceita, abrindo a janela Terminal (botão disponível na barra de ferramentas principal) e copiando / colando os mesmos.

Nota: A visualização Pesquisa do Sourcetree pode fazer parcialmente a pesquisa de texto por você. Pressione Ctrl+ 3para ir para o modo de pesquisa (ou clique na guia Pesquisa disponível na parte inferior). Na extremidade direita, defina o Tipo de pesquisa como Alterações de arquivo e digite a string que deseja pesquisar. Este método possui as seguintes limitações em comparação com o comando acima:

  1. O Sourcetree mostra apenas as confirmações que contêm a palavra de pesquisa em um dos arquivos alterados. Encontrar o arquivo exato que contém o texto da pesquisa é novamente uma tarefa manual.
  2. RegEx não é suportado.
ponto Net
fonte
4

Sempre que me encontro na sua casa, uso a seguinte linha de comando:

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

Explicação:

  1. git log- Preciso escrever mais aqui; mostra os logs em ordem cronológica.
  2. -S "<words/phrases i am trying to find>" - Ele mostra todos os commits do Git em que qualquer arquivo (adicionado / modificado / excluído) tem as palavras / frases que estou tentando encontrar sem os símbolos '<>'.
  3. --all - Aplicar e pesquisar em todos os ramos.
  4. --oneline - Comprime o log do Git em uma linha.
  5. --graph - Cria o gráfico de confirmações ordenadas cronologicamente.
surajs1n
fonte
1
"Sempre que me encontro na sua casa, sinto a necessidade de usar o git!"
Sebi
1
Esta é uma ótima resposta!
Alf Eaton
@AlfEaton meu prazer!
surajs1n 23/02
2

A resposta de Jeet funciona no PowerShell.

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

A seguir, são exibidos todos os arquivos, em qualquer confirmação, que contenham a password.

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

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

Então, você está tentando folhear versões mais antigas do código, procurando ver onde algo existe por último?

Se eu estivesse fazendo isso, provavelmente usaria git bisect . Usando o bisect, você pode especificar uma versão boa conhecida, uma versão ruim conhecida e um script simples que verifica se a versão é boa ou ruim (nesse caso, um grep para ver se o código que você está procurando está presente ) A execução disso encontrará quando o código foi removido.

Rob Di Marco
fonte
2
Sim, mas seu "teste" pode ser um script que cumprimenta o código e retorna "true" se o código existir e "false" se não existir.
Rob Di Marco
2
Bem, e se o código foi ruim em revisão 10, tornar-se bom em revisão 11 e tornar-se mal novamente na revisão 15 ...
Paolo
2
Eu concordo com o Paolo. A pesquisa binária é apropriada apenas para valores "ordenados". No caso do git bisect, isso significa que todas as revisões "boas" vêm antes de todas as revisões "ruins", começando no ponto de referência, mas essa suposição não pode ser feita ao procurar código transitório. Essa solução pode funcionar em alguns casos, mas não é uma boa solução de uso geral.
Kent
Eu acho que isso é altamente ineficiente, pois toda a árvore é verificada várias vezes quanto à divisão.
U. Windl
0

Cenário: você fez uma grande limpeza do seu código usando seu IDE. Problema: O IDE limpou mais do que deveria e agora o código não é compilado (recursos ausentes etc.)

Solução:

git grep --cached "text_to_find"

Ele encontrará o arquivo em que "text_to_find" foi alterado.

Agora você pode desfazer essa alteração e compilar seu código.

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

é um ajuste na solução do Jeet , por isso mostra resultados enquanto pesquisa e não apenas no final (o que pode levar muito tempo em um grande repositório).

Laktak
fonte
-1

No meu caso, eu precisava pesquisar um commit curto e as soluções listadas infelizmente não estavam funcionando.

Consegui fazer isso com (substitua o 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
user9869932
fonte