有没有一种简单的方法可以将文件与.gitignore规则进行匹配?

Chr*_* B. 8 python git gitignore

我正在用Python写一个git pre-commit钩子,我想定义一个像.gitignore文件一样的黑名单,以便在处理文件之前检查文件。有没有一种简单的方法来检查是否根据一组.gitignore规则定义了文件?这些规则有点神秘,我宁愿不必重新执行它们。

Edd*_*ddy 5

假设您位于包含.gitignore文件的目录中,那么一个shell命令将列出所有被忽略的文件:

git ls-files
Run Code Online (Sandbox Code Playgroud)

从python您可以简单地调用:

import os
os.system("git ls-files")
Run Code Online (Sandbox Code Playgroud)

您可以像这样提取文件列表:

import subprocess
list_of_files = subprocess.check_output("git ls-files", shell=True).splitlines()
Run Code Online (Sandbox Code Playgroud)

如果要列出忽略(也称为未跟踪)的文件,则可以添加选项“ --other”:

git ls-files --other
Run Code Online (Sandbox Code Playgroud)


tor*_*rek 1

这相当笨拙,但应该有效:

\n\n
    \n
  • 创建临时 git 存储库
  • \n
  • 用您的建议填充它.gitignore
  • \n
  • 还为每个路径名填充一个文件
  • \n
  • 使用git status --porcelain在生成的临时存储库上
  • \n
  • 清空它(将其完全删除,或将其保留为空以供下一次通过,以看起来更合适者为准)。
  • \n
\n\n

然而,这确实有点像XY 问题Y 的笨拙解决方案对于实际问题X来说可能是一个糟糕的解决方案可能是一个糟糕的解决方案。

\n\n

评论后的答案包含详细信息(和旁注)

\n\n

因此,您有一些文件需要检查,可能是通过检查提交来检查的。以下代码可能比您需要的更通用(我们实际上并不需要status部分),但我将其包含在内是为了说明:

\n\n
import subprocess\n\nproc = subprocess.Popen([\'git\',\n     \'diff-index\',                        # use plumbing command, not user diff\n     \'--cached\',                          # compare index vs HEAD\n     \'-r\',                                # recurse into subdirectories\n     \'--name-status\',                     # show status & pathname\n     # \'--diff-filter=AM\',                # optional: only A and M files\n     \'-z\',                                # use machine-readable output\n     \'HEAD\'],                             # the commit to compare against\n     stdout=subprocess.PIPE)\ntext = proc.stdout.read()\nstatus = proc.wait()\n# and check for failure as usual: Git returns 0 on success\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们需要类似迭代列表中的每两个元素pairwise的东西东西:

\n\n
import sys\n\nif sys.version_info[0] >= 3:\n    izip = zip\nelse:\n    from itertools import izip\ndef pairwise(it):\n    "s -> (s0, s1), (s2, s3), (s4, s5), ..."\n    a = iter(it)\n    return izip(a, a)\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们可以分解git status为:

\n\n
for state, path in pairwise(text.split(b\'\\0\')):\n    ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在,每个文件都有一个状态(b\'A\'= 添加、b\'M\'= 修改等)。(如果允许符号链接,请务必检查状态T,以防文件从普通文件更改为符号链接,反之亦然。请注意,我们依赖于丢弃末尾pairwise不成对的空字符串,这是因为Git 生成一个以 NUL结尾的列表,而不是一个以 NUL分隔的列表。)b\'\'text.split(b\'\\0\')

\n\n

让我们假设在某个时候我们将文件收集到一个名为的列表(或可迭代)中candidates

\n\n
>>> candidates\n[b\'a.py\', b\'dir/b.py\', b\'z.py\']\n
Run Code Online (Sandbox Code Playgroud)\n\n

我假设您已避免将其放入.gitignore此列表或可迭代对象中,因为我们计划将其用于我们自己的目的。

\n\n

现在我们有两个大问题:忽略一些文件,并获取这些文件的实际将被 linted 的版本。

\n\n

仅仅因为文件被列为已修改,并不意味着工作树中的版本就是将提交的版本。例如:

\n\n
$ git status\n$ echo foo >> README\n$ git add README\n$ echo bar >> README\n$ git status --short\nMM README\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里的第一个M意味着索引版本不同于HEAD(这是我们从上面得到的git diff-index),而第二个M意味着索引版本不同于工作树版本。

\n\n

将提交的版本是索引版本,而不是工作树版本。我们需要检查的不是工作树版本而是索引版本。

\n\n

所以,现在我们需要一个临时目录。这里要使用的是tempfile.mkdtemp如果你的Python是旧的,或者如果不是的话,使用幻想的上下文管理器版本。请注意,使用 Python3 时我们有上面的字节字符串路径名,而使用 Python2 时我们有普通(字符串)路径名,因此这也与版本相关。

\n\n

由于这是普通的 Python,而不是棘手的 Git 交互,因此我将这部分留作练习\xe2\x80\x94,并且我将忽略所有字节与字符串路径名内容。:-) 但是,对于--stdin -z下面的部分,请注意 Git 将需要以 b 分隔的字节形式的文件名列表\\0

\n\n

cwd=一旦我们有了适合传递给in 的格式的(空)临时目录subprocess.Popen,我们现在需要运行git checkout-index. 有几个选项,但让我们这样做:

\n\n
import os\n\nproc = subprocess.Popen([\'git\', \'rev-parse\', \'--git-dir\'],\n    stdout=subprocess.PIPE)\ngit_dir = proc.stdout.read().rstrip(b\'\\n\')\nstatus = proc.wait()\nif status:\n    raise ...\nif sys.version_info[0] >= 3:  # XXX ugh, but don\'t want to getcwdb etc\n    git_dir = git_dir.decode(\'utf8\')\ngit_dir = os.path.join(os.getcwd(), git_dir)\n\nproc = subprocess.Popen([\'git\',\n    \'--git-dir={}\'.format(git_dir),\n    \'checkout-index\', \'-z\', \'--stdin\'],\n    stdin=subprocess.PIPE, cwd=tmpdir)\nproc.stdin.write(b\'\\0\'.join(candidates))\nproc.stdin.close()\nstatus = proc.wait()\nif status:\n    raise ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们要将特殊的忽略文件写入os.path.join(tmpdir, \'.gitignore\'). 当然我们tmpdir现在也需要像它自己的 Git 存储库一样。这三件事就可以解决问题:

\n\n
import shutil\n\nsubprocess.check_call([\'git\', \'init\'], cwd=tmpdir)\nshutil.copy(os.path.join(git_dir, \'.pylintignore\'),\n    os.path.join(tmpdir, \'.gitignore\'))\nsubprocess.check_call([\'git\', \'add\', \'-A\'], cwd=tmpdir)\n
Run Code Online (Sandbox Code Playgroud)\n\n

因为我们现在将使用 Git 的忽略规则来处理.pylintignore我们复制到的文件.gitignore

\n\n

现在我们只需要再git status通过一次(使用-zgit b\'\\0\' style output, likediff-index`)来处理被忽略的文件;但有一个更简单的方法。我们可以让 Git 删除所有忽略的文件:

\n\n
subprocess.check_call([\'git\', \'clean\', \'-fqx\'], cwd=tmpdir)\nshutil.rmtree(os.path.join(tmpdir, \'.git\'))\nos.remove(os.path.join(tmpdir, \'.gitignore\')\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在所有内容都tmpdir正是我们应该检查的。

\n\n

警告:如果您的 python linter 需要查看导入的代码,您将不想删除文件。相反,您将想要使用git statusgit diff-index计算被忽略的文件。然后您将需要重复该操作git checkout-index,但可以选择将所有-a文件提取到临时目录中。

\n\n

完成后,只需像往常一样删除临时目录(务必自行清理!)。

\n\n

请注意,上面的某些部分是分段测试的,但将其全部组装成完整工作的 Python2 或 Python3 代码仍然是一项练习。

\n