Пропустить обработку версий Git в обработчике после получения, которые уже были обработаны ранее
У меня есть ловушка git post-receive, которая извлекает все ревизии, которые были добавлены во время "git push", и выполняет некоторую обработку каждой из них (например, отправку уведомлений по электронной почте). Это прекрасно работает, за исключением случаев слияния; например:
- Я делаю некоторые коммиты на branch1 и затем нажимаю branch1. Хук post-receive обрабатывает коммиты правильно.
- Я объединяю 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, подход, принятый в
У @BrunoOliveira и @Kenaniah есть обходные пути, которые включают сохранение дополнительной информации.
Я считаю, что существует также жизнеспособный подход, который включает в себя явную передачу «других ссылок, ранее рассмотренных в этом толчке», в
#!/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, я не знаю, где может быть предел для любой данной серверной среды и шаблона ссылок.
Вообще говоря, если вы работаете с сервером, где вы выполняете такого рода проверки, я предполагаю, что вы действительно хотите иметь ограничение на количество ссылок, которые могут быть отправлены за один раз, в любом случае.