From de20a9b228f0ffbf05c20f4f0e7a8bc7c15f89ee Mon Sep 17 00:00:00 2001 From: csh Date: Sat, 13 Dec 2025 09:24:37 +0800 Subject: [PATCH] fix(sync_standards): preserve project gitattributes and clarify tsl/tsf - default to managed .gitattributes block (block/overwrite/skip) - ensure .agents/tsl points to docs/standards/tsl snapshot paths - document expected structure and TSL/TSF constraints --- .agents/code_quality.md | 2 +- .agents/index.md | 19 ++++++-- README.md | 41 ++++++++++++++-- docs/index.md | 2 +- docs/tsl/code_style.md | 2 +- docs/tsl/naming.md | 1 + scripts/sync_standards.bat | 99 +++++++++++++++++++++++++++++++++++--- scripts/sync_standards.ps1 | 75 +++++++++++++++++++++++------ scripts/sync_standards.sh | 87 ++++++++++++++++++++++++++++----- 9 files changed, 282 insertions(+), 46 deletions(-) diff --git a/.agents/code_quality.md b/.agents/code_quality.md index fc3b180..980bfdf 100644 --- a/.agents/code_quality.md +++ b/.agents/code_quality.md @@ -4,7 +4,7 @@ ## 1. 总体要求 -- 遵守本标准快照中的 `docs/tsl/code_style.md` 与 `docs/tsl/naming.md`(通常位于目标项目的 `docs/standards/tsl/docs/tsl/`)。 +- 对 `.tsl`/`.tsf` 文件一律按 TSL 规范处理(`.tsf` 也是 TSL 源文件):遵守标准快照中的 `docs/tsl/code_style.md` 与 `docs/tsl/naming.md`(在目标项目中应 vendoring 到 `docs/standards/tsl/docs/tsl/`)。 - 改动聚焦目标;避免“顺手重构”。 - API 变更要显式说明影响与迁移方式。 diff --git a/.agents/index.md b/.agents/index.md index ddeb09a..55d5f53 100644 --- a/.agents/index.md +++ b/.agents/index.md @@ -35,17 +35,26 @@ ## 分类(本仓库现状) -当前本规则集下的文件全部为 **跨语言通用规则**(不绑定具体语言语法/工具链): +当前本规则集下的文件以 **跨语言通用规则** 为主(不绑定具体语言语法/工具链),但包含 TSL 代码在仓库里最容易踩坑的“语言级硬约束”,以避免代理在缺少上下文时写出不符合 TSL/TSF 约定的代码。 - `auth.md`:敏感信息/鉴权边界 - `code_quality.md`:质量底线与 review 清单 - `performance.md`:性能原则与验证 - `testing.md`:测试策略 -若需要 TSL/C++ 等语言专属的代理要求,建议在目标项目新增对应目录(例如 `.agents/tsl/`、`.agents/cpp/`)或在源码子目录放置更具体的 `.agents` 覆盖规则。 +若项目需要更细的语言/模块专属代理要求,建议在更靠近源码的目录放置更具体的规则(例如 `src/.agents/`),或并行新增其他规则集(例如 `.agents/cpp/`)。 + +## TSL/TSF 必要约定(必须遵守) + +- `.tsl` 与 `.tsf` 都是 Tinysoft Language 源文件;修改它们时统一按 TSL 规范处理(不要把 `.tsf` 当成“另一种语言/无风格约束的脚本”)。 +- 文件级约束:一个文件只能有一个顶层声明,且文件基名必须与该顶层声明同名(推荐 `PascalCase`);`.tsl` 顶层声明只能是 `function`。 +- 格式:空格缩进(默认 4 空格),关键字用小写,复杂分支/多语句分支用 `begin/end` 块表达结构。 +- 命名:类型/顶层函数/property 用 `PascalCase`;局部变量/参数用 `snake_case`;私有成员变量用 `snake_case_`。 ## 与开发规范的关系 -- 代码风格:标准快照 `docs/tsl/code_style.md`(通常位于目标项目 `docs/standards/tsl/docs/tsl/code_style.md`) -- 命名规范:标准快照 `docs/tsl/naming.md`(通常位于目标项目 `docs/standards/tsl/docs/tsl/naming.md`) -- 提交信息:标准快照 `docs/common/commit_message.md`(通常位于目标项目 `docs/standards/tsl/docs/common/commit_message.md`) +- 在本仓库内:`docs/tsl/` 与 `docs/common/`。 +- 在目标项目内:标准快照应 vendoring 到 `docs/standards/tsl/`,对应路径为: + - 代码风格:`docs/standards/tsl/docs/tsl/code_style.md` + - 命名规范:`docs/standards/tsl/docs/tsl/naming.md` + - 提交信息:`docs/standards/tsl/docs/common/commit_message.md` diff --git a/README.md b/README.md index 7a2ead2..8115635 100644 --- a/README.md +++ b/README.md @@ -85,17 +85,26 @@ TSL Playbook:Tinysoft Language(`.tsl` / `.tsf`)工程规范与代理规则 根目录的 `.agents/tsl/` 与 `.gitattributes` 通过同步脚本获得: - - 直接运行 Playbook 提供的脚本(子树快照里自带): + - 说明:在 **本 playbook 仓库** 内脚本位于 `scripts/`;在 **目标项目** 里通过 `git subtree` 引入到 `docs/standards/tsl/` 后,脚本路径变为 `docs/standards/tsl/scripts/`。 + - 在目标项目里直接运行 Playbook 提供的脚本(子树快照里自带): - `docs/standards/tsl/scripts/sync_standards.sh`(推荐) - `docs/standards/tsl/scripts/sync_standards.ps1`(推荐) - `docs/standards/tsl/scripts/sync_standards.bat`(推荐) - 脚本会从快照目录同步到项目根目录,并先备份旧文件(`.bak.*`)。 - 注:`docs/standards/tsl/` 只是推荐目录名;你可以用任意 `--prefix`(例如 `docs/standards/tsl_playbook/`)。同步脚本会从脚本自身路径推导快照根目录,不再依赖目录名。 + 注:建议固定使用 `--prefix docs/standards/tsl`,因为同步后的 `.agents/tsl/` 会引用该路径下的标准快照文档(`docs/standards/tsl/docs/...`)。 注:默认同步到 `.agents/tsl/`;如需指定规则集名称,可通过环境变量 `AGENTS_NS`(例如 `AGENTS_NS=tsl`、`AGENTS_NS=common`)。 这样 clone 任意项目时都能直接读取规范文件,不依赖外部访问权限。 + 同步脚本行为(目标项目内的最终落地内容): + + - 覆盖/更新:`.agents//`(默认 `.agents/tsl/`) + - 更新 `.gitattributes`:默认只维护 `# BEGIN tsl-playbook .gitattributes` 区块(可用 `SYNC_GITATTR_MODE=overwrite|block|skip` 控制) + - 缺省创建:`.agents/index.md` + - 覆盖前备份:写入同目录的 `*.bak.*`(或 Windows 下随机后缀) + - 不修改:`.gitignore`(项目自行维护) + ### 方式二:手动复制快照 如果不使用 `git subtree`,也可以由有权限的人手动复制 Playbook 到目标项目中(适合规范不频繁更新或项目数量较少的情况)。 @@ -126,13 +135,39 @@ TSL Playbook:Tinysoft Language(`.tsl` / `.tsf`)工程规范与代理规则 建议:仓库级规则尽量少且稳定;语言级规则各自独立,避免互相“污染”。 -本仓库提供的代理规则集(同步后位于目标项目的 `.agents/tsl/`)当前全部为**跨语言通用规则**: +本仓库提供的代理规则集(同步后位于目标项目的 `.agents/tsl/`)以**跨语言通用规则**为主,但包含 TSL/TSF 文件(`.tsl`/`.tsf`)的必要约定(避免代理在缺少上下文时写出不符合 TSL 约束的代码): - `auth.md`:敏感信息/鉴权边界 - `code_quality.md`:质量底线与 review 清单 - `performance.md`:性能原则与验证 - `testing.md`:测试策略 +多语言项目推荐结构(示例:TSL + C++ + Python): + +```txt +. +├── .agents/ +│ ├── index.md # 多语言索引(缺省时由脚本创建) +│ ├── tsl/ # 由本 Playbook 同步(适用于 .tsl/.tsf) +│ ├── cpp/ # C++ 规则集(来自另一个 playbook 或项目自建) +│ └── python/ # Python 规则集(同上) +├── .gitattributes # 行尾/文本规范(可由某个 playbook 同步) +├── docs/ +│ ├── standards/ +│ │ ├── tsl/ # 本 Playbook 快照(git subtree/vendoring) +│ │ ├── cpp/ # C++ playbook 快照(可选) +│ │ └── python/ # Python playbook 快照(可选) +│ └── project/ # 项目自有文档(架构、ADR、运行方式等) +├── scripts/ +│ └── sync_standards.sh # 项目包装脚本:依次调用各 playbook 的 sync +└── src/ # 源码目录(按项目实际情况) +``` + +规则优先级建议: + +- 同一项目内多个规则集并行放在 `.agents//`,不要互相覆盖。 +- 若某个子目录需要更具体规则(模块/子系统差异),在更靠近代码的目录放置更具体规则(例如 `src/foo/.agents/`),并以“离代码更近者优先”为准。 + #### `.agents` 的覆盖/合并策略(可执行流程) 同步脚本会同步到项目根目录的 `.agents/tsl/`(并不会覆盖 `.agents/` 下的其他语言目录)。若项目需要追加 C++ 等语言/模块专属规则,建议二选一: diff --git a/docs/index.md b/docs/index.md index dccaf2c..3101a0b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,6 +8,6 @@ ## TSL(tsl) +- TSL 源文件后缀同时包含:`.tsl`(脚本)与 `.tsf`(模块/库代码)。 - 代码风格:`tsl/code_style.md` - 命名规范:`tsl/naming.md` - diff --git a/docs/tsl/code_style.md b/docs/tsl/code_style.md index 674ad4e..e800b09 100644 --- a/docs/tsl/code_style.md +++ b/docs/tsl/code_style.md @@ -5,7 +5,7 @@ ## 1. 文件与组织 - 一个文件只做一件事;职责明确。 -- 文件名使用 `PascalCase`,并与文件内唯一的顶层声明同名(语法要求)。扩展名按类型使用 `.tsl`/`.tsf`。 +- 文件名使用 `PascalCase`,并与文件内唯一的顶层声明同名(语法要求)。扩展名按类型使用 `.tsl`/`.tsf`(两者都属于 TSL 源文件,风格规则一致)。 - 避免循环依赖;公共能力下沉到可复用模块。 - 同类代码按“对外 API → 核心实现 → 辅助工具 → 测试/示例”的顺序组织。 diff --git a/docs/tsl/naming.md b/docs/tsl/naming.md index 4a12df8..e9e5093 100644 --- a/docs/tsl/naming.md +++ b/docs/tsl/naming.md @@ -40,6 +40,7 @@ TSL 的语法要求:每个文件只能有一个顶层声明,且**文件基 - 顶层声明可能是 `class`、`unit` 或 `function`(见类型命名)。 - `.tsl` 脚本文件:顶层声明只能是 `function`,因此文件基名 = 顶层函数名。 - `.tsf` 代码文件:顶层声明可为 `class`/`unit`/`function`,文件基名需与之同名。 + - 注:`.tsf` 也是 TSL 源文件,命名/风格与 `.tsl` 遵循同一套规则。 命名建议: diff --git a/scripts/sync_standards.bat b/scripts/sync_standards.bat index 7eb4eae..e8b53fc 100644 --- a/scripts/sync_standards.bat +++ b/scripts/sync_standards.bat @@ -3,7 +3,7 @@ setlocal enabledelayedexpansion rem Sync standards snapshot to project root. rem - Copies \.agents -> \.agents\tsl -rem - Copies \.gitattributes -> \.gitattributes +rem - Updates \.gitattributes (managed block by default) rem Existing targets are backed up before overwrite. set "SCRIPT_DIR=%~dp0" @@ -16,9 +16,19 @@ set "AGENTS_SRC=%SRC%\.agents" set "GITATTR_SRC=%SRC%\.gitattributes" set "AGENTS_NS=%AGENTS_NS%" if "%AGENTS_NS%"=="" set "AGENTS_NS=tsl" +echo %AGENTS_NS%| findstr /r "[\\/]" >nul && ( + echo ERROR: invalid AGENTS_NS=%AGENTS_NS% + exit /b 1 +) +echo %AGENTS_NS%| findstr /c:".." >nul && ( + echo ERROR: invalid AGENTS_NS=%AGENTS_NS% + exit /b 1 +) set "AGENTS_ROOT=%ROOT%\.agents" set "AGENTS_DST=%AGENTS_ROOT%\%AGENTS_NS%" set "GITATTR_DST=%ROOT%\.gitattributes" +set "SYNC_GITATTR_MODE=%SYNC_GITATTR_MODE%" +if "%SYNC_GITATTR_MODE%"=="" set "SYNC_GITATTR_MODE=block" if not exist "%AGENTS_SRC%" ( echo ERROR: Standards snapshot not found at "%AGENTS_SRC%". @@ -56,30 +66,103 @@ if not exist "%AGENTS_ROOT%\index.md" ( >> "%AGENTS_ROOT%\index.md" echo. >> "%AGENTS_ROOT%\index.md" echo 建议约定: >> "%AGENTS_ROOT%\index.md" echo. - >> "%AGENTS_ROOT%\index.md" echo - `.agents/tsl/`:TSL 相关标准(由 `sync_standards.*` 同步) + >> "%AGENTS_ROOT%\index.md" echo - `.agents/tsl/`:TSL 相关规则集(由 `sync_standards.*` 同步;适用于 `.tsl`/`.tsf`) >> "%AGENTS_ROOT%\index.md" echo - `.agents/cpp/`、`.agents/python/` 等:其他语言的规则集(按需增加) >> "%AGENTS_ROOT%\index.md" echo. >> "%AGENTS_ROOT%\index.md" echo 规则发生冲突时,建议以“更靠近代码的目录规则更具体”为准。 + >> "%AGENTS_ROOT%\index.md" echo. + >> "%AGENTS_ROOT%\index.md" echo 入口建议从: + >> "%AGENTS_ROOT%\index.md" echo. + >> "%AGENTS_ROOT%\index.md" echo - `.agents/tsl/index.md`(TSL 规则集入口) + >> "%AGENTS_ROOT%\index.md" echo - `docs/standards/tsl/docs/tsl/`(TSL 人类开发规范快照,推荐 subtree/vendoring 到该路径) echo Created .agents\index.md ) :SyncGitAttr if exist "%GITATTR_SRC%" ( - for %%I in ("%GITATTR_SRC%") do set "GITATTR_SRC_F=%%~fI" - for %%I in ("%GITATTR_DST%") do set "GITATTR_DST_F=%%~fI" - if /I "!GITATTR_SRC_F!"=="!GITATTR_DST_F!" ( - echo Skip: .gitattributes source equals destination. + if /I "%SYNC_GITATTR_MODE%"=="skip" ( + echo Skip: .gitattributes sync ^(SYNC_GITATTR_MODE=skip^). goto AfterGitAttr ) + if /I "%SYNC_GITATTR_MODE%"=="overwrite" ( + for %%I in ("%GITATTR_SRC%") do set "GITATTR_SRC_F=%%~fI" + for %%I in ("%GITATTR_DST%") do set "GITATTR_DST_F=%%~fI" + if /I "!GITATTR_SRC_F!"=="!GITATTR_DST_F!" ( + echo Skip: .gitattributes source equals destination. + goto AfterGitAttr + ) + + if exist "%GITATTR_DST%" ( + set "RAND=%RANDOM%" + set "BAK_NAME=.gitattributes.bak.!RAND!" + ren "%GITATTR_DST%" "!BAK_NAME!" + echo Backed up existing .gitattributes -> !BAK_NAME! + ) + copy /y "%GITATTR_SRC%" "%GITATTR_DST%" >nul + echo Synced .gitattributes from standards ^(overwrite^). + goto AfterGitAttr + ) + + if /I not "%SYNC_GITATTR_MODE%"=="block" ( + echo ERROR: invalid SYNC_GITATTR_MODE=%SYNC_GITATTR_MODE% ^(use block^|overwrite^|skip^) + exit /b 1 + ) + + rem block mode: maintain a managed block inside the destination file + set "BEGIN=# BEGIN tsl-playbook .gitattributes" + set "END=# END tsl-playbook .gitattributes" + set "TMP_FILE=%TEMP%\\gitattributes.%RANDOM%.tmp" + if exist "%GITATTR_DST%" ( set "RAND=%RANDOM%" set "BAK_NAME=.gitattributes.bak.!RAND!" ren "%GITATTR_DST%" "!BAK_NAME!" echo Backed up existing .gitattributes -> !BAK_NAME! + set "DST_IN=%ROOT%\\!BAK_NAME!" + ) else ( + set "DST_IN=" ) - copy /y "%GITATTR_SRC%" "%GITATTR_DST%" >nul - echo Synced .gitattributes from standards. + + set "IN_BLOCK=0" + set "DONE=0" + + if not "%DST_IN%"=="" ( + > "!TMP_FILE!" ( + for /f "usebackq delims=" %%L in ("!DST_IN!") do ( + set "LINE=%%L" + if "!LINE!"=="%BEGIN%" ( + if "!DONE!"=="0" ( + echo %BEGIN% + type "%GITATTR_SRC%" + echo %END% + set "DONE=1" + ) + set "IN_BLOCK=1" + ) else if "!LINE!"=="%END%" ( + set "IN_BLOCK=0" + ) else ( + if "!IN_BLOCK!"=="0" echo(!LINE! + ) + ) + if "!DONE!"=="0" ( + echo. + echo %BEGIN% + type "%GITATTR_SRC%" + echo %END% + ) + ) + ) else ( + > "!TMP_FILE!" ( + echo %BEGIN% + type "%GITATTR_SRC%" + echo %END% + ) + ) + + copy /y "!TMP_FILE!" "%GITATTR_DST%" >nul + del /q "!TMP_FILE!" >nul 2>nul + echo Updated .gitattributes from standards ^(managed block^). ) :AfterGitAttr diff --git a/scripts/sync_standards.ps1 b/scripts/sync_standards.ps1 index 2aecefc..ffd72da 100644 --- a/scripts/sync_standards.ps1 +++ b/scripts/sync_standards.ps1 @@ -1,6 +1,6 @@ # Sync standards snapshot to project root. # - Copies /.agents -> /.agents/tsl -# - Copies /.gitattributes -> /.gitattributes +# - Updates /.gitattributes (managed block by default) # Existing targets are backed up before overwrite. $ErrorActionPreference = "Stop" @@ -48,35 +48,80 @@ Write-Host "Synced .agents/$AgentsNs from standards." $AgentsIndex = Join-Path $AgentsRoot "index.md" if (-not (Test-Path $AgentsIndex)) { -@" +@' # .agents(多语言) 本目录用于存放仓库级/语言级的代理规则集。 建议约定: -- `.agents/tsl/`:TSL 相关标准(由 `sync_standards.*` 同步) +- `.agents/tsl/`:TSL 相关规则集(由 `sync_standards.*` 同步;适用于 `.tsl`/`.tsf`) - `.agents/cpp/`、`.agents/python/` 等:其他语言的规则集(按需增加) 规则发生冲突时,建议以“更靠近代码的目录规则更具体”为准。 -"@ | Set-Content -Path $AgentsIndex -Encoding UTF8 + +入口建议从: + +- `.agents/tsl/index.md`(TSL 规则集入口) +- `docs/standards/tsl/docs/tsl/`(TSL 人类开发规范快照,推荐 subtree/vendoring 到该路径) +'@ | Set-Content -Path $AgentsIndex -Encoding UTF8 Write-Host "Created .agents/index.md" } $GitAttrDst = Join-Path $Root ".gitattributes" if (Test-Path $GitAttrSrc) { - if ($GitAttrSrc -ieq $GitAttrDst) { - Write-Host "Skip: .gitattributes source equals destination." - Write-Host "Done." - exit 0 + $mode = $env:SYNC_GITATTR_MODE + if (-not $mode) { $mode = "block" } + switch ($mode.ToLowerInvariant()) { + "skip" { + Write-Host "Skip: .gitattributes sync (SYNC_GITATTR_MODE=skip)." + break + } + "overwrite" { + if ($GitAttrSrc -ieq $GitAttrDst) { + Write-Host "Skip: .gitattributes source equals destination." + break + } + if (Test-Path $GitAttrDst) { + $bak = "$GitAttrDst.bak.$timestamp" + Move-Item $GitAttrDst $bak + Write-Host "Backed up existing .gitattributes -> $bak" + } + Copy-Item $GitAttrSrc $GitAttrDst -Force + Write-Host "Synced .gitattributes from standards (overwrite)." + break + } + "block" { + $begin = "# BEGIN tsl-playbook .gitattributes" + $end = "# END tsl-playbook .gitattributes" + $src = Get-Content -Path $GitAttrSrc -Raw + $block = $begin + "`r`n" + $src.TrimEnd() + "`r`n" + $end + "`r`n" + + $dst = "" + if (Test-Path $GitAttrDst) { + $bak = "$GitAttrDst.bak.$timestamp" + Move-Item $GitAttrDst $bak + Write-Host "Backed up existing .gitattributes -> $bak" + $dst = Get-Content -Path $bak -Raw + } + + $pattern = "(?ms)^" + [regex]::Escape($begin) + "\\R.*?^" + [regex]::Escape($end) + "\\R?" + if ($dst -and ($dst -match $pattern)) { + $new = [regex]::Replace($dst, $pattern, $block) + } elseif ($dst) { + $new = $dst.TrimEnd() + "`r`n`r`n" + $block + } else { + $new = $block + } + + $new | Set-Content -Path $GitAttrDst -Encoding UTF8 + Write-Host "Updated .gitattributes from standards (managed block)." + break + } + default { + throw "Invalid SYNC_GITATTR_MODE=$mode (use block|overwrite|skip)" + } } - if (Test-Path $GitAttrDst) { - $bak = "$GitAttrDst.bak.$timestamp" - Move-Item $GitAttrDst $bak - Write-Host "Backed up existing .gitattributes -> $bak" - } - Copy-Item $GitAttrSrc $GitAttrDst -Force - Write-Host "Synced .gitattributes from standards." } Write-Host "Done." diff --git a/scripts/sync_standards.sh b/scripts/sync_standards.sh index 95294f2..fd16178 100644 --- a/scripts/sync_standards.sh +++ b/scripts/sync_standards.sh @@ -3,7 +3,7 @@ set -eu # Sync standards snapshot to project root. # - Copies /.agents -> /.agents/tsl -# - Copies /.gitattributes -> /.gitattributes +# - Updates /.gitattributes (managed block by default) # Existing targets are backed up before overwrite. SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)" @@ -56,10 +56,15 @@ if [ ! -f "$AGENTS_INDEX" ]; then 建议约定: -- `.agents/tsl/`:TSL 相关标准(由 `sync_standards.*` 同步) +- `.agents/tsl/`:TSL 相关规则集(由 `sync_standards.*` 同步;适用于 `.tsl`/`.tsf`) - `.agents/cpp/`、`.agents/python/` 等:其他语言的规则集(按需增加) 规则发生冲突时,建议以“更靠近代码的目录规则更具体”为准。 + +入口建议从: + +- `.agents/tsl/index.md`(TSL 规则集入口) +- `docs/standards/tsl/docs/tsl/`(TSL 人类开发规范快照,推荐 subtree/vendoring 到该路径) EOF echo "Created .agents/index.md" fi @@ -68,16 +73,74 @@ echo "Synced agents ruleset to $AGENTS_DST." GITATTR_DST="$ROOT/.gitattributes" if [ -f "$GITATTR_SRC" ]; then - if [ "$(CDPATH= cd -- "$(dirname -- "$GITATTR_SRC")" && pwd -P)/$(basename -- "$GITATTR_SRC")" = "$GITATTR_DST" ]; then - echo "Skip: .gitattributes source equals destination." - else - if [ -e "$GITATTR_DST" ]; then - mv "$GITATTR_DST" "$ROOT/.gitattributes.bak.$timestamp" - echo "Backed up existing .gitattributes -> .gitattributes.bak.$timestamp" - fi - cp "$GITATTR_SRC" "$GITATTR_DST" - echo "Synced .gitattributes from standards." - fi + : "${SYNC_GITATTR_MODE:=block}" + case "$SYNC_GITATTR_MODE" in + skip) + echo "Skip: .gitattributes sync (SYNC_GITATTR_MODE=skip)." + ;; + overwrite) + if [ "$(CDPATH= cd -- "$(dirname -- "$GITATTR_SRC")" && pwd -P)/$(basename -- "$GITATTR_SRC")" = "$GITATTR_DST" ]; then + echo "Skip: .gitattributes source equals destination." + else + if [ -e "$GITATTR_DST" ]; then + mv "$GITATTR_DST" "$ROOT/.gitattributes.bak.$timestamp" + echo "Backed up existing .gitattributes -> .gitattributes.bak.$timestamp" + fi + cp "$GITATTR_SRC" "$GITATTR_DST" + echo "Synced .gitattributes from standards (overwrite)." + fi + ;; + block) + begin="# BEGIN tsl-playbook .gitattributes" + end="# END tsl-playbook .gitattributes" + + if [ -e "$GITATTR_DST" ]; then + mv "$GITATTR_DST" "$ROOT/.gitattributes.bak.$timestamp" + echo "Backed up existing .gitattributes -> .gitattributes.bak.$timestamp" + fi + + tmp="${GITATTR_DST}.tmp.${timestamp}" + if [ -f "$ROOT/.gitattributes.bak.$timestamp" ]; then + src_dst="$ROOT/.gitattributes.bak.$timestamp" + else + src_dst="" + fi + + if [ -n "$src_dst" ]; then + awk -v begin="$begin" -v end="$end" -v src="$GITATTR_SRC" ' + function emit_src() { + print begin + while ((getline line < src) > 0) print line + close(src) + print end + } + BEGIN { in_block=0; done=0 } + $0 == begin { in_block=1; if (!done) { emit_src(); done=1 } ; next } + $0 == end { in_block=0; next } + !in_block { print } + END { + if (!done) { + if (NR > 0) print "" + emit_src() + } + } + ' "$src_dst" >"$tmp" + else + { + printf "%s\n" "$begin" + cat "$GITATTR_SRC" + printf "\n%s\n" "$end" + } >"$tmp" + fi + + mv "$tmp" "$GITATTR_DST" + echo "Updated .gitattributes from standards (managed block)." + ;; + *) + echo "ERROR: invalid SYNC_GITATTR_MODE=$SYNC_GITATTR_MODE (use block|overwrite|skip)" >&2 + exit 1 + ;; + esac fi echo "Done."