这有点笨拙,但应该可以:
- 创建一个临时 git 存储库
- 用您提议的
.gitignore 填充它
- 也为每个路径名填充一个文件
- 在生成的临时存储库上使用
git status --porcelain
- 将其清空(将其完全删除,或将其保留为空以供下一次通过,以更合适的方式为准)。
不过,这确实有点像XY problem。 Y 的笨拙解决方案可能是真正问题 X 的糟糕解决方案。
附有详细信息(和附注)的评论后回答
因此,您有一些文件要 lint,可能来自检查提交。以下代码可能比您需要的更通用(在大多数情况下,我们实际上并不需要 status 部分),但我将其包括在内以作说明:
import subprocess
proc = subprocess.Popen(['git',
'diff-index', # use plumbing command, not user diff
'--cached', # compare index vs HEAD
'-r', # recurse into subdirectories
'--name-status', # show status & pathname
# '--diff-filter=AM', # optional: only A and M files
'-z', # use machine-readable output
'HEAD'], # the commit to compare against
stdout=subprocess.PIPE)
text = proc.stdout.read()
status = proc.wait()
# and check for failure as usual: Git returns 0 on success
现在我们需要来自Iterating over every two elements in a list 的pairwise 之类的东西:
import sys
if sys.version_info[0] >= 3:
izip = zip
else:
from itertools import izip
def pairwise(it):
"s -> (s0, s1), (s2, s3), (s4, s5), ..."
a = iter(it)
return izip(a, a)
我们可以将git status 输出分解为:
for state, path in pairwise(text.split(b'\0')):
...
现在每个文件都有一个状态(b'A' = 已添加,b'M' = 已修改,等等)。 (如果您允许符号链接,请务必检查状态 T,以防文件从普通文件更改为符号链接,反之亦然。请注意,我们依赖 pairwise 来丢弃未配对的空 b'' 字符串text.split(b'\0') 的结尾,因为 Git 生成的是 NUL-终止列表,而不是 NUL-分隔列表。)
让我们假设在某个时候我们将文件收集到可能是 lint 到一个名为 candidates 的列表(或可迭代)中:
>>> candidates
[b'a.py', b'dir/b.py', b'z.py']
我假设您已经避免将 .gitignore 放入此列表或可迭代对象中,因为我们计划将其用于我们自己的目的。
现在我们有两个大问题:忽略一些文件,以及获取那些将实际被 linted 的文件的版本。
仅仅因为一个文件被列为已修改,并不意味着工作树中的版本就是将要提交的版本。例如:
$ git status
$ echo foo >> README
$ git add README
$ echo bar >> README
$ git status --short
MM README
这里的第一个M 表示索引版本与HEAD 不同(这是我们从上面的git diff-index 得到的)而这里的第二个M 表示索引版本也是 不同于工作树版本。
将被提交的版本是index版本,而不是工作树版本。我们需要 lint 的不是工作树版本,而是索引版本。
所以,现在我们需要一个临时目录。如果你的 Python 是旧的,这里使用的东西是 tempfile.mkdtemp,或者如果不是,则使用幻想的上下文管理器版本。请注意,上面使用 Python3 时有字节字符串路径名,使用 Python2 时有普通(字符串)路径名,因此这也取决于版本。
因为这是普通的 Python,而不是棘手的 Git 交互,所以我把这部分留作练习——我将略过所有字节与字符串路径名的东西。 :-) 但是,对于下面的 --stdin -z 位,请注意 Git 需要文件名列表为 b\0-分隔字节。
一旦我们有了(空的)临时目录,其格式适合在subprocess.Popen 中传递给cwd=,我们现在需要运行git checkout-index。有几个选择,但让我们这样吧:
import os
proc = subprocess.Popen(['git', 'rev-parse', '--git-dir'],
stdout=subprocess.PIPE)
git_dir = proc.stdout.read().rstrip(b'\n')
status = proc.wait()
if status:
raise ...
if sys.version_info[0] >= 3: # XXX ugh, but don't want to getcwdb etc
git_dir = git_dir.decode('utf8')
git_dir = os.path.join(os.getcwd(), git_dir)
proc = subprocess.Popen(['git',
'--git-dir={}'.format(git_dir),
'checkout-index', '-z', '--stdin'],
stdin=subprocess.PIPE, cwd=tmpdir)
proc.stdin.write(b'\0'.join(candidates))
proc.stdin.close()
status = proc.wait()
if status:
raise ...
现在我们要将我们的特殊忽略文件写入os.path.join(tmpdir, '.gitignore')。当然,我们现在还需要tmpdir 来充当自己的 Git 存储库。这三件事可以解决问题:
import shutil
subprocess.check_call(['git', 'init'], cwd=tmpdir)
shutil.copy(os.path.join(git_dir, '.pylintignore'),
os.path.join(tmpdir, '.gitignore'))
subprocess.check_call(['git', 'add', '-A'], cwd=tmpdir)
因为我们现在将对复制到 .gitignore 的 .pylintignore 文件使用 Git 的忽略规则。
现在我们只需要一个git status pass(-z for b'\0' style output, likegit diff-index`)来处理被忽略的文件;但有一个更简单的方法。我们可以让 Git 删除所有 non 忽略的文件:
subprocess.check_call(['git', 'clean', '-fqx'], cwd=tmpdir)
shutil.rmtree(os.path.join(tmpdir, '.git'))
os.remove(os.path.join(tmpdir, '.gitignore')
现在tmpdir 中的所有内容正是我们应该 lint 的内容。
警告:如果您的 python linter 需要查看导入的代码,您不会想要删除文件。相反,您需要使用git status 或git diff-index 来计算被忽略的文件。然后您需要重复 git checkout-index,但使用 -a 选项,将所有文件提取到临时目录中。
完成后,像往常一样删除临时目录(始终自己清理!)。
请注意,上面的某些部分是分段测试的,但将它们全部组装成完整的 Python2 或 Python3 代码仍然是一个练习。