feat(scripts): sync-statements.sh --in-place — раскладка копии в рабочее дерево хоста с защитой от .gitignore и путей для контура (#120)
Также: evaluate при коммите VERSION без arm_statements/ даёт код 20 вместо падения с 128 (T2.1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7851446999
commit
b5ad8a0f4c
@ -11,7 +11,8 @@
|
|||||||
# --check только проверить рабочее дерево хоста (незакоммиченная правка тоже расхождение):
|
# --check только проверить рабочее дерево хоста (незакоммиченная правка тоже расхождение):
|
||||||
# 0 — копия синхронна, 10 — предупреждение (копия отстала или перечень кодов в docs отличается),
|
# 0 — копия синхронна, 10 — предупреждение (копия отстала или перечень кодов в docs отличается),
|
||||||
# 20 — расхождение (прямая правка копии, чужая версия, нет копии, нет зависимости, код вне перечня)
|
# 20 — расхождение (прямая правка копии, чужая версия, нет копии, нет зависимости, код вне перечня)
|
||||||
# --in-place разложить копию в --host-dir (режим пока не реализован)
|
# --in-place разложить копию в рабочее дерево хоста (без git-операций); 1 — защиты (.gitignore хоста, пути для контура),
|
||||||
|
# 20 — отказ (правка копии, чужая версия, нет зависимости, код вне перечня)
|
||||||
# --dry-run worktree хоста без issue и PR (режим пока не реализован); вместе с --in-place — код 2
|
# --dry-run worktree хоста без issue и PR (режим пока не реализован); вместе с --in-place — код 2
|
||||||
# --host arm-core | tabular-parser | scan-gateway; по умолчанию — по origin хоста (arm-core, arm-tabular-parser,
|
# --host arm-core | tabular-parser | scan-gateway; по умолчанию — по origin хоста (arm-core, arm-tabular-parser,
|
||||||
# arm-scan-gateway), иначе по имени каталога
|
# arm-scan-gateway), иначе по имени каталога
|
||||||
@ -58,7 +59,7 @@ while [ $# -gt 0 ]; do
|
|||||||
--docs) [ $# -ge 2 ] || die "--docs требует значение" 2; DOCS=$2; shift ;;
|
--docs) [ $# -ge 2 ] || die "--docs требует значение" 2; DOCS=$2; shift ;;
|
||||||
--core-dir) [ $# -ge 2 ] || die "--core-dir требует значение" 2; CORE_DIR=$2; shift ;;
|
--core-dir) [ $# -ge 2 ] || die "--core-dir требует значение" 2; CORE_DIR=$2; shift ;;
|
||||||
--wt-root) [ $# -ge 2 ] || die "--wt-root требует значение" 2; WT_ROOT=$2; shift ;;
|
--wt-root) [ $# -ge 2 ] || die "--wt-root требует значение" 2; WT_ROOT=$2; shift ;;
|
||||||
-h|--help) sed -n "2,23p" "$0"; exit 0 ;;
|
-h|--help) sed -n "2,24p" "$0"; exit 0 ;;
|
||||||
*) die "неизвестный аргумент $1" 2 ;;
|
*) die "неизвестный аргумент $1" 2 ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
@ -137,11 +138,13 @@ helper() {
|
|||||||
# Печатает находки, выставляет EVAL_CODE (0/10/20) и EVAL_WARNS (причины предупреждения через «; »).
|
# Печатает находки, выставляет EVAL_CODE (0/10/20) и EVAL_WARNS (причины предупреждения через «; »).
|
||||||
EVAL_CODE=0
|
EVAL_CODE=0
|
||||||
EVAL_WARNS=""
|
EVAL_WARNS=""
|
||||||
|
COPY_BAD=0 # 1 — copy негодна по условиям 1–4 (не из-за зависимостей и словаря)
|
||||||
evaluate() {
|
evaluate() {
|
||||||
local copy=$1 reqs=$2 lines=$3 host=$4
|
local copy=$1 reqs=$2 lines=$3 host=$4
|
||||||
local version="" v7="" archive="$TMPD/archive" known=1 line
|
local version="" v7="" archive="$TMPD/archive" known=1 line
|
||||||
EVAL_CODE=0; EVAL_WARNS=""
|
EVAL_CODE=0; EVAL_WARNS=""; COPY_BAD=0
|
||||||
bad() { echo "$1"; EVAL_CODE=20; }
|
bad() { echo "$1"; EVAL_CODE=20; }
|
||||||
|
badcopy() { bad "$1"; COPY_BAD=1; } # условия 1–4: сама копия негодна (правка, чужая версия)
|
||||||
warn() { echo "$1"; [ "$EVAL_CODE" = 20 ] || EVAL_CODE=10; EVAL_WARNS="${EVAL_WARNS:+$EVAL_WARNS; }$2"; }
|
warn() { echo "$1"; [ "$EVAL_CODE" = 20 ] || EVAL_CODE=10; EVAL_WARNS="${EVAL_WARNS:+$EVAL_WARNS; }$2"; }
|
||||||
|
|
||||||
if [ ! -d "$copy" ]; then
|
if [ ! -d "$copy" ]; then
|
||||||
@ -153,22 +156,28 @@ evaluate() {
|
|||||||
if ! printf '%s' "$version" | grep -Eq '^[0-9a-f]{40}$' \
|
if ! printf '%s' "$version" | grep -Eq '^[0-9a-f]{40}$' \
|
||||||
|| ! git -C "$SRC" cat-file -e "$version^{commit}" 2>/dev/null; then
|
|| ! git -C "$SRC" cat-file -e "$version^{commit}" 2>/dev/null; then
|
||||||
known=0
|
known=0
|
||||||
bad "VERSION ${version:-(нет файла)} нет в истории arm-statements$([ "$FETCH_OK" = 1 ] || echo '; fetch не удался — сделайте fetch в arm-statements и повторите')"
|
badcopy "VERSION ${version:-(нет файла)} нет в истории arm-statements$([ "$FETCH_OK" = 1 ] || echo '; fetch не удался — сделайте fetch в arm-statements и повторите')"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$known" = 1 ]; then
|
if [ "$known" = 1 ]; then
|
||||||
# 3. версия должна быть из истории ref
|
# 3. версия должна быть из истории ref
|
||||||
if ! git -C "$SRC" merge-base --is-ancestor "$version" "$REF_SHA" 2>/dev/null; then
|
if ! git -C "$SRC" merge-base --is-ancestor "$version" "$REF_SHA" 2>/dev/null; then
|
||||||
bad "чужая версия: $v7 не из истории $REF"
|
badcopy "чужая версия: $v7 не из истории $REF"
|
||||||
fi
|
fi
|
||||||
# 4. файлы копии против git archive <VERSION>
|
# 4. файлы копии против git archive <VERSION>
|
||||||
rm -rf "$archive"; mkdir -p "$archive"
|
rm -rf "$archive"; mkdir -p "$archive"
|
||||||
git -C "$SRC" archive "$version" arm_statements | tar -x -C "$archive"
|
local diffs="" have_archive=1
|
||||||
local diffs
|
if ! git -C "$SRC" archive "$version" arm_statements 2>/dev/null | tar -x -C "$archive" 2>/dev/null \
|
||||||
diffs=$(diff -rq -x VERSION -x __pycache__ -x '*.pyc' -x .pytest_cache -x .mypy_cache -x .ruff_cache -x .DS_Store \
|
|| [ ! -d "$archive/arm_statements" ]; then
|
||||||
"$archive/arm_statements" "$copy" 2>&1 || true)
|
have_archive=0
|
||||||
|
badcopy "в коммите VERSION ($v7) нет arm_statements/ — чужая версия"
|
||||||
|
fi
|
||||||
|
if [ "$have_archive" = 1 ]; then
|
||||||
|
diffs=$(diff -rq -x VERSION -x __pycache__ -x '*.pyc' -x .pytest_cache -x .mypy_cache -x .ruff_cache -x .DS_Store \
|
||||||
|
"$archive/arm_statements" "$copy" 2>&1 || true)
|
||||||
|
fi
|
||||||
if [ -n "$diffs" ]; then
|
if [ -n "$diffs" ]; then
|
||||||
bad "прямая правка копии (файлы относительно arm_statements/):"
|
badcopy "прямая правка копии (файлы относительно arm_statements/):"
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
[ -n "$line" ] || continue
|
[ -n "$line" ] || continue
|
||||||
case "$line" in
|
case "$line" in
|
||||||
@ -214,6 +223,15 @@ evaluate() {
|
|||||||
}
|
}
|
||||||
VERSION7=""
|
VERSION7=""
|
||||||
|
|
||||||
|
# summary: итоговая строка как у --check
|
||||||
|
summary() {
|
||||||
|
case "$EVAL_CODE" in
|
||||||
|
0) echo "statements-check $HOST_NAME: 0 — копия синхронна с arm-statements@$REF ($VERSION7)" ;;
|
||||||
|
10) echo "statements-check $HOST_NAME: 10 — ПРЕДУПРЕЖДЕНИЕ: $EVAL_WARNS" ;;
|
||||||
|
*) echo "statements-check $HOST_NAME: 20 — расхождение, копию не принимать" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# --- режимы --------------------------------------------------------------------------------------------------------
|
# --- режимы --------------------------------------------------------------------------------------------------------
|
||||||
if [ "$MODE" = check ]; then
|
if [ "$MODE" = check ]; then
|
||||||
LINES=""
|
LINES=""
|
||||||
@ -229,4 +247,79 @@ if [ "$MODE" = check ]; then
|
|||||||
exit "$EVAL_CODE"
|
exit "$EVAL_CODE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# lay_copy <host_root> <commit>: заменить <host_root>/api/arm_statements копией из источника, записать VERSION;
|
||||||
|
# затем защиты (игнор .gitignore хоста, содержимое, на котором остановится перенос в контур) — любая даёт код 1
|
||||||
|
lay_copy() {
|
||||||
|
local root=$1 commit=$2 dest="$1/api/arm_statements" ign pth bad=0
|
||||||
|
rm -rf "$dest"; mkdir -p "$root/api"
|
||||||
|
git -C "$SRC" archive "$commit" arm_statements | tar -x -C "$root/api"
|
||||||
|
printf '%s\n' "$commit" > "$dest/VERSION"
|
||||||
|
ign=$(git -C "$root" ls-files --others --ignored --exclude-standard -- api/arm_statements 2>/dev/null \
|
||||||
|
| grep -Ev '(^|/)(__pycache__|\.pytest_cache|\.mypy_cache|\.ruff_cache)(/|$)|\.pyc$' || true)
|
||||||
|
if [ -n "$ign" ]; then
|
||||||
|
echo "файлы копии игнорируются .gitignore хоста, в коммит не попадут:"
|
||||||
|
printf '%s\n' "$ign" | sed 's/^/ /'
|
||||||
|
bad=1
|
||||||
|
fi
|
||||||
|
pth=$(grep -rIn "/Users/\|/home/[a-z]\|git.aepif.ru\|uv venv\|uv pip" "$dest" || true)
|
||||||
|
if [ -n "$pth" ]; then
|
||||||
|
echo "копия сломает снимок для контура:"
|
||||||
|
printf '%s\n' "$pth" | sed 's/^/ /'
|
||||||
|
bad=1
|
||||||
|
fi
|
||||||
|
[ "$bad" = 0 ] || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$MODE" = in-place ]; then
|
||||||
|
COPY="$HOST_DIR/api/arm_statements"
|
||||||
|
REQS="$HOST_DIR/api/requirements.txt"
|
||||||
|
LINES=""
|
||||||
|
if [ "$HOST_NAME" = arm-core ] && [ -f "$HOST_DIR/api/rules/statement_lines.yaml" ]; then
|
||||||
|
LINES="$HOST_DIR/api/rules/statement_lines.yaml"
|
||||||
|
fi
|
||||||
|
# 1. существующая копия не должна быть правленой или чужой
|
||||||
|
if [ -d "$COPY" ]; then
|
||||||
|
evaluate "$COPY" "$REQS" "" "$HOST_NAME" > "$TMPD/ev.out"
|
||||||
|
if [ "$COPY_BAD" = 1 ]; then
|
||||||
|
cat "$TMPD/ev.out"
|
||||||
|
echo "statements-check $HOST_NAME: 20 — отказ: копия правлена или чужая, ничего не записано"
|
||||||
|
exit 20
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# 2. зависимости и словарь имён по коммиту --ref
|
||||||
|
refuse=""
|
||||||
|
git -C "$SRC" show "$REF_SHA:pyproject.toml" > "$TMPD/ref-pyproject.toml" 2>/dev/null || : > "$TMPD/ref-pyproject.toml"
|
||||||
|
helper deps "$TMPD/ref-pyproject.toml" "$REQS"
|
||||||
|
if [ "$HELPER_RC" = 20 ]; then refuse="$HELPER_OUT"; fi
|
||||||
|
if [ -n "$LINES" ]; then
|
||||||
|
git -C "$SRC" show "$REF_SHA:arm_statements/rules/statement_code_orders.yaml" > "$TMPD/ref-orders.yaml" 2>/dev/null \
|
||||||
|
|| : > "$TMPD/ref-orders.yaml"
|
||||||
|
helper lines "$LINES" "$TMPD/ref-orders.yaml"
|
||||||
|
if [ "$HELPER_RC" = 20 ]; then refuse="${refuse:+$refuse
|
||||||
|
}$HELPER_OUT"; fi
|
||||||
|
fi
|
||||||
|
if [ -n "$refuse" ]; then
|
||||||
|
echo "$refuse"
|
||||||
|
echo "statements-check $HOST_NAME: 20 — отказ: $REF несовместим с хостом, ничего не записано"
|
||||||
|
exit 20
|
||||||
|
fi
|
||||||
|
# 3. уже на ref?
|
||||||
|
LAID=0
|
||||||
|
if [ -d "$COPY" ] && [ -f "$COPY/VERSION" ] && [ "$(tr -d ' \t\r\n' < "$COPY/VERSION")" = "$REF_SHA" ]; then
|
||||||
|
echo "раскладывать нечего: копия уже на $REF ($REF7)"
|
||||||
|
else
|
||||||
|
# 4. раскладка
|
||||||
|
lay_copy "$HOST_DIR" "$REF_SHA" || die "раскладка выполнена, но копию нельзя коммитить (см. выше)" 1
|
||||||
|
LAID=1
|
||||||
|
fi
|
||||||
|
# 5. итог
|
||||||
|
evaluate "$COPY" "$REQS" "$LINES" "$HOST_NAME"
|
||||||
|
summary
|
||||||
|
if [ "$LAID" = 1 ]; then
|
||||||
|
echo "закоммитьте api/arm_statements отдельным коммитом: git -C $HOST_DIR add api/arm_statements"
|
||||||
|
fi
|
||||||
|
exit "$EVAL_CODE"
|
||||||
|
fi
|
||||||
|
|
||||||
die "режим не реализован" 1
|
die "режим не реализован" 1
|
||||||
|
|||||||
@ -230,5 +230,66 @@ echo "# правка" >> "$AC/arm_statements/steps.py"
|
|||||||
sync 20 "check: правка + отставание = 20" --check --host-dir "$WORK/arm-core"
|
sync 20 "check: правка + отставание = 20" --check --host-dir "$WORK/arm-core"
|
||||||
reset_hosts
|
reset_hosts
|
||||||
|
|
||||||
|
# коммит без arm_statements/ (T2.1): код 20 с итоговой строкой, а не 128
|
||||||
|
NOARCH=$(git -C "$SRC" commit-tree "$(git -C "$SRC" hash-object -t tree /dev/null)" -m "без пакета")
|
||||||
|
printf '%s\n' "$NOARCH" > "$AC/arm_statements/VERSION"
|
||||||
|
sync 20 "check: в коммите VERSION нет arm_statements/" --check --host-dir "$WORK/arm-core"
|
||||||
|
check "$(has 'нет arm_statements/' && has 'statements-check arm-core: 20' && echo 0 || echo 1)" "check: нет arm_statements/ — сообщение и итог"
|
||||||
|
reset_hosts
|
||||||
|
|
||||||
|
# --- sync-statements.sh --in-place --------------------------------------------------------------------------------
|
||||||
|
S2=$(git -C "$SRC" rev-parse origin/main)
|
||||||
|
FR="$WORK/fresh/tabular-parser"
|
||||||
|
mkdir -p "$WORK/fresh"
|
||||||
|
git init -q --bare "$WORK/fresh/arm-tabular-parser.git"
|
||||||
|
git clone -q "$WORK/fresh/arm-tabular-parser.git" "$FR" 2>/dev/null
|
||||||
|
git -C "$FR" checkout -q -b main
|
||||||
|
put "$FR/api/requirements.txt" $'PyYAML==6.0.3'
|
||||||
|
commit_push "$FR" "fresh" main
|
||||||
|
|
||||||
|
sync 0 "in-place: первичная раскладка" --in-place --host-dir "$FR" --ref origin/main
|
||||||
|
check "$([ "$(tr -d '\n' < "$FR/api/arm_statements/VERSION")" = "$S2" ] && echo 0 || echo 1)" "in-place: VERSION = полный хэш S2"
|
||||||
|
rm -rf "$WORK/arc"; mkdir -p "$WORK/arc"; git -C "$SRC" archive "$S2" arm_statements | tar -x -C "$WORK/arc"
|
||||||
|
check "$(diff -r -x VERSION "$WORK/arc/arm_statements" "$FR/api/arm_statements" >/dev/null && echo 0 || echo 1)" "in-place: копия равна git archive S2"
|
||||||
|
check "$(git -C "$FR" status --porcelain | grep -qv '^?? api/arm_statements' && echo 1 || echo 0)" "in-place: хост менялся только в api/arm_statements"
|
||||||
|
check "$(has закоммитьте && echo 0 || echo 1)" "in-place: подсказка про коммит"
|
||||||
|
sync 0 "in-place: повтор" --in-place --host-dir "$FR" --ref origin/main
|
||||||
|
check "$(has 'раскладывать нечего' && echo 0 || echo 1)" "in-place: повтор — раскладывать нечего"
|
||||||
|
|
||||||
|
sync 0 "in-place: обновление arm-core S1 -> S2" --in-place --host-dir "$WORK/arm-core"
|
||||||
|
check "$([ "$(tr -d '\n' < "$AC/arm_statements/VERSION")" = "$S2" ] && echo 0 || echo 1)" "in-place: arm-core VERSION = S2"
|
||||||
|
echo "# правка" >> "$AC/arm_statements/steps.py"
|
||||||
|
sync 20 "in-place: отказ при правке" --in-place --host-dir "$WORK/arm-core" --ref origin/main
|
||||||
|
check "$(grep -q '^# правка' "$AC/arm_statements/steps.py" && [ "$(tr -d '\n' < "$AC/arm_statements/VERSION")" = "$S2" ] && echo 0 || echo 1)" "in-place: правка на месте, VERSION прежний"
|
||||||
|
reset_hosts
|
||||||
|
|
||||||
|
TPR="$WORK/tabular-parser/api"
|
||||||
|
put "$TPR/requirements.txt" "aiohttp==3.14.3"
|
||||||
|
sync 20 "in-place: отказ без зависимости (обновление)" --in-place --host-dir "$WORK/tabular-parser"
|
||||||
|
check "$([ "$(tr -d '\n' < "$TPR/arm_statements/VERSION")" = "$S1" ] && has PyYAML && echo 0 || echo 1)" "in-place: копия не тронута, в выводе PyYAML"
|
||||||
|
reset_hosts
|
||||||
|
cp "$FR/api/requirements.txt" "$WORK/req.bak"
|
||||||
|
put "$FR/api/requirements.txt" "aiohttp==3.14.3"
|
||||||
|
sync 20 "in-place: отказ без зависимости (fresh)" --in-place --host-dir "$FR" --ref origin/main
|
||||||
|
cp "$WORK/req.bak" "$FR/api/requirements.txt"
|
||||||
|
cp "$H/lines_x.yaml" "$AC/rules/statement_lines.yaml"
|
||||||
|
sync 20 "in-place: код словаря вне перечня" --in-place --host-dir "$WORK/arm-core" --ref origin/main
|
||||||
|
check "$([ "$(tr -d '\n' < "$AC/arm_statements/VERSION")" = "$S1" ] && has 1120 && echo 0 || echo 1)" "in-place: словарь — копия не тронута, 1120 в выводе"
|
||||||
|
reset_hosts
|
||||||
|
|
||||||
|
# защиты: коммиты S3/S4 на ветке test-guards, чтобы origin/main (S2) остался прежним
|
||||||
|
git -C "$SRC" checkout -q -b test-guards
|
||||||
|
put "$SRC/arm_statements/tests/golden/case.json" '{}'
|
||||||
|
commit_push "$SRC" "S3 json" test-guards
|
||||||
|
put "$FR/.gitignore" '*.json'
|
||||||
|
sync 1 "in-place: .gitignore прячет файл" --in-place --host-dir "$FR" --ref origin/test-guards
|
||||||
|
check "$(has case.json && has .gitignore && echo 0 || echo 1)" "in-place: в выводе case.json и .gitignore"
|
||||||
|
rm "$FR/.gitignore"
|
||||||
|
echo "# /Users/someone" >> "$SRC/arm_statements/steps.py"
|
||||||
|
commit_push "$SRC" "S4 путь" test-guards
|
||||||
|
sync 1 "in-place: содержимое для контура" --in-place --host-dir "$FR" --ref origin/test-guards
|
||||||
|
check "$(has '/Users/' && echo 0 || echo 1)" "in-place: в выводе /Users/"
|
||||||
|
git -C "$SRC" checkout -q main
|
||||||
|
|
||||||
[ "$fail" = 0 ] || exit 1
|
[ "$fail" = 0 ] || exit 1
|
||||||
echo "ok: sync-statements.sh — все сценарии пройдены"
|
echo "ok: sync-statements.sh — все сценарии пройдены"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user