Git для команды 1С без EDT: как я спрятал git-flow за шестью кнопками конфигуратора

в 9:38, , рубрики: 1С, Git, ibcmd, командная разработка, хранилище конфигурации

Если в команде 1С больше трёх разработчиков, хранилище конфигурации рано или поздно становится узким местом. Общий модуль захвачен коллегой, непроверенную задачу уже не вынуть из релиза, а сам релиз собирается вечером пятницы вручную. Обычный ответ на это — «переходите на EDT и git». Ответ правильный, но дорогой: новая среда, тяжёлые машины, переобучение всей команды и несколько месяцев, пока процесс снова заработает.

Я пошёл третьим путём: разработчики остаются в обычном конфигураторе, а git становится транспортом между их базами. Получилось Windows-приложение Konflow: ветки, коммиты и слияния спрятаны в нём за кнопками «Новая задача», «Сохранить», «Переключиться», «Завершить», «Собрать тест» и «Релиз». Выгружать и загружать конфигурацию приложение тоже берёт на себя.

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

Что не так с хранилищем

Хранилище — это одна общая конфигурация с пессимистичными блокировками. Пока команда маленькая, это удобно. С ростом команды вылезают три проблемы.

  1. Захваты. Общий модуль «держит» коллега. Остаётся ждать, пока он поместит изменения, или править в обход и потом переносить руками.

  2. Нет изоляции задач. Всё помещённое сразу попадает в общую конфигурацию. Не успели проверить одну задачу — откатываете её вручную или переносите весь релиз.

  3. Релиз — ручная работа. Собрать CF и CFU, описать, что вошло, поставить метку. Каждый раз одно и то же.

Хранилище и ветки: одна и та же неделя

Хранилище и ветки: одна и та же неделя

Git решает все три проблемы: веток сколько угодно, захватывать нечего, а релиз — это слияние выбранных веток. Сложность в другом: git работает с файлами, а разработчик 1С — с базой. Значит, кто-то должен постоянно перекладывать конфигурацию из базы в файлы и обратно, причём быстро и без ошибок. EDT решает это, заменяя конфигуратор целиком. Я хотел решить это, конфигуратор не трогая.

Идея: база — это рабочая копия

Каждый разработчик работает в своей базе в обычном конфигураторе. Между базой и сервером git лежит каталог исходников — XML-выгрузка конфигурации средствами платформы (src/cf, расширения — src/cfe/<Имя>). Все кнопки Konflow сводятся к перекладыванию конфигурации между этими тремя местами.

База, файлы ветки и сервер

База, файлы ветки и сервер
  • Новая задача — ветка feature/<N>-описание от свежего develop, конфигурация ветки загружается в базу.

  • Сохранить — выгрузка из базы, список изменённых объектов «по-русски», комментарий #124: что сделано, коммит и push. Каждый объект можно снять галочкой: в коммит уйдёт только отмеченное.

  • Переключиться — несохранённое откладывается WIP-коммитом, git checkout, загрузка другой ветки в базу.

  • Завершить — в ветку вливается свежий develop, конфигурация проверяется загрузкой в базу и синтаксическим контролем модулей, задача уходит «На проверке».

  • Собрать тест — ветка test пересобирается от develop с отмеченными задачами и грузится в тестовую базу.

  • Релиз — мастер, который собирает черновик релиза, CF и CFU поставки и описание.

Разработчику не нужно знать слова rebase и merge-base. Перед каждой загрузкой в базу Konflow показывает предпросмотр: загрузка будет частичной или полной, какие объекты изменятся и удалятся, сколько коммитов develop придёт и ожидается ли конфликт.

Главное окно Konflow

Главное окно Konflow

Модель веток: задачи встречаются только в релизе

Ветвление — вариация git-flow, но с одним важным отличием. В классическом git-flow готовая фича вливается в develop, и дальше её оттуда не достать. Здесь задача не вливается в develop до выпуска. Она живёт в своей ветке до тех пор, пока релиз-менеджер не отметит её галочкой в мастере релиза.

Ветки: задачи встречаются только в релизе

Ветки: задачи встречаются только в релизе

Мастер релиза пересоздаёт ветку release/<версия> от develop и вливает в неё отмеченные задачи. Затем грузит её в сборочную базу и кладёт в папку версии CF поставки, CFU от предыдущего релиза и release-notes.md. Пока релиз не выпущен, это черновик: develop и master не тронуты, тега нет. Не успели проверить задачу — сняли галочку и нажали «Пересобрать». Откатывать нечего, потому что она никуда не попадала.

Мастер релиза

Мастер релиза

«Выпустить» — уже необратимый шаг: release → master с тегом v1.3.0, затем master → develop, задачи получают статус «Готово», ветки удаляются. Если продуктив по-прежнему обновляется из хранилища, Konflow захватывает конфигурацию релизной базы, частично загружает файлы, изменённые со времени прошлого помещения (исходники тега берутся во временном git worktree), помещает их с комментарием «Релиз 1.3.0: задачи» и ставит метку версии. Для разработки хранилище больше не нужно, а для эксплуатации его можно оставить.

Статусы задач хранятся в самом git — в тегах и ветках на сервере. Отдельный трекер для них не нужен: после «С сервера» вся команда видит одно и то же.

Путь задачи

Путь задачи

Самое сложное: держать базу и файлы в согласии

Сделать кнопки «Сохранить» и «Переключиться» нетрудно. Трудно сделать так, чтобы они никогда не испортили работу разработчика. Главная опасность — выгрузить базу «не в ту» рабочую копию. Например, в базе лежит ветка #121, а в каталоге уже checkout’нута #124. Выгрузка молча перезапишет файлы #124 содержимым #121, и следующий коммит унесёт это на сервер.

Поэтому вся синхронизация построена вокруг одного инварианта:

LastLoadedCommit[база] — коммит, содержимое которого сейчас в базе (плюс правки разработчика, ещё не выгруженные). Выгружать можно только в рабочую копию этого коммита.

Каждая база помнит, какой коммит в неё загружен. Если HEAD не совпадает с этим коммитом, «Сохранить» не выгружает, а спрашивает: в базе версия другой ветки — выгрузить её полностью поверх файлов или сначала загрузить текущую ветку?

Вторая половина состояния — ConfigDumpInfo.xml. Платформа пишет в него версии объектов при выгрузке, и по нему можно понять, что изменилось в базе с прошлого раза. Беда в том, что этот файл один на каталог, а баз у разработчика несколько: разработки, тестовая, сборочная. Если хранить его в репозитории, каждая база будет затирать чужой. Поэтому ConfigDumpInfo.xml исключён из git, а Konflow держит свою копию для каждой базы и каждого расширения в ней: .konflow/state/<база>/. Заодно это даёт кнопку «Проверить базу»: какие объекты разработчик уже поменял в конфигураторе, но ещё не выгрузил.

Инкрементальная загрузка

Полная загрузка типовой конфигурации идёт долго, и переключение между задачами не может каждый раз занимать минуты. Раз база помнит загруженный коммит A, а грузить нужно коммит B, разница — это просто git diff --name-status A B. Дальше остаётся решить, можно ли загрузить только её.

Как Konflow решает, что грузить в базу

Как Konflow решает, что грузить в базу

В коде это короткая функция:

public static LoadPlan Plan(IReadOnlyList<(char Status, string Path)>? changes, int threshold)
{
    if (changes is null)
        return LoadPlan.Full("в базу ещё не загружался известный коммит");

    var relevant = changes.Where(c => !IsIgnored(c.Path)).ToList();
    if (relevant.Count == 0)
        return new LoadPlan(LoadKind.None, [], "изменений нет", []);

    var deletedObjects = relevant.Where(c => c.Status == 'D' && IsTopLevelObject(c.Path))
        .Select(c => ObjectName(c.Path)).ToList();

    if (relevant.Any(c => c.Path.Equals("Configuration.xml", StringComparison.OrdinalIgnoreCase)))
        return LoadPlan.Full("изменился Configuration.xml", deletedObjects);
    if (relevant.Any(c => c.Path.Contains("ParentConfigurations", StringComparison.OrdinalIgnoreCase)))
        return LoadPlan.Full("изменились настройки поддержки", deletedObjects);
    if (relevant.Any(c => c.Status == 'D'))
        return LoadPlan.Full("удалены файлы", deletedObjects);
    if (relevant.Count > threshold)
        return LoadPlan.Full($"изменено {relevant.Count} файлов (порог {threshold})", deletedObjects);

    return new LoadPlan(LoadKind.Partial, relevant.Select(c => c.Path).ToList(), $"изменено файлов: {relevant.Count}", []);
}

Частичная загрузка — это ibcmd config import files --partial со списком изменённых файлов. Состав конфигурации, настройки поддержки и удаления частичной загрузкой корректно не переносятся, поэтому в этих случаях загрузка полная. У каждого расширения свой инкремент: сначала грузится основная конфигурация, потом каждое расширение со своим git diff и своим ConfigDumpInfo.xml.

Типовая ERP 2.6.2 — 125 тыс. файлов выгрузки, 26 тыс. модулей. Файловая база, ibcmd.

Сколько это занимает

Сколько это занимает

Замеры сделаны на файловой базе и платформе 8.3.27. На вашей конфигурации и вашем железе цифры будут другими, но порядок — секунды, а не минуты.

Грабли платформы

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

ibcmd config import в серверную базу «успешно» ломает её. Проверено на 8.3.27.2214 и MS SQL 2022: ibcmd config import (и config load) в серверную базу завершается без ошибок, но оставляет конфигурацию с ошибкой «Нарушена целостность структуры конфигурации». После этого не открываются ни конфигуратор, ни config apply. Не помогают ни import → apply --dynamic=disable, ни предварительная инициализация базы, ни снятие с поддержки. Та же выгрузка через ibcmd в файловую базу и через конфигуратор в серверную грузится чисто. Испорченную так базу возвращает ibcmd config reset: основная конфигурация снова становится равной конфигурации БД. Вывод: в серверные базы Konflow загружает только конфигуратором (/S сервербаза), а ibcmd оттуда только читает — выгрузка и проверка изменений идут напрямую из СУБД.

Базы с хранилищем — тоже только конфигуратором. ibcmd ничего не знает о захвате объектов.

Синтаксический контроль без режимов ничего не проверяет. /CheckModules без указания режимов (тонкий клиент, сервер, внешнее соединение и так далее) честно отвечает «ошибок не обнаружено», не проверив ни строчки. Поэтому включить проверку модулей без выбранных режимов в Konflow нельзя.

Платформа 8.5 и «Unknown exception». На 8.5 частичная загрузка изредка падает с Unknown exception на исправном наборе файлов, а повтор проходит. Раньше в этом случае Konflow сразу переходил к полной загрузке, и переключение задачи занимало полчаса. Теперь он один раз повторяет частичную загрузку и только потом сдаётся.

Открытый конфигуратор держит файловую базу монопольно. Пакетные операции в это время невозможны. Приложение распознаёт тексты ошибок 1С и ibcmd о занятой базе и предлагает сохранить конфигурацию (Ctrl+S), закрыть конфигуратор и повторить, а не показывает стектрейс.

Серверные базы: пять шагов через RAS

Тестовая и сборочная базы в командах обычно серверные, и в них работают люди. Загружать конфигурацию, пока тестировщик проводит документы, нельзя. Для этого Konflow ходит в кластер через RAS, и последовательность шагов здесь важнее, чем кажется.

Загрузка в серверную базу: пять шагов через RAS

Загрузка в серверную базу: пять шагов через RAS

Два шага неочевидны.

  • Повторная проверка (шаг 3). Между «в базе никого» и «вход запрещён» кто-то может успеть войти. Без повторной проверки загрузка упадёт на середине или, хуже, пройдёт при активном сеансе.

  • Прежняя блокировка сначала пишется на диск (шаг 2). Если приложение упадёт или его закроют посреди загрузки, в базе останется запрет входа, и пользователи утром не смогут зайти. Поэтому прежние значения сначала сохраняются в .konflow/state/blocked-bases.json. Возврат блокировки выполняется с неотменяемым токеном, так что кнопка «Отменить» не оставит базу закрытой. А если запись в файле пережила перезапуск, при следующем открытии проекта Konflow предложит снять блокировку.

Если RAS недоступен, будет предупреждение «сеансы не проверены». О конфликте тогда сообщит сам конфигуратор, он ходит через кластер.

Конфликты лучше видеть до слияния

В 1С конфликт слияния особенно неприятен: формы и модули объектов — это большие XML и BSL-файлы, и разбирать их перед релизом не хочется никому. Гораздо дешевле договориться в начале работы: «я тоже трогаю ЧекККМ, давай я подожду твою задачу».

Пересечения задач видно до слияния

Пересечения задач видно до слияния

После каждого «С сервера» и фоновой проверки (тихий git fetch раз в пять минут) Konflow строит для каждой активной задачи — своей и чужой, по веткам на сервере — список изменённых объектов метаданных. Пути файлов сворачиваются в объекты: Documents/ЧекККМ/Forms/ФормаДокумента/Ext/Form.xml → «Документ.ЧекККМ, форма ФормаДокумента». Пересечение объектов двух задач — это ⚠. Для пар с общими объектами дополнительно запускается git merge-tree: он считает слияние без рабочей копии и честно говорит, будет ли конфликт. Это уже ⛔. Результат кэшируется по паре вершин, ведь история не меняется.

Если в задаче есть объекты с ⛔, в карточке статуса появляется красный баннер. Если develop уже ушёл вперёд и принёс правки объектов задачи, появляется напоминание и кнопка «Подтянуть develop».

Когда конфликт всё-таки случился, сценарий останавливается на панели с конфликтными файлами. В ней можно открыть файл во внешнем инструменте (VS Code, KDiff3, TortoiseGitMerge), взять свою версию или вливаемую (это нужно для двоичных файлов вроде ParentConfigurations.bin), а затем нажать «Продолжить» или «Отменить».

Ещё по мелочи, но важно

  • Откат выпущенной задачи. Задача сломала продуктив — «Откатить задачу…» создаёт ветку hotfix/<N>-revert от master и делает в ней git revert -m 1 слияния задачи. Само слияние находится по служебному тегу. Подвох в том, что повторное слияние старой ветки после revert ничего не вернёт. Поэтому новая задача с тем же номером предлагает вернуть изменения обратным коммитом отката.

  • Резервные копии. Перед полной загрузкой можно делать .dt (ibcmd infobase dump или /DumpIB) внутри той же монопольной операции, что и загрузка, чтобы между копией и загрузкой никто не вошёл. Восстановление возвращает и базу, и состояние синхронизации — загруженный коммит и ConfigDumpInfo.xml, — поэтому после него загрузки снова инкрементальные.

  • Отмена посреди операции. Процесс 1С можно остановить. Сценарий прерывается на ближайшем безопасном шаге: посреди слияния или checkout git не прерывается. Прерванная загрузка помечает базу, и следующая загрузка будет полной.

Как это сделано

  • .NET 10 и WPF, один самодостаточный .exe. Ядро (Konflow.Core) не знает про интерфейс: git, 1С, сценарии, релизы, лицензирование. Приложение (Konflow.App) — окна и диалоги.

  • git — через CLI, а не через libgit2: нужны Git LFS, merge-tree, worktree и поведение, полностью совпадающее с тем, что увидит разработчик, если откроет консоль.

  • 1С — через ibcmd и конфигуратор в пакетном режиме, кластер — через rac. Маршрутизатор сам выбирает, кем выполнять операцию: ibcmd там, где он годится, а для базы с хранилищем и записи в серверную — конфигуратор.

  • Тесты. Сквозные сценарии гоняются на настоящем git, а 1С в них заменена заглушкой, которая «выгружает» и «загружает» каталоги. Так сценарии «Сохранить / Переключиться / Завершить / Релиз» со всеми ветвлениями проверяются без установленной платформы. Отдельный набор тестов идёт на настоящей платформе: проверка модулей, резервные копии, загрузка.

  • Справка встроена в exe и сверяется тестом: поднять версию приложения нельзя, пока документы не перечитаны и не отмечены новой версией.

Чего Konflow не делает

Чтобы не создавать ложных ожиданий:

  • Работает только под Windows, нужна платформа 8.3 с ibcmd (проверено на 8.3.27, есть поддержка 8.5) и Git for Windows с LFS.

  • Не заменяет EDT. Если команда уже на EDT, Konflow ей не нужен. Если когда-нибудь захотите перейти — репозиторий останется обычной XML-выгрузкой конфигурации.

  • Не создаёт серверные базы — их создают в консоли кластера.

  • Не меняет версию конфигурации сам: её поднимают в свойствах конфигурации в одной из задач релиза. Собрать релиз с версией не выше предыдущей мастер не даст.

  • Не свой git-сервер. Подойдёт любой: GitLab, Gitea, GitHub, Bitbucket, даже сетевая папка.

Итого

Главный вывод для меня: проблема «git для 1С» — не в git и не в формате выгрузки. Проблема в том, чтобы база и рабочая копия никогда не разошлись незаметно. Инвариант «база помнит загруженный коммит», отдельный ConfigDumpInfo.xml на каждую базу и инкрементальная загрузка по git diff закрывают большую часть сложности. Остальное — аккуратная работа с кластером и знание того, где платформа ведёт себя неожиданно.

А как у вас: хранилище, EDT или свой конвейер вокруг git? И на какие грабли ibcmd наступали вы? Буду рад историям в комментариях.

Автор: vanya_gonchar

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js