fix(db-dump-xml): Changes без тихой полной перевыгрузки, инкремент в ibcmd

- -update без -force: при другой версии формата — отказ с подсказкой
  про -Mode Full, а не молчаливая перезапись всего дерева в новом формате.
- Первая выгрузка режимом по умолчанию: в каталог без выгрузки (пусто,
  только .git/README) — полностью; выгрузка без ConfigDumpInfo.xml — ошибка.
- ibcmd: Changes через --sync; Full в непустой каталог — через временный
  каталог и копирование поверх, как пишет конфигуратор; ошибка копирования
  даёт код 1. UpdateInfo с -AllExtensions отвергается, а не выгружает всё.
- py-порт печатает вывод ibcmd, как PS.

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 ce7238792e
commit f98e7e9e81
12 changed files with 514 additions and 18 deletions
@@ -1,4 +1,4 @@
# db-dump-xml v1.22 — Dump 1C configuration to XML files
# db-dump-xml v1.23 — Dump 1C configuration to XML files
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
<#
@@ -8,7 +8,7 @@
.DESCRIPTION
Выполняет выгрузку конфигурации 1С в файлы в четырёх режимах:
- Full: полная выгрузка всей конфигурации
- Changes: инкрементальная выгрузка изменённых объектов
- Changes: инкрементальная выгрузка изменённых объектов (в пустой каталог — полная)
- Partial: выгрузка конкретных объектов из списка
- UpdateInfo: обновление только ConfigDumpInfo.xml
@@ -519,6 +519,32 @@ function Test-DirNonEmpty {
return (Test-Path $Path -PathType Container) -and ([bool](Get-ChildItem -LiteralPath $Path -Force -ErrorAction SilentlyContinue | Select-Object -First 1))
}
function Copy-TreeOver {
# Скопировать дерево поверх каталога: файлы заменяются, лишнее в приёмнике остаётся —
# так пишет полную выгрузку конфигуратор (устаревшие объекты, .git и прочее не трогает).
# Через .NET: исключение на первой же ошибке (а не пропуск файла с продолжением), и без
# накладных расходов командлетов на десятках тысяч файлов. Пустые каталоги не переносятся.
param([string]$From, [string]$To)
$root = [System.IO.Path]::GetFullPath($From).TrimEnd('\')
$dest = (Resolve-Path -LiteralPath $To).ProviderPath
foreach ($f in [System.IO.Directory]::EnumerateFiles($root, '*', [System.IO.SearchOption]::AllDirectories)) {
$dst = [System.IO.Path]::Combine($dest, $f.Substring($root.Length + 1))
[void][System.IO.Directory]::CreateDirectory([System.IO.Path]::GetDirectoryName($dst))
[System.IO.File]::Copy($f, $dst, $true)
}
}
function Write-FormatMismatchHint {
# Отказ инкрементальной выгрузки из-за версии формата: каталог выгружен другой платформой.
# Тексты: 1cv8 — «версия формата платформы отличается от версии формата выгрузки»,
# ibcmd — «Версия формат выгрузки и платформы не совпадают».
param([string]$Text)
if ($Text -match 'верси\S* формат\S* .{0,40}(отличается|не совпадают)') {
Write-Host "[подсказка] Каталог выгружен в другой версии формата. -Mode Full перевыгрузит конфигурацию" -ForegroundColor Yellow
Write-Host " целиком в формате текущей платформы — изменятся все файлы каталога." -ForegroundColor Yellow
}
}
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
# --- Resolve additional arguments for the selected engine ---
@@ -567,6 +593,26 @@ if ($Mode -eq "Partial" -and -not $Objects) {
exit 1
}
# --- Changes: инкремент ведётся от файла версий прошлой выгрузки ---
# Без ConfigDumpInfo.xml платформа инкремент отвергает. Выгрузки в каталоге ещё нет (пусто или
# только .git, README и т.п.) — первая выгрузка, она полная. Выгрузка есть, а файла версий нет —
# не трогаем: полная выгрузка перепишет её целиком, и это решение пользователя. У -AllExtensions
# файлы версий лежат в подкаталогах расширений — их проверяет платформа.
if ($Mode -eq "Changes") {
$firstDump = if ($AllExtensions) { -not (Test-DirNonEmpty $ConfigDir) } else {
-not (Test-Path -LiteralPath (Join-Path $ConfigDir "ConfigDumpInfo.xml")) -and
-not (Test-Path -LiteralPath (Join-Path $ConfigDir "Configuration.xml"))
}
if ($firstDump) {
Write-Host "[note] выгрузки в каталоге ещё нет — первая выгрузка выполняется полностью" -ForegroundColor Yellow
$Mode = "Full"
} elseif (-not $AllExtensions -and -not (Test-Path -LiteralPath (Join-Path $ConfigDir "ConfigDumpInfo.xml"))) {
Write-Host "Error: no ConfigDumpInfo.xml in $ConfigDir — incremental dump (Changes) is impossible" -ForegroundColor Red
Write-Host " -Mode Full выгрузит конфигурацию целиком поверх каталога." -ForegroundColor Yellow
exit 1
}
}
# --- Create output dir if needed ---
if (-not (Test-Path $ConfigDir)) {
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
@@ -584,11 +630,23 @@ try {
Write-Host "Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
exit 1
}
if ($AllExtensions) {
$arguments = @("infobase", "config", "export", "all-extensions", "$ConfigDir", "--db-path=$InfoBasePath")
} elseif ($Mode -eq "UpdateInfo") {
# Полный экспорт ibcmd в непустой каталог отказывает («Каталог не пуст»), а конфигуратор
# пишет поверх. Чтобы движки вели себя одинаково, такой экспорт идёт во временный каталог
# и копируется поверх ConfigDir.
if ($AllExtensions -and $Mode -eq "Changes") {
Write-Host "[note] ibcmd выгружает все расширения только полностью" -ForegroundColor Yellow
}
$overlay = ($Mode -ne "Partial") -and ($AllExtensions -or $Mode -eq "Full") -and (Test-DirNonEmpty $ConfigDir)
$outDir = if ($overlay) { Join-Path $tempDir "export" } else { $ConfigDir }
$dataDir = Join-Path $tempDir "data"
New-Item -ItemType Directory -Path $dataDir -Force | Out-Null
if ($Mode -eq "UpdateInfo") {
# Раньше развилки по -AllExtensions: иначе вместо обновления файла версий ушла бы
# полная выгрузка всех расширений.
Write-Host "Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8" -ForegroundColor Red
exit 1
} elseif ($AllExtensions) {
$arguments = @("infobase", "config", "export", "all-extensions", "$outDir", "--db-path=$InfoBasePath")
} elseif ($Mode -eq "Partial") {
$objList = @($Objects -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
$arguments = @("infobase", "config", "export", "objects") + $objList
@@ -597,26 +655,36 @@ try {
} else {
$arguments = @("infobase", "config", "export", "--db-path=$InfoBasePath")
if ($Extension) { $arguments += "--extension=$Extension" }
$arguments += "$ConfigDir"
# Инкремент — --sync; без --force: при другой версии формата ibcmd честно отказывает.
if ($Mode -eq "Changes") { $arguments += "--sync" }
$arguments += "$outDir"
}
if ($UserName) { $arguments += "--user=$UserName" }
if ($Password) { $arguments += "--password=$Password" }
$arguments += "--data=$tempDir"
$arguments += "--data=$dataDir"
$arguments += $extraArgs
Write-Host "Running: ibcmd $(Protect-Secrets ((Format-ArgsForDisplay $arguments $engine) -join ' ') @($Password, $UserName))"
$__ib = Invoke-PlatformProcess $V8Path $arguments
$output = $__ib.Output
$exitCode = $__ib.ExitCode
$outMissing = ($exitCode -eq 0) -and -not (Test-DirNonEmpty $ConfigDir)
$outMissing = ($exitCode -eq 0) -and -not (Test-DirNonEmpty $outDir)
if ($outMissing) { $exitCode = 1 }
$copyError = $null
if ($exitCode -eq 0 -and $overlay) {
try { Copy-TreeOver $outDir $ConfigDir } catch { $copyError = $_.Exception.Message; $exitCode = 1 }
}
if ($exitCode -eq 0) {
Write-Host "Configuration exported successfully to: $ConfigDir" -ForegroundColor Green
} elseif ($copyError) {
Write-Host "Error: export succeeded, but copying it over $ConfigDir failed: $copyError" -ForegroundColor Red
Write-Host " Каталог мог остаться частично обновлённым — повторите выгрузку." -ForegroundColor Yellow
} elseif ($outMissing) {
Write-Host "Error: exit code 0 but no files under $ConfigDir — configuration was not exported" -ForegroundColor Red
} else {
Write-Host "Error exporting configuration (code: $exitCode)" -ForegroundColor Red
}
Write-PlatformOutput $output
if ($exitCode -ne 0) { Write-FormatMismatchHint $output }
exit $exitCode
}
@@ -647,8 +715,8 @@ try {
}
"Changes" {
Write-Host "Executing incremental configuration dump..."
# Без -force: с ним при другой версии формата платформа молча выгружает всё заново.
$arguments += "-update"
$arguments += "-force"
}
"Partial" {
Write-Host "Executing partial configuration dump..."
@@ -699,6 +767,7 @@ try {
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
}
$logContent = $null
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) {
@@ -708,6 +777,7 @@ try {
}
}
Write-PlatformOutput $__v8.Output
if ($exitCode -ne 0) { Write-FormatMismatchHint "$logContent`n$($__v8.Output)" }
exit $exitCode