mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-25 03:35:52 +03:00
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:
co-authored by
Claude Opus 5.5
parent
f98e7e9e81
commit
80618f0b93
@@ -15,6 +15,8 @@ allowed-tools:
|
||||
|
||||
> Если в изменениях есть `Configuration.xml`, платформа выполнит **полную загрузку конфигурации**, а не только изменённых объектов.
|
||||
|
||||
> Удалённый объект уйдёт из базы, только если удалён и из состава владельца (`ChildObjects` объекта или `Configuration.xml`). Удалённый файл-часть объекта (модуль, файлы `Ext/`) загрузкой частями не удаляется: скрипт перечислит такие удаления в `[ВНИМАНИЕ]`, а если кроме них загружать нечего, завершится ошибкой. Применить их можно только полной загрузкой (`/db-load-xml -Mode Full`); чтобы убрать код модуля, достаточно оставить файл пустым.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
@@ -58,7 +60,6 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" <
|
||||
| `-CommitRange <range>` | для Commit | Диапазон коммитов (напр. `HEAD~3..HEAD`) |
|
||||
| `-Extension <имя>` | нет | Загрузить в расширение |
|
||||
| `-NoApplyCheck` | нет | Не проверять применимость расширения после загрузки |
|
||||
| `-AllExtensions` | нет | Загрузить все расширения |
|
||||
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
||||
| `-DryRun` | нет | Только показать что будет загружено (без загрузки) |
|
||||
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# db-load-git v1.28 — Load Git changes into 1C database
|
||||
# db-load-git v1.29 — Load Git changes into 1C database
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||
<#
|
||||
@@ -40,7 +40,7 @@
|
||||
Имя расширения для загрузки
|
||||
|
||||
.PARAMETER AllExtensions
|
||||
Загрузить все расширения
|
||||
Не поддерживается: загрузка частями идёт по одному расширению (-Extension)
|
||||
|
||||
.PARAMETER Format
|
||||
Формат файлов: Hierarchical или Plain (по умолчанию Hierarchical)
|
||||
@@ -425,6 +425,73 @@ function Get-ObjectXmlFromSubFile {
|
||||
return $null
|
||||
}
|
||||
|
||||
# Каталоги выгрузки объектов верхнего уровня → элемент в <ChildObjects> конфигурации.
|
||||
$script:TypeDirTag = @{
|
||||
"Languages"="Language"; "Subsystems"="Subsystem"; "StyleItems"="StyleItem"; "Styles"="Style"
|
||||
"CommonPictures"="CommonPicture"; "SessionParameters"="SessionParameter"; "Roles"="Role"; "CommonTemplates"="CommonTemplate"
|
||||
"FilterCriteria"="FilterCriterion"; "CommonModules"="CommonModule"; "Bots"="Bot"; "PaletteColors"="PaletteColor"; "CommonAttributes"="CommonAttribute"; "ExchangePlans"="ExchangePlan"
|
||||
"XDTOPackages"="XDTOPackage"; "WebServices"="WebService"; "HTTPServices"="HTTPService"; "WSReferences"="WSReference"
|
||||
"EventSubscriptions"="EventSubscription"; "ScheduledJobs"="ScheduledJob"; "SettingsStorages"="SettingsStorage"; "FunctionalOptions"="FunctionalOption"
|
||||
"FunctionalOptionsParameters"="FunctionalOptionsParameter"; "DefinedTypes"="DefinedType"; "CommonCommands"="CommonCommand"; "CommandGroups"="CommandGroup"
|
||||
"Constants"="Constant"; "CommonForms"="CommonForm"; "Catalogs"="Catalog"; "Documents"="Document"
|
||||
"DocumentNumerators"="DocumentNumerator"; "Sequences"="Sequence"; "DocumentJournals"="DocumentJournal"; "Enums"="Enum"
|
||||
"Reports"="Report"; "DataProcessors"="DataProcessor"; "InformationRegisters"="InformationRegister"; "AccumulationRegisters"="AccumulationRegister"
|
||||
"ChartsOfCharacteristicTypes"="ChartOfCharacteristicTypes"; "ChartsOfAccounts"="ChartOfAccounts"; "AccountingRegisters"="AccountingRegister"
|
||||
"ChartsOfCalculationTypes"="ChartOfCalculationTypes"; "CalculationRegisters"="CalculationRegister"
|
||||
"BusinessProcesses"="BusinessProcess"; "Tasks"="Task"; "ExternalDataSources"="ExternalDataSource"; "IntegrationServices"="IntegrationService"
|
||||
}
|
||||
|
||||
function Get-DeletionVerdicts {
|
||||
# Что загрузка частями сделает с путями, удалёнными в git. Удалённый ОБЪЕКТ (его XML-описание)
|
||||
# исчезнет из базы, только если в списке его владелец и в составе владельца (ChildObjects)
|
||||
# объекта уже нет. Удалённую ЧАСТЬ объекта (модуль, файлы Ext/) загрузка частями не удаляет
|
||||
# никогда — платформа молча пропускает отсутствующий файл, даже перечисленный явно.
|
||||
# Пути вне каталогов объектов (README, docs/…) — не конфигурация, их удаление не оценивается.
|
||||
# Возвращает вердикт по каждому удалению: @{ Path; Applied; Reason }.
|
||||
param([string[]]$Deleted, [string[]]$Loaded, [string]$Root, [string]$Format)
|
||||
$result = @()
|
||||
$ours = @($Deleted | Where-Object { $script:TypeDirTag.ContainsKey(($_ -split '/')[0]) -and ($_ -split '/').Count -ge 2 })
|
||||
if ($Format -eq "Plain") {
|
||||
foreach ($d in $ours) { $result += @{ Path = $d; Applied = $false; Reason = "формат Plain — удаление загрузкой частями не применяется" } }
|
||||
return $result
|
||||
}
|
||||
foreach ($d in $ours) {
|
||||
$segs = $d -split '/'
|
||||
$extIdx = [array]::IndexOf($segs, 'Ext')
|
||||
if ($extIdx -lt 0 -and $d -match '\.xml$' -and ($segs.Count -eq 2 -or $segs.Count -ge 4)) {
|
||||
# Описание объекта: владелец — Configuration.xml или описание объекта двумя уровнями выше.
|
||||
# Элемент состава — по каталогу: Catalogs → Catalog, Forms → Form.
|
||||
if ($segs.Count -eq 2) {
|
||||
$owner = "Configuration.xml"
|
||||
$tag = $script:TypeDirTag[$segs[0]]
|
||||
} else {
|
||||
$owner = ($segs[0..($segs.Count - 3)] -join '/') + ".xml"
|
||||
$tag = $segs[-2] -replace 's$', ''
|
||||
}
|
||||
if ($Deleted -contains $owner) { continue }
|
||||
$name = [System.IO.Path]::GetFileNameWithoutExtension($segs[-1])
|
||||
if ($Loaded -notcontains $owner) {
|
||||
$result += @{ Path = $d; Applied = $false; Reason = "владелец $owner не изменён — состав в базе прежний" }
|
||||
continue
|
||||
}
|
||||
$ownerText = [System.IO.File]::ReadAllText((Join-Path $Root $owner))
|
||||
$m = [regex]::Match($ownerText, '(?s)<ChildObjects>.*</ChildObjects>')
|
||||
$pattern = '(?m)^\s*<' + $tag + '>' + [regex]::Escape($name) + '</' + $tag + '>\s*$'
|
||||
if ($m.Success -and [regex]::IsMatch($m.Value, $pattern)) {
|
||||
$result += @{ Path = $d; Applied = $false; Reason = "$owner всё ещё содержит объект в составе" }
|
||||
} else {
|
||||
$result += @{ Path = $d; Applied = $true; Reason = "через $owner" }
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ($extIdx -lt 0) { continue }
|
||||
# Часть объекта: сам объект — сегменты до Ext. Удалён вместе с объектом — судьба объекта.
|
||||
if ($Deleted -contains (($segs[0..($extIdx - 1)] -join '/') + ".xml")) { continue }
|
||||
$result += @{ Path = $d; Applied = $false; Reason = "часть объекта — загрузка частями файлы не удаляет (пустой файл вместо удаления очистит модуль)" }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
# --- Resolve V8Path (skip if DryRun) ---
|
||||
if (-not $DryRun) {
|
||||
function Find-ProjectV8Path {
|
||||
@@ -677,6 +744,14 @@ if ($Source -eq "Commit" -and -not $CommitRange) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- -AllExtensions: загрузка частями идёт по одному расширению ---
|
||||
# Конфигуратор сочетание -AllExtensions с -listFile отвергает сам, ibcmd его не поддерживает.
|
||||
if ($AllExtensions) {
|
||||
Write-Host "Error: -AllExtensions cannot be combined with a partial load" -ForegroundColor Red
|
||||
Write-Host " Загружайте расширения по одному: -Extension <имя>, -ConfigDir — каталог этого расширения." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Check git ---
|
||||
try {
|
||||
$null = git --version 2>&1
|
||||
@@ -688,6 +763,8 @@ try {
|
||||
# --- Get changed files from Git ---
|
||||
# Все git-вызовы для сбора путей идут через один хелпер с -c core.quotePath=false,
|
||||
# иначе кириллические пути возвращаются в octal-виде и не распознаются (зеркало run_git в .py).
|
||||
# diff — с --no-renames: при переименовании иначе виден только новый путь, а старый — это
|
||||
# удаление, о котором надо предупредить.
|
||||
function Invoke-GitLines {
|
||||
param([string[]]$GitArgs)
|
||||
$out = git -c core.quotePath=false @GitArgs 2>&1
|
||||
@@ -704,21 +781,21 @@ try {
|
||||
switch ($Source) {
|
||||
"Staged" {
|
||||
Write-Host "Getting staged changes..."
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--cached', '--name-only', '--relative')
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--cached', '--name-only', '--no-renames', '--relative')
|
||||
}
|
||||
"Unstaged" {
|
||||
Write-Host "Getting unstaged changes..."
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--name-only', '--relative')
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--name-only', '--no-renames', '--relative')
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('ls-files', '--others', '--exclude-standard')
|
||||
}
|
||||
"Commit" {
|
||||
Write-Host "Getting changes from $CommitRange..."
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--name-only', '--relative', $CommitRange)
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--name-only', '--no-renames', '--relative', $CommitRange)
|
||||
}
|
||||
"All" {
|
||||
Write-Host "Getting all uncommitted changes..."
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--cached', '--name-only', '--relative')
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--name-only', '--relative')
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--cached', '--name-only', '--no-renames', '--relative')
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('diff', '--name-only', '--no-renames', '--relative')
|
||||
$changedFiles += Invoke-GitLines -GitArgs @('ls-files', '--others', '--exclude-standard')
|
||||
}
|
||||
}
|
||||
@@ -738,6 +815,7 @@ Write-Host "Git changes detected: $($changedFiles.Count) files"
|
||||
# --- Filter and map to config files ---
|
||||
$configFiles = @()
|
||||
$supportSkipped = @()
|
||||
$deletedFiles = @()
|
||||
|
||||
foreach ($file in $changedFiles) {
|
||||
$file = $file.Trim().Replace('\', '/')
|
||||
@@ -749,6 +827,7 @@ foreach ($file in $changedFiles) {
|
||||
if ($file -eq "ConfigDumpInfo.xml" -or $file -match '(^|/)ConfigDumpInfo\.xml$') { continue }
|
||||
|
||||
$fullPath = Join-Path $ConfigDir $file
|
||||
if (-not (Test-Path -LiteralPath $fullPath)) { $deletedFiles += $file }
|
||||
|
||||
if ($file -match '\.xml$') {
|
||||
# XML file — add directly if exists
|
||||
@@ -795,7 +874,20 @@ if ($supportSkipped.Count -gt 0) {
|
||||
Write-Host " Смена состояния поддержки применяется только полной загрузкой (db-load-xml -Mode Full)." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$verdicts = @(Get-DeletionVerdicts -Deleted $deletedFiles -Loaded $configFiles -Root $ConfigDir -Format $Format)
|
||||
$unapplied = @($verdicts | Where-Object { -not $_.Applied })
|
||||
foreach ($v in @($verdicts | Where-Object { $_.Applied })) { Write-Host "[note] удаление применится $($v.Reason): $($v.Path)" }
|
||||
if ($unapplied.Count -gt 0) {
|
||||
Write-Host "[ВНИМАНИЕ] Удаления, которые загрузка частями НЕ применит — в базе они останутся:" -ForegroundColor Yellow
|
||||
foreach ($u in $unapplied) { Write-Host " - $($u.Path): $($u.Reason)" -ForegroundColor Yellow }
|
||||
Write-Host " Применить их можно полной загрузкой (db-load-xml -Mode Full)." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if ($configFiles.Count -eq 0) {
|
||||
if ($unapplied.Count -gt 0) {
|
||||
Write-Host "Error: changes found, but none of them can be applied by a partial load (see above)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host "No configuration files found in changes"
|
||||
exit 0
|
||||
}
|
||||
@@ -829,10 +921,6 @@ try {
|
||||
Write-Host "Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
if ($AllExtensions) {
|
||||
Write-Host "Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$arguments = @("infobase", "config", "import", "files") + $configFiles
|
||||
$arguments += "--base-dir=$ConfigDir", "--db-path=$InfoBasePath"
|
||||
if ($Extension) { $arguments += "--extension=$Extension" }
|
||||
@@ -867,7 +955,7 @@ try {
|
||||
Write-Host "Error updating database configuration (code: $exitCode)$(Get-ExitAnnotation $exitCode)" -ForegroundColor Red
|
||||
}
|
||||
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
|
||||
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
|
||||
if ($exitCode -eq 0 -and $Extension -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
|
||||
$acConn = @("/F", "`"$InfoBasePath`"")
|
||||
if ($UserName) { $acConn += "/N`"$UserName`"" }
|
||||
if ($Password) { $acConn += "/P`"$Password`"" }
|
||||
@@ -913,8 +1001,6 @@ try {
|
||||
# --- Extensions ---
|
||||
if ($Extension) {
|
||||
$arguments += "-Extension", "`"$Extension`""
|
||||
} elseif ($AllExtensions) {
|
||||
$arguments += "-AllExtensions"
|
||||
}
|
||||
|
||||
# --- UpdateDB ---
|
||||
@@ -967,7 +1053,7 @@ try {
|
||||
}
|
||||
|
||||
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
|
||||
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
|
||||
if ($exitCode -eq 0 -and $Extension -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
|
||||
if ((Invoke-ApplyCheckReport $V8Path $connArgs $Extension $extraArgs) -and $StrictLog) { $exitCode = 1 }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# db-load-git v1.28 — Load Git changes into 1C database
|
||||
# db-load-git v1.29 — Load Git changes into 1C database
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -619,8 +619,87 @@ def get_object_xml_from_subfile(relative_path):
|
||||
return None
|
||||
|
||||
|
||||
# Каталоги выгрузки объектов верхнего уровня → элемент в <ChildObjects> конфигурации.
|
||||
TYPE_DIR_TAG = {
|
||||
"Languages": "Language", "Subsystems": "Subsystem", "StyleItems": "StyleItem", "Styles": "Style",
|
||||
"CommonPictures": "CommonPicture", "SessionParameters": "SessionParameter", "Roles": "Role",
|
||||
"CommonTemplates": "CommonTemplate", "FilterCriteria": "FilterCriterion", "CommonModules": "CommonModule",
|
||||
"Bots": "Bot", "PaletteColors": "PaletteColor", "CommonAttributes": "CommonAttribute",
|
||||
"ExchangePlans": "ExchangePlan", "XDTOPackages": "XDTOPackage", "WebServices": "WebService",
|
||||
"HTTPServices": "HTTPService", "WSReferences": "WSReference", "EventSubscriptions": "EventSubscription",
|
||||
"ScheduledJobs": "ScheduledJob", "SettingsStorages": "SettingsStorage", "FunctionalOptions": "FunctionalOption",
|
||||
"FunctionalOptionsParameters": "FunctionalOptionsParameter", "DefinedTypes": "DefinedType",
|
||||
"CommonCommands": "CommonCommand", "CommandGroups": "CommandGroup", "Constants": "Constant",
|
||||
"CommonForms": "CommonForm", "Catalogs": "Catalog", "Documents": "Document",
|
||||
"DocumentNumerators": "DocumentNumerator", "Sequences": "Sequence", "DocumentJournals": "DocumentJournal",
|
||||
"Enums": "Enum", "Reports": "Report", "DataProcessors": "DataProcessor",
|
||||
"InformationRegisters": "InformationRegister", "AccumulationRegisters": "AccumulationRegister",
|
||||
"ChartsOfCharacteristicTypes": "ChartOfCharacteristicTypes", "ChartsOfAccounts": "ChartOfAccounts",
|
||||
"AccountingRegisters": "AccountingRegister", "ChartsOfCalculationTypes": "ChartOfCalculationTypes",
|
||||
"CalculationRegisters": "CalculationRegister", "BusinessProcesses": "BusinessProcess", "Tasks": "Task",
|
||||
"ExternalDataSources": "ExternalDataSource", "IntegrationServices": "IntegrationService",
|
||||
}
|
||||
_TYPE_DIR_TAG_CI = {k.lower(): v for k, v in TYPE_DIR_TAG.items()}
|
||||
|
||||
|
||||
def get_deletion_verdicts(deleted, loaded, root, fmt):
|
||||
"""Что загрузка частями сделает с путями, удалёнными в git. Удалённый ОБЪЕКТ (его XML-описание)
|
||||
исчезнет из базы, только если в списке его владелец и в составе владельца (ChildObjects)
|
||||
объекта уже нет. Удалённую ЧАСТЬ объекта (модуль, файлы Ext/) загрузка частями не удаляет
|
||||
никогда — платформа молча пропускает отсутствующий файл, даже перечисленный явно.
|
||||
Пути вне каталогов объектов (README, docs/…) — не конфигурация, их удаление не оценивается.
|
||||
Возвращает вердикт по каждому удалению: {path, applied, reason}."""
|
||||
ours = [d for d in deleted if len(d.split("/")) >= 2 and d.split("/")[0].lower() in _TYPE_DIR_TAG_CI]
|
||||
if fmt == "Plain":
|
||||
return [{"path": d, "applied": False,
|
||||
"reason": "формат Plain — удаление загрузкой частями не применяется"} for d in ours]
|
||||
deleted_set = {d.lower() for d in deleted}
|
||||
loaded_set = {x.lower() for x in loaded}
|
||||
result = []
|
||||
for d in ours:
|
||||
segs = d.split("/")
|
||||
ext_idx = segs.index("Ext") if "Ext" in segs else -1
|
||||
if ext_idx < 0 and d.lower().endswith(".xml") and (len(segs) == 2 or len(segs) >= 4):
|
||||
# Описание объекта: владелец — Configuration.xml или описание объекта двумя уровнями выше.
|
||||
# Элемент состава — по каталогу: Catalogs → Catalog, Forms → Form.
|
||||
if len(segs) == 2:
|
||||
owner = "Configuration.xml"
|
||||
tag = _TYPE_DIR_TAG_CI[segs[0].lower()]
|
||||
else:
|
||||
owner = "/".join(segs[:-2]) + ".xml"
|
||||
tag = re.sub(r"s$", "", segs[-2])
|
||||
if owner.lower() in deleted_set:
|
||||
continue
|
||||
name = os.path.splitext(segs[-1])[0]
|
||||
if owner.lower() not in loaded_set:
|
||||
result.append({"path": d, "applied": False,
|
||||
"reason": f"владелец {owner} не изменён — состав в базе прежний"})
|
||||
continue
|
||||
with open(os.path.join(root, owner), encoding="utf-8-sig") as f:
|
||||
owner_text = f.read()
|
||||
m = re.search(r"<ChildObjects>.*</ChildObjects>", owner_text, re.S)
|
||||
pattern = r"(?m)^\s*<" + tag + ">" + re.escape(name) + "</" + tag + r">\s*$"
|
||||
if m and re.search(pattern, m.group(0)):
|
||||
result.append({"path": d, "applied": False,
|
||||
"reason": f"{owner} всё ещё содержит объект в составе"})
|
||||
else:
|
||||
result.append({"path": d, "applied": True, "reason": f"через {owner}"})
|
||||
continue
|
||||
if ext_idx < 0:
|
||||
continue
|
||||
# Часть объекта: сам объект — сегменты до Ext. Удалён вместе с объектом — судьба объекта.
|
||||
if ("/".join(segs[:ext_idx]) + ".xml").lower() in deleted_set:
|
||||
continue
|
||||
result.append({"path": d, "applied": False,
|
||||
"reason": "часть объекта — загрузка частями файлы не удаляет "
|
||||
"(пустой файл вместо удаления очистит модуль)"})
|
||||
return result
|
||||
|
||||
|
||||
def run_git(config_dir, git_args):
|
||||
"""Run a git command in config_dir and return output lines on success."""
|
||||
"""Run a git command in config_dir and return output lines on success.
|
||||
diff вызывается с --no-renames: при переименовании иначе виден только новый путь, а старый —
|
||||
это удаление, о котором надо предупредить."""
|
||||
result = subprocess.run(
|
||||
["git", "-c", "core.quotePath=false"] + git_args,
|
||||
capture_output=True,
|
||||
@@ -691,7 +770,7 @@ def main():
|
||||
)
|
||||
parser.add_argument("-CommitRange", default="", help="Commit range (for Source=Commit), e.g. HEAD~3..HEAD")
|
||||
parser.add_argument("-Extension", default="", help="Extension name to load")
|
||||
parser.add_argument("-AllExtensions", action="store_true", help="Load all extensions")
|
||||
parser.add_argument("-AllExtensions", action="store_true", help="Not supported: partial load goes one extension at a time")
|
||||
parser.add_argument(
|
||||
"-Format",
|
||||
default="Hierarchical",
|
||||
@@ -758,6 +837,13 @@ def main():
|
||||
print("Error: -CommitRange required for Source=Commit")
|
||||
sys.exit(1)
|
||||
|
||||
# --- -AllExtensions: загрузка частями идёт по одному расширению ---
|
||||
# Конфигуратор сочетание -AllExtensions с -listFile отвергает сам, ibcmd его не поддерживает.
|
||||
if args.AllExtensions:
|
||||
print("Error: -AllExtensions cannot be combined with a partial load")
|
||||
print(" Загружайте расширения по одному: -Extension <имя>, -ConfigDir — каталог этого расширения.")
|
||||
sys.exit(1)
|
||||
|
||||
# --- Check git ---
|
||||
try:
|
||||
subprocess.run(["git", "--version"], capture_output=True, text=True, check=True)
|
||||
@@ -770,18 +856,18 @@ def main():
|
||||
|
||||
if args.Source == "Staged":
|
||||
print("Getting staged changes...")
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--cached", "--name-only", "--relative"])
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--cached", "--name-only", "--no-renames", "--relative"])
|
||||
elif args.Source == "Unstaged":
|
||||
print("Getting unstaged changes...")
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--name-only", "--relative"])
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--name-only", "--no-renames", "--relative"])
|
||||
changed_files += run_git(args.ConfigDir, ["ls-files", "--others", "--exclude-standard"])
|
||||
elif args.Source == "Commit":
|
||||
print(f"Getting changes from {args.CommitRange}...")
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--name-only", "--relative", args.CommitRange])
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--name-only", "--no-renames", "--relative", args.CommitRange])
|
||||
elif args.Source == "All":
|
||||
print("Getting all uncommitted changes...")
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--cached", "--name-only", "--relative"])
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--name-only", "--relative"])
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--cached", "--name-only", "--no-renames", "--relative"])
|
||||
changed_files += run_git(args.ConfigDir, ["diff", "--name-only", "--no-renames", "--relative"])
|
||||
changed_files += run_git(args.ConfigDir, ["ls-files", "--others", "--exclude-standard"])
|
||||
|
||||
# Deduplicate and filter blanks
|
||||
@@ -796,6 +882,7 @@ def main():
|
||||
# --- Filter and map to config files ---
|
||||
config_files = []
|
||||
support_skipped = []
|
||||
deleted_files = []
|
||||
|
||||
for file in changed_files:
|
||||
file = file.strip().replace("\\", "/")
|
||||
@@ -811,6 +898,8 @@ def main():
|
||||
continue
|
||||
|
||||
full_path = os.path.join(args.ConfigDir, file)
|
||||
if not os.path.exists(full_path):
|
||||
deleted_files.append(file)
|
||||
|
||||
if file.endswith(".xml"):
|
||||
# XML file — add directly if exists
|
||||
@@ -846,7 +935,21 @@ def main():
|
||||
print(f" - {sf}")
|
||||
print(" Смена состояния поддержки применяется только полной загрузкой (db-load-xml -Mode Full).")
|
||||
|
||||
verdicts = get_deletion_verdicts(deleted_files, config_files, args.ConfigDir, args.Format)
|
||||
unapplied = [v for v in verdicts if not v["applied"]]
|
||||
for v in verdicts:
|
||||
if v["applied"]:
|
||||
print(f"[note] удаление применится {v['reason']}: {v['path']}")
|
||||
if unapplied:
|
||||
print("[ВНИМАНИЕ] Удаления, которые загрузка частями НЕ применит — в базе они останутся:")
|
||||
for u in unapplied:
|
||||
print(f" - {u['path']}: {u['reason']}")
|
||||
print(" Применить их можно полной загрузкой (db-load-xml -Mode Full).")
|
||||
|
||||
if len(config_files) == 0:
|
||||
if unapplied:
|
||||
print("Error: changes found, but none of them can be applied by a partial load (see above)")
|
||||
sys.exit(1)
|
||||
print("No configuration files found in changes")
|
||||
sys.exit(0)
|
||||
|
||||
@@ -877,9 +980,6 @@ def main():
|
||||
if args.Format == "Plain":
|
||||
print("Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)")
|
||||
sys.exit(1)
|
||||
if args.AllExtensions:
|
||||
print("Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)")
|
||||
sys.exit(1)
|
||||
arguments = ["infobase", "config", "import", "files"] + config_files
|
||||
arguments += [f"--base-dir={args.ConfigDir}", f"--db-path={args.InfoBasePath}"]
|
||||
if args.Extension:
|
||||
@@ -916,7 +1016,7 @@ def main():
|
||||
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}")
|
||||
print_platform_output(ar)
|
||||
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
|
||||
if (exit_code == 0 and (args.Extension or args.AllExtensions)
|
||||
if (exit_code == 0 and args.Extension
|
||||
and apply_check_enabled(args.NoApplyCheck)):
|
||||
ac_conn = ["/F", f'"{args.InfoBasePath}"']
|
||||
if args.UserName:
|
||||
@@ -963,8 +1063,6 @@ def main():
|
||||
# --- Extensions ---
|
||||
if args.Extension:
|
||||
arguments += ["-Extension", f'"{args.Extension}"']
|
||||
elif args.AllExtensions:
|
||||
arguments.append("-AllExtensions")
|
||||
|
||||
# --- UpdateDB ---
|
||||
if args.UpdateDB:
|
||||
@@ -1021,7 +1119,7 @@ def main():
|
||||
exit_code = 1
|
||||
|
||||
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
|
||||
if (exit_code == 0 and (args.Extension or args.AllExtensions)
|
||||
if (exit_code == 0 and args.Extension
|
||||
and apply_check_enabled(args.NoApplyCheck)):
|
||||
if apply_check_report(v8path, conn_args, args.Extension, extra_args) and args.StrictLog:
|
||||
exit_code = 1
|
||||
|
||||
@@ -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": "сухой прогон ничего не пишет — проверяется вердикт по старому пути"
|
||||
}
|
||||
Reference in New Issue
Block a user