Skip to content

Instantly share code, notes, and snippets.

@Borda
Created May 12, 2026 09:04
Show Gist options
  • Select an option

  • Save Borda/63a217e7a143a044259595d49e044b04 to your computer and use it in GitHub Desktop.

Select an option

Save Borda/63a217e7a143a044259595d49e044b04 to your computer and use it in GitHub Desktop.
Takes one or more CSV files as positional arguments, extracts repo slugs from the first column, deduplicates across all inputs, and archives each via gh repo archive --yes. Runs in dry-run mode by default — set APPLY=1 to actually archive, since GitHub's API has no bulk un-archive operation. Each archive attempt is logged to archive-log.csv with…
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# archive-repos.sh
#
# Archive GitHub repos listed in one or more CSV files. Reads the first
# column (repo slug) of each CSV, deduplicates across files, and calls
# `gh repo archive --yes` on each.
#
# Defaults to DRY RUN — prints what would happen but doesn't touch anything.
# Set APPLY=1 to actually archive. Note: GitHub's API does NOT support
# un-archiving in bulk, so review the dry-run output carefully first.
#
# Usage: bash archive-repos.sh <csv> [<csv> ...] # dry run
# APPLY=1 bash archive-repos.sh <csv> [<csv> ...] # real archive
# Defaults: LOG=archive-log.csv APPLY=0
# Outputs: archive-log.csv (repo, status, detail) — only when APPLY=1
# ─────────────────────────────────────────────────────────────────────────────
set -uo pipefail
APPLY="${APPLY:-0}"
LOG="${LOG:-archive-log.csv}"
if [[ $# -eq 0 ]]; then
echo "Usage: $0 <csv> [<csv> ...]" >&2
echo " APPLY=1 $0 <csv> [<csv> ...]" >&2
exit 1
fi
# Extract repo slug from first column, skipping CSV header
extract_slugs() {
local f="$1"
if [[ ! -f "$f" ]]; then
echo " (skip: $f not found)" >&2
return 0
fi
echo " + $f" >&2
tail -n +2 "$f" | awk -F'","' 'NF { gsub(/"/, "", $1); print $1 }'
}
echo "→ Reading $# input file(s):" >&2
mapfile -t repos < <(
for f in "$@"; do extract_slugs "$f"; done | sort -u | grep -v '^$'
)
total=${#repos[@]}
if [[ $total -eq 0 ]]; then
echo "→ Nothing to archive — input files empty or missing." >&2
exit 0
fi
echo "$total unique repo(s) selected" >&2
# Dry run
if [[ "$APPLY" != "1" ]]; then
echo "→ DRY RUN (set APPLY=1 to actually archive):" >&2
printf ' %s\n' "${repos[@]}" >&2
exit 0
fi
# Apply
echo 'repo,status,detail' > "$LOG"
ok=0; fail=0
for repo in "${repos[@]}"; do
out=$(gh repo archive "$repo" --yes 2>&1)
if [[ $? -eq 0 ]]; then
printf '"%s","ok",""\n' "$repo" >> "$LOG"
printf " %-50s ARCHIVED\n" "$repo" >&2
ok=$((ok + 1))
else
detail=$(echo "$out" | tr '\n' ' ' | tr '"' "'" | cut -c1-120)
printf '"%s","fail","%s"\n' "$repo" "$detail" >> "$LOG"
printf " %-50s FAIL %s\n" "$repo" "$detail" >&2
fail=$((fail + 1))
fi
done
echo "→ Done. $ok archived, $fail failed. Log → $LOG" >&2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment