| 1 | #!/usr/bin/env bash |
| 2 | # Mirror Diffinery's codician/diffinery main to github.com/codician-team/diffinery. |
| 3 | # |
| 4 | # Every author and committer becomes codician-team and the attribution trailers |
| 5 | # (Co-authored-by, Claude-Session, Signed-off-by) are dropped, so GitHub lists no |
| 6 | # personal contributors; old personal addresses in wrangler.jsonc are replaced. |
| 7 | # The rewrite is deterministic: the same Diffinery history always gives the same |
| 8 | # GitHub commits, so a sync is a fast-forward unless Diffinery's own history was |
| 9 | # rewritten (then GitHub follows it). Only main is mirrored. |
| 10 | # |
| 11 | # Reads Diffinery with DIFFINERY_TOKEN when set (not needed once the repo is public); |
| 12 | # MIRROR_SRC can also be a local clone that has main. Pushes with GITHUB_MIRROR_TOKEN |
| 13 | # when set, otherwise with whatever credentials git already has for github.com. |
| 14 | # Needs git-filter-repo 2.47.0 (pip install git-filter-repo==2.47.0); another |
| 15 | # version could write different commits. |
| 16 | # |
| 17 | # Usage: scripts/mirror-to-github.sh [--dry-run] |
| 18 | # |
| 19 | # MIRROR_WORKDIR=<empty dir> keeps the rewritten repository for inspection. |
| 20 | set -euo pipefail |
| 21 | |
| 22 | SRC=${MIRROR_SRC:-https://diffinery.com/codician/diffinery.git} |
| 23 | DST=${MIRROR_DST:-https://github.com/codician-team/diffinery.git} |
| 24 | NAME=${MIRROR_NAME:-codician-team} |
| 25 | EMAIL=${MIRROR_EMAIL:-codician-team@users.noreply.diffinery.com} |
| 26 | dry_run=${1:-} |
| 27 | |
| 28 | work=${MIRROR_WORKDIR:-} |
| 29 | if [ -z "$work" ]; then |
| 30 | work=$(mktemp -d) |
| 31 | trap 'rm -rf "$work"' EXIT |
| 32 | fi |
| 33 | |
| 34 | auth=() |
| 35 | if [ -n "${DIFFINERY_TOKEN:-}" ]; then |
| 36 | # The helper reads the token from the environment, so it never lands on a command line. |
| 37 | auth=(-c 'credential.https://diffinery.com.helper=!f() { [ "$1" = get ] && printf "username=x-token\npassword=%s\n" "$DIFFINERY_TOKEN"; }; f') |
| 38 | fi |
| 39 | if [ -n "${GITHUB_MIRROR_TOKEN:-}" ]; then |
| 40 | auth+=(-c 'credential.https://github.com.helper=!f() { [ "$1" = get ] && printf "username=x-access-token\npassword=%s\n" "$GITHUB_MIRROR_TOKEN"; }; f') |
| 41 | fi |
| 42 | git "${auth[@]}" clone --quiet --bare --single-branch --branch main "$SRC" "$work/repo.git" |
| 43 | cd "$work/repo.git" |
| 44 | src_head=$(git rev-parse main) |
| 45 | |
| 46 | # Old wrangler.jsonc versions listed the bootstrap admins' personal addresses. |
| 47 | cat > "$work/replacements.txt" <<'RULES' |
| 48 | regex:("BOOTSTRAP_ADMINS": ")[^"]*@[^"]*"==>\1admin@example.com" |
| 49 | RULES |
| 50 | |
| 51 | export MIRROR_NAME="$NAME" MIRROR_EMAIL="$EMAIL" |
| 52 | git filter-repo --force --quiet --refs main --replace-refs delete-no-add \ |
| 53 | --name-callback 'return os.environ["MIRROR_NAME"].encode()' \ |
| 54 | --email-callback 'return os.environ["MIRROR_EMAIL"].encode()' \ |
| 55 | --replace-text "$work/replacements.txt" \ |
| 56 | --message-callback ' |
| 57 | import re |
| 58 | drop = re.compile(r"^(co-authored-by|claude-session|signed-off-by):|^\U0001f916 generated with \[claude code\]|^https://claude\.ai/code/session_", re.I) |
| 59 | text = message.decode("utf-8", "surrogateescape") |
| 60 | lines = [l for l in text.split("\n") if not drop.match(l.strip())] |
| 61 | return ("\n".join(lines).rstrip() + "\n").encode("utf-8", "surrogateescape")' |
| 62 | |
| 63 | dst_head=$(git rev-parse main) |
| 64 | echo "Diffinery main $src_head -> GitHub main $dst_head" |
| 65 | if [ "$dry_run" = "--dry-run" ]; then |
| 66 | git log --format='%h %an <%ae> %s' -3 main |
| 67 | exit 0 |
| 68 | fi |
| 69 | remote=$(git "${auth[@]}" ls-remote "$DST" refs/heads/main | cut -f1) |
| 70 | if [ "$remote" = "$dst_head" ]; then |
| 71 | echo "GitHub is up to date." |
| 72 | exit 0 |
| 73 | fi |
| 74 | git "${auth[@]}" push --force "$DST" main:main |