如何让'git diff'忽略注解

9o685dep  于 2023-04-28  发布在  Git
关注(0)|答案(8)|浏览(142)

我正在尝试生成在特定提交中更改的文件的列表。问题是,每个文件的版本号都在文件顶部的注解中--因为这个提交引入了一个新的版本,这意味着 * 每个文件都发生了变化
我不关心修改的注解,所以我想让git diff忽略所有匹配^\s*\*.*$的行,因为这些都是注解(/
*/的一部分)。
我找不到任何方法来告诉git diff忽略特定的行。
我已经尝试过设置一个textconv属性,让Git在区分文件之前先把文件传递给sed,这样sed就可以去掉那些有问题的行--这个问题是git diff --name-status实际上并不区分文件,只是比较散列,当然所有的散列都改变了。
有办法做到这一点吗?

nom7f22z

nom7f22z1#

这里有一个解决方案,对我来说效果很好。我已经写了解决方案和一些关于git (log|diff) -G<regex>选项的其他文档。
它基本上使用了与前面的答案相同的解决方案,但特别是针对以*#开头的注解,有时在*之前有空格。..但是它仍然需要允许#ifdef#include等。变化。
-G选项似乎不支持向前看和向后看,?一般也不支持,我在使用*时也遇到过问题。+似乎工作得很好。
(Note,在Git v2上测试。7.0)

多行注解版本

git diff -w -G'(^[^\*# /])|(^#\w)|(^\s+[^\*#/])'
  • -w忽略空白
  • -G仅显示与以下正则表达式匹配的差异行
  • (^[^\*# /])任何不以星号、散列或空格开头的行
  • (^#\w)#开头的任何行,后面跟着字母
  • (^\s+[^\*#/])任何以空格开头,后跟注解字符的行

基本上,SVN钩子现在修改每个文件的进出,并修改每个文件上的多行注解块。现在,我可以将我的更改与SVN进行比较,而不需要SVN在注解中丢弃的FYI信息。
从技术上讲,这将允许Python和Bash注解(如#TODO)显示在diff中,如果除法运算符在C++中的新行开始,则可以忽略:

a = b
    / c;

另外,Git中关于-G的文档似乎非常缺乏,所以这里的信息应该有所帮助:

git diff -G<regex>

-G<regex>

查找修补程序文本包含与<regex>匹配的添加/删除行的差异。
为了说明-S<regex> --pickaxe-regex-G<regex>之间的区别,考虑在同一个文件中具有以下差异的提交:

+    return !regexec(regexp, two->ptr, 1, &regmatch, 0);
...
-    hit = !regexec(regexp, mf2.ptr, 1, &regmatch, 0);

虽然git log -G"regexec\(regexp"将显示此提交,但git log -S"regexec\(regexp" --pickaxe-regex不会(因为该字符串的出现次数没有改变)。
有关详细信息,请参阅gitdiffcore(7)中的 pickaxe 条目。
(Note,在Git v2上测试。7.0)

  • -G使用基本正则表达式。
  • 不支持?*!{}正则表达式语法。
  • 使用()进行分组,使用|进行OR分组。
  • 通配符,如\s\W等。得到支持。
    *支持前看和后看。
  • 起止线锚点^$工作。
  • 自Git 1以来,该功能已经可用。7.4.

排除的文件与排除的差异

请注意,-G选项会过滤将被区分的文件。
但是如果一个文件被“diffed”,那么之前被“excluded/included”的那些行将在diff中显示 all

示例

仅显示至少有一行提到foo的文件差异。

git diff -G'foo'

显示除以#开头的行之外的所有文件差异

git diff -G'^[^#]'

显示在提到FIXMETODO时存在差异的文件

git diff -G`(FIXME)|(TODO)`

另请参见git log -Ggit grepgit log -S--pickaxe-regex--pickaxe-all

UPDATE:-G选项使用了哪个正则表达式工具?

https://github.com/git/git/search?utf8=%E2%9C%93&q=regcomp&type=
https://github.com/git/git/blob/master/diffcore-pickaxe.c

if (opts & (DIFF_PICKAXE_REGEX | DIFF_PICKAXE_KIND_G)) {
    int cflags = REG_EXTENDED | REG_NEWLINE;
    if (DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE))
        cflags |= REG_ICASE;
    regcomp_or_die(&regex, needle, cflags);
    regexp = &regex;

// and in the regcom_or_die function
regcomp(regex, needle, cflags);

http://man7.org/linux/man-pages/man3/regexec.3.html

REG_EXTENDED
          Use POSIX Extended Regular Expression syntax when interpreting
          regex.  If not set, POSIX Basic Regular Expression syntax is
          used.

//我的朋友

REG_NEWLINE
          Match-any-character operators don't match a newline.

          A nonmatching list ([^...])  not containing a newline does not
          match a newline.

          Match-beginning-of-line operator (^) matches the empty string
          immediately after a newline, regardless of whether eflags, the
          execution flags of regexec(), contains REG_NOTBOL.

          Match-end-of-line operator ($) matches the empty string
          immediately before a newline, regardless of whether eflags
          contains REG_NOTEOL.
n3ipq98p

n3ipq98p2#

git diff -G <regex>

并指定一个与您的版本号行不匹配的正则表达式。

szqfcxe2

szqfcxe23#

我发现使用git difftool启动外部diff工具是最简单的:

git difftool -y -x "diff -I '<regex>'"
hfwmuf9z

hfwmuf9z4#

我找到解决办法了我可以使用这个命令:

git diff --numstat --minimal <commit> <commit> | sed '/^[1-]\s\+[1-]\s\+.*/d'

来显示在提交之间有多行更改的文件,这消除了那些唯一更改的是注解中的版本号的文件。

nxowjjhe

nxowjjhe5#

在'git diff'输出中使用'grep',

git diff -w | grep -c -E "(^[+-]\s*(\/)?\*)|(^[+-]\s*\/\/)"

可以单独计算注解行改变。(A)

使用'git diff --stat'输出,

git diff -w --stat

可以计算所有的线变化。(B)
要获得无注解源行更改(NCSL)计数,请从(B)中减去(A)。

说明:

在'git diff '输出中(忽略空格更改),

  • 注意以“+”或“-”开头的行,这意味着修改过的行。
  • 后面可以有可选的空白字符。“\s*”
  • 然后查找注解行模式'/'(或)只是''(或)'//'。
  • 由于grep中提供了'-c'选项,因此只需打印计数。删除“-c”选项,以便在差异中单独查看注解。
    **注意:**由于以下假设,注解行数可能会有小错误,结果应视为大致数字。
  • 1.)源文件基于C语言。Makefile和shell脚本文件有一个不同的约定,“#”来表示注解行,如果它们是diffset的一部分,则它们的注解行不会被计算在内。
  • 2.)Git的行更改约定:如果一行被修改了,Git会看到这一行被删除,并在那里插入了一个新的行,看起来可能像两行被修改,而实际上只有一行被修改。
In the below example, the new definition of 'FOO' looks like a two-line change.

 $  git diff --stat -w abc.h
 ...
 -#define FOO 7
 +#define FOO 105
 ...
 1 files changed, 1 insertions(+), 1 deletions(-)
 $
  • 3.)不匹配模式的有效注解行(或)匹配模式的有效源代码行可能会导致计算错误。

在下面的示例中,不以'*'开头的“+ blah blah”行不会被检测为注解行。

+ /*
           +  blah blah
           + *
           + */

在下面的示例中,“+ *ptr”行将被视为注解行,因为它以 * 开头,尽管它是一个有效的源代码行。

+ printf("\n %p",
            +         *ptr);
wribegjk

wribegjk6#

对于大多数语言,要正确执行,必须解析原始源文件/ast,并以这种方式排除注解。
一个原因是多行注解的开头可能不包括在diff中。另一个原因是,语言解析并不简单,经常有一些事情会让一个简单的解析器出错。
我打算为python做这件事,但字符串黑客已经足够满足我的需要了。
对于python,你可以使用自定义过滤器忽略注解和尝试忽略文档字符串,比如:

#!/usr/bin/env python

import sys
import re
import configparser
from fnmatch import fnmatch
from unidiff import PatchSet

EXTS = ["py"]

class Opts:  # pylint: disable=too-few-public-methods
    debug = False
    exclude = []

def filtered_hunks(fil):
    path_re = ".*[.](%s)$" % "|".join(EXTS)
    for patch in PatchSet(fil):
        if not re.match(path_re, patch.path):
            continue
        excluded = False
        if Opts.exclude:
            if Opts.debug:
                print(">", patch.path, "=~", Opts.exclude)
            for ex in Opts.exclude:
                if fnmatch(patch.path, ex):
                    excluded = True
        if excluded:
            continue
        for hunk in patch:
            yield hunk

class Typ:  # pylint: disable=too-few-public-methods
    LINE = "."
    COMMENT = "#"
    DOCSTRING = "d"
    WHITE = "w"

def classify_lines(fil):
    for hunk in filtered_hunks(fil):
        yield from classify_hunk(hunk)

def classify_line(lval):
    """Classify a single python line, noting comments, best efforts at docstring start/stop and pure-whitespace."""
    lval = lval.rstrip("\n\r")
    remaining_lval = lval
    typ = Typ.LINE
    if re.match(r"^ *$", lval):
        return Typ.WHITE, None, ""

    if re.match(r"^ *#", lval):
        typ = Typ.COMMENT
        remaining_lval = ""
    else:
        slug = re.match(r"^ *(\"\"\"|''')(.*)", lval)
        if slug:
            remaining_lval = slug[2]
            slug = slug[1]
            return Typ.DOCSTRING, slug, remaining_lval
    return typ, None, remaining_lval

def classify_hunk(hunk):
    """Classify lines of a python diff-hunk, attempting to note comments and docstrings.

    Ignores context lines.
    Docstring detection is not guaranteed (changes in the middle of large docstrings won't have starts.)
    Using ast would fix, but seems like overkill, and cannot be done on a diff-only.
    """

    p = ""
    prev_typ = 0
    pslug = None
    for line in hunk:
        lval = line.value
        lval = lval.rstrip("\n\r")
        typ = Typ.LINE
        naive_typ, slug, remaining_lval = classify_line(lval)
        if p and p[-1] == "\\":
            typ = prev_typ
        else:
            if prev_typ != Typ.DOCSTRING and naive_typ == Typ.COMMENT:
                typ = naive_typ
            elif naive_typ == Typ.DOCSTRING:
                if prev_typ == Typ.DOCSTRING and pslug == slug:
                    # remainder of line could have stuff on it
                    typ, _, _ = classify_line(remaining_lval)
                else:
                    typ = Typ.DOCSTRING
                    pslug = slug
            elif prev_typ == Typ.DOCSTRING:
                # continue docstring found in this context/hunk
                typ = Typ.DOCSTRING

        p = lval
        prev_typ = typ

        if typ == Typ.DOCSTRING:
            if re.match(r"(%s) *$" % pslug, remaining_lval):
                prev_typ = Typ.LINE

        if line.is_context:
            continue

        yield typ, lval

def count_lines(fil):
    """Totals changed lines of python code, attempting to strip comments and docstrings.

    Deletes/adds are counted equally.
    Could miss some things, don't rely on exact counts.
    """

    count = 0

    for (typ, line) in classify_lines(fil):
        if Opts.debug:
            print(typ, line)
        if typ == Typ.LINE:
            count += 1

    return count

def main():
    Opts.debug = "--debug" in sys.argv
    Opts.exclude = []

    use_covrc = "--covrc" in sys.argv

    if use_covrc:
        config = configparser.ConfigParser()
        config.read(".coveragerc")
        cfg = {s: dict(config.items(s)) for s in config.sections()}
        exclude = cfg.get("report", {}).get("omit", [])
        Opts.exclude = [f.strip() for f in exclude.split("\n") if f.strip()]

    for i in range(len(sys.argv)):
        if sys.argv[i] == "--exclude":
            Opts.exclude.append(sys.argv[i + 1])

    if Opts.debug and Opts.exclude:
        print("--exclude", Opts.exclude)

    print(count_lines(sys.stdin))

example = '''
diff --git a/cryptvfs.py b/cryptvfs.py
index c68429cf6..ee90ecea8 100755
--- a/cryptvfs.py
+++ b/cryptvfs.py
@@ -2,5 +2,17 @@

 from src.main import proc_entry

-if __name__ == "__main__":
-    proc_entry()
+
+
+class Foo:
+    """some docstring
+    """
+    # some comment
+    pass
+
+class Bar:
+    """some docstring
+    """
+    # some comment
+    def method():
+        line1 + 1
'''

def strio(s):
    import io

    return io.StringIO(s)

def test_basic():
    assert count_lines(strio(example)) == 10

def test_main(capsys):
    sys.argv = []
    sys.stdin = strio(example)
    main()
    cap = capsys.readouterr()
    print(cap.out)
    assert cap.out == "10\n"

def test_debug(capsys):
    sys.argv = ["--debug"]
    sys.stdin = strio(example)
    main()
    cap = capsys.readouterr()
    print(cap.out)
    assert Typ.DOCSTRING + '     """some docstring' in cap.out

def test_exclude(capsys):
    sys.argv = ["--exclude", "cryptvfs.py"]
    sys.stdin = strio(example)
    main()
    cap = capsys.readouterr()
    print(cap.out)
    assert cap.out == "0\n"

def test_covrc(capsys):
    sys.argv = ["--covrc"]
    sys.stdin = strio(example)
    main()
    cap = capsys.readouterr()
    print(cap.out)
    assert cap.out == "10\n"

if __name__ == "__main__":
    main()

That code可以被简单地修改以产生文件名,而不是计数。
但是,当然,它可能会错误地将文档字符串的一部分算作“代码”(这不是为了覆盖率等)。

cgvd09ve

cgvd09ve7#

比如这样的Bash脚本:

#!/bin/bash
git diff --name-only "$@" | while read FPATH ; do
    LINES_COUNT=`git diff --textconv "$FPATH" "$@" | sed '/^[1-]\s\+[1-]\s\+.*/d' | wc -l`
    if [ $LINES_COUNT -gt 0 ] ; then
        echo -e "$LINES_COUNT\t$FPATH"
    fi
done | sort -n
uidvcgyl

uidvcgyl8#

我使用meld作为工具,通过设置其选项来忽略注解,然后使用meld作为difftool:

git difftool --tool=meld -y

相关问题