114 lines
3.2 KiB
Bash
Executable file
114 lines
3.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
safe_ts="$(date -u +"%Y-%m-%dT%H-%M-%SZ")"
|
|
note="${1:-}"
|
|
|
|
mkdir -p out/checkpoints
|
|
|
|
echo "== running ci ==" >&2
|
|
ci_json="$(./scripts/ci.sh)"
|
|
echo "$ci_json" > "out/checkpoints/ci_${safe_ts}.json"
|
|
|
|
echo "== writing session state ==" >&2
|
|
./scripts/state.sh >/dev/null
|
|
|
|
git_branch="(unknown)"
|
|
git_head_short=""
|
|
dirty_count="0"
|
|
modified_count="0"
|
|
untracked_count="0"
|
|
diffstat=""
|
|
diffstat_lines="0"
|
|
diffstat_note=""
|
|
|
|
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
git_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "(unknown)")"
|
|
git_head_short="$(git rev-parse --short HEAD 2>/dev/null || echo "")"
|
|
porcelain="$(git status --porcelain=v1 2>/dev/null || true)"
|
|
if [ -n "${porcelain}" ]; then
|
|
dirty_count="$(printf "%s\n" "${porcelain}" | wc -l | awk '{print $1}')"
|
|
untracked_count="$(printf "%s\n" "${porcelain}" | grep -c '^??' || true)"
|
|
modified_count="$(printf "%s\n" "${porcelain}" | grep -vc '^??' || true)"
|
|
fi
|
|
diffstat="$(git diff --stat 2>/dev/null || true)"
|
|
if [ -n "${diffstat}" ]; then
|
|
diffstat_lines="$(printf "%s\n" "${diffstat}" | wc -l | awk '{print $1}')"
|
|
if [ "${diffstat_lines}" -gt 200 ]; then
|
|
diffstat="$(printf "%s\n" "${diffstat}" | head -n 200)"
|
|
diffstat_note="(diffstat truncated to first 200 lines)"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
tar_path="out/checkpoints/iftypeset_checkpoint_${safe_ts}.tar.gz"
|
|
details_path="out/checkpoints/checkpoint_${safe_ts}.md"
|
|
|
|
echo "== creating snapshot ${tar_path} ==" >&2
|
|
snapshot_paths=(.forgejo .gitignore README.md STATUS.md app docs fixtures forgejo pyproject.toml requirements.txt scripts spec src tests tools)
|
|
if [ -f AGENTS.md ]; then
|
|
snapshot_paths+=(AGENTS.md)
|
|
fi
|
|
tar \
|
|
--exclude=".git" \
|
|
--exclude=".venv" \
|
|
--exclude="out" \
|
|
-czf "$tar_path" \
|
|
"${snapshot_paths[@]}"
|
|
|
|
sha="$(sha256sum "$tar_path" | awk '{print $1}')"
|
|
|
|
cat > "${details_path}" <<EOF
|
|
# iftypeset checkpoint
|
|
|
|
- timestamp_utc: ${ts}
|
|
- snapshot: ${tar_path}
|
|
- snapshot_sha256: ${sha}
|
|
- ci: \`out/checkpoints/ci_${safe_ts}.json\`
|
|
- git: ${git_branch} ${git_head_short}
|
|
- working_tree: dirty_paths=${dirty_count} (modified=${modified_count}, untracked=${untracked_count})
|
|
$( [ -n "${note}" ] && printf -- "- note: %s\n" "${note}" )
|
|
|
|
## Diffstat (uncommitted changes)
|
|
|
|
${diffstat_note}
|
|
|
|
\`\`\`text
|
|
${diffstat}
|
|
\`\`\`
|
|
|
|
EOF
|
|
|
|
if [ ! -s docs/CHECKPOINTS.md ]; then
|
|
cat > docs/CHECKPOINTS.md <<'EOF'
|
|
# Checkpoints
|
|
|
|
Durable restore points for this repo (useful when chat context resets).
|
|
|
|
How to create a checkpoint:
|
|
|
|
- `./scripts/checkpoint.sh "short note about what changed"`
|
|
|
|
Each entry records the snapshot tarball path + sha256 and the CI JSON for that moment.
|
|
EOF
|
|
fi
|
|
|
|
cat >> docs/CHECKPOINTS.md <<EOF
|
|
|
|
## ${ts}
|
|
|
|
- snapshot: \`${tar_path}\`
|
|
- snapshot_sha256: \`${sha}\`
|
|
- ci_json: \`out/checkpoints/ci_${safe_ts}.json\`
|
|
- details: \`${details_path}\`
|
|
- git: \`${git_branch}\` \`${git_head_short}\`
|
|
- working_tree: dirty_paths=${dirty_count} (modified=${modified_count}, untracked=${untracked_count})
|
|
$( [ -n "${note}" ] && printf -- "- note: %s\n" "${note}" )
|
|
|
|
EOF
|
|
|
|
echo "checkpoint complete: ${tar_path} (sha256=${sha})" >&2
|