После SAT на флешке у ведущего лежит папка финал_2_правда, на ноутбуке второго наладчика - копия_с_объекта_пятница, в шкафу - USB с надписью маркером «не трогать». Через полгода гарантийный вызов: на контроллере не та блокировка, что в акте. Online в среде разработки показывает checksum, которого нет ни в одном из трёх архивов. Спор идёт не о логике клапана, а о том, кто и когда залил «чуть поправленную» версию без записи в журнале объекта.
Статья про рабочий минимум Git для команды из двух-трёх человек, которая ведёт проект ПЛК и не хочет жить файлами «final». Ветки main и release, тег под SAT, запрет возить истину на флешке, что класть в репозиторий и что нельзя (пароли, лицензии), сверка того, что online в контроллере, с тегом. Это не CI/CD, не промышленный DevOps и не повтор общего разговора про версионирование как идею - здесь про ветки, теги и след «кто залил на объект».
Короткий ответ
Для проекта контроллера достаточно короткой схемы: ветка main как согласованная линия, ветка или тег release под конкретный объект и приёмку, тег на состояние SAT (и на каждое последующее изменение, которое уехало в CPU). На объект заливают только сборку, у которой есть тег и запись в журнале: кто, когда, какой тег, online change или полный download. Флешка и папка на рабочем столе - транспорт, не архив. В Git не кладут пароли, ключевые файлы лицензий и дампы с секретами; бинарники IDE - по правилу команды, с пониманием, что diff по ним бесполезен. Сверка: checksum или версия application в контроллере совпадает с тегом, иначе линия работает «непонятно на чём».
Зачем Git, если «нас двое и объект один»
Пока один человек ведёт шкаф от эскиза до SAT, папка на диске ещё живёт. Ломается схема, когда появляется второй ноутбук, ночная правка, гарантия через год и заказчик, который просит as-built. Без общего журнала версий каждый уверен, что «у меня актуальная». Контроллер при этом хранит только то, что в него загрузили последним, без истории авторов.
Git здесь не про красоту истории коммитов. Он про три вопроса, на которые начальник АСУ должен ответить за пять минут: какая версия должна быть в этом CPU по акту, какая там сейчас, кто имел право это изменить. Если на любой из трёх ответов нужен детектив по почте и флешкам - регламента нет.
Минимальная команда: один репозиторий на объект или на линию, доступ у тех, кто имеет право менять проект, одно правило «на CPU только из тега». Всё, что сверх этого (merge request, боты, зеркало в облаке), полезно, но не обязательно, чтобы перестать терять версии.
Связка с уже описанной практикой версионирования проектов ПЛК такая: та статья - зачем вообще нужен учёт версий и что бывает без него; эта - как назвать ветки, когда ставить тег и как вписать Git в журнал объекта, чтобы гарантийный инженер не гадал.
Ветки без религии: main и то, что уехало на площадку
Две сущности закрывают 90% жизни малого проекта. main (или master, если так завели и не хотят переименовывать) - линия, которую команда считает текущей разработкой после согласования. Сюда не коммитят в 03:00 «чтобы линия поехала», сюда попадает правка после того, как её поняли днём.
Вторая сущность - то, что реально в контроллере. Кто-то делает ветку release/linia-3, кто-то только теги на main. Оба варианта рабочие, если соблюдено правило: нельзя залить на объект commit, который не помечен. Тег дешевле ветки, когда объект один и правок мало. Ветка release удобнее, когда на площадке живёт свой набор «полевых» исправлений, которые ещё не все хотят в общую линию, но уже стоят в CPU.
Ветки feature/клапан-24 имеют смысл, если правка живёт дни и её надо смотреть второму человеку. Для хотфикса на смене достаточно: взяли тег, который в CPU, сделали commit на hotfix-ветке от этого тега, проверили на стенде, залили, поставили новый тег, записали журнал, потом влили в main уже в рабочее время. Обратный порядок - «набросал в main и сразу в шкаф» - снова смешивает эксперимент и объект.
Запрещённая схема: у каждого наладчика своя папка, Git «потом наведём». Потом не наведут. Репозиторий заводят до выезда на пуско, не после акта.
Тег под SAT и теги после него
Тег SAT - снимок того application, который заказчик принял. Имя лучше скучное и однозначное: sat-2026-03-12-linia3, не окончательный. В аннотации тега: объект, шкаф, среда (CODESYS или MasterSCADA 4D - какая заказана на этом железе), короткий список отличий от предыдущей версии, кто присутствовал на приёмке.
После SAT жизнь проекта не замирает. Ночная правка блокировки, смена масштаба датчика, новый насос - каждый уезд в CPU получает новый тег, даже если «это на три строки». Иначе через месяц тег SAT врёт, а журнал объекта ссылается на приёмку.
Тег не заменяет бэкап retain и файлов рецептов. Git хранит проект; уставки, которые оператор крутит с панели, живут в контроллере. В журнале после загрузки пишут оба факта: тег application и «retain сохранён / сброшен / восстановлен из файла такого-то».
На гарантии первым делом сравнивают тег SAT, последний тег в журнале и то, что online. Расхождение - не обязательно злой умысел, чаще забытый хотфикс. Без тегов это не доказать и не исправить предсказуемо.
Флешка: транспорт, не источник истины
USB удобен в шкафу без сети и в режиме, когда ноутбук наладчика не лезет в корпоративный Git. Это не оправдание хранить единственную копию проекта на флешке в дверце. После работы файл с флешки либо попадает в репозиторий с тегом, либо считается черновиком и уничтожается как рабочая копия.
Типичный сбой родственен истории, когда рецепт с USB загрузили, а активной осталась старая версия: операция «скопировал» не равна «сделал действующим». Для проекта ПЛК то же самое: лежит на флешке - ещё не в CPU и ещё не в Git. В журнале пишут не «залил с флешки», а «залил тег X, носитель - USB как транспорт».
Маркировка флешек: объект, тег, дата. Флешка без тега в шкафу - кандидат на заливку прошлогодней аварии в новый контроллер при замене модуля. Регламент ЗИП это не отменяет: запасной CPU загружают из архива объекта, не «с той флешки, что была в ящике».
Что класть в репозиторий, что нет
Кладут исходники проекта среды, экспортируемые текстовые представления, если среда их даёт, список библиотек с версиями, краткий README: как открыть, какой target, какой runtime. Полезно положить экспорт карты сигналов и пометку, какой тег соответствует as-built.
Бинарники IDE (служебные каталоги компилятора, кэш, локальные настройки пользователя) засоряют историю и плодят ложные конфликты. Их закрывают .gitignore по документации среды, а не «как получится после первого commit на 4 ГБ». Если команда сознательно хранит скомпилированный application как артефакт релиза - отдельная папка releases/ с тегом, без ежедневных промежуточных сборок.
Не кладут: пароли панелей и VPN, файлы лицензий, дампы с ключами, персональные пути наладчика, архивы чужих объектов «на всякий случай». Лицензия среды привязана к договору и рабочему месту, а не к Git объекта. Утёкший репозиторий с паролем operator на все линии - отдельный инцидент, не «удобство команды».
Конфликты слияния в ST и текстовых экспортах решаются человеком, который понимает логику, не автослиянием. Конфликты в бинарниках проекта - сигнал, что в Git попало то, что нельзя мержить; тогда договариваются, чей файл канонический, и больше не правят вдвоём один бинарник без ветки.
CODESYS 3.5 и MasterSCADA 4D на одном типе контроллера дают разные деревья файлов. Репозиторий не обязан быть «универсальным под обе среды». Он обязан соответствовать той среде, которая реально заказана и крутится в CPU. Два варианта заказа на одном железе не смешивают в одной ветке «на всякий случай».
Право влить в main и право залить в CPU - не одно и то же. В малой команде достаточно явного правила: merge в main делает ведущий проекта после просмотра diff (или после устного разбора, если diff по бинарнику бесполезен). Заливку на объект делает человек с допуском в журнал, после тега, не «кто ближе к шкафу». Если оба права висят на одном человеке, это допустимо при численности два, но тогда второй хотя бы видит тег и checksum в журнале на следующее утро. Без второго глаза ночной хотфикс превращается в личную память.
.gitignore для проекта контроллера закрывает кэш компилятора, пользовательские layout IDE, временные upload-каталоги и локальные пути лицензии. Что именно игнорировать, берут из документации среды, не из чужого репозитория с другой IDE. Если в истории уже лежат гигабайты кэша - один раз чистят, пишут ignore, дальше не тащат это на объект как «часть проекта».
Сверка online с тегом
Среда в online показывает версию, дату сборки или checksum application - то, что есть у конкретного runtime. Это число (или строка) заносят в журнал рядом с тегом Git в момент загрузки. Через год сверка: подключились, сравнили, совпало - можно разбирать логику. Не совпало - сначала восстанавливают соответствие, потом спорят о клапане.
Практика: в проекте заводят константу или строку визуализации APP_VERSION, которую коммитят вместе с тегом и выводят на сервисном экране. Оператор не обязан её понимать; наладчик видит без IDE, что за сборка. Расхождение экрана и тега в журнале - снова стоп для догадок.
Если online недоступен (нет кабеля, другой IP), не делают вывод «наверное, тот же тег». Либо подключаются, либо читают штатный способ производителя выгрузить идентификатор. Гарантийный акт, написанный по флешке из шкафа без сверки CPU, описывает желаемое, не факт.
Журнал объекта и Git - два слоя одного факта
Git отвечает на вопрос «какой исходник». Журнал объекта отвечает на «что сделали с железом». Оба нужны. Commit без загрузки не меняет линию. Загрузка без commit оставляет линию без исходника.
В журнале после события коротко: дата и смена, шкаф и CPU, тег Git, checksum online после загрузки, тип операции (download / online change), retain, ФИО кто залил, ФИО кто подтвердил со стороны эксплуатации, результат прогона, ссылка на заявку или акт. Это стык с паспортом объекта после пуска: заказчик получает не только PDF мнемосхем, но и правило, где лежит репозиторий и кто имеет доступ.
Доступ к Git после акта: заказчик или его служба АСУ должна иметь возможность получить тег SAT и последующие теги. Репозиторий, который живёт только на ноутбуке интегратора, - тот же риск, что флешка в шкафу, только современнее. Передача as-built включает экспорт или зеркало по договору, не «напишите, вышлем архивом».
Ночная правка, пуско и гарантия - разные события
На пусконаладке поток коммитов высокий, теги ставят на вехи: «пошла вода», «прошли блокировки», SAT. Не каждый пробный download достоин тега, но каждый, который оставляют в CPU на ночь, - достоин. Утром смена имеет право знать, на чём проснулась линия.
Ночной хотфикс после пуска: минимум людей, минимум изменений, обязательный тег, обязательная запись, днём - разбор, нужен ли этот хотфикс в main как есть или его перепишут аккуратно. Хотфикс, который стыдятся влить в main, часто стыдятся потому, что он грязный; тогда его нельзя оставлять единственной правдой в CPU без плана замены.
Гарантийный выезд начинают со сверки тегов, не с переписывания POU. Если в CPU не то, что в акте, сначала фиксируют факт, потом решают, откатывать к SAT или оформлять новую версию. Тихий «подкрутил, чтобы не свистело» без тега превращает гарантию в спор о комплектации проекта.
Технический долг накапливается и в Git: ветка release уехала далеко от main, хотфиксы не влиты, .gitignore не закрыл кэш. Когда рефакторить логику - отдельный разговор про долг legacy ПЛК; здесь достаточно не копить второй долг - рассинхрон репозитория и железа.
Таблица: событие, действие в Git, запись в журнале объекта
| Событие | Действие в Git | Что писать в журнале объекта |
|---|---|---|
| Старт проектирования | Репозиторий, `main`, `.gitignore`, README с target и средой | Где лежит репозиторий, кто владелец доступа |
| Выезд на пуско | Ветка/тег `pusk-start`, запрет личных папок как архива | Какой тег увезли на площадку, какие шкафы |
| Правка на пуске, остаётся в CPU на ночь | Commit от текущего объектного состояния, новый тег | Тег, checksum online, кто залил, retain |
| SAT | Аннотированный тег SAT, архив application в `releases/` по правилу команды | Тег SAT, состав комиссии, checksum, ссылка на акт |
| Ночной хотфикс после пуска | Ветка от тега, который в CPU → commit → новый тег; влить в `main` на следующий рабочий день | Причина, тег до/после, подтверждение смены, план дневного разбора |
| Плановое обновление проекта | Тег релиза, загрузка только из него | Как в чек-листе обновления: окно, бэкап, результат |
| Замена CPU / модуль из ЗИП | Загрузка того же тега, что в паспорте шкафа | Серийный номер нового CPU, тег, сверка online |
| Гарантийный вызов | Сверка: тег журнала vs online vs SAT | Факт совпадения или расхождения до любых правок |
| Передача заказчику | Зеркало или экспорт тегов по договору, отзыв лишних учёток | Акт передачи репозитория/архива, список актуальных тегов |
| Обнаружен « levый» application в CPU | Не коммитить это как истину, пока не разобрали источник | Расхождение, кто последний по журналу, решение: откат или оформление новой версии |
Типовые ошибки
Тег только на SAT, дальше «и так помним». Через полгода не помнит никто, журнал врёт.
Commit после заливки «когда будет время». Времени нет, в CPU живёт призрак.
Пароли панелей в README репозитория. Удобно на пуске, дорого после утечки.
Два наладчика правят один бинарник проекта без ветки. Merge превращается в ручную лотерею, в шкаф уезжает чей-то «более свежий» файл по дате Windows.
Флешка в дверце как backup strategy. Флешка теряется, перезаписывается, путается между линиями.
Считать Git заменой стенда. Тег не доказывает, что блокировка работает; он доказывает, какой исходник должны были залить. Проверка - на стенде и в окне на объекте, в духе приёмки FAT/SAT.
Вопросы при работе
Нужен ли Git, если проект ведёт один человек?
Да, как только появляется объект, акт и вероятность второго человека через год. Одиночный репозиторий с тегами дешевле следствия по папкам.
Можно ли хранить репозиторий только у интегратора?
До акта - да, если договором так задано. После передачи as-built заказчик должен иметь доступ к тегам, которые соответствуют CPU. Иначе гарантия опирается на чужой ноутбук.
Что делать с проектом, который среда отдаёт одним бинарным файлом?
Хранить файл как артефакт релиза с тегом, вести рядом текстовый changelog. Параллельно не редактировать его вдвоём. Если среда умеет экспорт исходников - в Git прежде всего они.
Как связать тег и checksum, если среда не показывает удобный хеш?
Фиксируют то, что даёт runtime: дата сборки, имя application, версия визуализации, строка APP_VERSION на сервисном экране. Главное - одно и то же поле в журнале каждый раз.
Online change тоже нужен тег?
Если изменение остаётся в CPU - да. Иначе утром online не совпадёт с последним тегом, и регламент мёртв.
Где держать Git физически?
Там, где команда реально коммитит: локальный сервер АСУ, закрытый хостинг заказчика, зеркало интегратора по договору. Публичный репозиторий с проектом линии - нет.
Ссылки по теме
- Версионирование проектов ПЛК: Git на практике;
- Рецепт с USB загрузили, осталась старая версия;
- Паспорт объекта АСУ ТП после пуска: что передать заказчику;
- Пакет документов при вводе АСУ ТП в эксплуатацию;
- Технический долг legacy ПЛК: когда рефакторить;
- FAT и SAT: приёмка АСУ ТП без сюрпризов на пуске.
Обсуждение