fix(deploy): stop hanging on per-file sticky-bit scan
Local Build and Deploy / deploy (push) Successful in 1m48s

Only clear paths that already have skip-worktree/assume-unchanged; keep nuclear src replace and content verify.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-18 20:34:34 +02:00
1 parent 3eaffe658d
commit 80c6559143
2 files changed
+26 -36

No files matched your search

+22 -33
View File
@@ -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
+4 -3
View File
@@ -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");
});