Заставить git log игнорировать изменения для определенных путей

121

Как я могу сделать так, чтобы git logпоказывались только те коммиты, которые изменили файлы, кроме тех, которые я указал?

С помощью git logя могу отфильтровать наблюдаемые мной коммиты до тех, которые касаются заданного набора путей. Я хочу инвертировать этот фильтр, чтобы в списке отображались только те пути касания, которые не указаны.

Я могу получить то, что хочу, с

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

где filter-log.plнаходится:

#!/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";
    }
}

за исключением того, что я хочу что-то более элегантное, чем это.

Обратите внимание, что я не спрашиваю, как заставить git игнорировать файлы. Эти файлы следует отслеживать и фиксировать. Просто в большинстве случаев мне не интересно их видеть.

Связанный вопрос: Как инвертировать `git log --grep = <pattern>` или Как показать журналы git, не соответствующие шаблону. Это тот же вопрос, за исключением сообщений о фиксации, а не путей.

Обсуждение этой темы на форуме в 2008 году: Re: Исключение файлов из git-diff Это выглядело многообещающе, но, похоже, тема иссякла.

Anonymoose
источник
Я не уверен, есть ли встроенный способ, и ваше решение на Perl выглядит довольно прилично. Если вы измените его, чтобы он принимал пути в качестве аргументов командной строки, вы можете просто создать псевдоним, например !f() { git log ... | path/to/filter-log.pl "$@" | git log --stdin --no-walk; f, или даже обернуть эту часть конвейера в скрипт.
Cascabel
В качестве обходного пути я использую findдля фильтрации каталогов, чьи коммиты я не хочу видеть. Если бы я хотел игнорировать записи журнала из коммитов, сделанных в каталог корневого уровня, SiteConfigя бы сказал:git log `find . -type d -mindepth 1 -maxdepth 1 ! -name *SiteConfig`
Ной Суссман
Для Git 1.9 / 2.0 (первый квартал 2014 г.) см. Мой ответ ниже : git log --oneline --format=%s -- . ":!sub"будет работать (с магией pathspec :(exclude)и ее краткой формой:! )
VonC

Ответы:

214

Он реализован сейчас (git 1.9 / 2.0, Q1 2014) с вводной магией pathspec :(exclude)и его краткой формой:! в commit ef79b1f и commit 1649612 , автор Nguyễn Thái Ngọc Duy ( pclouds) , документацию можно найти здесь .

Теперь вы можете регистрировать все, кроме содержимого подпапки:

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

Или вы можете исключить определенные элементы в этой подпапке

  • конкретный файл:

      git log -- . ":(exclude)sub/sub/file"
      git log -- . ":!sub/sub/file"
  • любой данный файл в sub:

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

Вы можете сделать это исключение нечувствительным к регистру!

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

Как отметил Кенни Эвитт

Если вы запускаете Git в оболочке Bash, используйте ':!sub'или ":\!sub"вместо этого, чтобы избежать bash: ... event not foundошибок


Примечание. Git 2.13 (второй квартал 2017 г.) добавит синоним ^к!

См. Commit 859b7f1 , commit 42ebeb9 (8 февраля 2017 г.) Линус Торвальдс ( torvalds) .
(Объединено Junio ​​C Hamano - gitster- в коммите 015fba3 , 27 февраля 2017 г.)

pathspec magic: добавьте ' ^' как псевдоним для ' !'

Выбор " !" для отрицательной pathspec не только не соответствует тому, что мы делаем для ревизий, но и ужасный символ для расширения оболочки, поскольку он требует цитирования.

Поэтому добавьте ' ^' в качестве альтернативного псевдонима для исключающей записи pathspec.


Обратите внимание, что до Git 2.28 (3 квартал 2020 г.) использование отрицательных pathspec при сборе путей, в том числе неотслеживаемых, в рабочем дереве было нарушено.

См. Commit f1f061e (5 июня 2020 г.) Элайджа Ньюрен ( newren) .
(Объединено Junio ​​C Hamano - gitster- в коммите 64efa11 , 18 июня 2020 г.)

dir: исправить обращение с инвертированными указателями путей

Автор отчета : Джон Милликин.
Подпись: Элайджа Ньюрен.

do_match_pathspec()начал жизнь так match_pathspec_depth_1()и для правильности должен был быть вызван только из match_pathspec_depth(). match_pathspec_depth()позже был переименован в match_pathspec(), поэтому сегодня мы ожидаем, что у do_match_pathspec()него нет прямых вызывающих абонентов за пределами match_pathspec().

К сожалению, это намерение было потеряно с переименованием двух функций, и дополнительные вызовы do_match_pathspec()были добавлены в коммитах 75a6315f74 (" ls-files: добавить соответствие pathspec для подмодулей", 2016-10-07, Git v2.11.0-rc0 - слияние, перечисленное в batch # 11 ) и 89a1f4aaf7 (" dir: если наш путь может соответствовать файлам в каталоге, рекурсивно в него", 2019-09-17, Git v2.24.0-rc0).

Конечно, у do_match_pathspec()него было важное преимущество match_pathspec()- match_pathspec()они жестко кодировали флаги на одно из двух значений, и этим новым вызывающим объектам требовалось передать другое значение для флагов.

Кроме того, хотя do_match_pathspec()прямой вызов был неправильным, вероятно, не было никакой разницы в наблюдаемом конечном выходе, потому что ошибка просто означала, что это fill_diretory()будет рекурсивно в ненужные каталоги.

Поскольку последующие проверки соответствия пути на отдельных путях в каталоге приведут к отфильтровыванию этих дополнительных путей, единственным отличием от использования неправильной функции были ненужные вычисления.

Второй из этих плохих вызовов do_match_pathspec()был задействован - через прямое перемещение или через копирование + редактирование - в ряд более поздних рефакторингов.

См. Коммиты 777b420347 (" dir: синхронизировать treat_leading_path()и read_directory_recursive()", 2019-12-19, Git v2.25.0-rc0 - merge ), 8d92fb2927 (" dir: заменить экспоненциальный алгоритм на линейный", 2020-04-01, Git v2.27.0 -rc0 - объединение указано в пакете № 5 ) и 95c11ecc73 («Исправить подверженный ошибкам fill_directory()API; сделать так, чтобы он возвращал только совпадения», 2020-04-01, Git v2.27.0-rc0 - объединение указано в пакете № 5 ) ,

Последний из них ввел использование в do_match_pathspec()отдельном файле и, таким образом, привел к возврату отдельных путей, которых не должно быть.

Проблема с вызовом do_match_pathspec()вместо match_pathspec()заключается в том, что любые отвергнутые шаблоны, такие как ':! Unwanted_path`, будут игнорироваться .

Добавьте новую match_pathspec_with_flags()функцию, чтобы удовлетворить потребности в указании специальных флагов, при этом все еще правильно проверяя отрицательные шаблоны, добавьте большой комментарий выше, do_match_pathspec()чтобы предотвратить его неправильное использование другими, и исправьте текущие вызывающие абоненты do_match_pathspec()вместо использования либо match_pathspec()или match_pathspec_with_flags().

И последнее замечание: DO_MATCH_LEADING_PATHSPECпри работе с DO_MATCH_EXCLUDE.

Дело в DO_MATCH_LEADING_PATHSPECтом, что если у нас есть путь вроде

*/Makefile

и мы проверяем путь к каталогу, например

src/module/component

что мы хотим считать это совпадением, чтобы мы рекурсивно перешли в каталог, потому что у него _might_ есть файл с именем Makefileгде-то ниже.

Однако, когда мы используем шаблон исключения, то есть у нас есть pathspec, например

:(exclude)*/Makefile

мы НЕ хотим говорить, что путь к каталогу, например

src/module/component

является (отрицательным) совпадением.

Хотя где-то под этим каталогом может быть файл с именем «Makefile», могут быть и другие файлы, и мы не можем упреждающе исключить все файлы в этом каталоге; нам нужно выполнить рекурсию, а затем проверить отдельные файлы.

Отрегулируйте DO_MATCH_LEADING_PATHSPECлогику, чтобы она активировалась только для положительных характеристик пути.

VonC
источник
7
Вы можете делать несколько файлов?
Джастин Томас
12
@JustinThomas Я считаю (еще не проверял), что вы можете повторять этот шаблон исключения пути несколько раз ":(exclude)pathPattern1" ":(exclude)pathPattern2", игнорируя несколько папок / файлов.
VonC
7
Если вы запускаете Git в оболочке Bash, используйте ':!sub'вместо этого, чтобы избежать bash: ... event not foundошибок . ":\!sub"не работает.
Кенни Эвитт 02
1
@KennyEvitt Спасибо за ваше редактирование и комментарий. Я включил последнее в ответ для большей наглядности.
VonC
2
Для тех, кто интересуется, где находится официальная документация по этой функциональности, см. git help glossary(Я нашел ее в git help -g[которую я нашел предложенной в git help]).
ravron 06
4

ТЛ; др: shopt -s extglob && git log !(unwanted/glob|another/unwanted/glob)

Если вы используете Bash, вы сможете использовать расширенную функцию подстановки, чтобы получить только те файлы, которые вам нужны:

$ 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 <jdoe@example.org>
Date:   Wed Jun 5 10:58:45 2013 +0200

    First commit

Вы можете комбинировать это с globstarдля рекурсивного действия.

l0b0
источник
7
При этом не отображаются коммиты, влияющие на файлы, которые больше не существуют. Очень близко и все равно неплохо взломать.
Anonymoose 05
-2

Вы можете временно игнорировать изменения в файле с помощью:

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

В дальнейшем, все изменения в эти файлы будут игнорироваться git status, git commit -aи т.д. Когда вы будете готовы совершить эти файлы, просто отменить его:

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

и зафиксируйте как обычно.

rubysolo
источник
9
Это похоже на несколько иную ситуацию. git update-index --skip-worktreeне вызывает git logфильтрацию уже сделанных коммитов.
Anonymoose