问题1:提交
获取提交列表通常不是很困难,因为您只需要运行git rev-list
。但是在这里有一些边缘情况。正如 githooks文档所说:
“push”操作的信息通过标准输入提供,格式如下所示:
SP SP SP LF
例如,如果运行命令“git push origin master:foreign”,则hook将接收以下类似的行:
refs/heads/master 67890 refs/heads/foreign 12345
其中将提供完整的40个字符的SHA-1。如果外部引用尚不存在,则将为40个0。如果要删除引用,则会将提供为“(delete)”,并且将为40个0。如果本地提交是由其他无法扩展的名称指定(例如HEAD〜或SHA-1),则将按原样提供。因此,您必须读取每个stdin行并将其解析为其组件,然后决定:
- 这是一个分支更新吗?(即,远程引用是否具有
refs/heads/*
的形式,作为通配符匹配?)如果不是,您是否仍要检查任何提交?
- 正在创建或销毁引用吗?如果是,您应该怎么做?
- 您是否拥有由外部哈希指定的对象?(如果没有,并且推送成功-它可能会失败-这将删除一些提交对象,但您无法确定哪些。此外,您无法正确列出将传输的本地提交:您知道您要求他们将其名称设置为什么,但您不知道您和他们共享的提交,因为您无法遍历其历史记录。)
假设您已经确定了这些答案——我们称之为“否”,“跳过”和“本地拒绝不可分析的推送”——我们继续列出提交,这只是输出:
git rev-list remotehash..localhash
你可能会用以下方式实现:
proc = subprocess.Popen(['git', 'rev-list',
'{}..{}'.format(remotehash, localhash)], stdout=subprocess.PIPE)
text = proc.stdout.read()
if proc.wait():
raise ...
if not isinstance(text, str):
text = text.decode('utf-8')
lines = text.split('\n')
请注意,如果远程或本地哈希值为全零,或者远程哈希值对应的对象在您的本地存储库中不存在(您可以使用“git rev-parse --verify --quiet”进行检查并检查返回状态,或者在此处使用失败作为无法检查提交的指示,尽管创建新分支时有其他选项),则
此git rev-list
调用将失败(退出并显示非零状态)。
请注意,必须对要更新的
每个引用运行上述
git rev-list
。同样的提交或某些相同的提交可能会发送到不同的引用。例如:
git push origin HEAD:br1 HEAD:br2 HEAD~3:br3
我希望您能远程更新三个分支
br1
到
br3
,将
br1
和
br2
设置为与
HEAD
相同的提交,并将
br3
设置为比
HEAD
早三步的提交。我们不知道哪些提交是真正的新提交-另一端的预接收挂钩可以弄清楚,但我们不能-但如果远程的
br1
和
br2
都从
HEAD~3
更新到
HEAD
,并且远程的
br3
正在从
HEAD~2
向后更新到
HEAD~3
,则最多只能有
HEAD~1
到
HEAD
的提交是新的。是否要检查
HEAD~2
,因为它现在很可能会出现在其他存储库中的
br1
和
br2
上(即使它已经在那里的
br3
上),这也取决于您。
问题2:文件
现在你面临更困难的问题。你在编辑中提到:
编辑:我认为同一文件可能会在将要推送的多个提交中被修改 - 因此最好不要重复验证同一文件。
每个要发送的提交都有存储库的完整快照。也就是说,每个提交都有每个文件。我不知道你打算运行什么验证,但你是正确的:如果你要发送总共六个提交,那么所有六个提交中的大部分文件很可能是相同的,只有少数文件被修改。然而,在提交1234567
(相对于父提交)中可能会修改文件foo.py
,然后在提交fedcba9
中再次修改,你可能应该检查两个版本。
此外,当提交是一个合并提交时,它至少有两个不同的父级。如果一个文件与任一父级不同,那么你应该检查它吗?还是只有在它与两个父级都不同时才检查它,这表明它包含了来自合并的“两侧”的更改?如果它只有来自“一侧”的更改,那么该文件可能已经被“预检查”过了,因为发生在另一侧提交的检查,因此它可能不需要重新检查(尽管这当然取决于检查的类型)。
(对于八爪鱼合并,即具有多个父级的合并,这个问题变得更加难以思考。)
相对而言,很容易看出哪些文件在提交中与其父级或祖先有所改变:只需使用适当的选项(特别是-r
以递归到提交的子树)运行git diff-tree
即可。默认输出格式非常适合机器解析,但您可能希望添加-z
以便直接在Python中处理。如果您一次只处理一个提交(您可能也想这样做),则可能还需要--no-commit-id
,以便无需读取并跳过提交头。
是否启用重命名检测以及阈值设置取决于您。再次取决于您要验证文件的具体操作,最好关闭重命名检测:这样,您将“看到”重命名的文件作为旧路径的删除和新路径的添加。
特定提交的git diff-tree -r --no-commit-id
输出如下:
:000000 100644 0000000000000000000000000000000000000000 b0b4c36f9780eaa600232fec1adee9e6ba23efe5 A Documentation/RelNotes/2.13.0.txt
:100755 100755 6a208e92bf30c849028268b5fca54b902f671bbd 817d1cf7ef2a2a99ab11e5a88a27dfea673fec79 M GIT-VERSION-GEN
:120000 120000 d09c3d51093ac9e4da65e8a127b17ac9023520b5 125bf78f3b9ed2f1444e1873ed02cce9f0f4c5b8 M RelNotes
哈希 ID 是旧和新的 blob 哈希值;字母代码和路径名如所述。 您可以使用
git cat-file -p
命令检索新哈希 ID 的文件内容。 如果您的 Git 足够新,您甚至可以添加
--textconv
--filters
和
--path=<path>
(或者使用文件的路径和提交 ID,而不是
--path=...
来命名要提取的对象的哈希),以应用任何基于
.gitattributes
的过滤和行尾转换。 或者如果过滤器不重要,您可以直接使用存储在仓库中的对象形式。
根据您所检查的内容,您可能需要将整个提交提取到临时工作树中进行检查。 (例如,静态分析器可能需要执行任何
import
)。 在这种情况下,您可以直接运行
git checkout
,使用
GIT_INDEX_FILE
环境变量(像往常一样通过
subprocess
传递)指定一个临时索引文件,以便不会干扰主索引。 使用
--work-tree=
或通过
GIT_WORK_TREE
环境变量指定替代工作树。 无论如何,
git diff-tree
都会告诉您哪些文件已被
修改,因此应进行检查。 (您可以使用
shutil.rmtree
在测试完成后处理临时工作树。)
如果您要检查合并提交,请特别注意
合并所做的组合差异的描述,因为它们需要稍微不同的处理方式(或使用
-m
拆分合并)。
编辑:一些代码以展示我的意思
这里有一段代码,可以获取所有输入并显示每个提交被添加到每个外部分支。请注意,如果只删除提交,则添加提交的列表将为空。这也只是经过轻微测试,不打算做到健壮、可维护、良好的风格等,只是一个最小的例子。
import re, subprocess, sys
lines = sys.stdin.read().splitlines()
for line in lines:
localref, localhash, foreignref, foreignhash = line.split()
if not foreignref.startswith('refs/heads/'):
print('skip {}'.format(foreignref))
continue
if re.match('0+$', localhash):
print('deleting {}, do nothing'.format(foreignref))
continue
if re.match('0+$', foreignhash):
print('creating {}, too hard for now'.format(foreignref))
continue
proc = subprocess.Popen(['git', 'rev-parse', '--quiet', '--verify',
foreignhash],
stdout=subprocess.PIPE)
_ = proc.stdout.read()
status = proc.wait()
if status:
print('we do not have {} for {}, try '
'git fetch'.format(foreignhash, foreignref))
# can try to run git fetch here ourselves, but for now:
continue
print('sending these commits for {}:'.format(foreignref))
subprocess.call(['git', 'rev-list', '{}..{}'.format(localhash, foreignhash)])
git rebase
之后)时,当您进行强制推送时,该解决方案在某些情况下(不一定全部)会失败。它还没有检查任何中间提交,也没有检查第一个引用以外的任何其他更新的引用,如果您推送多个引用(例如,使用git push --all
或将push.default
设置为matching
的git push
)。但是,如果这就是您想要的,那么它应该足够了。(好吧,它需要一些Python3的工作,但这也没关系:您可以继续使用Python 2.7。) - torekgit rev-list
(这确实需要偶尔的git fetch
来填充未知的哈希ID - 这可能可以在pre-push钩子内完成,但我从未尝试过),并查看git diff-tree
结果。 - toreksys.stdin.read()
确实会读取所有标准输入。 - Ogensys.stdin.read()
确实读取了所有内容。 :-) ) - torek