Привет, Хабр!
Тем, кто работает с Git, хорошо знаком способ отредактировать последний коммит командой
Но с данным способом правки коммитов следует быть осторожным в случае, когда вы работаете с удалённым репозиторием и ещё более осторожным, когда вы работаете над исходным кодом в составе команды. Область безопасного использования опции
При коммите с опцией
Если вы отредактируете свой уже опубликованный коммит (отправленный в удалённый репозиторий командой
Большинство рекомендаций сводятся к совету: НЕ ПРАВЬТЕ ОПУБЛИКОВАННЫЕ КОММИТЫ!
И всё было бы отлично, если бы команда
Для подобной автоматизации в системе Git отлично послужат перехватчики (hooks), а именно те, которые работают на стороне клиента. Хранятся они в служебном каталоге
Суть идеи такова:
Пример работы данного хука приведён ниже:

Как видно, у пользователя остаётся возможность при желании произвести коммит — для этого ему следует раскомментировать сообщение коммита. Если стоит задача более жёсткого пресечения попыток правки опубликованных коммитов, то в системе Git имеется перехватчик pre-commit, который запускается ещё до создания сообщения коммита.
Тем, кто работает с 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, который запускается ещё до создания сообщения коммита.