komikku/scripts/check-upstream.sh

197 lines
5.4 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
# Compare this fork against upstream and optionally write UPSTREAM_STATUS.md.
set -euo pipefail
UPSTREAM_REMOTE="${UPSTREAM_REMOTE:-upstream}"
UPSTREAM_URL="${UPSTREAM_URL:-https://github.com/komikku-app/komikku.git}"
UPSTREAM_BRANCH="${UPSTREAM_BRANCH:-master}"
FORK_BRANCH="${FORK_BRANCH:-main}"
BEHIND_WARN="${BEHIND_WARN:-10}"
OUTPUT_MD="${OUTPUT_MD:-UPSTREAM_STATUS.md}"
OUTPUT_JSON="${OUTPUT_JSON:-docs/upstream-status.json}"
FETCH="${FETCH:-1}"
WRITE="${WRITE:-0}"
usage() {
cat <<'EOF'
Usage: scripts/check-upstream.sh [options]
Options:
--no-fetch Skip git fetch upstream
--write Write UPSTREAM_STATUS.md and docs/upstream-status.json
--json Print JSON status to stdout
-h, --help Show this help
Environment:
UPSTREAM_REMOTE Git remote name (default: upstream)
UPSTREAM_BRANCH Upstream branch (default: master)
FORK_BRANCH Local fork branch (default: main)
BEHIND_WARN Warn when behind by this many commits (default: 10)
EOF
}
print_json=0
while [[ $# -gt 0 ]]; do
case "$1" in
--no-fetch) FETCH=0 ;;
--write) WRITE=1 ;;
--json) print_json=1 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
esac
shift
done
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
if ! git rev-parse --verify "$FORK_BRANCH" >/dev/null 2>&1; then
echo "Fork branch not found: $FORK_BRANCH" >&2
exit 1
fi
if ! git remote get-url "$UPSTREAM_REMOTE" >/dev/null 2>&1; then
echo "Adding upstream remote: $UPSTREAM_REMOTE -> $UPSTREAM_URL" >&2
git remote add "$UPSTREAM_REMOTE" "$UPSTREAM_URL"
fi
if [[ "$FETCH" == "1" ]]; then
git fetch "$UPSTREAM_REMOTE" "$UPSTREAM_BRANCH" --prune
fi
upstream_ref="$UPSTREAM_REMOTE/$UPSTREAM_BRANCH"
if ! git rev-parse --verify "$upstream_ref" >/dev/null 2>&1; then
echo "Upstream ref not found: $upstream_ref" >&2
exit 1
fi
merge_base="$(git merge-base "$FORK_BRANCH" "$upstream_ref")"
behind="$(git rev-list --count "$FORK_BRANCH".."$upstream_ref")"
ahead="$(git rev-list --count "$upstream_ref".."$FORK_BRANCH")"
fork_head="$(git rev-parse --short "$FORK_BRANCH")"
upstream_head="$(git rev-parse --short "$upstream_ref")"
checked_at="$(date -u +"%Y-%m-%d %H:%M UTC")"
if [[ "$behind" -eq 0 ]]; then
status="up_to_date"
status_label="Up to date"
status_emoji="✅"
elif [[ "$behind" -le "$BEHIND_WARN" ]]; then
status="slightly_behind"
status_label="Slightly behind"
status_emoji="🟡"
else
status="behind"
status_label="Behind upstream"
status_emoji="🔴"
fi
upstream_sample="$(git log --oneline "$FORK_BRANCH".."$upstream_ref" -10)"
fork_sample="$(git log --oneline "$upstream_ref".."$FORK_BRANCH" -10)"
if [[ "$print_json" == "1" ]]; then
python3 - <<PY
import json
print(json.dumps({
"checked_at": "$checked_at",
"status": "$status",
"status_label": "$status_label",
"behind": $behind,
"ahead": $ahead,
"fork_branch": "$FORK_BRANCH",
"upstream_remote": "$UPSTREAM_REMOTE",
"upstream_branch": "$UPSTREAM_BRANCH",
"upstream_url": "$UPSTREAM_URL",
"merge_base": "$(git rev-parse --short "$merge_base")",
"fork_head": "$fork_head",
"upstream_head": "$upstream_head",
"behind_warn": $BEHIND_WARN,
}, indent=2))
PY
fi
echo "Upstream: $UPSTREAM_REMOTE ($UPSTREAM_URL) @ $UPSTREAM_BRANCH"
echo "Fork: $FORK_BRANCH @ $fork_head"
echo "Merge base: $(git rev-parse --short "$merge_base")"
echo "Behind upstream: $behind commits"
echo "Ahead of upstream: $ahead commits"
echo "Status: $status_emoji $status_label"
if [[ "$WRITE" != "1" ]]; then
exit 0
fi
mkdir -p "$(dirname "$OUTPUT_JSON")"
python3 - <<PY
import json
from pathlib import Path
Path("$(dirname "$OUTPUT_JSON")").mkdir(parents=True, exist_ok=True)
json.dump({
"schemaVersion": 1,
"checked_at": "$checked_at",
"status": "$status",
"status_label": "$status_label",
"behind": $behind,
"ahead": $ahead,
"fork_branch": "$FORK_BRANCH",
"upstream_remote": "$UPSTREAM_REMOTE",
"upstream_branch": "$UPSTREAM_BRANCH",
"upstream_url": "$UPSTREAM_URL",
"merge_base": "$(git rev-parse "$merge_base")",
"fork_head": "$(git rev-parse "$FORK_BRANCH")",
"upstream_head": "$(git rev-parse "$upstream_ref")",
"behind_warn": $BEHIND_WARN,
}, open("$OUTPUT_JSON", "w"), indent=2)
PY
{
cat <<EOF
# Upstream sync status
> Auto-generated by [\`scripts/check-upstream.sh\`](scripts/check-upstream.sh). Last check: **$checked_at**.
| | |
|---|---|
| Status | $status_emoji **$status_label** |
| Behind upstream | **$behind** commits on [\`$UPSTREAM_REMOTE/$UPSTREAM_BRANCH\`]($UPSTREAM_URL) |
| Ahead of upstream | **$ahead** fork-only commits on \`$FORK_BRANCH\` |
| Merge base | \`$(git rev-parse --short "$merge_base")\` |
| Fork HEAD | \`$fork_head\` |
| Upstream HEAD | \`$upstream_head\` |
EOF
if [[ "$behind" -gt 0 ]]; then
cat <<EOF
## Action recommended
This fork is **$behind commits behind** [komikku-app/komikku]($UPSTREAM_URL). Consider merging upstream soon.
See [docs/upstream-sync.md](docs/upstream-sync.md) for the manual sync procedure.
EOF
else
echo "## No merge needed"
echo
echo "The fork includes all commits from upstream \`$UPSTREAM_BRANCH\`."
echo
fi
cat <<EOF
## Recent upstream commits not in fork
\`\`\`
$(printf '%s\n' "$upstream_sample")
\`\`\`
## Recent fork-only commits
\`\`\`
$(printf '%s\n' "$fork_sample")
\`\`\`
EOF
} >"$OUTPUT_MD"
echo "Wrote $OUTPUT_MD and $OUTPUT_JSON"