Если вы хоть раз пытались вести «одно расширение на несколько конфигураций» (УНФ/Розница/УТ/КА/ERP) в Git, то знаете три чувства: diff, merge и «почему опять конфликт в Configuration.xml». Мы прошли этот квест на проекте 1C AI Autofill и собрали рабочую схему, которую можно забрать себе.
Дисклеймер: если я говорю «мы», это значит я и мои агенты в Cursor IDE. Они тоже участвуют в мерже — и тоже нервничают, когда конфликтов в Configuration.xml больше, чем изменений в коде.
Что это за проект (в двух строках): расширение для 1С:Предприятие 8.3, которое помогает генерировать продающие описания номенклатуры и (по настройкам) заполнять доп. реквизиты/характеристики с помощью AI. Исходники открыты, релизы лежат на GitHub, а логика разработки — внятная и повторяемая.
Почему опенсорс: изначально это был платный продукт. Со временем коммерческая ценность именно этого кода стала для нас несущественной — поэтому мы решили открыть исходники, чтобы наработки жили дальше и приносили пользу сообществу.
Коротко (TL;DR)
main — ядро (вся логика и «истина»), а ветки конфигураций — тонкие адаптеры (в основном отличаются
xml/Configuration.xml).git worktree — чтобы держать одновременно несколько веток рядом и не переключаться туда-сюда.
Только XML-выгрузка — чтобы diff был читаемым, а merge — предсказуемым.
Сборка автоматизирована (PowerShell + 1cv8 DESIGNER): из XML → .cfe → загрузка → обновление БД (опционально).
Мем-пауза, чтобы пережить merge

Моя прелесть — merge без конфликтов.
Содержание: Git-схема · Почему XML · Фрагменты кода · Сборка .cfe · Чек-лист · Планы · Как контрибьютить · Что сделать прямо сейчас · Ссылки
0. Для кого статья
Разработчики 1С, которым нужно поддерживать расширение сразу для нескольких конфигураций.
Те, кто хочет «как в нормальном мире»: GitHub, релизы, воспроизводимая сборка, минимум ручных действий.
Те, кто устал от EDT-артефактов и конфликтов, когда «вроде ничего не менял, а изменилось всё».
1. Git «по-взрослому»: одно ядро и несколько веток конфигураций
Фраза «main — это состояние души» смешная ровно до первого релиза. Потом она становится правилом: в main живёт ядро, а ветки конфигураций периодически подтягивают изменения и собирают свои .cfe.
Структура веток
В репозитории держим несколько веток/сборок:
main — ядро расширения: полная XML-выгрузка (включая
Configuration.xmlиConfigDumpInfo.xml).UNF_Rozn — сборка для УНФ и Розницы.
UNF_Rozn_Fresh — сборка для 1cfresh (отличается, в том числе, именем расширения).
UT_KA_ERP — сборка для УТ, КА и ERP.
Где обычно отличия: в xml/Configuration.xml — имя конфигурации (<Name>) и иногда версия (<Version>). Всё остальное — как в main (если не было осознанного точечного отличия под конкретную конфигурацию).
Worktrees: «хочу видеть всё сразу»
Вместо вечного git checkout — отдельный worktree на каждую ветку. Это резко снижает количество «ой, я правил не там».
git worktree add ../worktrees/UNF_Rozn UNF_Rozn git worktree add ../worktrees/UT_KA_ERP UT_KA_ERP
Поток разработки (как живём каждый день)
Фичи и общая логика — делаем в main.
Ветки конфигураций — периодически делают
git merge main, разрешают конфликты по правилам и выпускают свой релиз.Редкие иск��ючения: если правка нужна только одной конфигурации — она живёт в её ветке (и вы заранее понимаете, что на следующем merge это может дать конфликт).
Merge main → ветка конфигурации
Команды максимально скучные (а это хорошо):
cd worktrees/UNF_Rozn git merge main
Конфликты: быстрые правила, чтобы не «вспоминать проект по ходу мержа»
Если Git не знает, кого любить — пусть любит ваши правила:
xml/Configuration.xml: оставляем своё значение<Name>(и при необходимости<Version>) для ветки конфигурации.xml/ConfigDumpInfo.xml: оставляем свою версию/идентификаторы (или разрешаем по смыслу, если менялись осознанно).Модули, формы, общая логика: почти всегда берём из main (main — источник правды).
Подробно (и тем же смыслом, но в формате «инструкции») это уже оформлено в репозитории: MERGE.md.
2. Почему «чистая XML-выгрузка» выигрывает у EDT в таких репозиториях
EDT мы пробовали. Оно умеет многое, но для сценария «одно ядро → несколько веток конфигураций → частые мержи» цена оказывается слишком высокой.
Что обычно ломает кайф
Лишние артефакты в репозитории (служебные файлы, метаданные).
Тяжёлый diff: «как понять, что реально изменилось?»
Merge как квест: конфликтов больше, чем изменений.
Что делаем вместо
Храним расширение в чистой XML-выгрузке (каталог xml/). В результате:
diff читается глазами разработчика 1С;
merge становится процедурой, а не приключением;
репозиторий ос��аётся прозрачным (без «а это что за файл?»).
Следы эпохи EDT обычно выглядят так (и да, их лучше игнорировать):
# EDT/Eclipse служебные файлы .metadata/ .project .classpath .settings/ *.launch
3. Как выглядит код: два фрагмента, чтобы было «осязаемо»
Ниже — пара кусочков из расширения: серверный вызов к API и клиентская часть с длительной операцией. Это не «идеальный эталон», а понятный скелет, который работает и хорошо мержится.
Сервер: HTTP + JSON, получение ответа и нормальная ошибка
Функция ОтправитьЗапрос(Промт) Экспорт СтрокаJSON = СериализоватьЗапросВJSON(Промт); Соединение = ПолучитьHTTPСоединение(); Запрос = Новый HTTPЗапрос(ПолучитьПутьChatCompletions(), ПолучитьHTTPЗаголовки()); Запрос.УстановитьТелоИзСтроки(СтрокаJSON); Результат = Соединение.ОтправитьДляОбработки(Запрос); //POST Если Результат.КодСостояния=200 Тогда ЧтениеJson = Новый ЧтениеJson; ЧтениеJson.УстановитьСтроку(Результат.ПолучитьТелоКакСтроку()); РезСтруктура = ПрочитатьJSON(ЧтениеJson); МассивРезультатов = РезСтруктура["choices"]; Если МассивРезультатов <> Неопределено И МассивРезультатов.Количество() И ТипЗнч(МассивРезультатов[0]) = Тип("Структура") И МассивРезультатов[0]["message"] <> Неопределено И ТипЗнч(МассивРезультатов[0]["message"]) = Тип("Структура") Тогда Ответ = СокрЛП(МассивРезультатов[0]["message"]["content"]); Конецесли; Иначе СообщениеобОшибке = "Не удалось получить ответ. Код ошибки: "+Результат.КодСостояния +" "+ Результат.ПолучитьТелоКакСтроку(); ВызватьИсключение СообщениеобОшибке; КонецЕсли; Возврат Ответ; КонецФункции Функция СериализоватьЗапросВJSON(ТекстЗапроса) Настройки = НастройкаПоУмолчанию(); Сообщения = Новый Массив; Сообщение = Новый Структура; Сообщение.Вставить("role", "user"); Сообщение.Вставить("content", ТекстЗапроса); Сообщения.Добавить(Сообщение); Структура = Новый Структура; Модель = Настройки.МодельИИ; Если НЕ ЗначениеЗаполнено(Модель) Тогда Модель = "auto"; КонецЕсли; Структура.Вставить("model", Модель); Структура.Вставить("messages", Сообщения); ЗаписьJSON = Новый ЗаписьJSON; ЗаписьJSON.УстановитьСтроку(); ЗаписатьJSON(ЗаписьJSON,Структура); Возврат ЗаписьJSON.Закрыть(); КонецФункции
Клиент: длительная операция, чтобы форма не зависала
Когда пользователь нажимает «Заполнить ИИ», запрос может выполняться заметно долго. Поэтому — длительная операция: запускаем на сервере и ждём с окном ожидания.
&НаКлиенте Процедура GPT_ЗаполнитьОписание(Команда) СостояниеНастроек = GPT_ОбщийВызовСервера.GPT_СостояниеНастроек(); Если НЕ СостояниеНастроек.Готово Тогда ОткрытьФорму("ОбщаяФорма.GPT_Регистрация", , ЭтотОбъект, , , , , РежимОткрытияОкнаФормы.БлокироватьОкноВладельца); Возврат; КонецЕсли; GPT_ОтправитьЗапросНаСервере(); ОписаниеОповещения = Новый ОписаниеОповещения("ПолучениеОтветаChatGPT", ЭтотОбъект); ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект); ПараметрыОжидания.ВыводитьОкноОжидания = Истина; ДлительныеОперацииКлиент.ОжидатьЗавершение(ЭтаФорма.GPT_ДлительнаяОперация, ОписаниеОповещения, ПараметрыОжидания); КонецПроцедуры &НаСервере Функция GPT_ОтправитьЗапросНаСервере() ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФункции(Новый УникальныйИдентификатор); ПараметрыВыполнения.ОжидатьЗавершение = Ложь; ЭтаФорма.GPT_ДлительнаяОперация = ДлительныеОперации.ВыполнитьФункцию( ПараметрыВыполнения, "GPT_ОбщийМодульСервер.СгенерироватьОписаниеИДопСвойстваНоменклатуры", Объект.Ссылка ); КонецФункции
4. Автоматизация сборки: .cfe без рутины «LoadCfg / UpdateDBCfg вручную»
Ручная сборка .cfe — это десятки одинаковых действий, умноженные на количество веток. Автоматизация убирает человеческий фактор: скрипт делает всё одинаково каждый раз.
Типовая схема: main → merge в ветки → скрипт собирает .cfe для каждой ветки → (опционально) можно повесить на CI.
Пример скрипта (PowerShell): параметры
Смысл простой: путь к платформе, строка подключения, путь к XML, куда положить .cfe, имя расширения и флаги.
param( [string]$PlatformExe = 'C:\Program Files\1cv8\8.5.1.1150\bin\1cv8.exe', [string]$ConnectionString = '', [string]$XmlPath = (Join-Path (Split-Path $PSScriptRoot -Parent) 'xml'), [string]$ExtensionCfePath = (Join-Path (Split-Path $PSScriptRoot -Parent) 'bin\Расширение.cfe'), [string]$ExtensionName = 'GPT_ОписаниеНоменклатуры', [switch]$BuildFromXml, [switch]$SkipLoadExtension, [switch]$SkipDbUpdate, [switch]$SkipRunClient, [switch]$Wait )
Сборка .cfe из XML: два вызова DESIGNER
# Сборка .cfe из xml (если -BuildFromXml) if ($BuildFromXml) { $xmlFullPath = [System.IO.Path]::GetFullPath($XmlPath) $loadXmlArgs = $baseArgs + @('/LoadConfigFromFiles', $xmlFullPath, '-Extension', $ExtensionName) Invoke-Platform -Arguments $loadXmlArgs -OperationName 'Сборка: загрузка расширения из XML в конфигурацию' $dumpArgs = $baseArgs + @('/DumpCfg', $cfeFullPath, '-Extension', $ExtensionName) Invoke-Platform -Arguments $dumpArgs -OperationName 'Сборка: выгрузка расширения в .cfe' }
Загрузка .cfe и обновление БД (если нужно)
$loadArgs = $baseArgs + @('/LoadCfg', $cfeFullPath, '-Extension', $ExtensionName) Invoke-Platform -Arguments $loadArgs -OperationName 'Загрузка расширения из .cfe в конфигурацию' $updateArgs = $baseArgs + @('/UpdateDBCfg', '-Extension', $ExtensionName) Invoke-Platform -Arguments $updateArgs -OperationName 'Обновление конфигурации БД'
Полный цикл одной командой обычно выглядит так: .\update-extension-and-run-db.ps1 -BuildFromXml. Скрипт соберёт, загрузит, обновит (если не отключено) и может запустить клиент (если предусмотрено вашей обвязкой).
5. Мини-чек-лист релиза (чтобы не держать всё в голове)
В main: влитые фичи/фиксы, при необходимости обновлён
<Version>вxml/Configuration.xml.В ветке конфигурации:
git merge main+ конфликты по правилам (имя/версия свои, логика из main).Сборка: скрипт → получаем .cfe (и при необходимости прогоняем установку в тестовой базе).
Публикация: релиз в GitHub (отдельно на каждую ветку/сборку).
6. Планы по развитию
Самое очевидное следующее улучшение — автоматизированное тестирование, чтобы релизы не держались на «проверил руками и вроде ок».
Автотест сборки: воспроизводимая сборка
.cfeизxml/(скриптом) в CI.Smoke-тест установки: установка/обновление расширения в тестовой базе и проверка, что оно подключилось без ошибок.
Проверка базовых сценариев: «кнопка есть», «запрос ушёл», «ошибка показывается понятно» (минимум, который ловит регрессии).
Ссылки
Репозиторий: msrv-tech/1c-ai-autofill
Релизы (.cfe): GitHub Releases
Заключение
Опенсорс на 1С — это не «страшно и больно», если у вас предсказуемая структура: main как ядро, ветки как сборки, чистая XML-выгрузка и скрипт сборки. Остальное — дисциплина и пару раз пережить первый merge.
