mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
One census rewrites the commit grid: each commit once, forks only after the fork, other branches included, and commits that landed upstream kept apart as contributed back. Zach's addresses count as one person. The forge account merge is a dry-run script and does not delete user id 1. Co-authored-by: z <[email protected]>
124 lines
4.7 KiB
JavaScript
124 lines
4.7 KiB
JavaScript
/**
|
|
* One person on the forge: Zach Kelling.
|
|
*
|
|
* GitHub login zeekay is the account. There is no GitHub user [email protected].
|
|
* GitHub login `z` is Tyler Mulligan and is not touched.
|
|
* On the forge, `z` (id 1, the account the server was installed with) and
|
|
* `zeekay` (id 91) are still two users. This moves what `z` owns onto zeekay
|
|
* and points zeekay at https://github.com/zeekay.
|
|
*
|
|
* It does not delete user id 1.
|
|
*
|
|
* Dry run unless --apply. Apply needs FORGE_TOKEN (a forge admin token).
|
|
*
|
|
* Memberships are copied onto zeekay. This forge has no PUT on
|
|
* /orgs/{org}/members/{user}; a member is added with
|
|
* PUT /teams/{id}/members/zeekay. z stays on those teams.
|
|
* Repositories z owns are transferred (POST /repos/{owner}/{repo}/transfer).
|
|
* zeekay is linked with PATCH /admin/users/zeekay (source_id, login_name).
|
|
*
|
|
* node scripts/forge-link-user.mjs
|
|
* FORGE_TOKEN=… node scripts/forge-link-user.mjs --apply
|
|
*
|
|
* FORGE_TOKEN is an admin token for https://forge.hanzo.ai (Authorization: token …).
|
|
* Nothing here deletes user id 1. A 202 on a transfer still needs zeekay to accept it.
|
|
*/
|
|
const FORGE = "https://forge.hanzo.ai/v1";
|
|
const FROM = "z";
|
|
const TO = "zeekay";
|
|
const GITHUB = "https://github.com/zeekay";
|
|
const apply = process.argv.includes("--apply");
|
|
const token = process.env.FORGE_TOKEN;
|
|
|
|
const headers = token
|
|
? { authorization: `token ${token}`, "content-type": "application/json", accept: "application/json" }
|
|
: { accept: "application/json" };
|
|
|
|
async function api(path, opts = {}) {
|
|
const res = await fetch(`${FORGE}${path}`, { ...opts, headers: { ...headers, ...(opts.headers ?? {}) } });
|
|
const text = await res.text();
|
|
let body = null;
|
|
if (text) {
|
|
try { body = JSON.parse(text); } catch { body = text; }
|
|
}
|
|
return { status: res.status, body };
|
|
}
|
|
|
|
async function pages(path) {
|
|
const rows = [];
|
|
for (let page = 1; ; page++) {
|
|
const join = path.includes("?") ? "&" : "?";
|
|
const { status, body } = await api(`${path}${join}limit=50&page=${page}`);
|
|
if (status === 401 || status === 403) return { denied: true, rows };
|
|
if (!Array.isArray(body) || !body.length) break;
|
|
rows.push(...body);
|
|
if (body.length < 50) break;
|
|
}
|
|
return { denied: false, rows };
|
|
}
|
|
|
|
console.log(`forge: link ${FROM} (id 1) into ${TO}, GitHub ${GITHUB}`);
|
|
console.log(apply ? "apply" : "dry run (pass --apply with FORGE_TOKEN to change the forge)");
|
|
|
|
if (apply && !token) {
|
|
console.error("FORGE_TOKEN is not set");
|
|
process.exit(1);
|
|
}
|
|
|
|
const from = await api(`/users/${FROM}`);
|
|
const to = await api(`/users/${TO}`);
|
|
console.log(`${FROM}: ${from.status} id ${from.body?.id} ${from.body?.full_name ?? ""}`);
|
|
console.log(`${TO}: ${to.status} id ${to.body?.id} ${to.body?.full_name ?? ""}`);
|
|
if (from.body?.id === 1) console.log("user id 1 is kept; this script never deletes it");
|
|
|
|
const repos = await pages(`/users/${FROM}/repos`);
|
|
const orgs = await pages(`/users/${FROM}/orgs`);
|
|
if (repos.denied || orgs.denied) {
|
|
console.log("The forge will not say what z owns without an admin token. Nothing was changed.");
|
|
if (!apply) process.exit(0);
|
|
}
|
|
|
|
const teams = [];
|
|
for (const org of orgs.rows) {
|
|
const listed = await pages(`/orgs/${org.username}/teams`);
|
|
for (const team of listed.rows) {
|
|
const member = await api(`/teams/${team.id}/members/${FROM}`);
|
|
if (member.status === 200 || member.status === 204) teams.push({ org: org.username, id: team.id, name: team.name });
|
|
}
|
|
}
|
|
|
|
console.log(`repositories to move: ${repos.rows.length}`);
|
|
for (const repo of repos.rows) console.log(` transfer ${repo.full_name} → ${TO}`);
|
|
console.log(`organizations z belongs to: ${orgs.rows.length}`);
|
|
console.log(`teams to add ${TO} to: ${teams.length}`);
|
|
for (const team of teams) console.log(` team ${team.org}/${team.name} (${team.id}) += ${TO}`);
|
|
|
|
if (!apply) process.exit(0);
|
|
|
|
for (const repo of repos.rows) {
|
|
const { status } = await api(`/repos/${repo.owner.login}/${repo.name}/transfer`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ new_owner: TO }),
|
|
});
|
|
console.log(`transfer ${repo.full_name}: ${status}`);
|
|
}
|
|
|
|
for (const team of teams) {
|
|
const { status } = await api(`/teams/${team.id}/members/${TO}`, { method: "PUT" });
|
|
console.log(`team ${team.org}/${team.name}: ${status}`);
|
|
}
|
|
|
|
const auths = await api("/admin/auths");
|
|
const github = Array.isArray(auths.body) ? auths.body.find((a) => /github/i.test(`${a.name} ${a.type}`)) : null;
|
|
const edit = await api(`/admin/users/${TO}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify({
|
|
source_id: github?.id ?? 0,
|
|
login_name: "zeekay",
|
|
full_name: "Zach Kelling",
|
|
website: GITHUB,
|
|
description: "GitHub: zeekay",
|
|
}),
|
|
});
|
|
console.log(`link ${TO} → ${GITHUB}: ${edit.status}${github ? ` (login source ${github.id})` : " (no GitHub login source; website set)"}`);
|