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

Предисловие

По мере роста количества микро-сервисов в нашей команде, какие-то общие куски кода мы стали выносить в отдельные репозитории (далее такие репозитории буду называть библиотеками), которые подключаются к проектам через composer.

Когда количество микро-сервисов было мало, для отслеживания истории изменений нам хватало истории коммитов. Со временем нашими библиотеками стали пользоваться и другие команды. Стали часто появляться вопросы, вида: "Подскажите, какую версию библиотеки нам нужно подключить, чтобы появился такой-то функционал?".

Стало очевидно, что история коммитов уже не отвечает запросам, и нужно искать более дружелюбное решение. Таким решением стало ведение файла Changelog в библиотеках, который по началу мы заполняли руками и присваивали руками теги с версиями.

Пожив так какое-то время у меня появилось желание как-то этот процесс автоматизировать. И спустя какое-то время на глаза попалась информация о генераторах changelog'а и соглашении о правилах оформления коммитов https://www.conventionalcommits.org/.

Реализация

Генератором, который попался мне на глаза, был проект https://github.com/marcocesarato/php-conventional-changelog. Так как наши проекты так же пишутся на PHP, генератор показался интересным и захотелось его опробовать в деле.

Шаг 1

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

Шаг 2

Добавляем в проект задачу для CI (в моем случае это Gitlab CI).

generate-changelog:
    image: php:7.4-cli # у нас используется свой образ, поэтому пишу условно
    before_script:
        - git config --global user.email "$GITLAB_USER_EMAIL" # email и имя пользователя, от чьего имени будет создаваться коммит
        - git config --global user.name "$GITLAB_USER_NAME"
        - git fetch --tags -f
        - composer install --no-interaction --no-scripts
        - composer require marcocesarato/php-conventional-changelog # у нас используется свой форк с доработками    
    script:
        - bin/changelog-generator --commit --no-verify # у нас скрипты устанавливаются в папку bin
        - git push -o ci.skip --tags https://root:$ACCESS_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git HEAD:$CI_COMMIT_REF_NAME
    stage: changelog
    # делаем автозапуск задачи только для мастер ветки.
    # Так как мы не пушим напрямую в мастер, а используем merge request, то задача выполняется после слияния MR
    # так же запрещаем задачу, если проставляем тег и для коммитов с chore (значит, что команда уже была выполнена)
    only:
        - master
    except:
        refs:
            - tags
        variables:
            - $CI_COMMIT_MESSAGE =~ /chore/

У нас в команде используется feature-branch подход и требование, чтобы в merge request был только один коммит. Поэтому в нашем случае один коммит = новая версия библиотеки.

Идея скрипта следующая:

  • мы подключаем к проекту генератор и запускаем его с нужными флагами

  • затем генератор:

    • определяет текущий (он же последний) коммит и на основании заголовка коммита определяет, какую версию по семверу нужно присвоить

    • добавляет изменения в CHANGELOG.md

    • создает новый коммит с измененным CHANGELOG.md. Отмечу, что в нашей версии в коммит попадает только файл CHANGELOG'а

    • присваивает тег с полученной версией коммиту

  • пушим новый коммит обратно в репозиторий проекта

Шаг 3

Для того чтобы CI мог запушить изменения в репозиторий, мы создали специального технического пользователя, и добавили в проекты переменную ACCESS_TOKEN , с токеном доступа этого пользователя.

Шаг 4

Какое-то время мы работали с таким подходом. Но в голове крутилась мысль, что использовать composer require так себе идея и перфекционизм требует решения по-лучше.

Хотелось чтобы генератор либо присутствовал все время в образе либо его можно было скачать и запустить.

По некоторым причинам, я выбрал второй путь. Для этого я поместил генератор в phar-архив и загрузил в Gitlab Packages. И получился следующий вариант скрипта CI

generate-changelog:
    image: php:7.4-cli
    before_script:
        - git config --global user.email "$GITLAB_USER_EMAIL"
        - git config --global user.name "$GITLAB_USER_NAME"
        - git fetch --tags -f
        # скачиваем генератор из Packages
        - 'curl --header "PRIVATE-TOKEN: ${PACKAGE_TOKEN}" "${CI_API_V4_URL}/projects/615/packages/generic/changelog-generator/latest/changelog-generator.phar" --output changelog-generator.phar'
        - chown 755 changelog-generator.phar
    script:
        - php ./changelog-generator.phar --commit --no-verify
        - git push -o ci.skip --tags https://root:$ACCESS_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git HEAD:$CI_COMMIT_REF_NAME
    stage: changelog
    only:
        - master
    except:
        refs:
            - tags
        variables:
            - $CI_COMMIT_MESSAGE =~ /chore/

Проблемы

В какой-то момент из-за изменения прав пользователя у нас получилась ситуация с бесконечным созданием pipelines и запуском задачи с генератором. Точное решение уже не помню, одним из путей решения ситуации стало добавление в скрипт секций except и only

Результат

Я постарался изложить идею автоматизации, так как у разных проектов могут быть свои особенности.

Комментарии (18)


  1. x0rHamster
    27.10.2021 20:19
    +1

    Шаг 1 ... введение нового требования к оформлению коммитов внутри команды, чтобы они соответствовали соглашению выше

    Обычно это - самый сложный этап :) Точнее, сложно не ввести, а поддерживать его соблюдение на больших интервалах. Конечно, в коммите можно ограничиться ссылкой на задачу, а changelog строить по данным таск-трекера (теперь можно и коммитить раз в 15 минут, и squash'ить ветки), но это все еще требует дисциплины (адекватных названий карточек, которые и суть отразят, и пользователям показать не стыдно). У вас, насколько я понял, библиотеки сугубо для внутреннего использования, поэтому из-за случайно проскочившего слова "жопа" или изменения "this solves it" если и расстроятся, то не очень сильно. Или я неправ, и ваши процессы как-то гарантируют, что данные для changelog'ов всегда будут близки к идеалу?


    1. Aquahawk
      27.10.2021 20:43
      +3

      Pre push hook решает проблему кривых коммитов


    1. alexandrtumaykin Автор
      27.10.2021 20:47
      +1

      Вы правы, библиотеки используются внутри компании. Для соблюдения требований к формату сообщений я добавил со временем еще дополнительные этапы проверок:

      • локальные git-хуки, которые проверяют:

        • чтобы названия веток соответствовали номеру задачи в jira

        • заголовок коммита соответствовал требованию формата и чтобы номер задачи в заголовке соответствовал названию ветки

      • gitlab-хуки на пуши и merge request, которые обрабатываются веб-сервисом. Здесь так же делаются аналогичные проверки, + еще проверяются другие кейсы и некоторые автоматизации, и в случае косяка в MR создается дискуссия с замечанием.

      Похожим образом можно цензурировать сообщения + дополнительно проверять на ревью.


  1. AlexZaharow
    28.10.2021 00:36
    -1

    А вы не думали, чтобы подробности фиксов писать прямо в коде, а потом и оттуда их забирать? Я понимаю, что требуется анализатор кода, но в принципе для многих языков они есть, чтобы выцепить только комменты и их уже парсить?


    1. alexandrtumaykin Автор
      28.10.2021 10:00
      +2

      Плохо себе это представляю. Имеется в виду через комментарии к коду?


  1. Yser
    28.10.2021 05:06

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

    требование, чтобы в merge request был только один коммит

    Я правильно понимаю - речь о squash? Это дает какие-то еще преимущества, кроме очевидных - более "чистой" истории?


    1. alexandrtumaykin Автор
      28.10.2021 09:58

      Меньше рисков конфликтов. В MR только свежий код, который отстает от "master" ветки на один коммит. Если код отстает больше, чем на один коммит, до задачу возвращаем на rebase


      1. Yser
        29.10.2021 04:07

        Если код отстает больше, чем на один коммит, до задачу возвращаем на rebase

        Эта стратегия гуглится? В смысле, могу ли я прочитать как с этим работать в подробносятях?

        Я как раз сейчас разгребаю авгиевы конюшни, и к след проекту хочу подойти подготовленным)


        1. alexandrtumaykin Автор
          29.10.2021 12:33

          Интересный вопрос. Если честно, то не знаю. Мы как-то из практических соображений к этому подходу пришли.


          1. Yser
            29.10.2021 16:25

            если я правильно понимаю суть процесса:

            1. Если фича отстает от мастера > 1 - рибейс на последний мастера.

            2. Фича сквашится в один комит и создается МР.

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

            Есть какие-то явные минусы у подхода?


            1. alexandrtumaykin Автор
              29.10.2021 16:49

              Все верно.

              Есть какие-то явные минусы у подхода?

              Если накопились MR к одному сервису/библиотеке, их все придется периодически рибейсить, по мере попадания задач в тестирование.

              Зависит от принятого флоу и на каком этапе есть "узкие горлышки".


              1. Yser
                29.10.2021 20:17

                Принято. Огромное спасибо за статью и ответы)


  1. erratir
    29.10.2021 12:33
    +1

    Так как наши проекты так же пишутся на PHP, генератор показался интересным

    Для JavaScript взял за основу semantic-release. Который с помощью плагинов так же может генерировать CHANGELOG.md (плагин @semantic-release/changelog ) и релизы на основе заголовков коммитов.

    У GitLab в доках есть пример.

    Бонусом сделал отправку changelog в телеграмм. Дергаю API GitLab, парсю поле description (оно в формате markdown) и перегоняю в формат понятный телеграмму. В итоге выглядит примерно так:


    1. erratir
      29.10.2021 13:13
      +1

      Не загрузился скрин из телеграмма


    1. alexandrtumaykin Автор
      29.10.2021 13:35

      Идея хороша, когда мало репозиториев и версии не так часто растут


      1. erratir
        29.10.2021 14:52

        А в чем вы видите отличие от вашего варианта? Так же в пайплайн каждого репозитория добавлена джоба, которая стартует стартует на мердж в мастер, на основе заголовков коммитов повышает версию релиза в гите, повышает версию npm пакета (если надо), генерирует changlog.md и кидает сообщение в служебный канал телеграмма. Ручных действий не требуется.


        1. alexandrtumaykin Автор
          29.10.2021 15:05

          Концептуально отличия нет.

          Еще до ведения changelog'а мы начинали с ручного ведения истории в чате. По мере роста количества версий и репозиториев, в чат перестали смотреть.


          1. erratir
            29.10.2021 15:30

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

            Т.е. у нас есть группы/каналы в телеграмме, на которые подписаны IT-отделы клиентов и туда улетают ченджлоги некоторых репозиториев.