11

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

一个警告:

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

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

  1. 检查传递给钩子的 refname 以查看我们是否正在更新refs/heads/. 如果没有,请尽早退出。
  2. 用于git rev-list获取正在推送的修订列表。
  3. 对于每个修订:
    1. 调用git show <revision_id>并使用正则表达式来查看子模块是否已更新(通过搜索`+Subproject commit [0-9a-f]+)。
    2. 如果此提交确实更改了子模块,请获取该.gitmodules特定提交所看到的文件内容 ( git show <revision_id>:.gitmodules)。
    3. 使用 3.1 和 3.2 的结果来获取子模块 URL 及其更新的提交 ID 的列表。
    4. 对照将子模块 URL 映射到文件系统上的本地裸 git 存储库的外部文件检查 3.3 中创建的此列表。
    5. cd到 3.4 中找到的路径并执行git rev-parse --quiet --verify <updated_submodule_commit_id>以查看该提交是否存在于该存储库中。如果不是,则以非零状态退出。

(注意:我相信 3.2 的结果可能会跨版本缓存,只要输出git rev-parse --quiet --verify <revision_id>:.gitmodules不会从一个版本更改为下一个版本。我省略了这部分以简化解决方案。)

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

4

2 回答 2

3

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

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

处理完所有这些后,您就可以将问题简化为检查给定的提交是否存在于给定的远程存储库中。不幸的是,您似乎已经注意到,这并不简单,至少据我所知。保持本地的、最新的克隆可能是你最好的选择,听起来你在那里很好。

顺便说一句,我不认为缓存在这里是相关的,因为更新挂钩是每个 ref 一次。是的,您可以在 pre-receive 钩子中执行此操作,该钩子会获取标准输入上的所有参考,但我不明白您为什么要费心做更多的工作。这不会是一项昂贵的操作,并且使用更新挂钩,您可以单独接受或拒绝被推送的各个分支,而不是因为只有一个坏了而阻止所有分支更新。

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

于 2011-01-21T23:12:25.467 回答
3

这是我对 git update 钩子的一点尝试。在这里记录它,以便对其他人有用。已知的警告是未处理“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
于 2012-07-02T14:43:55.207 回答