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

发布于 2024-10-12 23:52:30 字数 1203 浏览 7 评论 0原文

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

需要注意的是:

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

我一直在考虑一个想法,但感觉必须有更好的方法来做到这一点。以下是我计划在更新挂钩中执行的操作:

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

(注意:我相信只要 git rev-parse --quiet --verify:.gitmodules 的输出不会从 是的,这看起来相当复杂

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

I am attempting to write an update hook for git that bounces if a submodule is being updated to a commit ID that does not exist in the submodule's upstream repository. To say it another way, I want to force users to push changes to the submodule repositories before they push changes to the submodule pointers.

One caveat:

  • I only want to test submodules whose bare, upstream repositories exist on the same server as the parent repository. Otherwise we start having to do crazy things like call 'git clone' or 'git fetch' from within a git hook, which would not be fun.

I have been playing around with an idea but it feels like there must be a better way to do this. Here is what I was planning on doing in the update hook:

  1. Check the refname passed into the hook to see if we are updating something under refs/heads/. If not, exit early.
  2. Use git rev-list to get a list of revisions being pushed.
  3. For each revision:
    1. Call git show <revision_id> and use a regular expression that looks to see if a submodule was updated (by searching for `+Subproject commit [0-9a-f]+).
    2. If this commit did change a submodule, get the contents of the .gitmodules files as seen by that particular commit (git show <revision_id>:.gitmodules).
    3. Use the results of 3.1 and 3.2 to get a list of submodule URLs and their updated commit IDs.
    4. Check this list created in 3.3 against an external file that maps submodule URLs to local bare git repositories on the filesystem.
    5. cd to the paths found in 3.4 and execute git rev-parse --quiet --verify <updated_submodule_commit_id> to see if that commit exists in that repository. If it does not, exit with a non-zero status.

(Note: I believe the results of 3.2 can potentially be cached across revisions as long as the output to git rev-parse --quiet --verify <revision_id>:.gitmodules doesn't change from one revision to the next. I left this part out to simplify the solution.)

So yeah, this seems pretty complex, and I can't help but wonder if there are some internal git commands that might make my life a lot easier. Or maybe there is a different way to think about the problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

↘紸啶 2024-10-19 23:52:30

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

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

一旦你处理好了所有这些,你就减少了检查给定远程存储库中是否存在给定提交的问题。不幸的是,正如您所注意到的那样,这并不简单,至少 据我所知。保留本地的、最新的克隆可能是你最好的选择,而且听起来你在这方面做得很好。

顺便说一句,我认为缓存在这里无关紧要,因为更新挂钩每个引用一次。是的,您可以在预接收挂钩中执行此操作,该挂钩获取标准输入上的所有引用,但我不明白为什么您应该费心做更多工作。这不会是一项昂贵的操作,并且通过更新挂钩,您可以单独接受或拒绝正在推送的各个分支,而不是因为只有一个分支不好而阻止所有分支更新。

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

Edit, much later: As of Git 1.7.7, git-push now has a --recurse-submodules=check option, which refuses to push the parent project if any submodule commits haven't been pushed to their remotes. It doesn't appear that a corresponding push.recurseSubmodules config parameter has been added yet. This of course doesn't entirely address the problem - a clueless user could still push without the check - but it's quite relevant!

I think the best approach, rather than examining each individual commit, is to look at the diff across all of the pushed commits: git diff <old> <new>. You don't want to look at the whole diff though, really; it could be enormous. Unfortunately, the git-submodule porcelain command doesn't work in bare repos, but you should still be able to quickly examine .gitmodules to get a list of paths (and maybe URLs). For each one, you can git diff <old> <new> -- path, and if there is a diff, grab the new submodule commit. (And if you're worried about a 000000 old commit possibility, you can just use git show on the new one, I believe.)

Once you get all that taken care of, you've reduced the problem to checking whether given commits exist in given remote repositories. Unfortunately, as it looks like you've noticed, that's not straightforward, at least as far as I know. Keeping local, up-to-date clones is probably your best bet, and it sounds like you're good there.

By the way, I don't think the caching is going to be relevant here, since the update hook is once per ref. Yes, you could do this in a pre-receive hook, which gets all the refs on stdin, but I don't see why you should bother doing more work. It's not going to be an expensive operation, and with an update hook, you can individually accept or reject the various branches being pushed, instead of preventing all of them from being updated because only one was bad.

If you want to save some trouble, I'd probably just avoid parsing the gitmodules file, and hardcode a list into the hook. I doubt your list of submodules changes very often, so it's probably cheaper to maintain that than to write something automated.

漫雪独思 2024-10-19 23:52:30

这是我对 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

Here is my little attempt at a git update hook. Documenting it here so that it could be useful to others. Known caveat is that the '0000...' special case is not handled.

#!/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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文