跳过之前已处理的接收后挂钩中的 Git 修订

  • 本文关键字:修订 Git 处理 git git-post-receive
  • 更新时间 :
  • 英文 :


我有一个 git 接收后钩子,它提取在"git push"期间添加的所有修订,并对每个修订进行一些处理(例如发送通知电子邮件(。 这很好用,除非合并时;例如:

    我在分支 1
  1. 上进行一些提交,然后推送分支 1。接收后挂钩正确处理提交。
  2. 我将分支 1 合并到分支 2 中,然后推送分支 2。 接收后挂钩会再次处理所有合并的提交。

我怎样才能避免这种情况? 下面是我的接收后钩子的开头,我在其中提取了应该处理的提交(最后$COMMITS保存要处理的提交列表(。

#!/bin/sh
REPO_PATH=`pwd`
COMMITS=''
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# for each ref that was updated during the push
while read OLD_REV NEW_REV REF_NAME; do
  OLD_REV="`git rev-parse $OLD_REV`"
  NEW_REV="`git rev-parse $NEW_REV`"
  if expr "$OLD_REV" : '0*$' >/dev/null; then
    # if the branch was created, add all revisions in the new branch; skip tags
    if ! expr "$REF_NAME" : 'refs/tags/' >/dev/null; then
      REF_REV="`git rev-parse $REF_NAME`"
      REF_NAME="`git name-rev --name-only $REF_REV`"
      COMMITS="$COMMITS `git rev-list $REF_NAME | git name-rev --stdin | grep -G ($REF_NAME.*) | awk '{ print $1 }' | tr 'n' ' '`"
    fi
  elif expr "$NEW_REV" : '0*$' >/dev/null; then
    # don't think branch deletes ever hit a post-receive hook, so we should never get here
    printf ''
  else
    # add any commits in this push
    COMMITS="$COMMITS `git rev-parse --not --all | grep -v $(git rev-parse $REF_NAME) | git rev-list --reverse --stdin $(git merge-base $OLD_REV $NEW_REV)..$NEW_REV | tr 'n' ' '`"
  fi
done

看看$(prefix)/share/git-core/contrib/hooks/post-receive-email,它所做的(我认为(正是你想要的。 基本上,它使用 git for-each-ref 来查找所有分支的名称,然后排除从正在更新的分支以外的某个分支可以访问的每个提交:

if [ "$change_type" = create ]
then
    # Show all revisions exclusive to this (new) branch.
    revspec=$newrev
else
    # Branch update; show revisions not part of $oldrev.
    revspec=$oldrev..$newrev
fi
other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ |
     grep -F -v $refname)
git rev-parse --not $other_branches | git rev-list --pretty --stdin $revspec

(我在这里简化了它,希望不会损坏我的剪切和粘贴工作中的任何内容。 这里的输入是:如果$oldrev全零,则$change_type create,否则为update; $oldrev是最近从标准输入中读取的行中的旧修订版 SHA1; $newrev是新的修订版 SHA1;$refname是全名,例如 refs/heads/topic .(

我们要做的是将以前处理的提交的哈希保存在文本文件中。每次钩子运行时,它都会在该文件中查找以检查给定的提交是否已被处理。如果它尚未处理该提交,请处理它,然后将该提交记录到文件中。

这不是很可扩展,因为文本文件只会随着更多提交添加到存储库而增长,并且检查给定提交的时间也会增加。

我们通过让接收后钩子在遇到合并提交(具有两个或多个父级的提交(时停止处理来实现这一点。这在推动合并时需要一些纪律,以确保不会抛出其他"真实"提交。规则是始终在合并之前推送,然后分别推送合并。

我在接收后的钩子中完全实现了这个。它只通知 trac 自上次获取以来的新提交而不重复,无论新提交是同时推送到单个分支还是多个分支。此方法在 git 目录中保留一个名为 TRAC_HEAD 的文件,用于跟踪哪些提交已被处理。

建议您在启用挂钩之前在 .git 目录中运行 cat refs/heads/* > TRAC HEAD

#!/bin/sh
#
# Reads and notifies trac of only new commits that have not yet been dealt with.
#
# The "post-receive" script is run after receive-pack has accepted a pack
# and the repository has been updated.  It is passed arguments in through
# stdin in the form
#  <oldrev> <newrev> <refname>
# For example:
#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
TRAC_PATH="/path/to/trac/env"
# Read the standard input
while read oldrev newrev refname ; do
        echo "Processing branch: $refname"
        # Read the last revisions for each branch from TRAC_HEAD
        exclusions=$(cat TRAC_HEAD | uniq |  sed -e 's/^/^/' -e 's/ / ^/g' | xargs echo)
        echo "Exclusion list: $exclusions"
        git rev-list --reverse $newrev $exclusions | while read rev ; do
                trac-admin $TRAC_PATH changeset added '(default)' $rev
                echo "Processed: $rev"
        done
        # Add to the exclusions file the latest revision from this branch
        echo $newrev >> TRAC_HEAD
done
# Update the TRAC_HEAD file
cat refs/heads/* > TRAC_HEAD

正如@Matt White所指出的,$(prefix)/share/git-core/contrib/hooks/post-receive-email中采用的方法可以通过在同一推送中推送包含相同新提交的多个引用来轻松规避。

@BrunoOliveira 和 @Kenaniah 都有涉及保留额外信息的解决方法。

我相信还有一种可行的方法,当您迭代推送中的 ref 时,将"在此推送中先前考虑过的其他 ref"显式传递到--not参数的 --exclude -list 中:

#!/bin/bash
#
while read oldrev newrev refname
do
        if expr "$newrev" : '0*$' >/dev/null
        then
                echo "---Deleted: $refname---"
        else
                EXCLUDE_REFS+=($refname)
                # this is not safe against special characters in ref names; fixes welcome!
                new_commits_command=$(echo git rev-list "$refname" --not "${EXCLUDE_REFS[@]/#/--exclude }" --all --)
                echo "---New or updated: $refname--- (with" $($new_commits_command | wc -l) "new and unique commits)"
                $new_commits_command
        fi
done

据我所知,这种方法的主要问题是,您最终对git rev-list命令的参数数量是推送引用数量的两倍 - 这最终可能会导致错误。我已经在我的 Ubuntu 环境中测试了 1,000 个 2,000 个 refs 没有问题,我不知道任何给定服务器环境和 ref 模式的限制可能在哪里。

一般来说,如果你正在操作一个进行这种检查的服务器,我假设你实际上希望限制一个可以一次性推送的引用数量,无论如何,在一个pre-receive钩子中。

最新更新