使“git log”忽略特定路径的更改

180

如何让 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年有一个关于这个主题的论坛讨论:排除文件在git-diff中的影响。这看起来很有前途,但后来似乎变得停滞不前了。


我不确定是否有内置的方法,而你的 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`` - Noah Sussman
对于 Git 1.9/2.0(2014年第一季度),请参见下面的我的答案git log --oneline --format=%s -- . ":!sub" 将会工作(使用 **路径规范魔法 :(exclude) 及其简写形式 :!**)。 - VonC
3个回答

333
现在已经实现了(Git 1.9/2.0,2014年第一季度),引入了路径规范魔法 :(exclude) 及其简写形式:!,在 commit ef79b1fcommit 1649612中由 Nguyễn Thái Ngọc Duy(pclouds实现,可在 此处找到文档。
现在您可以记录除一个子文件夹内容以外的所有内容:
git log -- . ':(exclude)sub'
git log -- . ':!sub'

或者您可以在该子文件夹中排除特定元素

  • a specific file:

      git log -- . ':(exclude)sub/sub/file'
      git log -- . ':!sub/sub/file'
    
  • any given file within sub:

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

您可以使该排除不区分大小写!

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

Kenny Evitt指出

如果您在bash shell中运行 git,请不要忘记使用单引号或适当转义双引号,例如 ':!sub'":\!sub"。否则,您将遇到bash:...事件未找到错误


注意:Git 2.13(2017年第二季度)将“!”的同义词添加为“^”。请参见commit 859b7f1commit 42ebeb9(由Linus Torvalds(torvalds于2017年2月8日发布)。
(由Junio C Hamano -- gitster --合并于commit 015fba3,2017年2月27日)

pathspec魔法:添加'^'作为'!'的别名

在负路径规范(negative pathspec)中选择'!'不仅不能匹配我们对修订版本所做的操作,而且由于需要引用,它还是一个可怕的shell扩展字符。

因此,添加'^'作为除外路径规范条目的另一种替代别名。


请注意,在Git 2.28之前(2020年第三季度),在收集包括工作树中未跟踪的路径时使用负路径规范是有问题的。

参见commit f1f061e(2020年6月5日)由Elijah Newren (newren)提交。
(由Junio C Hamano -- gitster --合并于commit 64efa11,2020年6月18日)

dir: fix treatment of negated pathspecs

Reported-by: John Millikin
Signed-off-by: Elijah Newren

do_match_pathspec() started life as match_pathspec_depth_1() and for correctness was only supposed to be called from match_pathspec_depth(). match_pathspec_depth() was later renamed to match_pathspec(), so the invariant we expect today is that do_match_pathspec() has no direct callers outside of match_pathspec().

Unfortunately, this intention was lost with the renames of the two functions, and additional calls to do_match_pathspec() were added in commits

  • 75a6315f74 ("ls-files: add pathspec matching for submodules", 2016-10-07, Git v2.11.0-rc0 -- merge listed in batch #11)
  • 89a1f4aaf7 ("dir: if our pathspec might match files under a dir, recurse into it", 2019-09-17, Git v2.24.0-rc0).

Of course, do_match_pathspec() had an important advantge over match_pathspec() -- match_pathspec() would hardcode flags to one of two values, and these new callers needed to pass some other value for flags.

Also, although calling do_match_pathspec() directly was incorrect, there likely wasn't any difference in the observable end output, because the bug just meant that fill_diretory() would recurse into unneeded directories.

Since subsequent does-this-path-match checks on individual paths under the directory would cause those extra paths to be filtered out, the only difference from using the wrong function was unnecessary computation.

The second of those bad calls to do_match_pathspec() was involved -- via either direct movement or via copying+editing -- into a number of later refactors.

See commits

  • 777b420347 ("dir: synchronize treat_leading_path() and read_directory_recursive()", 2019-12-19, Git v2.25.0-rc0 -- merge)
  • 8d92fb2927 ("dir: replace exponential algorithm with a linear one", 2020-04-01, Git v2.27.0-rc0 -- merge listed in batch #5)
  • 95c11ecc73 ("Fix error-prone fill_directory() API; make it only return matches", 2020-04-01, Git v2.27.0-rc0 -- merge listed in batch #5)

The last of those introduced the usage of do_match_pathspec() on an individual file, and thus resulted in individual paths being returned that shouldn't be.

The problem with calling do_match_pathspec() instead of match_pathspec() is that any negated patterns such as :!unwanted_path will be ignored.

Add a new match_pathspec_with_flags() function to fulfill the needs of specifying special flags while still correctly checking negated patterns, add a big comment above do_match_pathspec() to prevent others from misusing it, and correct current callers of do_match_pathspec() to instead use either match_pathspec() or match_pathspec_with_flags().

One final note is that DO_MATCH_LEADING_PATHSPEC needs special consideration when working with DO_MATCH_EXCLUDE.

The point of DO_MATCH_LEADING_PATHSPEC is that if we have a pathspec like

*/Makefile

and we are checking a directory path like

src/module/component

that we want to consider it a match so that we recurse into the directory because it might have a file named Makefile somewhere below.

However, when we are using an exclusion pattern, i.e. we have a pathspec like

:(exclude)*/Makefile

we do NOT want to say that a directory path like

src/module/component

is a (negative) match.

While there might be a file named Makefile somewhere below that directory, there could also be other files and we cannot pre-emptively rule all the files under that directory out; we need to recurse and then check individual files.

Adjust the DO_MATCH_LEADING_PATHSPEC logic to only get activated for positive pathspecs.


9
可以翻译多个文件吗? - Justin Thomas
16
@JustinThomas 我认为(尚未测试)你可以多次重复使用路径排除模式 ":(exclude)pathPattern1" ":(exclude)pathPattern2",从而忽略多个文件夹/文件。 - VonC
8
如果您在Bash shell中运行Git,请改用':!sub'以避免出现bash: ... event not found错误。":\!sub"无效。 - Kenny Evitt
2
@KennyEvitt 感谢您的编辑和评论。我已将后者包含在答案中,以获得更多的关注。 - VonC
2
对于那些想知道此功能的官方文档在哪里的人,请参阅git help glossary(我在git help -g中找到它[我在git help中找到推荐它])。 - ravron
显示剩余12条评论

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结合使用以进行递归操作。

7
尽管如此,这并未显示影响已不再存在的文件的提交。不过,这仍是一项非常巧妙且出色的技巧。 - Anonymoose

-2
您可以使用以下命令临时忽略文件中的更改:
git update-index --skip-worktree path/to/file

从现在开始,所有对这些文件的更改都将被 git statusgit commit -a 等忽略。当您准备提交这些文件时,只需反转它:

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

然后像正常提交一样提交。


9
这似乎涉及到一个略微不同的情况。git update-index --skip-worktree 不会导致 git log 过滤已经提交的提交记录。 - Anonymoose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接