fix(db-load-git): предупреждать об удалениях, которые загрузка частями не применит

Удалённый объект уходит из базы, только если в списке его владелец и в
составе владельца объекта уже нет; удалённый файл-часть (модуль, Ext/)
загрузка частями не удаляет никогда. Скрипт классифицирует удаления:
применимые — [note], остальные — [ВНИМАНИЕ] с причиной; если загружать
нечего, а неприменимые удаления есть — код 1. git diff с --no-renames,
чтобы старый путь переименования был виден. -AllExtensions отвергается
на обоих движках: частями грузится одно расширение за раз.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANXwbMUhuRFSCsq2RgmTTm
This commit is contained in:
Nick Shirokov
2026-09-24 18:08:20 +03:00
co-authored by Claude Opus 5.5
parent f98e7e9e81
commit 80618f0b93
11 changed files with 385 additions and 31 deletions
@@ -0,0 +1,27 @@
{
"name": "Файлы формы удалены, а владелец всё ещё держит её в составе: удаление не применится",
"preRun": [
{ "writeFile": { "path": "src/Configuration.xml", "content": "<MetaDataObject>\n\t<Configuration>\n\t\t<ChildObjects>\n\t\t\t<Catalog>Товары</Catalog>\n\t\t</ChildObjects>\n\t</Configuration>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары.xml", "content": "<MetaDataObject>\n\t<Catalog>\n\t\t<ChildObjects>\n\t\t\t<Form>ФормаЭлемента</Form>\n\t\t</ChildObjects>\n\t</Catalog>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары/Forms/ФормаЭлемента.xml", "content": "<MetaDataObject/>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары/Forms/ФормаЭлемента/Ext/Form.xml", "content": "<Form/>\n" } },
{ "git": ["-C", "src", "init", "-q"] },
{ "git": ["-C", "src", "add", "-A"] },
{ "git": ["-C", "src", "commit", "-qm", "base"] },
{ "writeFile": { "path": "src/Catalogs/Товары.xml", "content": "<MetaDataObject>\n\t<Catalog>\n\t\t<Comment>правка</Comment>\n\t\t<ChildObjects>\n\t\t\t<Form>ФормаЭлемента</Form>\n\t\t</ChildObjects>\n\t</Catalog>\n</MetaDataObject>\n" } },
{ "deletePath": "src/Catalogs/Товары/Forms" },
{ "git": ["-C", "src", "add", "-A"] }
],
"args_extra": ["-ConfigDir", "{workDir}/src", "-Source", "Staged", "-DryRun"],
"expect": {
"stdoutContains": [
"Catalogs/Товары/Forms/ФормаЭлемента.xml: Catalogs/Товары.xml всё ещё содержит объект в составе",
" Catalogs/Товары.xml"
],
"stdoutNotContains": [
"[note] удаление применится",
"Ext/Form.xml:"
]
},
"noSnapshot": "сухой прогон ничего не пишет — проверяется вердикт по удалению"
}
@@ -0,0 +1,25 @@
{
"name": "Форма удалена вместе с правкой владельца: удаление применится через владельца",
"preRun": [
{ "writeFile": { "path": "src/Configuration.xml", "content": "<MetaDataObject>\n\t<Configuration>\n\t\t<ChildObjects>\n\t\t\t<Catalog>Товары</Catalog>\n\t\t</ChildObjects>\n\t</Configuration>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары.xml", "content": "<MetaDataObject>\n\t<Catalog>\n\t\t<ChildObjects>\n\t\t\t<Form>ФормаЭлемента</Form>\n\t\t</ChildObjects>\n\t</Catalog>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары/Forms/ФормаЭлемента.xml", "content": "<MetaDataObject/>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары/Forms/ФормаЭлемента/Ext/Form.xml", "content": "<Form/>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары/Forms/ФормаЭлемента/Ext/Form/Module.bsl", "content": "\n" } },
{ "git": ["-C", "src", "init", "-q"] },
{ "git": ["-C", "src", "add", "-A"] },
{ "git": ["-C", "src", "commit", "-qm", "base"] },
{ "writeFile": { "path": "src/Catalogs/Товары.xml", "content": "<MetaDataObject>\n\t<Catalog>\n\t\t<ChildObjects>\n\t\t\t<Attribute>\n\t\t\t\t<Properties>\n\t\t\t\t\t<Name>ФормаЭлемента</Name>\n\t\t\t\t</Properties>\n\t\t\t</Attribute>\n\t\t</ChildObjects>\n\t</Catalog>\n</MetaDataObject>\n" } },
{ "deletePath": "src/Catalogs/Товары/Forms" },
{ "git": ["-C", "src", "add", "-A"] }
],
"args_extra": ["-ConfigDir", "{workDir}/src", "-Source", "Staged", "-DryRun"],
"expect": {
"stdoutContains": [
"[note] удаление применится через Catalogs/Товары.xml: Catalogs/Товары/Forms/ФормаЭлемента.xml",
" Catalogs/Товары.xml"
],
"stdoutNotContains": "[ВНИМАНИЕ] Удаления"
},
"noSnapshot": "сухой прогон ничего не пишет — проверяется вердикт по удалению"
}
@@ -0,0 +1,22 @@
{
"name": "Удалённый модуль объекта: загрузка частями его не удалит — [ВНИМАНИЕ]",
"preRun": [
{ "writeFile": { "path": "src/Configuration.xml", "content": "<MetaDataObject>\n\t<Configuration>\n\t\t<ChildObjects>\n\t\t\t<Catalog>Товары</Catalog>\n\t\t</ChildObjects>\n\t</Configuration>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары.xml", "content": "<MetaDataObject>\n\t<Catalog>\n\t\t<ChildObjects/>\n\t</Catalog>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары/Ext/ObjectModule.bsl", "content": "Процедура А() КонецПроцедуры\n" } },
{ "git": ["-C", "src", "init", "-q"] },
{ "git": ["-C", "src", "add", "-A"] },
{ "git": ["-C", "src", "commit", "-qm", "base"] },
{ "deletePath": "src/Catalogs/Товары/Ext/ObjectModule.bsl" },
{ "git": ["-C", "src", "add", "-A"] }
],
"args_extra": ["-ConfigDir", "{workDir}/src", "-Source", "Staged", "-DryRun"],
"expect": {
"stdoutContains": [
"[ВНИМАНИЕ] Удаления, которые загрузка частями НЕ применит — в базе они останутся:",
"Catalogs/Товары/Ext/ObjectModule.bsl: часть объекта — загрузка частями файлы не удаляет",
"DryRun mode - no changes applied"
]
},
"noSnapshot": "сухой прогон ничего не пишет — проверяется вердикт по удалению"
}
@@ -0,0 +1,20 @@
{
"name": "Удалены файлы вне конфигурации (README, docs): не удаления объектов, выход без ошибки",
"preRun": [
{ "writeFile": { "path": "src/Configuration.xml", "content": "<MetaDataObject>\n\t<Configuration>\n\t\t<ChildObjects/>\n\t</Configuration>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/README.md", "content": "readme\n" } },
{ "writeFile": { "path": "src/docs/notes.xml", "content": "<notes/>\n" } },
{ "git": ["-C", "src", "init", "-q"] },
{ "git": ["-C", "src", "add", "-A"] },
{ "git": ["-C", "src", "commit", "-qm", "base"] },
{ "deletePath": "src/README.md" },
{ "deletePath": "src/docs" },
{ "git": ["-C", "src", "add", "-A"] }
],
"args_extra": ["-ConfigDir", "{workDir}/src", "-Source", "Staged", "-DryRun"],
"expect": {
"stdoutContains": "No configuration files found in changes",
"stdoutNotContains": "[ВНИМАНИЕ] Удаления"
},
"noSnapshot": "сухой прогон ничего не пишет — проверяется, что посторонние удаления не оцениваются"
}
@@ -0,0 +1,20 @@
{
"name": "Удалён документ, а справочник с тем же именем остался: удаление применится (состав сверяется по типу)",
"preRun": [
{ "writeFile": { "path": "src/Configuration.xml", "content": "<MetaDataObject>\n\t<Configuration>\n\t\t<ChildObjects>\n\t\t\t<Catalog>Товары</Catalog>\n\t\t\t<Document>Товары</Document>\n\t\t</ChildObjects>\n\t</Configuration>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары.xml", "content": "<MetaDataObject/>\n" } },
{ "writeFile": { "path": "src/Documents/Товары.xml", "content": "<MetaDataObject/>\n" } },
{ "git": ["-C", "src", "init", "-q"] },
{ "git": ["-C", "src", "add", "-A"] },
{ "git": ["-C", "src", "commit", "-qm", "base"] },
{ "writeFile": { "path": "src/Configuration.xml", "content": "<MetaDataObject>\n\t<Configuration>\n\t\t<ChildObjects>\n\t\t\t<Catalog>Товары</Catalog>\n\t\t</ChildObjects>\n\t</Configuration>\n</MetaDataObject>\n" } },
{ "deletePath": "src/Documents" },
{ "git": ["-C", "src", "add", "-A"] }
],
"args_extra": ["-ConfigDir", "{workDir}/src", "-Source", "Staged", "-DryRun"],
"expect": {
"stdoutContains": "[note] удаление применится через Configuration.xml: Documents/Товары.xml",
"stdoutNotContains": "[ВНИМАНИЕ] Удаления"
},
"noSnapshot": "сухой прогон ничего не пишет — проверяется вердикт по удалению"
}
@@ -0,0 +1,11 @@
{
"name": "Ошибка: -AllExtensions — загрузка частями идёт по одному расширению",
"args_extra": ["-ConfigDir", ".", "-Source", "Staged", "-DryRun", "-AllExtensions"],
"expectError": true,
"expect": {
"stdoutContains": [
"-AllExtensions cannot be combined with a partial load",
"Загружайте расширения по одному: -Extension <имя>"
]
}
}
@@ -0,0 +1,25 @@
{
"name": "Ошибка: в изменениях только удаление без правки состава — загружать нечего",
"preRun": [
{ "writeFile": { "path": "src/Configuration.xml", "content": "<MetaDataObject>\n\t<Configuration>\n\t\t<ChildObjects>\n\t\t\t<Catalog>Товары</Catalog>\n\t\t</ChildObjects>\n\t</Configuration>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары.xml", "content": "<MetaDataObject>\n\t<Catalog>\n\t\t<ChildObjects/>\n\t</Catalog>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Товары/Ext/ObjectModule.bsl", "content": "\n" } },
{ "git": ["-C", "src", "init", "-q"] },
{ "git": ["-C", "src", "add", "-A"] },
{ "git": ["-C", "src", "commit", "-qm", "base"] },
{ "deletePath": "src/Catalogs" },
{ "git": ["-C", "src", "add", "-A"] }
],
"args_extra": ["-ConfigDir", "{workDir}/src", "-Source", "Staged", "-DryRun"],
"expectError": true,
"expect": {
"stdoutContains": [
"Catalogs/Товары.xml: владелец Configuration.xml не изменён — состав в базе прежний",
"Error: changes found, but none of them can be applied by a partial load (see above)"
],
"stdoutNotContains": [
"No configuration files found in changes",
"ObjectModule.bsl:"
]
}
}
@@ -0,0 +1,19 @@
{
"name": "Переименование объекта: старый путь — удаление, о нём предупреждаем (git не прячет его за rename)",
"preRun": [
{ "writeFile": { "path": "src/Configuration.xml", "content": "<MetaDataObject>\n\t<Configuration>\n\t\t<ChildObjects>\n\t\t\t<Catalog>Старое</Catalog>\n\t\t</ChildObjects>\n\t</Configuration>\n</MetaDataObject>\n" } },
{ "writeFile": { "path": "src/Catalogs/Старое.xml", "content": "<MetaDataObject>\n\t<Catalog>\n\t\t<ChildObjects/>\n\t</Catalog>\n</MetaDataObject>\n" } },
{ "git": ["-C", "src", "init", "-q"] },
{ "git": ["-C", "src", "add", "-A"] },
{ "git": ["-C", "src", "commit", "-qm", "base"] },
{ "git": ["-C", "src", "mv", "Catalogs/Старое.xml", "Catalogs/Новое.xml"] }
],
"args_extra": ["-ConfigDir", "{workDir}/src", "-Source", "Staged", "-DryRun"],
"expect": {
"stdoutContains": [
"Catalogs/Старое.xml: владелец Configuration.xml не изменён — состав в базе прежний",
" Catalogs/Новое.xml"
]
},
"noSnapshot": "сухой прогон ничего не пишет — проверяется вердикт по старому пути"
}