OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / scripts / migrate-v1-extract.mjs

86 lines · javascript ref:
#!/usr/bin/env bare
/*
 * migrate-v1-extract.mjs — Phase 1 of the v1 → v3 migration.
 *
 * Runs under BARE because v1's MetadataBase imports bare-path / bare-fs
 * / bare-os (with native bindings). Reads v1 issues + comments from the
 * MetadataBase Autobase view and dumps them to a JSON file for Phase 2
 * (migrate-v1-issues.mjs, which runs under Node and writes to v3).
 *
 * Usage:
 *   bare scripts/migrate-v1-extract.mjs \
 *     --v1-storage /path/to/pear-git-storage \
 *     --out /tmp/v1-migration.json
 *
 * v1 daemon MUST be stopped (RocksDB exclusive lock).
 */

import fs from 'bare-fs'
import { MetadataBase } from '/storage/projects/code/hypercore-kb/hyper-fleeky/pear-git/lib/metadata-base.js'

function parseArgs (argv) {
  const args = { v1Storage: null, out: null }
  for (let i = 2; i < argv.length; i++) {
    if (argv[i] === '--v1-storage') args.v1Storage = argv[++i]
    else if (argv[i] === '--out') args.out = argv[++i]
  }
  if (!args.v1Storage) throw new Error('--v1-storage required')
  if (!args.out) throw new Error('--out required')
  return args
}

async function main () {
  const args = parseArgs(Bare.argv)
  console.log('[extract] v1 storage:', args.v1Storage)
  console.log('[extract] output    :', args.out)

  const mb = new MetadataBase({ storagePath: args.v1Storage, swarmOpts: { bootstrap: [] } })
  await mb.ready()
  try { await mb.swarm.destroy() } catch {}
  await mb._base.update()
  console.log('[extract] v1 MetadataBase opened')

  const byRepo = new Map()
  for await (const { key, value } of mb._base.view.createReadStream({ gt: 'issue:', lt: 'issue:\xff' })) {
    const parts = key.split(':')
    if (parts.length < 3) continue
    const repo = parts[1]
    const id = parseInt(parts[2], 10)
    if (isNaN(id)) continue
    if (!byRepo.has(repo)) byRepo.set(repo, { issues: [], comments: [] })
    byRepo.get(repo).issues.push({ id, ...value })
  }
  for await (const { key, value } of mb._base.view.createReadStream({ gt: 'comment:', lt: 'comment:\xff' })) {
    const parts = key.split(':')
    if (parts.length < 4) continue
    const repo = parts[1]
    const issueId = parseInt(parts[2], 10)
    const commentId = parts.slice(3).join(':')
    if (isNaN(issueId)) continue
    if (!byRepo.has(repo)) continue
    byRepo.get(repo).comments.push({ issueId, commentId, ...value })
  }

  let totalIssues = 0, totalComments = 0
  const dump = {}
  for (const [repo, b] of byRepo) {
    totalIssues += b.issues.length
    totalComments += b.comments.length
    console.log(`[extract] ${repo}: ${b.issues.length} issues, ${b.comments.length} comments`)
    dump[repo] = b
  }
  console.log(`[extract] total: ${totalIssues} issues, ${totalComments} comments across ${byRepo.size} repos`)

  try { await mb.close?.() } catch {}
  try { await mb.store.close() } catch {}

  fs.writeFileSync(args.out, JSON.stringify(dump, null, 2))
  console.log('[extract] wrote', args.out)
}

main().catch(err => {
  console.error('[extract] ERROR:', err.message)
  console.error(err.stack)
  Bare.exit(1)
})