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

Если в команде 1С больше трёх разработчиков, хранилище конфигурации рано или поздно становится узким местом. Общий модуль захвачен коллегой, непроверенную задачу уже не вынуть из релиза, а сам релиз собирается вечером пятницы вручную. Обычный ответ на это — «переходите на EDT и git». Ответ правильный, но дорогой: новая среда, тяжёлые машины, переобучение всей команды и несколько месяцев, пока процесс снова заработает.
Я пошёл третьим путём: разработчики остаются в обычном конфигураторе, а git становится транспортом между их базами. Получилось Windows-приложение Konflow: ветки, коммиты и слияния спрятаны в нём за кнопками «Новая задача», «Сохранить», «Переключиться», «Завершить», «Собрать тест» и «Релиз». Выгружать и загружать конфигурацию приложение тоже берёт на себя.
В статье — как это устроено внутри: модель веток, синхронизация базы с рабочей копией, инкрементальная загрузка, работа с серверными базами через RAS и грабли платформы, на которые я наступил по пути.
Что не так с хранилищем
Хранилище — это одна общая конфигурация с пессимистичными блокировками. Пока команда маленькая, это удобно. С ростом команды вылезают три проблемы.
Захваты. Общий модуль «держит» коллега. Остаётся ждать, пока он поместит изменения, или править в обход и потом переносить руками.
Нет изоляции задач. Всё помещённое сразу попадает в общую конфигурацию. Не успели проверить одну задачу — откатываете её вручную или переносите весь релиз.
Релиз — ручная работа. Собрать 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 придёт и ожидается ли конфликт.

Модель веток: задачи встречаются только в релизе
Ветвление — вариация 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. Дальше остаётся решить, можно ли загрузить только её.

В коде это короткая функция:
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, и последовательность шагов здесь важнее, чем кажется.

Два шага неочевидны.
Повторная проверка (шаг 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 наступали вы? Буду рад историям в комментариях.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.