首页 > 解决方案 > 预接收挂钩中的差异

问题描述

我用 Python 编写了一个简单的服务器端 git pre-receive 钩子。目标是分析差异并拒绝包含我们认为无效的某些文本的推送。我使用以下命令集编写了钩子:

git ls-tree
git diff --name-only
git cat-file 

但是我只是注意到我正在扫描作为提交的一部分推送的整个文件。但我只想扫描差异,即这次推送中更改的行。

原因是一些无效的文本可能是误报并且没关系。它可以被强制推动。但是,如果再次编辑相同的文件并添加有效文本,推送将被拒绝,因为该文件先前具有无效文本。每次编辑文件时都会发生这种情况,这有点烦人

所以基本上问题是,如何在当前推送服务器端钩子代码中获取更改的linesdiff,而不是扫描完整的文件。

谢谢

标签: pythongitgithooks

解决方案


...如何获得更改后的行

这个问题不完整。假设我告诉你有一些人,包括 Alice、Bob、Carol 等等。现在我告诉你鲍勃是不同的。 与谁或什么不同?

在预接收挂钩中,您必须从标准输入中读取行。每行具有以下形式:

old-hash new-hash reference-name

这些是什么意思?(这是您在继续下一部分之前回答的练习,尽管答案包含在下面的最后一部分中。)

获得差异需要您选择两个项目

提交是文件的快照——被冻结到该提交中的每个文件的完整副本。不涉及任何差异;只有完整的文件。

但是,您想要差异。要获得某些文件的差异file.ext,您必须选择其他版本file.ext比较两者。什么是正确的“其他版本”?

对于某些提交,您很幸运:有一个非常明确正确的“其他版本” file.ext,即:该file.ext提交的提交中的副本。事实上,这对提交中的每个文件都重复:我们希望将该文件的提交版本与该文件的父版本进行比较,以查看发生了什么变化。

有一个方便的脚本(“管道”)命令,它是git diff-tree:给定普通非合并提交的哈希 ID,git diff-tree将提交的父级与提交进行比较。添加-p--patch获得文本差异(这自动暗示该-r选项)。考虑使用-U0删除上下文行。当然,您仍然需要解析输出行,以检测大块标题和添加/删除的标记。

然而,一个简单的git diff-tree <hash>方法不适用于两种提交情况:

  • 提交没有父级。幸运的是,空树来拯救:成功git diff-tree -p $(git hash-object -t tree /dev/null) $hash了。

  • 合并提交有两个或多个父级。这里git diff-tree默认产生一个组合差异。如果没问题,你可以忽略这种情况。如果没有,您可能会考虑使用--first-parent -m或仅-m针对每个父级(默认)或第一个父级(--first-parent)拆分合并并获取多个差异。

这让你获得了一次提交的差异,所以现在我们进入最后一部分。

现在是时候处理钩子的标准输入行了

当您阅读每一行时,您的工作是:

  • 检查特殊的全零位空散列的旧散列和新散列。在 Python 中,有多种表达方式;一种是:

    def is_null(hash):
        return all(i == '0' for i in hash)
    

    如果旧散列为空,则在新散列处创建引用。如果新哈希为空,则引用曾经具有给定的旧哈希,并且正在被删除。否则——哈希值都不为空——引用被更新:它有旧的哈希值,并且将有新的哈希值。

  • 弄清楚要做什么,如果有的话,改变特定的参考。是否允许删除?允许创作吗?如果这是一个分支名称(以 开头refs/heads/)与一个标签名称(以 开头)还是refs/tags/完全不同的东西,这有关系吗?

    创作尤其困难。新引入的名称使给定的对象可以通过该名称访问。如果对象是一个标签或提交,这使得其他对象也可以通过该名称访问。这些对象中的一些或全部可能是新的。这些对象中的一些或全部可能已经存在。经典案例是当有人创建一个新的分支名称时:它可能指向一个现有的提交,已经在某个其他分支上,或者它可能指向一个新的提交,新分支的新提示,它可能有许多额外的新提交在加入一些现有的分支机构之前。

    更新是最常见的,通常也是最容易处理的。您知道现有的引用名称使旧对象可访问,而建议的更新是使新对象可访问。如果引用是一个分支名称,那么这两个对象实际上都是提交对象,并且很容易找到哪些提交(如果有)是从提议的新哈希中新近可访问的,以及哪些提交(如果有)正在从可访问性中删除提议的新哈希:

    git rev-list $old..$new
    

    生成一组新近可达的哈希 ID,并且:

    git rev-list $new..$old
    

    产生不再可达的集合。(使用git rev-list --left-right $old...$new带有三个点的 ,一次获得两组哈希 ID,并带有区分标记。您可以使用$new...$old: 这产生的对称差异本身是对称的,当然除了左右两边是相反的。)

假设您已经以某种方式处理了创建,如果您的目标是检查新近可访问的提交(无论它们对存储库是否是新的),您可以简单地遍历所有新提交,测试每个提交以查看它是否是根提交,普通(单父)提交或合并提交。(提示:添加--parentsgit rev-list命令以获取包含的父 ID,以便您可以轻松地知道每个提交有多少父。另外,考虑您正在行走的提交图片段的图结构:$old..$new可能包括合并,这可能会产生很多可访问的提交,对于存储库可能是新的,也可能不是新的。)

您现在拥有所有的提交哈希,以及它们的父级计数。您还知道如何根据git diff-tree需要将每个提交与其父级或空树进行比较。所以现在你已经准备好编写你喜欢的预接收钩子了。


推荐阅读