Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

138 lines · 6.4 KB · executableRaw History
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.
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, the tags on it and their releases
10# are mirrored.
11#
12# Reads Diffinery with DIFFINERY_TOKEN when set (not needed once the repo is public);
13# MIRROR_SRC can also be a local clone that has main. Pushes with GITHUB_MIRROR_TOKEN
14# when set, otherwise with whatever credentials git already has for github.com.
15# Needs git-filter-repo 2.47.0 (pip install git-filter-repo==2.47.0); another
16# version could write different commits.
17#
18# Usage: scripts/mirror-to-github.sh [--dry-run]
19#
20# MIRROR_WORKDIR=<empty dir> keeps the rewritten repository for inspection.
21set -euo pipefail
22
23SRC=${MIRROR_SRC:-https://diffinery.com/codician/diffinery.git}
24DST=${MIRROR_DST:-https://github.com/codician-team/diffinery.git}
25NAME=${MIRROR_NAME:-codician-team}
26EMAIL=${MIRROR_EMAIL:-hi@codician.com}
27dry_run=${1:-}
28
29work=${MIRROR_WORKDIR:-}
30if [ -z "$work" ]; then
31 work=$(mktemp -d)
32 trap 'rm -rf "$work"' EXIT
33fi
34
35auth=()
36if [ -n "${DIFFINERY_TOKEN:-}" ]; then
37 # The helper reads the token from the environment, so it never lands on a command line.
38 auth=(-c 'credential.https://diffinery.com.helper=!f() { [ "$1" = get ] && printf "username=x-token\npassword=%s\n" "$DIFFINERY_TOKEN"; }; f')
39fi
40if [ -n "${GITHUB_MIRROR_TOKEN:-}" ]; then
41 auth+=(-c 'credential.https://github.com.helper=!f() { [ "$1" = get ] && printf "username=x-access-token\npassword=%s\n" "$GITHUB_MIRROR_TOKEN"; }; f')
42fi
43git "${auth[@]}" clone --quiet --bare --single-branch --branch main "$SRC" "$work/repo.git"
44cd "$work/repo.git"
45src_head=$(git rev-parse main)
46
47export MIRROR_NAME="$NAME" MIRROR_EMAIL="$EMAIL"
48git filter-repo --force --quiet --refs main --replace-refs delete-no-add \
49 --name-callback 'return os.environ["MIRROR_NAME"].encode()' \
50 --email-callback 'return os.environ["MIRROR_EMAIL"].encode()' \
51 --message-callback '
52import re
53drop = re.compile(r"^(co-authored-by|claude-session|signed-off-by):|^\U0001f916 generated with \[claude code\]|^https://claude\.ai/code/session_", re.I)
54text = message.decode("utf-8", "surrogateescape")
55lines = [l for l in text.split("\n") if not drop.match(l.strip())]
56return ("\n".join(lines).rstrip() + "\n").encode("utf-8", "surrogateescape")'
57
58dst_head=$(git rev-parse main)
59echo "Diffinery main $src_head -> GitHub main $dst_head"
60if [ "$dry_run" = "--dry-run" ]; then
61 git log --format='%h %an <%ae> %s' -3 main
62 exit 0
63fi
64remote=$(git "${auth[@]}" ls-remote "$DST" refs/heads/main | cut -f1)
65if [ "$remote" = "$dst_head" ]; then
66 echo "GitHub main is up to date."
67else
68 git "${auth[@]}" push --force "$DST" main:main
69fi
70
71# Tags on main follow their commits, as lightweight tags (so no tagger identity).
72declare -A rewritten
73while read -r old new; do rewritten[$old]=$new; done < <(tail -n +2 filter-repo/commit-map)
74remote_tags=$(git "${auth[@]}" ls-remote --tags "$DST")
75mirrored=()
76for tag in $(git tag); do
77 target=${rewritten[$(git rev-parse "$tag^{commit}")]:-}
78 [ -n "$target" ] && [ "$target" != 0000000000000000000000000000000000000000 ] || continue
79 if grep -qx "$target"$'\t'"refs/tags/$tag" <<<"$remote_tags" || git "${auth[@]}" push --force "$DST" "$target:refs/tags/$tag"; then
80 mirrored+=("$tag")
81 else
82 echo "warning: could not push tag $tag" >&2
83 fi
84done
85
86# Releases on the mirrored tags get the same name, notes and flags on GitHub (assets aren't copied).
87# Needs GITHUB_MIRROR_TOKEN for GitHub's API.
88if [ -n "${GITHUB_MIRROR_TOKEN:-}" ] && [ ${#mirrored[@]} -gt 0 ]; then
89 MIRROR_TAGS="${mirrored[*]}" python3 - <<'PY'
90import json, os, urllib.error, urllib.request
91
92src = os.environ.get("MIRROR_SRC_API", "https://diffinery.com/api/v3/repos/codician/diffinery")
93dst = os.environ.get("MIRROR_DST_API", "https://api.github.com/repos/codician-team/diffinery")
94tags = set(os.environ["MIRROR_TAGS"].split())
95
96def call(method, url, body=None, token=None):
97 req = urllib.request.Request(url, method=method, data=None if body is None else json.dumps(body).encode())
98 req.add_header("Accept", "application/vnd.github+json")
99 req.add_header("User-Agent", "diffinery-mirror")
100 req.add_header("Content-Type", "application/json")
101 if token:
102 req.add_header("Authorization", f"Bearer {token}")
103 try:
104 with urllib.request.urlopen(req, timeout=30) as r:
105 data = r.read()
106 return r.status, json.loads(data) if data else None
107 except urllib.error.HTTPError as e:
108 return e.code, None
109
110token = os.environ["GITHUB_MIRROR_TOKEN"]
111status, me = call("GET", f"{dst.split('/repos/')[0]}/user", token=token)
112me = me.get("login") if status == 200 else None
113status, releases = call("GET", f"{src}/releases?per_page=100", token=os.environ.get("DIFFINERY_TOKEN"))
114if status != 200:
115 raise SystemExit(f"could not list Diffinery releases ({status})")
116for r in releases:
117 if r["draft"] or r["tag_name"] not in tags:
118 continue
119 want = {"name": r["name"], "body": r["body"] or "", "prerelease": r["prerelease"]}
120 status, have = call("GET", f"{dst}/releases/tags/{r['tag_name']}", token=token)
121 if status == 200 and me and (have.get("author") or {}).get("login") != me:
122 # A mirrored release belongs to the mirror's account: one made by anyone else is made again.
123 status, _ = call("DELETE", f"{dst}/releases/{have['id']}", token=token)
124 if status != 204:
125 print(f"warning: could not replace GitHub release {r['tag_name']} ({status})")
126 continue
127 print(f"release {r['tag_name']}: deleted the copy {have['author']['login']} made")
128 status = 404
129 if status == 404:
130 status, _ = call("POST", f"{dst}/releases", {"tag_name": r["tag_name"], **want}, token)
131 print(f"release {r['tag_name']}: created ({status})")
132 elif status == 200 and any(have.get(k) != v for k, v in want.items()):
133 status, _ = call("PATCH", f"{dst}/releases/{have['id']}", want, token)
134 print(f"release {r['tag_name']}: updated ({status})")
135 elif status != 200:
136 print(f"warning: could not read GitHub release {r['tag_name']} ({status})")
137PY
138fi