Пропустить обработку версий Git в обработчике после получения, которые уже были обработаны ранее

У меня есть ловушка git post-receive, которая извлекает все ревизии, которые были добавлены во время "git push", и выполняет некоторую обработку каждой из них (например, отправку уведомлений по электронной почте). Это прекрасно работает, за исключением случаев слияния; например:

  1. Я делаю некоторые коммиты на branch1 и затем нажимаю branch1. Хук post-receive обрабатывает коммиты правильно.
  2. Я объединяю branch1 в branch2 и затем нажимаю branch2. Хук post-receive обрабатывает все объединенные коммиты во второй раз.

Как я могу избежать этого? Ниже приведено начало моего хука после получения, где я извлекаю коммиты, которые должны быть обработаны (в конце $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

5 ответов

Решение

Смотреть на $(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

(Я упростил это здесь, и, надеюсь, ничего не повредил в моей работе вырезать и вставить. Входные данные здесь: $change_type является create если $oldrev все нули, иначе это update; $oldrev старая версия SHA1 из строки, недавно прочитанной из stdin; $newrev это новая версия SHA1; а также $refname полное имя, например, refs/heads/topic.)

Что мы делаем, это сохраняем хэш ранее обработанных коммитов в текстовом файле. Каждый раз, когда запускается ловушка, он просматривает этот файл, чтобы проверить, был ли данный коммит уже обработан или нет. Если он еще не обработал этот коммит, обработайте его и затем запишите этот коммит в файл.

Это не очень масштабируемо, так как текстовые файлы будут увеличиваться только по мере добавления коммитов в репозиторий и увеличения времени для проверки данного коммита.

Мы сделали это, обработав обработчик перехвата после получения, когда он обнаружил коммит слияния (коммит с двумя или более родителями). Это требует некоторой дисциплины при использовании слияний, чтобы другие "настоящие" коммиты не отбрасывались. Дисциплина состоит в том, чтобы всегда подталкивать перед слиянием, а затем раздвигать слияние отдельно.

Я реализовал это полностью в зацепке после получения. Он уведомляет об отслеживании только новых коммитов с момента последней выборки без дублирования, независимо от того, были ли новые коммиты перенесены в одну ветку или несколько ветвей одновременно. Этот метод хранит файл с именем TRAC_HEAD в вашем каталоге git для отслеживания, какие коммиты уже были обработаны.

Рекомендуется запустить cat refs/heads/* > TRAC HEAD в вашем .git каталог до включения хука.

#!/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, подход, принятый в можно легко обойти, отправив несколько ссылок, содержащих одни и те же новые коммиты, в одном и том же push.

У @BrunoOliveira и @Kenaniah есть обходные пути, которые включают сохранение дополнительной информации.

Я считаю, что существует также жизнеспособный подход, который включает в себя явную передачу «других ссылок, ранее рассмотренных в этом толчке», в аргумент -list, когда вы перебираете ссылки в push-уведомлении:

      #!/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

Основная проблема с этим подходом, насколько я могу судить, заключается в том, что в итоге вы получаете как можно больше аргументов для удвоить количество передаваемых ссылок - это может в конечном итоге вызвать ошибку. Я протестировал 1000 2000 ссылок без проблем в своей среде Ubuntu, я не знаю, где может быть предел для любой данной серверной среды и шаблона ссылок.

Вообще говоря, если вы работаете с сервером, где вы выполняете такого рода проверки, я предполагаю, что вы действительно хотите иметь ограничение на количество ссылок, которые могут быть отправлены за один раз, в любом случае. крюк.

Другие вопросы по тегам