[Из песочницы] Использование перехватчиков (hooks) в Git для блокирования правки опубликованных коммитов
Привет, Хабр! Тем, кто работает с Git, хорошо знаком способ отредактировать последний коммит командой git commit --amend. Это удобно для мелких правок (изменить комментарий к коммиту, поправить строчку в коде и т.п.), потому что частенько хорошие мысли по поводу коммита приходят в голову уже после того, как этот коммит сделан.
Но с данным способом правки коммитов следует быть осторожным в случае, когда вы работаете с удалённым репозиторием и ещё более осторожным, когда вы работаете над исходным кодом в составе команды. Область безопасного использования опции --amend заканчивается там, где начинается область использования команды git push.При коммите с опцией --amend создаётся новый коммит (с новым хешем), который заменяет собой предыдущий, а тот предыдущий коммит удаляется из истории.
Если вы отредактируете свой уже опубликованный коммит (отправленный в удалённый репозиторий командой git push), то в будущем создадите проблемы себе (когда попытаетесь опубликовать свой отредактированный коммит) и (самое важное!) создадите проблемы другим разработчикам, которые успели получить в свои локальные репозитории ваш старый коммит. В таком случае, и вам, и другим разработчикам обеспечена головная боль по чистке истории коммитов для исправления ситуации.
Большинство рекомендаций сводятся к совету: НЕ ПРАВЬТЕ ОПУБЛИКОВАННЫЕ КОММИТЫ!
И всё было бы отлично, если бы команда git commit --amend не была так хороша и удобна! Ею хочется пользоваться, не оглядываясь на историю коммитов в удалённом репозитории. Поэтому у меня возникло желание автоматизировать такие проверки.
Для подобной автоматизации в системе Git отлично послужат перехватчики (hooks), а именно те, которые работают на стороне клиента. Хранятся они в служебном каталоге .git/hooks и представляют собой файлы скриптов с предопределёнными именами, соответствующими их функциональному предназначению. Для моей задачи подойдёт перехватчик формирования сообщения для коммита prepare-commit-msg.
Суть идеи такова:
Перехватываем команду git commit --amend; Получаем значение хеша последнего коммита в локальной ветке; Получаем значение хеша последнего коммита в удалённой ветке; Сравниваем их, и если они равны, то выводим предупреждение и комментируем содержательную часть сообщения коммита — для того, чтобы при выходе из редактирования коммит отменялся из-за пустого сообщения («Aborting commit due to empty commit message»). Содежимое prepare-commit-msg #!/bin/bash
case »$2,$3» in commit, HEAD) # получаем short SHA-1 последнего коммита в локальной ветке sha1_local=$(git branch -vv | \ perl -lne 'print »$1» if /\*{1}\s+\S+\s+(\w+)\s+\[(\S+)\/(\S+).*\]\s+.*/') # получаем имя удалённой ветки (remote branch) remote_branch=$(git branch -vv | \ perl -lne 'print »$1/$2» if /\*{1}\s+\S+\s+\w+\s+\[(\S+)\/(\S+).*\]\s+.*/')
# получаем short SHA-1 последнего коммита в удалённой ветке (remote branch) sha1_remote=$(git branch -rv | \ awk -v branch=$remote_branch '{ if ($1 == branch) print $2 }')
if [ -n »$sha1_local» ] && [ -n »$sha1_remote» ] && [ »$sha1_local» = »$sha1_remote» ] then # Закомментируем сообщение коммита, чтобы выход из редактора приводил # к отмене коммита из-за отсутствия сообщения ci_comment=$(cat »$1» | grep -v '#' | perl -lne 'print »# $_»') ci_autogen=$(cat »$1» | grep '#') echo -e »$ci_comment» > »$1»
# добавим текст предупреждения echo -e »# ВНИМАНИЕ! ВЫ ПЫТАЕТЕСЬ ОТРЕДАКТИРОВАТЬ УЖЕ ОПУБЛИКОВАННЫЙ КОММИТ!\n» >> »$1» echo -e »$ci_autogen» >> »$1» fi ;;
*) ;; esac Пример работы данного хука приведён ниже:
Как видно, у пользователя остаётся возможность при желании произвести коммит — для этого ему следует раскомментировать сообщение коммита. Если стоит задача более жёсткого пресечения попыток правки опубликованных коммитов, то в системе Git имеется перехватчик pre-commit, который запускается ещё до создания сообщения коммита.