编写拒绝无效子模块提交的git update hook的最佳方法是什么?

时间:2011-01-21 20:51:40

标签: git hook git-submodules githooks

我正在尝试为git编写一个update挂钩,如果子模块正在更新为子模块的上游存储库中不存在的提交ID,则会跳转。换句话说,我想强制用户在将更改推送到子模块指针之前将更改推送到子模块存储库。

一个警告:

  • 我只想测试其子上游存储库与父存储库位于同一服务器上的子模块。否则我们开始不得不做一些疯狂的事情,比如在git hook中调用'git clone'或'git fetch',这不会很有趣。

我一直在玩一个想法,但感觉必须有更好的方法来做到这一点。这是我计划在更新钩子中做的事情:

  1. 检查传入钩子的refname,看看我们是否正在更新refs/heads/下的内容。如果没有,请提前退出。
  2. 使用git rev-list获取正在推送的修订列表。
  3. 每次修订:
    1. 调用git show <revision_id>并使用正则表达式查看子模块是否已更新(通过搜索`+子项目提交[0-9a-f] +)。
    2. 如果此提交确实更改了子模块,请获取该特定提交(.gitmodules)所见的git show <revision_id>:.gitmodules文件的内容。
    3. 使用3.1和3.2的结果获取子模块URL及其更新的提交ID列表。
    4. 检查3.3中创建的此列表,对照将子模块URL映射到文件系统上的本地裸git存储库的外部文件。
    5. cd到3.4中找到的路径并执行git rev-parse --quiet --verify <updated_submodule_commit_id>以查看该存储库中是否存在该提交。如果没有,则以非零状态退出。
  4. (注意:我相信3.2的结果可能会在修订版中缓存,只要git rev-parse --quiet --verify <revision_id>:.gitmodules的输出不会从一个修订版更改为下一个修订版。我将此部分留下来简化解决方案。 )

    所以是的,这看起来相当复杂,我不禁想知道是否有一些内部git命令可能会让我的生活变得更轻松。或者可能有不同的方式来思考这个问题?

2 个答案:

答案 0 :(得分:3)

编辑,很久以后:从Git 1.7.7开始,git-push现在有一个--recurse-submodules=check选项,如果没有任何子模块提交被推送到他们的遥控器,它会拒绝推送父项目。似乎尚未添加相应的push.recurseSubmodules配置参数。这当然不能完全解决这个问题 - 一个无能的用户仍然可以在没有检查的情况下推动 - 但它非常相关!

我认为最好的方法,而不是检查每个单独的提交,是在所有推送的提交中查看差异:git diff <old> <new>。你真的不想看整个差异;它可能是巨大的。不幸的是,git-submodule porcelain命令在裸存储库中不起作用,但您仍应该能够快速检查.gitmodules以获取路径列表(可能还有URL)。对于每一个,您可以git diff <old> <new> -- path,如果存在差异,则获取新的子模块提交。 (如果你担心000000旧的提交可能性,我相信你可以在新版本上使用git show。)

一旦你完成了所有这些工作,你就减少了问题,检查给定的远程存储库中是否存在给定的提交。不幸的是,看起来你已经注意到了,这不是直截了当的,至少是as far as I know。保持本地的,最新的克隆可能是你最好的选择,听起来你在那里很好。

顺便说一句,我认为缓存在这里并不重要,因为更新挂钩是每个ref一次。是的,你可以在预接收钩子中执行此操作,该钩子获取stdin上的所有引用,但我不明白为什么你应该打扰做更多的工作。它不会是一个昂贵的操作,并且使用更新挂钩,您可以单独接受或拒绝被推送的各个分支,而不是阻止所有更新它们,因为只有一个是坏的。< / p>

如果你想省一些麻烦,我可能只是避免解析gitmodules文件,并将列表硬编码到钩子中。我怀疑你的子模块列表经常变化,因此维护它可能比写自动化更便宜。

答案 1 :(得分:3)

这是我对git update hook的小尝试。在此记录,以便对其他人有用。已知的警告是'0000 ......'特殊情况未得到处理。

#!/bin/bash

REF=$1
OLD=$2
NEW=$3

# This update hook is based on the following information:
# http://stackoverflow.com/questions/3418674/bash-shell-script-function-to-verify-git-tag-or-commit-exists-and-has-been-pushe

# Get a list of submodules
git config --file <(git show $NEW:.gitmodules) --get-regexp 'submodule..*.path' | while read key path
do
    url=$(git config --file <(git show $NEW:.gitmodules) --get "${key/.path/.url}")
    git diff "$OLD..$NEW" -- "$path" | grep -e '^+Subproject commit ' |
    cut -f3 -d ' ' | while read new_rev
    do
        LINES=$(GIT_DIR="$url" git branch --quiet --contains "$new_rev" 2>/dev/null | wc -l)
        if [ $LINES == 0 ]
        then
            echo "Commit $new_rev not found in submodule $path ($url)" >&2
            echo "Please push that submodule first" >&2
            exit 1
        fi
    done || exit 1
done || exit 1

exit 0