Fazer com que o 'git log' ignore as mudanças em certos caminhos

121

Como posso git logmostrar apenas commits que alteraram arquivos diferentes dos que eu especificar?

Com git log, posso filtrar os commits que vejo para aqueles que tocam um determinado conjunto de caminhos. O que eu quero é inverter esse filtro para que apenas os commits que tocam caminhos diferentes dos especificados sejam listados.

Eu posso conseguir o que quero com

git log --format="%n/%n%H" --name-only | ~/filter-log.pl | git log --stdin --no-walk

onde filter-log.plestá:

#!/usr/bin/perl
use strict;
use warnings;

$/ = "\n/\n";
<>;

while (<>) {
    my ($commit, @files) = split /\n/, $_;

    if (grep { $_ && $_ !~ m[^(/$|.etckeeper$|lvm/(archive|backup)/)] } @files) {
        print "$commit\n";
    }
}

exceto que eu quero algo um pouco mais elegante do que isso.

Note que estou não perguntando como fazer git ignorar os arquivos. Esses arquivos devem ser rastreados e confirmados. É que, na maioria das vezes, não estou interessado em vê-los.

Questão relacionada: Como inverter `git log --grep = <pattern>` ou como mostrar logs do git que não correspondem a um padrão É a mesma questão, exceto para mensagens de confirmação ao invés de caminhos.

Discussão do fórum sobre este assunto de 2008: Re: Excluindo arquivos do git-diff Isso parecia promissor, mas o tópico parece ter secado.

Anonymoose
fonte
Não tenho certeza se existe uma forma integrada e sua solução perl parece bastante decente. Se você modificá-lo para aceitar os caminhos como argumentos de linha de comando, poderá apenas criar um alias ou algo parecido !f() { git log ... | path/to/filter-log.pl "$@" | git log --stdin --no-walk; fou até mesmo envolver essa parte do pipeline no script.
Cascabel
Como solução alternativa, eu uso findpara filtrar diretórios cujos commits não quero ver. Se eu quisesse ignorar as entradas de log dos commits feitos no diretório de nível raiz SiteConfig, eu diria:git log `find . -type d -mindepth 1 -maxdepth 1 ! -name *SiteConfig`
Noah Sussman
Para Git 1.9 / 2.0 (primeiro trimestre de 2014), veja minha resposta abaixo : git log --oneline --format=%s -- . ":!sub"funcionará (com a magia:(exclude):! do pathspec e sua forma abreviada )
VonC

Respostas:

214

Ele está implementado agora (git 1.9 / 2.0, Q1 2014) com a introdução pathspec magic :(exclude)e sua forma abreviada:! em commit ef79b1f e commit 1649612 , de Nguyễn Thái Ngọc Duy ( pclouds) , a documentação pode ser encontrada aqui .

Agora você pode registrar tudo, exceto o conteúdo de uma subpasta:

git log -- . ":(exclude)sub"
git log -- . ":!sub"

Ou você pode excluir elementos específicos dessa subpasta

  • um arquivo específico:

      git log -- . ":(exclude)sub/sub/file"
      git log -- . ":!sub/sub/file"
  • qualquer arquivo dentro de sub:

      git log -- . ":(exclude)sub/*file"
      git log -- . ":!sub/*file"
      git log -- . ":(exclude,glob)sub/*/file"

Você pode tornar essa exclusão insensível a maiúsculas e minúsculas!

git log -- . ":(exclude,icase)SUB"

Como Kenny Evitt observou

Se você estiver executando o Git em um shell Bash, use ':!sub'ou ":\!sub"para evitar bash: ... event not founderros


Nota: Git 2.13 (Q2 2017) adicionará um sinônimo ^para!

Consulte commit 859b7f1 , commit 42ebeb9 (08 de fevereiro de 2017) de Linus Torvalds ( torvalds) .
(Fundido por Junio ​​C Hamano - gitster- no commit 015fba3 , 27 de fevereiro de 2017)

pathspec magic: adicione ' ^' como alias para ' !'

A escolha de ' !' para um pathspec negativo acaba não apenas por não corresponder ao que fazemos para as revisões, mas também é um caractere horrível para a expansão do shell, uma vez que precisa ser citado.

Portanto, adicione ' ^' como um alias alternativo para uma entrada de exclusão de pathspec.


Observe que, antes do Git 2.28 (Q3 2020), o uso de pathspec negativo, enquanto os caminhos de coleta, incluindo os não rastreados na árvore de trabalho, foi interrompido.

Consulte commit f1f061e (05 de junho de 2020) de Elijah Newren ( newren) .
(Fundido por Junio ​​C Hamano - gitster- no commit 64efa11 , 18 de junho de 2020)

dir: corrigir tratamento de pathspecs negados

Relatado por: John Millikin
Assinado por: Elijah Newren

do_match_pathspec()começou a vida como match_pathspec_depth_1()e para correção só deveria ser chamado de match_pathspec_depth(). match_pathspec_depth()foi renomeado posteriormente para match_pathspec(), de modo que o invariante que esperamos hoje é aquele que do_match_pathspec()não tem chamadores diretos fora de match_pathspec().

Infelizmente, essa intenção foi perdida com a renomeação das duas funções e chamadas adicionais para do_match_pathspec()foram adicionadas nos commits 75a6315f74 (" ls-files: add pathspec matching for submodules", 2016-10-07, Git v2.11.0-rc0 - merge listado em batch # 11 ) e 89a1f4aaf7 (" dir: se nosso pathspec pode corresponder a arquivos em um diretório, recurse nele", 2019-09-17, Git v2.24.0-rc0).

Obviamente, ele do_match_pathspec()tinha uma vantagem importante sobre match_pathspec()- match_pathspec()codificaria os sinalizadores para um de dois valores e esses novos chamadores precisavam passar algum outro valor para os sinalizadores.

Além disso, embora a chamada do_match_pathspec()direta estivesse incorreta, provavelmente não havia nenhuma diferença na saída final observável, porque o bug apenas significava que fill_diretory()retornaria para diretórios desnecessários.

Como as verificações subsequentes de correspondência de caminho em caminhos individuais sob o diretório fariam com que esses caminhos extras fossem filtrados, a única diferença em usar a função errada era o cálculo desnecessário.

A segunda dessas chamadas ruins para do_match_pathspec()estava envolvida - via movimento direto ou via cópia + edição - em uma série de refatores posteriores.

Consulte os commits 777b420347 (" dir: sincronizar treat_leading_path()e read_directory_recursive()", 2019-12-19, Git v2.25.0-rc0 - mesclar ), 8d92fb2927 (" dir: substituir o algoritmo exponencial por um linear", 2020-04-01, Git v2.27.0 -rc0 - mesclagem listada no lote 5 ) e 95c11ecc73 ("Corrigir fill_directory()API propensa a erros ; torná-la apenas retornar correspondências", 2020-04-01, Git v2.27.0-rc0 - mesclagem listada no lote 5 ) .

O último deles introduziu o uso de do_match_pathspec()em um arquivo individual e, portanto, resultou no retorno de caminhos individuais que não deveriam ser.

O problema de chamar ao do_match_pathspec()invés de match_pathspec()é que qualquer padrão negado como '`:! Caminho_rejante`` será ignorado .

Adicione uma nova match_pathspec_with_flags()função para atender às necessidades de especificar sinalizadores especiais enquanto verifica corretamente os padrões negados, adicione um grande comentário acima do_match_pathspec()para evitar que outros o do_match_pathspec()usem indevidamente e corrija os chamadores atuais de para usarem match_pathspec()ou match_pathspec_with_flags().

Uma observação final é que DO_MATCH_LEADING_PATHSPECrequer consideração especial ao trabalhar com DO_MATCH_EXCLUDE.

O ponto DO_MATCH_LEADING_PATHSPECé que, se tivermos um pathspec como

*/Makefile

e estamos verificando um caminho de diretório como

src/module/component

que queremos considerá-lo uma correspondência para que possamos recursar no diretório porque _might_ tem um arquivo nomeado em Makefilealgum lugar abaixo.

No entanto, quando estamos usando um padrão de exclusão, ou seja, temos um pathspec como

:(exclude)*/Makefile

NÃO queremos dizer que um caminho de diretório como

src/module/component

é uma correspondência (negativa).

Enquanto lá possa haver um arquivo chamado 'Makefile' em algum lugar abaixo desse diretório, também pode haver outros arquivos e não podemos excluir preventivamente todos os arquivos nesse diretório; precisamos recurse e, em seguida, verificar os arquivos individuais.

Ajuste a DO_MATCH_LEADING_PATHSPEClógica para ser ativada apenas para pathspecs positivos.

VonC
fonte
7
Você pode fazer vários arquivos?
Justin Thomas
12
@JustinThomas Eu acredito (ainda não testei) que você pode repetir esse padrão de exclusão de caminho várias vezes ":(exclude)pathPattern1" ":(exclude)pathPattern2", ignorando várias pastas / arquivos.
VonC de
7
Se você estiver executando o Git em um shell Bash, use ':!sub'para evitar bash: ... event not founderros . ":\!sub"não funciona.
Kenny Evitt
1
@KennyEvitt Obrigado por sua edição e comentário. Incluí o último na resposta para obter mais visibilidade.
VonC
2
Para aqueles que estão se perguntando onde está a documentação oficial sobre esta funcionalidade, consulte git help glossary(que encontrei listada em git help -g[que encontrei sugerida em git help]).
ravron
4

tl; dr: shopt -s extglob && git log !(unwanted/glob|another/unwanted/glob)

Se você estiver usando o Bash, deverá conseguir usar o recurso de globbing estendido para obter apenas os arquivos de que precisa:

$ cd -- "$(mktemp --directory)" 
$ git init
Initialized empty Git repository in /tmp/tmp.cJm8k38G9y/.git/
$ mkdir aye bee
$ echo foo > aye/foo
$ git add aye/foo
$ git commit -m "First commit"
[master (root-commit) 46a028c] First commit
 0 files changed
 create mode 100644 aye/foo
$ echo foo > bee/foo
$ git add bee/foo
$ git commit -m "Second commit"
[master 30b3af2] Second commit
 1 file changed, 1 insertion(+)
 create mode 100644 bee/foo
$ shopt -s extglob
$ git log !(bee)
commit ec660acdb38ee288a9e771a2685fe3389bed01dd
Author: My Name <[email protected]>
Date:   Wed Jun 5 10:58:45 2013 +0200

    First commit

Você pode combinar isso globstarpara uma ação recursiva.

l0b0
fonte
7
Isso não mostra os commits que afetam os arquivos que não existem mais. Muito próximo e agradável hack do mesmo jeito.
Anonymoose
-2

Você pode ignorar temporariamente as alterações em um arquivo com:

git update-index --skip-worktree path/to/file

Daqui para frente, todas as alterações a esses arquivos serão ignorados por git status, git commit -aetc. Quando estiver pronto para cometer esses arquivos, basta invertê-la:

git update-index --no-skip-worktree path/to/file

e comprometa-se normalmente.

Rubysolo
fonte
9
Isso parece abordar uma situação ligeiramente diferente. git update-index --skip-worktreenão causa git loga filtragem de commits que já foram feitos.
Anonymoose