CHANGELOG.md: ручное и автоматическое ведение истории изменений проекта в Git
С начала января я веду свой проектик, на котором обкатываю новые для меня технологии:
- Статический анализ кода, phpcs, phpmd, Scrutinizer
- Автоматическая сборка, Travis CI
- Unit тесты, PHPUnit
- Покрытие кода, Coveralls
- Работу через задачи для любых изменений, Github Issues, PhpStorm tasks
- Документирование всего: README, CHANGELOG, сайт проекта, –help
В этом посте изложена история изменений моего мнения о разных генераторах историй изменения.
Tl;dr: conventional-changelog, стандартизация коммитов.

CHANGELOG.md
Понятная для человека история изменений проекта нужна. Тут надо заметить что такими историями не являются:
- Issues проекта, ветка в менеджере задач, доска проекта и т.п.
- git log проекта
Файл CHANGELOG.md в корне проекта стал стандартом де-факто для проектов, в котором ведется история изменений, Gitlab даже делает для него отдельную вкладку на странице репозитория.
Про это, конечно, есть сайт, репозиторий на Github с тысячей звезд, проблема явно беспокоит людей.
Про ведение CHANGELOG я задумался, когда изучал проект otto, когда писал про него статью на хабр.
Структура у CHANGELOG более-менее у всех одна и та же:
- Версия и дата релиза
- Сломанные обратные совместимости
- Новые фичи
- Прочие изменения и улучшения
- Исправленные баги
Вести такой документ достаточно просто, я за 120 коммитов почти не забывал это делать. В файле нужно всегда держать вверху секцию Next Release с подготовленными заголовками, как-то так:
Перед коммитом я всегда просматриваю дифф, в это время я записываю в коммент к коммиту кратко изменение в первую строку и более подробно в третью, если изменений больше одного, делаю в виде списка. Если про это есть задача, нужно упомянуть ее в виде #123 ссылки, Github умный и такие ссылки делает активными.
Так вот, нужно просто добавить в этот процесс копипасту коммента к коммиту в CHANGELOG, с раскладыванием по категориям изменений.
Во время релиза называем секцию, ставим ей дату, копипастим заголовки.
Процедура очень простая, настолько простая, что хочется ее поручить роботу.
github_changelog_generator
github_changelog_generator — ruby утилита, которая умеет генерировать CHANGELOG.md из любого репозитория. На выходе получаем документ типа этого, наполненный ссылками на задачи и пулл-реквесты, разбитый по категориям, все круто, как в рекламе. У меня получилось совсем не так красиво.
Что мне не понравилось в этом генераторе:
- Текст коммитов никак не учитывает, как и текст задач.
- Чтобы она нормально работала, нужно по полной использовать Github Issues и метки для них, пулл-реквесты, в общем сильно завязано на Github (кто бы мог подумать?), иначе будут генериться просто ссылки на диффы между тегами.
- Нельзя указывать свои секции (например, Breaking changes встроенного нет), но есть issue #316 про это, судя по активности проекта, они скоро появятся.
- Поведение из коробки что-то генерирует, даже если вы не думали про CHANGELOG.md до этого и не использовали Github фишки, это лучше, чем ничего. Но не намного.
- Можно привязывать свои метки к существующим секциям лога.
- Можно настраивать как параметрами к команде, так и конфигом. При запуске скрипт говорит: Performing task with options , так вот, каждую строку из перечисленного ниже конфига можно вставить в файл .github_changelog_generator и переопределить, заменив _ на — .
- Поддерживает сосуществование заполняемой вручную версии (которая все равно лучше автоматической) и генерируемого лога, для этого нужно переложить старый CHANGELOG.md в HISTORY.md (или другой файл, указав его в конфиге).
В общем, github_changelog_generator в моем случае подходит хорошо, если вся работа ведется на Github, это самый простой способ получить красивый CHANGELOG.md
Но на этом я не успокоился, основная причина в том, что на рабочие проекты на Github я не делаю. Хотелось более общего решения.
git-extras changelog
tj/git-extras — это огромный (около 50) пакет дополнительных команд, упрощающих работу с git. Я его раньше уже видел, но в то время подумал, что мне и встроенных в git команд слишком много. Но в поисках генератора снова набрел на него, у него есть такая команда.
Вот таким нехитрым способом можно в одну команду сгенерировать и запушить лог для проекта, где его не было, но версии помечались тегами и комменты к коммитам были осмысленными:
Для пробы сделал лог для site-setup, server-scripts, drupal-scripts, на этом успокоился, больше в общем и тестить не на чем.
Ниже я отказался от него в пользу conventional changelog .
Плюсы:
- Простой как дверь, выполняешь команду, получаешь список изменений, разделенных версиями
Минусы:
- Нет почти никаких настроек
rafinskipg/git-changelog
rafinskipg/git-changelog — node.js cкрипт, который парсит коммиты, написанные по стандартам Angular. Я их прочитал, оказалось, что стандарты годные, к angular никак не привязаны.
Конфликтует с git-extras, так как оба они хотят называться git-changelog. Этот я сделал симлинком git-changelog-angular .
Параметров у скрипта немного, я с ними поигрался, но ничего хорошего у меня с этим тулом не вышло. Идем дальше.
conventional-changelog
stevemao/conventional-changelog-cli — node.js скрипт, также нацелен на стандарты Angular, но, по заявлениям авторов это как раз то, что нужно:
- поддерживает свои форматы коммитов и несколько общих: ‘angular’, ‘atom’, ‘codemirror’, ‘ember’, ‘eslint’, ‘express’, ‘jquery’, ‘jscs’, ‘jshint’
- поддерживает шаблоны
- протестирован, в отличие от github_changelog_generator
- отвязан от Github
- имеет модульную структуру и несколько модулей вокруг себя
Воспользовавшись conventional-commits-detector , узнал, что мои комменты к коммитам больше всего похожи на стандарт eslint .
Сгенерированный лог дал понять, что в eslint принято указывать категорию и через двоеточие суть, так коммиты в релизе разбиваются по категориям. Но в целом, конечно, коммиты были названы неправильно и хорошего лога не получилось.
Зато запуск без указания пресета сообщений выдал почти то же, что и git-extras , но вдобавок к этому задал мажорным и минорным версиям разный уровень и указал ссылку на коммит на Github для каждого коммита.
Сгенерировать лог с нуля можно командой:
После этого я конечно побежал исправлять логи у проектов, которым сделал логи час назад, вот что вышло: site-setup, server-scripts, drupal-scripts.
Для проектов на своем Gitlab все сложнее: чтобы правильно делались ссылки на коммиты, нужно, во-первых, указать адрес проекта через файл package.json:
А во-вторых не знаю, что надо сделать, он генерит ссылки с сокращенными хэшами, которые Github понимает, а Gitlab открывает страницу списка коммитов, т.к. ему нужен полный хэш, шаблон сходу не нашел.
Дальше искать не стал, думаю это оно самое.
Кроме лучшего результата из коробки и полной кастомизации мне в нем понравились модули:
-
— готовый валидатор сообщений к коммитам — скрипт для pre-commit хука, проверяющий сообщения коммитов на соответствие стандартам, стандарты описываются в файле — автоматическое создание релизов на Github. У меня они уже создаются, но приходится вручную заходить туда и править сообщение к релизу
Выводы
Для того, чтобы генератор создавал по-настоящему хорошие логи, важно определиться с форматом сообщений к коммитам, научиться следовать ему и научить роботов понимать наш формат, чтобы роботы поработили людей помогали правильно и не напрягаясь вести историю изменеий проекта в процессе, а не после работы над проектом.
Для себя я нашел инструмент, которым я теперь могу за 5 минут создавать историю изменений для проектов на основе коммитов.
Генерация CHANGELOG.md — шаг в сторону хорошей и актуальной документации по проекту, которая не будет занимать часы или дни, она будет частью рабочего процесса, конечно для маленького проекта из одного программиста это избыточно, мягко говоря, но надо же с чего-то начинать.
UPD 08.03.2016
После этого коммиты с неправильными сообщениями перестанут проходить.
Перед релизом генерирую CHANGELOG.md:
Это допишет в лог содержимое коммитов с последнего релиза (semver тега). После этого остается поправить руками то, что не нравится, проставить версию.
После этого я генерирую документацию специфичной для проекта командой, коммит, тег, пуш:
После этого релиз. Релиз будем делать через conventional-github-releaser :
Еще не разобрался с тем, как это скрестить с выкладкой PHAR архива с Travis: для github-releaser нужно, чтобы релиза еще не было, но он создается автоматически при пуше тега на Github. После удаления релиза (превращения в Draft), github-releaser отработал, вставил данные CHANGELOG в релиз, все как надо.
Автоматическая генерация лога изменений проекта с помощью GitLab
В этой небольшой статье поговорим о том, что такое лог изменений проекта, зачем он нужен и как можно автоматизировать его генерацию с помощью GitLab.
Что такое changelog и для чего он нужен?
Лог изменений проекта (changelog) — это документ, в котором обычно содержится упорядоченный список версий проекта с датами их выхода, а также перечень всех изменений для каждой из версий.
Changelog в первую очередь будет полезен самим разработчикам, с его помощью можно быстро найти в какой версии проекта была добавлена очередная фича ломающая прод или на демонстрации поможет вспомнить все изменения, сделанные за итерацию. Также будет полезен как для менеджеров, следящих за развитием продукта, так и для обычных пользователей которые хотят знать, какие изменения попали в текущую версию и нужно ли им обновлять приложение.
Как вести лог изменений?
Как минимум, хороший лог изменений должен придерживаться следующих принципов:
Для каждой версии должен быть отдельный раздел.
Группировать изменения по категориям (Feature, Bug fix, Changed и т.п).
Самые новые версии должны располагаться в начале документа.
Иметь возможность навигации по файлу.
Файл с логом можно вести вручную, но мало какой разработчик будет с энтузиазмом это делать, а если вы поддерживаете одновременно несколько версий продукта, то это превращается в очень нудное занятие. Другим вариантом будет автоматизация этого процесса.
Одним из способов генерации лога изменений (который мы и будем использовать), будет генерация лога из списка коммитов, но поскольку частенько их пишут как попало, то в первую очередь нужно, чтобы команда разработки, при написании коммитов придерживалась определенного стиля. Этот стиль будет зависеть от того, что вы хотите видеть в логе изменений, можно воспользоваться чем-то готовым или написать что-то свое.
Как генерировать?
Для генерации воспользуемся функциональностью GitLab API, с его помощью будет формироваться файл ( CHANGELOG.md ) с логом изменений, основанный на заголовках коммитов. Добавляются только те коммиты, которые включают в себя определенную метку (Git trailers). GitLab использует эти метки для того, чтобы сгруппировать изменения по категориям.
Для генерации нужно выполнить POST запрос:
gitlab-host — это адрес на котором расположен сервер GitLab с вашим репозиторием.
id — это project id вашего репозитория, который можно посмотреть в настройках на вкладке «General».
Также нужно передать токен доступа для api, который можно получить в настройках на вкладке «Access Tokens». (или в настройках вашего профиля, на вкладке «Access Tokens».) И как минимум, обязательный атрибут «version» — версия, для которой генерируется лог изменений. Список всех атрибутов.
Результатом выполнения запроса, будет новый раздел в файле CHANGELOG.md в выбранном репозитории. По умолчанию, диапазон коммитов начинается с последнего тега, идущего до версии, указанной в атрибуте «version» и заканчивается веткой по умолчанию в проекте, в этой же ветке и обновится файл с логом изменений.
Для того, чтобы GitLab мог автоматически находить последний тег, нужно чтобы и версия, и имя тега были оформлены в соответствии с семантикой версионирования.
Если мы хотим именовать как-то по-другому или использовать другой диапазон коммитов, то на помощь приходят атрибуты:
from — название ветки, последний коммит которой станет началом диапазона. Сам коммит не будет включен в список.
to — название ветки, последний коммит которой станет концом диапазона. Сам коммит будет включен в список.
branch — название ветки, в которой будет обновлен/создан файл CHANGELOG.md .
Например, выполним несколько запросов с исходными данными:
Project id — 111
API access token — token
Имя последнего тега — 0.9.0
Имя ветки по умолчанию — main
Результатом выполнения будет новый раздел в ветке main, с подзаголовком 1.0.0 и диапазоном включенных коммитов 0.9.0..main.
Результатом выполнения будет новый раздел в ветке develop, с подзаголовком 1.0.0 и диапазоном включенных коммитов release_0.9.0..develop.
Как кастомизировать?
Для кастомизации используется конфигурационный файл в формате YAML. Файл должен располагаться в корне репозитория по пути: .gitlab/changelog_config.yml
Чтобы GitLab начал распознавать файл конфигурации, он должен быть расположен в ветке по умолчанию.
Для редактирования доступны следующие переменные:
date_format — формат даты, который будет использоваться в заголовке нового раздела лога изменений
template — формат данных, который будет использоваться для отображения каждой категории лога изменений
categories — псевдоним, который будет использоваться как имя категории, вместо тех, которые мы используем, как метки коммитов (git trailers)
Для примера, структура наших коммитов будет выглядеть так:
<Тип>(номер задачи): Заголовок
— Подробное описание (если оно требуется)
Используя стандартные настройки, сгенерированный файл будет выглядеть так:
Теперь попробуем поменять заголовки категорий и добавить для каждой записи автора коммита.
В примере выше мы используем стандартный git trailer — Changelog и используемые для него значения: feature, bug, performance, которые и стали названиями категорий.
Чтобы изменить названия категорий, нужно добавить в файл конфигурации changelog_config.yml следующие строки:
Для генерации данных в категориях, используются шаблоны. Для примера выше, шаблон выглядит так:
Здесь мы в основном цикле проходим по всем категориям, отображаем название и количество записей в категории. В вложенном цикле совершаем обход всех записей и выписываем заголовок коммита и ссылку на него, если есть ссылка на МР, то добавляем её.
— используется для условий (if/else) и циклов (each). Каждое выражение, должно закрываться с помощью
<< . >> — используется для отображения данных доступных в шаблоне
Для отображения автора коммита, изменим текст шаблона, добавив в вложенный цикл строку by << author.reference >> после вывода ссылки на коммит.
После всех изменений, файл changelog_config.yml будет выглядеть следующим образом:
А сгенерированный файл CHANGELOG.md так:
Как автоматизировать?
Для начала настроить GitLab CI/CD. После того, как этот этап пройден, добавить в файл .gitlab-ci.yml новый stage (или изменить уже существующий) для обновления лога изменений. В нем мы должны получить имена веток для атрибутов from и to и после этого выполнить POST запрос для генерации файла CHANGELOG.md .
Например, пусть наша выборка коммитов начинается с последнего тега и заканчивается текущей версией релиза. Версию лога изменений, допустим, будем брать из файлов проекта, stage будет запускаться только для ветки релиз.
Теперь каждый раз при сборке релиза, будет добавляться новый раздел с версией currentVersion в файл CHANGELOG.md .
Важно, что если раздел с текущей версией уже существует, то дополняться новыми коммитами он не будет.
Мы за пару часов, настроили простую автоматическую генерацию файла с логом изменений, который даже в таком виде, будет полезен на небольших проектах, уменьшит рутину мейнтейнеров или например, поможет получать меньше обращений от тех.поддержки с вопросами в какой версии добавлена фича.
Руководство по написанию changelog
Этот документ описывает рекомендуемые правила оформления секции %changelog spec-файла пакета и характер/объём включаемой в неё информации.
Примеры [ править ]
Выпуск, включающий совместную работу:
либо более тяжеловесный вариант в случае значительных объёмов отмеченных изменений на нескольких участников выпуска:
Термины [ править ]
запись Всё, что относится к сборке 4.6.2-alt7.pre1 группа имя в квадратных скобках пункт текст, начинающийся с дефиса («- make visible_tabs and visible_tws mcedit options configurable through config file (Debian)») подпункт текст, начинающийся с плюса («+ recognize man pages with additional suffixes other than ‘x’, such as write.3p (Debian)») строка одна строка файла спека («- make visible_tabs and visible_tws mcedit options configurable through config»)
Синтаксис чейнджлога [ править ]
- (Опциональная) группа состоит из имени в квадратных скобках и начинается с первой позиции строки.
- Пункт верхнего уровня начинается с 1-й позиции в строке и состоит из дефиса, пробела и собственно текста.
- Подпункт начинается с 3-й позиции в строке и состоит из плюса, пробела и собственно текста.
- Строки переносятся по словам и содержат не более 78 символов. При этом текст новой строки выравнивается по началу текста предыдущей (то есть 3-я позиция для пунктов верхнего уровня и 5-я для подпунктов).
- Начинать новую строку с символа # нельзя (такая строка будет воспринята как комментарий).
- При использовании макросов следует экранировать символ «%» ещё одним «%» во избежание раскрытия оных, т.е. напрмер вместо %name следует записать %%name.
Содержимое [ править ]
- Чейнджлог пишется на английском языке.
- При сборке новой upstream-версии это указывается первым пунктом.
- При сборке из снэпшота системы контроля версий необходимо указать информацию, позволяющую идентифицировать это снэпшот (дата и время для CVS, ревизия SVN, 8 первых символов идентификатора коммита для git, mtn, bzr, hg, либо вывод git-describe для git).
Информация об уязвимостях (CVE и т.д.) в Changelog обязана соответствовать Vulnerability_Policy.
- Изменения, произведённые апстримом (кроме исправлений ошибок безопасности), в чейнджлоге не указываются (для этого есть чейнджлоги апстрима, зачастую включаемые в пакет).
- Косметические изменения спек-файла, не влияющие на получаемый пакет, указываются максимум одной строкой («spec cleanup»), либо не указываются вовсе. Это не относится к исправлениям тега License, изменениям параметров сборки и т. д.
- Исправления, необходимые для успешной сборки, соответствия требованиям ALT Linux и т. д., не указываются, если они были сопряжены с упаковкой новой версии и для старой версии были не нужны (эти исправления — адаптация новой версии, то есть часть процесса её упаковки). Если же они были вызваны изменением требований либо окружения, желательно это указать (пример: «fix FTBFS with new autotools», FTBFS == failure to build from source).
- Если .spec-файл адаптирован из другого дистрибутива, то майнтейнер вправе как оставить старые записи %changelog, так и удалить их. Следует помнить, что
- Записи в %changelog содержат информацию о том, как шла разработка пакета до импорта — в них есть адреса майнтейнеров и протокол их действий. Эта информация часто бывает полезной.
- Сам адаптированный спек является модификацией исходного кода, авторские права на который принадлежат не вам. Некоторые лицензии требуют сохрания атрибуции непосредственно в тексте исходного кода. Самый простой способ сделать это — оставить прошлый %changelog в неприкосновенности
- Формат унаследованного %changelog может отличаться от принятого в ALT, это иногда приводит к сбою в инструментах работы со спеками.
- Если вы оставляете исходный %changelog, первую запись о сборке адаптированного пакета стоит делать видимой (традиционно она содержит слова «Initial build for ALT»). Это поможет отделить историю пакета в ALT от исходной истории разработки
Указание источников и контекста [ править ]
- Если изменение взято извне либо основано на взятом извне, в соответствующем изменению пункте чейнджлога в скобках указывается источник. Это может быть название дистрибутива/репозитория, название внешней BTS и номер бага в ней, ID участника ALT Linux Team или произвольное указание на человека или сайт. Здесь под изменением подразумевается готовый патч, адаптированный патч или иные аналогичные указания.
Автозакрытие багов [ править ]
- Если изменение связано с багрепортом из bugzilla.altlinux.org, в соответствующем пункте можно указать (по вкусу; регистр не учитывается, но обязательно в скобках):
- (Closes: NNNN)
- (ALT NNNN)
- (ALT bug NNNN)
с опциональным знаком # перед номером бага. Можно также указать несколько багов: (Closes: NNNN, MMMM, ZZZZ). Синтаксис такого рода закрывает указанный баг после прохождения пакета в репозиторий при условии присутствия соответствующей записи в выводе rpmquery —lastchange .
- Номера багов в скобках разделяются запятыми.
Обратите внимание, что других слов в скобках при этом нельзя указывать.
Полный regexp для поиска номеров багов в changelog можно посмотреть здесь.