diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index ef2baa76..2e5be183 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -47,53 +47,42 @@ jobs: git remote set-url origin /docker/gitea/gitea/git/repositories/remco/epicnext-cms.git/ # 3. Fetch and update code + echo "Fetching origin/main..." git fetch origin --prune - # Clear EVERY skip-worktree / assume-unchanged bit. Those bits make - # `git reset --hard` and `git diff` lie: the working tree can keep - # ancient file contents while git reports a clean checkout. - STICKY=0 - while IFS= read -r -d '' f; do - if git ls-files -v -- "$f" | grep -qE '^[a-zS]'; then - STICKY=$((STICKY + 1)) - fi - git update-index --no-skip-worktree --no-assume-unchanged -- "$f" 2>/dev/null || true - done < <(git ls-files -z) - echo "Cleared skip-worktree/assume-unchanged bits (saw ${STICKY} sticky before clear)" + # Only touch files that actually have sticky bits (fast). Clearing + # every tracked path one-by-one can hang the runner for minutes. + echo "Clearing sticky git index bits (if any)..." + STICKY_LIST="$(git ls-files -v | awk '/^[a-zS]/ {print substr($0,3)}' || true)" + if [ -n "${STICKY_LIST}" ]; then + echo "${STICKY_LIST}" | while IFS= read -r f; do + [ -n "$f" ] || continue + git update-index --no-skip-worktree --no-assume-unchanged -- "$f" 2>/dev/null || true + done + echo "Cleared sticky bits on $(echo "${STICKY_LIST}" | grep -c . || true) path(s)" + else + echo "No sticky bits found" + fi + echo "Hard reset to origin/main..." git reset --hard origin/main # Nuclear: delete src/ on disk, then restore ONLY from git objects. - # This is the only reliable way to drop host-local ghosts that survive - # reset/checkout when index flags or permissions pin old bytes. + # Defeats host-local ghosts that survive reset when index flags pin old bytes. + echo "Nuclear-replacing src/ from HEAD..." rm -rf src git checkout -f HEAD -- src # Drop other stray untracked junk under the app root (keep secrets/env). git clean -fd -e .env -e .env.local -e .env.production -e .env*.local - # Prove EVERY tracked file under src/ matches the HEAD blob (content hash). - # `git diff` alone is not enough when skip-worktree was previously set. - MISMATCH=0 - while IFS= read -r -d '' f; do - case "$f" in - src/*) ;; - *) continue ;; - esac - expected="$(git rev-parse "HEAD:${f}")" - actual="$(git hash-object "${f}")" - if [ "${expected}" != "${actual}" ]; then - echo "ERROR: content hash mismatch: ${f}" >&2 - echo " expected=${expected}" >&2 - echo " actual=${actual}" >&2 - MISMATCH=1 - fi - done < <(git ls-files -z) - if [ "${MISMATCH}" -ne 0 ]; then - echo "ERROR: src/ working tree does not match HEAD after nuclear checkout" >&2 + # After sticky clear + nuclear replace, content diff is trustworthy again. + if ! git diff --exit-code HEAD -- src >/dev/null; then + echo "ERROR: src/ still differs from HEAD after nuclear checkout:" >&2 + git diff --stat HEAD -- src >&2 || true exit 1 fi - echo "Verified all tracked src/ blobs match HEAD" + echo "Verified src/ matches HEAD" # Drop incremental TS caches that can hide real type errors. rm -f tsconfig.tsbuildinfo .tsbuildinfo diff --git a/src/lib/deploy-workflow-contract.test.ts b/src/lib/deploy-workflow-contract.test.ts index fda4a6b2..c5d8d4fb 100644 --- a/src/lib/deploy-workflow-contract.test.ts +++ b/src/lib/deploy-workflow-contract.test.ts @@ -24,13 +24,14 @@ describe("production deploy workflow", () => { expect(resetAt).toBeGreaterThan(reclaimAt); }); - it("nuclear-replaces src/ and verifies every tracked blob hash", () => { + it("nuclear-replaces src/ and clears sticky bits without scanning every path", () => { expect(workflow).toContain("rm -rf src"); expect(workflow).toContain("git checkout -f HEAD -- src"); - expect(workflow).toContain("git hash-object"); expect(workflow).toContain("no-skip-worktree"); expect(workflow).toContain("no-assume-unchanged"); - expect(workflow).toContain("Verified all tracked src/ blobs match HEAD"); + expect(workflow).toContain("Verified src/ matches HEAD"); + expect(workflow).toContain("git ls-files -v"); + expect(workflow).not.toContain("git ls-files -z"); expect(workflow).toContain("pnpm typecheck"); });