feat(sync_standards): default gitattributes to append

This commit is contained in:
csh 2026-01-08 12:17:00 +08:00
parent da0ef2b7a2
commit e97fb00649
6 changed files with 237 additions and 21 deletions

View File

@ -153,13 +153,12 @@ git commit -m ":package: deps(playbook): add tsl standards"
- 规则入口可读:`.agents/tsl/index.md`
- 可选C++ 规则入口可读:`.agents/cpp/index.md`
- 标准文档可读:`docs/standards/playbook/docs/index.md`
- `.gitattributes` 包含区块:`# BEGIN playbook .gitattributes` /
`# END playbook .gitattributes`
- `.gitattributes` 包含追加块头:`# Added from playbook .gitattributes`
4. 将同步产物纳入版本控制(目标项目建议提交):
- `docs/standards/playbook/`(标准快照)
- `.agents/tsl/`(落地规则集)
- `.gitattributes`managed block 更新
- `.gitattributes`追加缺失规则
- `AGENTS.md`(若本次自动生成)
#### 新项目 / 旧项目(命令示例)
@ -240,8 +239,8 @@ sh docs/standards/playbook/scripts/sync_standards.sh tsl cpp
同步脚本行为(目标项目内的最终落地内容):
- 覆盖/更新:`.agents/<AGENTS_NS>/`(默认 `.agents/tsl/`
- 更新 `.gitattributes`:默认只维护 `# BEGIN playbook .gitattributes` 区块(可用
`SYNC_GITATTR_MODE=block|overwrite|skip` 控制)
- 更新 `.gitattributes`:默认追加缺失规则(可用
`SYNC_GITATTR_MODE=append|block|overwrite|skip` 控制)
- 缺省创建:`.agents/index.md`
- 覆盖前备份:写入同目录的 `*.bak.*`(或 Windows 下随机后缀)
- 不修改:`.gitignore`(项目自行维护)
@ -256,7 +255,7 @@ sh docs/standards/playbook/scripts/sync_standards.sh tsl cpp
| 变量名 | 默认值 | 说明 |
| ------------------- | ------- | ------------------------------------------------------------------------------------------- |
| `AGENTS_NS` | `tsl` | 同步的规则集名/落地目录名:`.agents/<AGENTS_NS>/`(例如 `tsl`、`cpp` |
| `SYNC_GITATTR_MODE` | `block` | `.gitattributes` 同步模式:`block` 仅维护 managed 区块;`overwrite` 全量覆盖;`skip` 不更新 |
| `SYNC_GITATTR_MODE` | `append` | `.gitattributes` 同步模式:`append` 仅追加缺失规则(忽略注释/空行,比对后按块追加);`block` 仅维护 managed 区块;`overwrite` 全量覆盖;`skip` 不更新 |
</details>

View File

@ -3,7 +3,7 @@ setlocal enabledelayedexpansion
rem Sync standards snapshot to project root.
rem - Copies <snapshot>\.agents\<AGENTS_NS> -> <project-root>\.agents\<AGENTS_NS>
rem - Updates <project-root>\.gitattributes (managed block by default)
rem - Updates <project-root>\.gitattributes (append missing rules by default)
rem Existing targets are backed up before overwrite.
rem
rem Multi rulesets:
@ -34,7 +34,7 @@ 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 "%SYNC_GITATTR_MODE%"=="" set "SYNC_GITATTR_MODE=append"
rem Multi rulesets: only on outer invocation.
if "%SYNC_STANDARDS_INNER%"=="" (
@ -155,8 +155,84 @@ if exist "%GITATTR_SRC%" (
goto AfterGitAttr
)
if /I "%SYNC_GITATTR_MODE%"=="append" (
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
)
set "TMP_DST=%TEMP%\\gitattributes.dst.%RANDOM%.tmp"
set "TMP_MISS=%TEMP%\\gitattributes.missing.%RANDOM%.tmp"
if exist "!TMP_DST!" del /q "!TMP_DST!" >nul 2>nul
if exist "!TMP_MISS!" del /q "!TMP_MISS!" >nul 2>nul
type nul > "!TMP_DST!"
type nul > "!TMP_MISS!"
if exist "%GITATTR_DST%" (
for /f "usebackq delims=" %%L in ("%GITATTR_DST%") do (
set "LINE=%%L"
for /f "tokens=* delims= " %%A in ("!LINE!") do set "LINE=%%A"
if not "!LINE!"=="" (
if /I not "!LINE:~0,1!"=="#" (
echo(!LINE!>>"!TMP_DST!"
)
)
)
)
for /f "usebackq delims=" %%L in ("%GITATTR_SRC%") do (
set "LINE=%%L"
for /f "tokens=* delims= " %%A in ("!LINE!") do set "LINE=%%A"
if not "!LINE!"=="" (
if /I not "!LINE:~0,1!"=="#" (
findstr /x /l /c:"!LINE!" "!TMP_DST!" >nul || (
findstr /x /l /c:"!LINE!" "!TMP_MISS!" >nul || echo(!LINE!>>"!TMP_MISS!"
)
)
)
)
set "MISS_SIZE=0"
if exist "!TMP_MISS!" for %%S in ("!TMP_MISS!") do set "MISS_SIZE=%%~zS"
if "!MISS_SIZE!"=="0" (
del /q "!TMP_DST!" "!TMP_MISS!" >nul 2>nul
echo No missing .gitattributes rules to append.
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!
set "DST_IN=%ROOT%\\!BAK_NAME!"
) else (
set "DST_IN="
)
set "TMP_OUT=%TEMP%\\gitattributes.out.%RANDOM%.tmp"
if exist "!TMP_OUT!" del /q "!TMP_OUT!" >nul 2>nul
if not "!DST_IN!"=="" (
type "!DST_IN!" > "!TMP_OUT!"
for %%S in ("!DST_IN!") do set "DST_SIZE=%%~zS"
if not "!DST_SIZE!"=="0" echo.>>"!TMP_OUT!"
)
set "SOURCE_NOTE=%GITATTR_SRC%"
>>"!TMP_OUT!" echo # Added from playbook .gitattributes ^(source: !SOURCE_NOTE!^)
type "!TMP_MISS!" >> "!TMP_OUT!"
copy /y "!TMP_OUT!" "%GITATTR_DST%" >nul
del /q "!TMP_DST!" "!TMP_MISS!" "!TMP_OUT!" >nul 2>nul
echo Appended missing .gitattributes rules from standards.
goto AfterGitAttr
)
if /I not "%SYNC_GITATTR_MODE%"=="block" (
echo ERROR: invalid SYNC_GITATTR_MODE=%SYNC_GITATTR_MODE% ^(use block^|overwrite^|skip^)
echo ERROR: invalid SYNC_GITATTR_MODE=%SYNC_GITATTR_MODE% ^(use block^|overwrite^|append^|skip^)
exit /b 1
)

View File

@ -1,6 +1,6 @@
# Sync standards snapshot to project root.
# - Copies <snapshot>/.agents/<AGENTS_NS> -> <project-root>/.agents/<AGENTS_NS>
# - Updates <project-root>/.gitattributes (managed block by default)
# - Updates <project-root>/.gitattributes (append missing rules by default)
# Existing targets are backed up before overwrite.
[CmdletBinding()]
param(
@ -38,7 +38,7 @@ if (-not $env:SYNC_STANDARDS_INNER -and $Langs -and $Langs.Count -gt 0) {
$oldMode = $env:SYNC_GITATTR_MODE
$syncModeFirst = $env:SYNC_GITATTR_MODE
if (-not $syncModeFirst) { $syncModeFirst = "block" }
if (-not $syncModeFirst) { $syncModeFirst = "append" }
$first = $true
foreach ($ns in $Langs) {
@ -136,7 +136,7 @@ if (-not (Test-Path $AgentsMd)) {
$GitAttrDst = Join-Path $Root ".gitattributes"
if (Test-Path $GitAttrSrc) {
$mode = $env:SYNC_GITATTR_MODE
if (-not $mode) { $mode = "block" }
if (-not $mode) { $mode = "append" }
switch ($mode.ToLowerInvariant()) {
"skip" {
Write-Host "Skip: .gitattributes sync (SYNC_GITATTR_MODE=skip)."
@ -156,6 +156,65 @@ if (Test-Path $GitAttrSrc) {
Write-Host "Synced .gitattributes from standards (overwrite)."
break
}
"append" {
if ($GitAttrSrc -ieq $GitAttrDst) {
Write-Host "Skip: .gitattributes source equals destination."
break
}
$dstLines = @{}
if (Test-Path $GitAttrDst) {
Get-Content $GitAttrDst | ForEach-Object {
$line = $_.Trim()
if ($line -eq "" -or $line.StartsWith("#")) { return }
$dstLines[$line] = $true
}
}
$missing = New-Object System.Collections.Generic.List[string]
Get-Content $GitAttrSrc | ForEach-Object {
$line = $_.Trim()
if ($line -eq "" -or $line.StartsWith("#")) { return }
if (-not $dstLines.ContainsKey($line)) {
$dstLines[$line] = $true
$missing.Add($line)
}
}
if ($missing.Count -eq 0) {
Write-Host "No missing .gitattributes rules to append."
break
}
$bak = $null
if (Test-Path $GitAttrDst) {
$bak = "$GitAttrDst.bak.$timestamp"
Move-Item $GitAttrDst $bak -Force
Write-Host "Backed up existing .gitattributes -> $bak"
}
$sourceNote = $GitAttrSrc
$rootPrefix = "$Root\"
if ($sourceNote.StartsWith($rootPrefix)) {
$sourceNote = $sourceNote.Substring($rootPrefix.Length)
}
$header = "# Added from playbook .gitattributes (source: $sourceNote)"
$content = @()
if ($bak -and (Test-Path $bak)) {
$existing = Get-Content $bak
if ($existing.Count -gt 0) {
$content += $existing
$content += ""
}
}
$content += $header
$content += $missing
$content | Set-Content -Path $GitAttrDst -Encoding UTF8
Write-Host "Appended missing .gitattributes rules from standards."
break
}
"block" {
$begin = "# BEGIN playbook .gitattributes"
$end = "# END playbook .gitattributes"
@ -186,7 +245,7 @@ if (Test-Path $GitAttrSrc) {
break
}
default {
throw "Invalid SYNC_GITATTR_MODE=$mode (use block|overwrite|skip)"
throw "Invalid SYNC_GITATTR_MODE=$mode (use block|overwrite|append|skip)"
}
}
}

View File

@ -3,7 +3,7 @@ set -eu
# Sync standards snapshot to project root.
# - Copies <snapshot>/.agents/<AGENTS_NS> -> <project-root>/.agents/<AGENTS_NS>
# - Updates <project-root>/.gitattributes (managed block by default)
# - Updates <project-root>/.gitattributes (append missing rules by default)
# Existing targets are backed up before overwrite.
#
# Multi rulesets:
@ -48,7 +48,7 @@ if [ "${SYNC_STANDARDS_INNER:-}" != "1" ]; then
langs="$*"
fi
if [ -n "${langs:-}" ]; then
sync_mode_first="${SYNC_GITATTR_MODE:-block}"
sync_mode_first="${SYNC_GITATTR_MODE:-append}"
first=1
old_ifs="${IFS}"
@ -141,7 +141,7 @@ echo "Synced agents ruleset to $AGENTS_DST."
GITATTR_DST="$ROOT/.gitattributes"
if [ -f "$GITATTR_SRC" ]; then
: "${SYNC_GITATTR_MODE:=block}"
: "${SYNC_GITATTR_MODE:=append}"
case "$SYNC_GITATTR_MODE" in
skip)
echo "Skip: .gitattributes sync (SYNC_GITATTR_MODE=skip)."
@ -158,6 +158,66 @@ if [ -f "$GITATTR_SRC" ]; then
echo "Synced .gitattributes from standards (overwrite)."
fi
;;
append)
if [ "$(CDPATH= cd -- "$(dirname -- "$GITATTR_SRC")" && pwd -P)/$(basename -- "$GITATTR_SRC")" = "$GITATTR_DST" ]; then
echo "Skip: .gitattributes source equals destination."
else
if [ -f "$GITATTR_DST" ]; then
dst_file="$GITATTR_DST"
else
dst_file="/dev/null"
fi
missing_tmp="$(mktemp 2>/dev/null || echo "$ROOT/.gitattributes.missing.$timestamp")"
awk '
function norm(line) {
gsub(/^[ \t]+|[ \t]+$/, "", line)
return line
}
FNR==NR {
line=norm($0)
if (line == "" || line ~ /^#/) next
seen[line]=1
next
}
{
line=norm($0)
if (line == "" || line ~ /^#/) next
if (!seen[line] && !out[line]++) print line
}
' "$dst_file" "$GITATTR_SRC" >"$missing_tmp"
if [ ! -s "$missing_tmp" ]; then
rm -f "$missing_tmp"
echo "No missing .gitattributes rules to append."
echo "Done."
exit 0
fi
if [ -e "$GITATTR_DST" ]; then
mv "$GITATTR_DST" "$ROOT/.gitattributes.bak.$timestamp"
echo "Backed up existing .gitattributes -> .gitattributes.bak.$timestamp"
fi
source_note="$GITATTR_SRC"
case "$GITATTR_SRC" in
"$ROOT"/*) source_note="${GITATTR_SRC#$ROOT/}" ;;
esac
header="# Added from playbook .gitattributes (source: $source_note)"
{
if [ -f "$ROOT/.gitattributes.bak.$timestamp" ]; then
cat "$ROOT/.gitattributes.bak.$timestamp"
if [ -s "$ROOT/.gitattributes.bak.$timestamp" ]; then
printf "\n"
fi
fi
printf "%s\n" "$header"
cat "$missing_tmp"
} >"$GITATTR_DST"
rm -f "$missing_tmp"
echo "Appended missing .gitattributes rules from standards."
fi
;;
block)
begin="# BEGIN playbook .gitattributes"
end="# END playbook .gitattributes"
@ -207,7 +267,7 @@ if [ -f "$GITATTR_SRC" ]; then
echo "Updated .gitattributes from standards (managed block)."
;;
*)
echo "ERROR: invalid SYNC_GITATTR_MODE=$SYNC_GITATTR_MODE (use block|overwrite|skip)" >&2
echo "ERROR: invalid SYNC_GITATTR_MODE=$SYNC_GITATTR_MODE (use block|overwrite|append|skip)" >&2
exit 1
;;
esac

View File

@ -72,7 +72,7 @@ sh check_doc_links.sh
- 单语言同步tsl/cpp
- 多语言同步tsl cpp
- **.gitattributes 同步**
- 默认模式创建 managed block
- 默认模式追加缺失规则
- 保留现有内容
- 更新已存在的 managed block
- **AGENTS.md 处理**

View File

@ -81,15 +81,15 @@ teardown() {
# .gitattributes 同步测试
# ==============================================
@test ".gitattributes - 默认模式创建 managed block" {
@test ".gitattributes - 默认模式追加缺失规则" {
cd "$TEST_DIR"
[ ! -f ".gitattributes" ]
sh "$SCRIPT_PATH" tsl
[ -f ".gitattributes" ]
grep -q "# BEGIN playbook .gitattributes" .gitattributes
grep -q "# END playbook .gitattributes" .gitattributes
grep -q "Added from playbook .gitattributes" .gitattributes
grep -q "\\*.tsl" .gitattributes
}
@test ".gitattributes - 保留现有内容" {
@ -111,6 +111,7 @@ teardown() {
# END playbook .gitattributes
EOF
export SYNC_GITATTR_MODE=block
sh "$SCRIPT_PATH" tsl
# 验证 block 已更新(不再包含 "Old content"
@ -232,6 +233,27 @@ EOF
! grep -q "# Custom content" .gitattributes
}
@test "环境变量 - SYNC_GITATTR_MODE=append 追加缺失规则" {
cd "$TEST_DIR"
echo "# Custom rules only" > .gitattributes
export SYNC_GITATTR_MODE=append
sh "$SCRIPT_PATH" tsl
grep -q "Added from playbook .gitattributes" .gitattributes
grep -q "\\*.tsl" .gitattributes
}
@test "环境变量 - SYNC_GITATTR_MODE=append 无缺失规则不追加" {
cd "$TEST_DIR"
cp "$PLAYBOOK_ROOT/.gitattributes" .gitattributes
export SYNC_GITATTR_MODE=append
sh "$SCRIPT_PATH" tsl
! grep -q "Added from playbook .gitattributes" .gitattributes
}
# ==============================================
# 幂等性测试
# ==============================================