Skip to content

Instantly share code, notes, and snippets.

@mshivam019
Last active February 16, 2026 14:17
Show Gist options
  • Select an option

  • Save mshivam019/11d618bb850ea7984189a35c978704bd to your computer and use it in GitHub Desktop.

Select an option

Save mshivam019/11d618bb850ea7984189a35c978704bd to your computer and use it in GitHub Desktop.
This is used to migrate an org from bit bucket to github
import fs from "fs";
import path from "path";
import { spawnSync } from "child_process";
/* ================= ENV ================= */
const BB_WORKSPACE = "";
const BB_TOKEN = "";
const BB_USERNAME = "";
const BB_EMAIL = "";
const GH_ORG = "";
const GH_TOKEN = "";
if (!BB_WORKSPACE || !BB_EMAIL || !BB_USERNAME || !BB_TOKEN || !GH_ORG || !GH_TOKEN) {
console.error("❌ Missing environment variables");
process.exit(1);
}
/* ================= PATHS ================= */
const ROOT = process.cwd();
const WORKDIR = path.join(ROOT, "migration");
const PROGRESS_FILE = path.join(ROOT, "progress.json");
const LARGE_FILE_LIMIT = 100 * 1024 * 1024;
if (!fs.existsSync(WORKDIR)) fs.mkdirSync(WORKDIR);
/* ================= PROGRESS ================= */
function loadProgress() {
if (!fs.existsSync(PROGRESS_FILE)) {
const init = { completed: [] };
fs.writeFileSync(PROGRESS_FILE, JSON.stringify(init, null, 2));
return init;
}
return JSON.parse(fs.readFileSync(PROGRESS_FILE, "utf8"));
}
function saveProgress(p) {
fs.writeFileSync(PROGRESS_FILE, JSON.stringify(p, null, 2));
}
/* ================= EXEC ================= */
function run(cmd, cwd) {
const res = spawnSync(cmd, {
cwd,
shell: true,
stdio: "inherit",
});
if (res.status !== 0) {
throw new Error(`Command failed: ${cmd}`);
}
}
/* ================= FETCH HELPERS ================= */
function bbFetch(url, options = {}) {
return fetch(`https://api.bitbucket.org/2.0${url}`, {
...options,
headers: {
Authorization:
"Basic " +
Buffer.from(`${BB_EMAIL}:${BB_TOKEN}`).toString("base64"),
"Content-Type": "application/json",
...(options.headers || {}),
},
});
}
function ghFetch(url, options = {}) {
return fetch(`https://api.github.com${url}`, {
...options,
headers: {
Authorization: `Bearer ${GH_TOKEN}`,
Accept: "application/vnd.github+json",
"Content-Type": "application/json",
...(options.headers || {}),
},
});
}
/* ================= BITBUCKET ================= */
async function getAllBitbucketRepos() {
let repos = [];
let url = `/repositories/${BB_WORKSPACE}?pagelen=100`;
while (url) {
const r = await bbFetch(url);
const data = await r.json();
repos.push(...data.values);
url = data.next
? data.next.replace("https://api.bitbucket.org/2.0", "")
: null;
}
return repos;
}
async function getBitbucketDefaultBranch(repo) {
const r = await bbFetch(`/repositories/${BB_WORKSPACE}/${repo}`);
const data = await r.json();
return data.mainbranch?.name;
}
/* ================= GITHUB ================= */
async function ensureGithubRepo(name) {
const res = await ghFetch(`/repos/${GH_ORG}/${name}`);
if (res.status === 404) {
console.log(`πŸ†• Creating GitHub repo: ${name}`);
await ghFetch(`/orgs/${GH_ORG}/repos`, {
method: "POST",
body: JSON.stringify({ name, private: true }),
});
return;
}
if (!res.ok) {
throw new Error(`GitHub repo check failed for ${name}`);
}
}
async function githubBranchExists(repo, branch) {
const r = await ghFetch(`/repos/${GH_ORG}/${repo}/branches/${branch}`);
return r.status === 200;
}
async function getGithubDefaultBranch(repo) {
const r = await ghFetch(`/repos/${GH_ORG}/${repo}`);
const data = await r.json();
return data.default_branch;
}
async function setGithubDefaultBranch(repo, branch) {
await ghFetch(`/repos/${GH_ORG}/${repo}`, {
method: "PATCH",
body: JSON.stringify({ default_branch: branch }),
});
}
/* ================= AUTO LFS ================= */
function setupLFS(repoDir) {
const output = spawnSync(
"git rev-list --objects --all",
{ cwd: repoDir, shell: true, encoding: "utf8" }
).stdout;
if (!output) return;
const largeFiles = output
.split("\n")
.map((l) => l.split(" ")[1])
.filter(Boolean)
.filter((f) => {
try {
return fs.statSync(path.join(repoDir, f)).size > LARGE_FILE_LIMIT;
} catch {
return false;
}
});
if (!largeFiles.length) return;
console.log(`πŸ“¦ Enabling Git LFS (${largeFiles.length} large files)`);
run("git lfs install", repoDir);
largeFiles.forEach((f) => run(`git lfs track "${f}"`, repoDir));
run("git add .gitattributes", repoDir);
run(`git commit -m "Track large files with Git LFS"`, repoDir);
}
/* ================= MIGRATE ================= */
async function migrateRepo(repo, progress) {
const name = repo.slug;
const repoDir = path.join(WORKDIR, `${name}.git`);
const lockFile = path.join(repoDir, "MIGRATED.lock");
if (progress.completed.includes(name)) {
console.log(`⏭️ Skipping ${name}`);
return;
}
console.log(`\nπŸš€ Migrating ${name}`);
const bbDefaultBranch = await getBitbucketDefaultBranch(name);
if (!fs.existsSync(repoDir)) {
run(
`git clone --mirror https://${BB_USERNAME}:${BB_TOKEN}@bitbucket.org/${BB_WORKSPACE}/${name}.git`,
WORKDIR
);
}
if (fs.existsSync(lockFile)) {
console.log(`πŸ”’ Already pushed ${name}`);
progress.completed.push(name);
saveProgress(progress);
return;
}
await ensureGithubRepo(name);
setupLFS(repoDir);
run(
`git push --mirror https://${GH_TOKEN}@github.com/${GH_ORG}/${name}.git`,
repoDir
);
/* ===== FIX DEFAULT BRANCH ===== */
if (bbDefaultBranch && (await githubBranchExists(name, bbDefaultBranch))) {
const ghDefault = await getGithubDefaultBranch(name);
if (ghDefault !== bbDefaultBranch) {
console.log(
`🌿 Fixing default branch: ${ghDefault} β†’ ${bbDefaultBranch}`
);
await setGithubDefaultBranch(name, bbDefaultBranch);
} else {
console.log(`βœ… Default branch correct (${bbDefaultBranch})`);
}
} else {
console.log(`⚠️ Could not verify default branch for ${name}`);
}
fs.writeFileSync(lockFile, "done");
progress.completed.push(name);
saveProgress(progress);
}
/* ================= MAIN ================= */
(async () => {
try {
const progress = loadProgress();
const repos = await getAllBitbucketRepos();
for (const repo of repos) {
await migrateRepo(repo, progress);
}
console.log("\nπŸŽ‰ MIGRATION COMPLETED SUCCESSFULLY");
} catch (e) {
console.error("\n❌ Migration stopped due to error");
console.error(e.message);
process.exit(1);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment