我正在Python中编写一个git pre-commit钩子,我想在处理它们之前定义一个像.gitignore
文件这样的黑名单来检查文件。有没有一种简单的方法来检查是否根据一组.gitignore
规则定义了文件?规则有点神秘,我宁愿不必重新实现它们。
答案 0 :(得分:4)
假设您位于包含.gitignore文件的目录中,那么一个shell命令将列出所有不被忽略的文件:
git ls-files
从python您可以简单地调用:
import os
os.system("git ls-files")
,您可以像这样提取文件列表:
import subprocess
list_of_files = subprocess.check_output("git ls-files", shell=True).splitlines()
如果要列出被忽略的文件(又称未跟踪文件),则可以添加选项“ --other”:
git ls-files --other
答案 1 :(得分:1)
这是相当笨拙的,但应该有效:
.gitignore
git status --porcelain
所以,你有一些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''
末尾的{1}}字符串,因为Git生成NUL- 终止的列表而不是NUL- 分隔的列表。)< / p>
让我们假设在某些时候我们将文件 - 可能 - lint收集到名为text.split(b'\0')
的列表(或可迭代)中:
candidates
我会假设您已避免将>>> candidates
[b'a.py', b'dir/b.py', b'z.py']
放入此列表或可迭代中,因为我们计划将其用于我们自己的目的。
现在我们遇到两个大问题:忽略一些文件,并获取那些实际上会被linted的文件的版本。
仅仅因为文件被列为已修改,并不意味着工作树中的版本是将提交的版本。例如:
.gitignore
此处的第一个$ git status
$ echo foo >> README
$ git add README
$ echo bar >> README
$ git status --short
MM README
表示索引版本与M
不同(这是我们从HEAD
获得的内容),而第二个git diff-index
在这里表示索引版本也与工作树版本不同。
将提交的版本是索引版本,而不是工作树版本。我们需要的lint不是工作树版本,而是索引版本。
所以,现在我们需要一个临时目录。如果你的Python是旧的,那么这里使用的是M
,如果不是,那么使用的是风格化的上下文管理器版本。请注意,在使用Python3时我们有上面的字节串路径名,在使用Python2时我们有普通(字符串)路径名,所以这也取决于版本。
由于这是普通的Python,而不是棘手的Git交互,我把这部分作为一个练习 - 我只是在所有的字节 - vs-strings路径名的东西上光泽。 :-)但是,对于下面的tempfile.mkdtemp
位,请注意Git需要文件名列表为b --stdin -z
- 分隔字节。
一旦我们拥有(空)临时目录,其格式适合传递到\0
中的cwd=
,我们现在需要运行subprocess.Popen
。有一些选择,但让我们走这条路:
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')
像自己的Git存储库一样。这三件事就可以解决问题了:
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)
的{{1}}文件一起使用。
现在我们只需要再传.pylintignore
次{.gitignore
git status
git diff-index`来处理被忽略的文件;但是有一种更简单的方法。我们可以让Git删除所有非 - 签名文件:
-z
现在b'\0' style output, like
中的所有内容正是我们应该发布的内容。
警告:如果你的python linter需要看到导入的代码,你不会想要删除文件。相反,您需要使用subprocess.check_call(['git', 'clean', '-fqx'], cwd=tmpdir)
shutil.rmtree(os.path.join(tmpdir, '.git'))
os.remove(os.path.join(tmpdir, '.gitignore')
或tmpdir
来计算被忽略的文件。然后,您需要重复git status
,但使用git diff-index
选项,将所有文件解压缩到临时目录中。
完成后,只需像往常一样删除临时目录(总是自己清理!)。
请注意,上面的某些部分是分段测试的,但是将它们全部组装成完整的Python2或Python3代码仍然是一个练习。