dwe render git
Сгенерировать shell git-хуки для каждого включённого сервиса из пакета шаблонов. Вывод идёт в git-каталог хуков сервиса (например, services/main/src/.git/hooks/pre-commit), с исполняемым режимом 0755.
render git разделяет пер-сервисную инфраструктуру с render ide и render ai — та же модель выборки, та же цепочка разрешения пакета, те же гарды безопасности путей, та же manifest-схема. Отличается в двух местах: назначение — внутри .git/ каталога сервиса (который никогда не отслеживается git), а to-пути в manifest ограничены basename (без подкаталогов под hooks/).
Содержание
Заголовок раздела «Содержание»- Конвейер
- Выборка сервисов
- Разрешение пакета шаблонов
- Схема manifest
- Пофайловый рендер
- Проработанный пример
- Сообщения вывода
- Worktree и submodule
- Частые ловушки
- Связанные справочники
Конвейер
Заголовок раздела «Конвейер»flowchart TD
CFG["Загрузить объединённую конфигурацию"] --> SEL{"Аргумент задан?"}
SEL -- нет --> SP["Выбрать сервисы<br/>фильтр политики + коллизия"]
SEL -- да --> EXV["Валидировать аргумент"]
EXV --> RHA["Разрешить hub-якорь<br/>глубочайший выигрывает"]
SP --> LIST["Отсортированный список сервисов"]
RHA --> LIST
LIST --> EACH{"Для каждого сервиса"}
EACH --> PH["PrepareHub<br/>contain + no-symlink"]
PH --> RP["Разрешить пакет шаблонов"]
RP --> GDP["Зондировать src/.git"]
GDP -- отсутствует --> SKIPM["warn + skip"]
GDP -- worktree-файл --> SKIPW["warn + skip"]
GDP -- каталог --> LM["Загрузить manifest.yml"]
LM --> VM["Валидировать manifest"]
VM --> R["Рендерить каждый manifest.render<br/>в .git/hooks/<basename>"]
R --> EACH
EACH -- "конец" --> DONE["готово"]
Выборка сервисов
Заголовок раздела «Выборка сервисов»Гейт активации
Заголовок раздела «Гейт активации»Сервис участвует в рендере git-хуков, только если оба флага истинны:
| Гейт | Источник | По умолчанию |
|---|---|---|
| Уровень проекта | services.<name>.enabled (3-слойный merged + обязательный service override) | зависит от сервиса |
| Политика git | services.<name>.render.git.enabled | true для type: app; false иначе |
Политика по умолчанию такая же, как у render ide и render ai: хуки рендерятся по умолчанию только для type: app, потому что там разработчики обычно коммитят код. Прочим типам сервисов нужно явно включить render.git.enabled: true.
Наследование через extends подчиняется тем же правилам, что у render.ide и render.ai: ребёнок без явного значения наследует у родителя render.git.enabled и render.git.template.
Hub preflight и зонд git-каталога
Заголовок раздела «Hub preflight и зонд git-каталога»До любой записи на диск каждый выбранный сервис проходит два preflight-чека:
- Containment hub.
svc.Dirдолжен разрешаться внутрь корня проекта и не достигаться через симлинк. Сервис сdir: ../outsideили симлинкнутым каталогомservices/<name>отвергается жёсткой ошибкой — рендерер никогда не создаётsrc/.git/hooks/вне дерева проекта. - Зонд git-каталога. Инспектируется
<absHub>/src/.git:- каталог → продолжить; хуки пишутся в
<absHub>/src/.git/hooks/. - обычный файл (worktree или submodule
gitdir:-указатель) → предупредить и пропустить сервис. См. Worktree и submodule. - отсутствует → предупредить и пропустить сервис.
- каталог → продолжить; хуки пишутся в
Симлинки в любом компоненте пути <absHub>/src/.git/{hooks} отвергаются.
Разрешение коллизий: выигрывает глубочайший
Заголовок раздела «Разрешение коллизий: выигрывает глубочайший»Когда более одного выживающего сервиса указывает на один и тот же dir, ровно один выигрывает по тому же правилу «глубочайший extends побеждает», что и в render ide: вычисляется глубина цепочки, выигрывает самый глубокий сервис, лексикографическое имя ломает ничью. Проигравшие сервисы выдают предупреждение, называя победителя.
Обоснование совпадает с IDE: git-хуки отражают вариант, с которым работают прямо сейчас (вариант main-debug может хотеть дополнительные проверки, которые канонический main не делает), и самый специализированный сервис в цепочке владеет отрендеренными хуками общего .git/.
Явный аргумент [service]
Заголовок раздела «Явный аргумент [service]»dwe render git <name> трактует <name> как hub-якорь. Аргумент валидируется по карте сервисов и гейту активации, затем применяется то же «глубочайший выигрывает» разрешение — поэтому dwe render git main может в итоге рендерить из конфигурации main-debug, когда оба делят dir.
Разрешение пакета шаблонов
Заголовок раздела «Разрешение пакета шаблонов»Для каждого выбранного сервиса рендерер выбирает один каталог-пакет под workspace/templates/git/. Цепочка точно совпадает с render ai и render ide, различается только базовый каталог:
- Если задано
render.git.template, пробуется толькоworkspace/templates/git/<render.git.template>/. Отсутствие пакета — жёсткая ошибка. - Иначе пробуется
workspace/templates/git/<service-name>/. Если отсутствует — провалиться дальше. - Иначе обходится цепочка
extends:предок-за-предком —workspace/templates/git/<ancestor>/. Побеждает первый существующий пакет. - Иначе используется
workspace/templates/git/default/. Если отсутствует — пропуск с предупреждением (implicit missing pack).
Символы имени пакета ограничены (^[A-Za-z0-9][A-Za-z0-9_-]*$); небезопасное имя (точка в начале, дефис в начале, разделители путей) молча пропускает этого кандидата, и обход продолжается со следующим предком или default/.
Схема manifest
Заголовок раздела «Схема manifest»Каждый пакет обязан содержать manifest.yml в своём корне по общей схеме:
render: - { from: pre-commit.tmpl, to: pre-commit } - { from: prepare-commit-msg.tmpl, to: prepare-commit-msg } - { from: commit-msg.tmpl, to: commit-msg } - { from: pre-push.tmpl, to: pre-push }symlinks для git-пакетов не используется и должен быть пуст или отсутствовать — git-открытие хуков не следует симлинкам внутри .git/hooks/.
Git-специфичные правила валидации (поверх общих правил формы):
| Правило | Поведение |
|---|---|
to — basename: без разделителей путей, без .., без .-сегментов | жёсткая ошибка |
Список symlinks непустой | жёсткая ошибка |
Каждый from разрешается либо в каноническом пакете, либо в <pack>.local/-оверрайде | жёсткая ошибка, если отсутствует |
Строгий YAML-декод означает, что опечатка в ключе (renders:, symlink:) отвергается на загрузке.
Пофайловый рендер
Заголовок раздела «Пофайловый рендер»Для каждой записи render источник разрешается через общий packroot-resolver (<pack>.local/<rel> → канонический <pack>/<rel>). Рендерер:
- Читает разрешённый файл шаблона.
- Парсит его как Go text/template в строгом режиме — любая ссылка на отсутствующее поле прерывает рендер вместо записи плейсхолдера
<no value>. - Выполняет шаблон по переменным шаблона.
- Отказывается перезаписывать назначение, если оно уже существует как симлинк (симлинкнутый хук на многих платформах git молча игнорирует).
- Пишет отрендеренные байты в
<svc.Dir>/src/.git/hooks/<basename>. - Явно ставит режим файла
0755черезchmod— см. Нормализация режима файла.
Переменные шаблона
Заголовок раздела «Переменные шаблона»Та же форма, что у render ide и render ai:
| Переменная | Источник |
|---|---|
.Project | блок project: из workspace.yml |
.Service | каноническая конфигурационная идентичность — корень цепочки extends: рендерящегося сервиса. Используйте для raw-config поисков по имени сервиса (например, (index .Cfg.Raw.git.hooks .Service)). Равно .Resolved, когда у рендерящегося сервиса нет extends:. |
.Resolved | render-идентичность — имя сервиса, чей hub реально рендерится (победитель коллизии по глубочайшему extends). Равно .Service без коллизии. |
.ServiceCfg | эффективная конфигурация сервиса .Resolved (рендерящегося), после разрешения extends. Поля вроде .ServiceCfg.Container отражают overlay расширителя. |
.Runtime | объединённый блок runtime |
.Cfg | объединённый DweConfig (продвинутое). .Cfg.Raw — мапа конфигурации после слияния и нормализации DWE (services.* подставляется из per-service service.yml) — см. Шаблоны. Для обычных случаев предпочитайте выделенные поля выше. |
Почему
.Serviceи.Resolvedразличаются. Когда два сервиса делят одинdir:(обычно база +extends:-ребёнок типаmainиmain-debug), политика коллизий выбирает самого глубокого расширителя владельцем hub — это и есть.Resolved. Но user-facing секции конфигурации, ключёванные по имени сервиса (git.hooks.<svc>,cs.<svc>, …), по соглашению заполняются только на базе, поэтому raw-config поиски должны использовать.Service(корень цепочки), чтобы найтись. Эти два поля разделяют поведенческую идентичность (к какому контейнеру цепляться, какой overlay действует) и конфигурационную идентичность (где искать пользовательские значения).
Строгий режим рендера означает, что опечатка {{.Servic.Name}} прерывает рендер вместо вывода <no value>. Используйте {{if ...}} для полей, которые могут быть законно пустыми.
Доступ к .Cfg.Raw
Заголовок раздела «Доступ к .Cfg.Raw»Go-овский text/template разрешает dot-сегменты, только если каждый совпадает с [A-Za-z_][A-Za-z0-9_]*. До ключей с дефисами, точками, ведущими цифрами или любыми другими не-идентификаторными символами нельзя добраться dot-синтаксисом — используйте index.
JIRA_PREFIX="{{ .Cfg.Raw.git.project_prefix }}" {{- /* точка — identifier-safe ключи */ -}}TOKEN='{{ index .Cfg.Raw "my-tool" "api-key" }}' {{- /* index — ключи с дефисами */ -}}{{- $hooks := index .Cfg.Raw.git.hooks .Service }}{{ index $hooks "pre_commit" }}Git-хуки рендерятся в <svc.Dir>/src/.git/hooks/ (gitignored), поэтому значения из local.yml в .Cfg.Raw дают вариацию хуков на разработчика, которая не коммитится, — это и есть здесь целевой кейс, в отличие от render ide / render ai, которые пишут отслеживаемые файлы.
Полный набор хелперов, доступных внутри *.tmpl (appURL, sprout-реестры, встроенные text/template), описан в Шаблоны.
Гарды безопасности путей
Заголовок раздела «Гарды безопасности путей»Рендерер применяет ту же цепочку границ, что и IDE/AI, адаптированную под назначение .git/hooks/:
- Hub preflight. Containment
svc.Dirи проверка на отсутствие симлинков до любогоMkdirAll. - Зонд git-каталога.
<absHub>/src/.gitдолжен быть реальным каталогом; любой симлинк-компонент в пути отвергается. toв manifest — basename. Подкаталоги подhooks/не допускаются (git не спускается в подкаталогиhooks/).- Граница реального пути после создания. После
MkdirAll(<hooksDir>)разрешённый реальный путь каталога хуков должен быть внутри и реального корня проекта, и реального.git/. Это ловит предсуществующий симлинкhooks -> /tmp/...в.git/. - Никаких симлинков на месте файла назначения. Предсуществующий симлинк
<hooksDir>/<basename>отвергается, а не следуется.
Нормализация режима файла
Заголовок раздела «Нормализация режима файла»os.WriteFile ставит режим файла только на создании. Чтобы гарантировать, что перерендер существующего хука восстанавливает исполняемые биты, даже если он лёг как 0644 (например, после предыдущего тула или checkout-а на Windows), рендерер на каждом прогоне явно делает chmod 0755 на каждый отрендеренный хук.
Проработанный пример
Заголовок раздела «Проработанный пример»Раскладка:
workspace/services/ main/ service.ymlworkspace/templates/git/ default/ manifest.yml pre-commit.tmpl pre-push.tmplManifest workspace/templates/git/default/manifest.yml:
render: - { from: pre-commit.tmpl, to: pre-commit } - { from: pre-push.tmpl, to: pre-push }Шаблон workspace/templates/git/default/pre-commit.tmpl:
#!/usr/bin/env sh# pre-commit hook for {{.Resolved}} ({{.ServiceCfg.Container}})exec dwe cmd lintworkspace/services/main/service.yml:
type: appcontainer: app-maindir: ./services/main# render.git.enabled defaults to true (type: app)dwe render git:
- Выборка:
mainпроходит гейт активации. Hub preflight успешен. - Разрешение пакета: implicit-цепочка —
workspace/templates/git/main/отсутствует, поэтому используетсяworkspace/templates/git/default/. services/main/src/.git/— каталог → зонд успешен.- Manifest валиден: две записи рендера, без симлинков, basename-значения
to, источники есть. - Каждая запись рендерится в
services/main/src/.git/hooks/с режимом0755.
Результат:
services/main/src/.git/hooks/ pre-commit (0755) pre-push (0755)Если бы services/main/src/.git был файлом (worktree/submodule-указателем) или вовсе отсутствовал, render git выдал бы предупреждение и вышел успешно, ничего не записав.
Сообщения вывода
Заголовок раздела «Сообщения вывода»| Поток | Триггер |
|---|---|
| info | Явный аргумент разрешился в другого сиблинга — называется выбранный победитель и общий hub-каталог. |
| info | Использован <pack>.local/<rel>-override вместо канонического файла. |
| warning | У выбранного сервиса нет каталога src/.git — пропущен. |
| warning | У выбранного сервиса src/.git — worktree/submodule-указатель (файл) — пропущен (см. Worktree и submodule). |
| warning | Выбранный сервис пропущен, потому что другой сервис выиграл коллизию каталога — победитель назван. |
| success | По одной строке на каждый отрендеренный хук, с относительным путём внутри проекта. |
| info | Ничего не выбрано после применения политики и правил коллизии. |
Ошибки возвращаются как падение команды и называют проблемный сервис, чтобы источник проблемы был очевиден.
Worktree и submodule
Заголовок раздела «Worktree и submodule»Когда <svc.Dir>/src/.git — обычный файл, а не каталог, это gitdir:-указатель, используемый git worktree и submodule-ами. Следование за такими указателями требует прочесть файл, разрешить относительный путь к настоящему git-каталогу и заново применить гарды безопасности путей к разрешённой цели. Эта итерация не следует за gitdir:-указателями — сервис пропускается с предупреждением и хуки не рендерятся.
Дальнейший план добавит поддержку worktree. Пока сервисам с worktree-чекаутами нужны хуки, установленные вручную, или направить core.hooksPath на отслеживаемый каталог внутри репозитория.
Частые ловушки
Заголовок раздела «Частые ловушки»- Сервисы не-
appне рендерятся по умолчанию. Явно задайтеservices.<name>.render.git.enabled: true, чтобы opt-in. toв manifest должен быть basename. Хуки живут прямо вhooks/; git туда не спускается.to: subdir/pre-commitотвергается.symlinksдля git не используется. Многие установки git игнорируют симлинкнутые хуки. Перенесите содержимое в отдельную запись рендера.- Выход рендера лежит внутри
.git/. Он никогда не отслеживается. Источник правды — перерендер; не правьтеsrc/.git/hooks/<name>руками и не ждите, что изменения выживут. - Worktree и submodule пропускаются. См. Worktree и submodule.
- Предсуществующий не-исполняемый хук. Перерендер нормализует режим обратно к
0755на каждом прогоне.
Связанные справочники
Заголовок раздела «Связанные справочники»- блок
services.<name>.render.git—enabled,template, наследование черезextends render ide— родственная команда с той же политикой «глубочайший выигрывает»render ai— родственная команда (поверхностнейший выигрывает), разделяющая схему manifest- Обзор render — общая схема manifest и механизм локальных оверрайдов
- Запустите
dwe render git --help, чтобы увидеть актуальный CLI-интерфейс