如何反转 `git log --grep=<pattern>` 或者如何显示不匹配某个模式的 git 日志

36

我希望使用git log 来展示所有不符合给定模式的提交记录。我知道可以使用以下命令来展示所有符合模式的提交记录:

git log --grep=<pattern>

如何反转匹配的含义?

我试图忽略提交信息中包含“bumped to version ...”的提交。

编辑:我想要最终输出结果非常详细,例如 git log --pretty --stat。因此,git log --format=oneline 的输出对我不起作用。


邮件列表的后续问题 http://groups.google.com/group/git-users/browse_thread/thread/5128b5e5a5089269 - Elazar Leibovich
3
Git 2.3.1+(2015年第一季度/第二季度)将推出git log --invert-grep=<pattern>,该命令应该可以回答你的问题。请参见我下面的答案 - VonC
git log --grep=<string> 应该在 Git 2.4(2015年第二季度)中发布:https://github.com/git/git/blob/9ab698f4000a736864c41f57fbae1e021ac27799/Documentation/RelNotes/2.4.0.txt#L25-L26 - VonC
8个回答

35
这将在Git 2.4+(2015年第二季度)中实现:请参见Christoph Junghans(junghans提交的commit 22dfa8a

log:添加--invert-grep选项

"git log --grep=<string>" shows only commits with messages that match the given string, but sometimes it is useful to be able to show only commits that do not have certain messages (e.g. "show me ones that are not FIXUP commits").

Originally, we had the invert-grep flag in grep_opt, but because "git grep --invert-grep" does not make sense except in conjunction with "--files-with-matches", which is already covered by "--files-without-matches", it was moved it to revisions structure.
To have the flag there expresses the function to the feature better.

When the newly inserted two tests run, the history would have commits with messages "initial", "second", "third", "fourth", "fifth", "sixth" and "Second", committed in this order.
The commits that does not match either "th" or "Sec" is "second" and "initial". For the case insensitive case only "initial" matches.

--invert-grep

Limit the commits output to ones with log message that do not match the pattern specified with --grep=<pattern>.

例子:

我首先使用grep命令筛选包含“sequencer”的信息:

vonc@voncm C:\Users\vonc\prog\git\git

> git log -2 --pretty="tformat:%s" --grep=sequencer
Merge branch 'js/sequencer-wo-die'
sequencer: ensure to release the lock when we could not read the index

如果我想要没有排序器的消息:
> git log -2 --pretty="tformat:%s" --grep=sequencer --invert-grep
Second batch for 2.11
Merge branch 'js/git-gui-commit-gpgsign'

警告:这只会反转由--grep指定的模式。
自Git 2.35+(2022年第一季度)开始,它将不再反转任何标题匹配项,如--author--committer
在 "equivalence of: git log --exclude-author?"中查看示例。
请注意,git -P log -1 --invert-grep 可能会导致段错误:
在没有提供 "--grep" 的情况下给 "git log"(man) 命令同时提供 "--invert-grep" 和 "--all-match" 会尝试访问未分配的 grep 模式表达式结构体,这个问题已经在 Git 2.39 (Q4 2022) 中得到了修复。请注意,保留 html 标签。

查看 commit db84376(2022年10月11日)由Ævar Arnfjörð Bjarmason(avar提交。
(由Junio C Hamano -- gitster --commit 91d3d7e中合并,2022年10月21日)

grep.c: 将“扩展”替换为“pattern_expression”,修复段错误

报告者:orygaw
签名者:Ævar Arnfjörð Bjarmason

Since 79d3696 ("git-grep: boolean expression on pattern matching.", 2006-06-30, Git v1.4.2-rc1 -- merge) the "pattern_expression" member has been used for complex queries (AND/OR...), with "pattern_list" being used for the simple OR queries.
Since then we've used both "pattern_expression" and its associated boolean "extended" member to see if we have a complex expression.

Since f41fb66 (revisions API: have release_revisions() release , 2022-04-13, Git v2.37.0-rc0 -- merge listed in batch #8) (revisions API: have release_revisions() release "grep_filter", 2022-04-13) we've had a subtle bug relating to that: If we supplied options that were only used for "complex queries", but didn't supply the query itself we'd set "opt->extended", but would have a NULL "pattern_expression".
As a result these would segfault as we tried to call "free_grep_patterns()" from "release_revisions()":

git -P log -1 --invert-grep
git -P log -1 --all-match

The root cause of this is that we were conflating the state management we needed in "compile_grep_patterns()" itself with whether or not we had an "opt->pattern_expression" later on.

In this cases as we're going through "compile_grep_patterns()" we have no "opt->pattern_list" but have "opt->no_body_match" or "opt->all_match".
So we'd set "opt->extended = 1", but not "return" on "opt->extended" as that's an "else if" in the same "if" statement.

That behavior is intentional and required, as the common case is that we have an "opt->pattern_list" that we're about to parse into the "opt->pattern_expression".

But we don't need to keep track of this "extended" flag beyond the state management in compile_grep_patterns() itself.
It needs it, but once we're out of that function we can rely on "opt->pattern_expression" being non-NULL instead for using these extended patterns.


嗯,对我来说不起作用:“无法识别的参数:--invert-grep。这是使用 git 2.9.2.windows.1。” - Kent Boogaart
@KentBoogaart 你输入了什么命令?我刚刚在 git/git 克隆的仓库中成功使用了(windows,git 2.10.1)git log -5 --pretty="tformat:%s" --grep=sequencer 命令:它完全正常。 - VonC
@Kent 我已经编辑了答案,以说明invert-grep也可以正常工作。再次问一下,你使用了什么确切的命令? - VonC
2
@VonC 啊,是的,我没有意识到它是一个开关。我一直在使用 --invert-grep=foo。现在这个命令对我来说已经可以了,但它比我想象中的要不那么有用。如果能够在一个命令中指定“包含这个 AND 不包含这个”,那就更好了。谢谢! - Kent Boogaart

16

生成所有提交记录的列表,从中减去日志消息包含不良模式的记录,然后将结果传递给带有所需选项的 git log。在最后阶段,使用 git log 的一些选项非常方便:

--stdin
除了命令行上列出的 commit 之外,还要从标准输入中读取它们。

--no-walk
仅显示给定的修订版本,但不会遍历其祖先。

您可以使用单个管道和进程替换完成此操作。

#! /bin/bash

if (( $# < 1 )); then
  echo >&2 "Usage: $0 pattern [<since>..<until>]"
  exit 1
fi

pattern=$1
shift

git log --format=%H $@ |
  grep -v -f <(git log --format=%H "--grep=$pattern" $@) |
  git log --pretty --stat --stdin --no-walk

如果您不想使用bash,您可以使用Perl完成。

#! /usr/bin/env perl

use strict;
use warnings;
no warnings "exec";

sub usage { "Usage: $0 pattern\n" }

sub commits_to_omit {
  my($pattern) = @_;

  open my $fh, "-|", "git", "log", "--grep=$pattern", "--format=%H", @ARGV
    or die "$0: exec: $!";
  my %omit = map +($_ => 1), <$fh>;
  %omit;
}

die usage unless @ARGV >= 1;
my $pattern = shift;

my %omit = commits_to_omit $pattern;

open my $all, "-|", "git", "log", "--format=%H", @ARGV
  or die "$0: exec: $!";

open my $out, "|-", "git", "log", "--pretty", "--stat", "--stdin", "--no-walk"
  or die "$0: exec: $!";

while (<$all>) {
  print $out $_ unless $omit{$_};
}
假设已将git-log-vgrep加入了你的PATH路径,并且有以下历史记录:
$ git lola
* b0f2a28 (tmp, feature1) D
* 68f87b0 C
* d311c65 B
* a092126 A
| * 83052e6 (HEAD, origin/master, master) Z
| * 90c3d28 Y
| * 4165a42 X
| * 37844cb W
|/  
* f8ba9ea V
此时,我们可以使用以下命令获取Z、Y、W和V:
$ git log-vgrep X
也可以查看其他分支的记录:
$ git log-vgrep A tmp
可以得到D、C、B和V;
$ git log-vgrep C tmp~2..tmp
则只会得到D。
需要注意的是,如果使用一个匹配所有提交记录的模式,例如"."或"^",则会得到HEAD。这与git log的工作原理相同:
$ git log --stdin --no-walk --pretty=oneline </dev/null
83052e62f0dc1c6ddfc1aff3463504a4bf23e3c4 Z

8

一种相对简单且灵活的方法是使用带有 -z 选项的 git log 然后将其传递给 awk。-z 选项在提交记录之间添加 nulls,因此可以轻松地通过 awk 进行解析:

git log --color=always -z | awk -v RS=\\0

(在输出是管道时,需要使用color = always来保持着色)。 然后,很容易添加任何适用于每个字段的布尔表达式。 例如,这将打印所有作者电子邮件不来自fugly.com且提交日期为星期日的条目:

git log --color=always -z | awk -v RS=\\0 '!/Author:.*fugly.com>/ && /Date:.* Sun /'

另一个好处是,您可以将任何格式选项或修订范围添加到git日志中,它仍然有效。最后一件事,如果您想分页,请使用“less -r”以保留颜色。编辑:更改为在awk中使用-v使其更简单。

1
使用Perl:git log -z . |perl -ln0e 'print unless /regex/' - Zac Thompson

1
与thebriguy的回答一样,grep也有一个-z选项,使其能够处理空终止字符串而不是行。这将像反转匹配一样简单:
git log -z --color | grep -vz "bumped to version"

为了安全起见,您可能只想在提交消息内进行匹配。要使用grep实现此操作,您需要使用珍珠表达式来匹配空终止字符串中的换行符。要跳过标题:

git log -z | grep -Pvz '^commit.*\nAuthor:.*\nDate:.*\n[\S\s]*bumped to version'

或者用颜色:

git log -z --color | \
  grep -Pvz '^.....commit.*\nAuthor:.*\nDate:.*\n[\S\s]*bumped to version'

最后,如果使用--stat选项,您还可以匹配此输出的开头以避免匹配包含提交字符串的文件名。因此,回答这个问题的完整答案如下:
log -z --color --pretty --stat | \
  grep -Pvz '^.....commit.*\nAuthor:.*\nDate:.*\n[\S\s]*?bumped to version[\S\s]*?\n [^ ]'

请注意,我的 man 手册中将 grep -P 描述为“高度实验性”。也许最好使用使用 libpcre 的 pcregrep 替代它,参见 如何在 grep 中匹配换行符?。虽然 grep -P 对我来说运作良好,但我不知道 pcregrep 是否有 -z 选项或等效选项。

在OSX上,grep -z表示读取压缩的zip输入,而不是处理空终止符。 - user486646
正确的做法是使用GNU grep,因为-z和-P选项不是POSIX标准。 - Graeme
如果您想在OS X上使用grep的“-z”选项,请安装gnu core utils。我更喜欢这个答案,因为您可以为git log指定任何其他选项:pattern = $ 1; shift; git log-z --color“$ @”| grep-vz“$ pattern”| tr'\ 0''\ n'| less-r - David Sanders

1

获取包含您结果的所有提交列表,然后过滤掉它们的哈希值。

git log --format=oneline | grep -v `git log --grep="bumped to version" --format="%h"`

0
据我所知,这似乎不可能直接使用单个命令行完成;您需要像Justin Lilly建议的那样执行某些操作,然后在生成的哈希列表上运行“git log”,例如:
git log --format="%h" | grep -v `git log -1 --grep="bumped to version" --format="%h"` > good-hashes
for h in `cat good-hashes`; do
    PAGER=cat git log -1 --pretty --stat $h
done

应该可以解决问题。


1
真遗憾。我本来希望有一些选项我错过了或者应该升级到新版本。ebneter:我尝试了你的解决方案,但对我没有用。 - saltycrane
嗯,对我来说有效,尽管我当然是在过滤不同的字符串。我很好奇什么没有起作用? - ebneter

0

正如VonC所提到的,最好的选择是更新到Git 2.4.0(目前在RC2上)。但即使您无法这样做,也没有理由编写复杂的脚本。一个(gnu) awk一行代码就可以解决问题。git log有一个有用的-z选项,可以通过NUL字符将提交分开,从而很容易解析它们:

git log -z --pretty --stat | awk 'BEGIN{ RS="\0"; FS="\n\n" } !match($2, /<pattern>/)'

如果您没有GNU awk,您可能至少应该安装它。或者将此脚本移植到您特定的awk版本,这留给读者作为练习;-)。


-1
git log --pretty --stat | grep -v "bumped to version"

那行不通,因为 --pretty --stat 会对每个提交产生多行输出。 - ebneter

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