| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- #!/usr/bin/env bun
- /**
- * Analyze upstream changes without merging
- *
- * Generates a detailed conflict report for a target upstream version.
- *
- * Usage:
- * bun run script/upstream/analyze.ts --version v1.1.49
- * bun run script/upstream/analyze.ts --commit abc123
- * bun run script/upstream/analyze.ts --version v1.1.49 --base-branch catrielmuller/kilo-opencode-v1.1.44
- */
- import { $ } from "bun"
- import * as git from "./utils/git"
- import * as version from "./utils/version"
- import * as report from "./utils/report"
- import { defaultConfig, loadConfig } from "./utils/config"
- import { header, info, success, warn, error, divider, list } from "./utils/logger"
- interface AnalyzeOptions {
- version?: string
- commit?: string
- output?: string
- baseBranch?: string
- }
- function parseArgs(): AnalyzeOptions {
- const args = process.argv.slice(2)
- const options: AnalyzeOptions = {}
- const versionIdx = args.indexOf("--version")
- if (versionIdx !== -1 && args[versionIdx + 1]) {
- options.version = args[versionIdx + 1]
- }
- const commitIdx = args.indexOf("--commit")
- if (commitIdx !== -1 && args[commitIdx + 1]) {
- options.commit = args[commitIdx + 1]
- }
- const outputIdx = args.indexOf("--output")
- if (outputIdx !== -1 && args[outputIdx + 1]) {
- options.output = args[outputIdx + 1]
- }
- const baseBranchIdx = args.indexOf("--base-branch")
- if (baseBranchIdx !== -1 && args[baseBranchIdx + 1]) {
- options.baseBranch = args[baseBranchIdx + 1]
- }
- return options
- }
- async function main() {
- const options = parseArgs()
- const config = loadConfig(options.baseBranch ? { baseBranch: options.baseBranch } : undefined)
- header("Upstream Change Analysis")
- // Check upstream remote
- if (!(await git.hasUpstreamRemote())) {
- error("No 'upstream' remote found. Please add it:")
- info(" git remote add upstream [email protected]:anomalyco/opencode.git")
- process.exit(1)
- }
- // Fetch upstream
- info("Fetching upstream...")
- await git.fetchUpstream()
- // Determine target
- let target: version.VersionInfo | null = null
- if (options.commit) {
- target = await version.getVersionForCommit(options.commit)
- if (!target) {
- target = { version: "unknown", tag: "unknown", commit: options.commit }
- }
- } else if (options.version) {
- const versions = await version.getAvailableUpstreamVersions()
- target = versions.find((v) => v.version === options.version || v.tag === options.version) || null
- if (!target) {
- error(`Version ${options.version} not found`)
- info("Use 'bun run script/upstream/list-versions.ts' to see available versions")
- process.exit(1)
- }
- } else {
- target = await version.getLatestUpstreamVersion()
- }
- if (!target) {
- error("Could not determine target version")
- process.exit(1)
- }
- success(`Analyzing: ${target.tag} (${target.commit.slice(0, 8)})`)
- divider()
- // Analyze conflicts
- info("Analyzing changes...")
- // Use commit hash directly since we may not have the tag fetched locally
- const conflicts = await report.analyzeConflicts(target.commit, config.baseBranch, config.keepOurs)
- // Group by type
- const byType = new Map<string, report.ConflictFile[]>()
- for (const c of conflicts) {
- const list = byType.get(c.type) || []
- list.push(c)
- byType.set(c.type, list)
- }
- // Group by recommendation
- const byRec = new Map<string, report.ConflictFile[]>()
- for (const c of conflicts) {
- const list = byRec.get(c.recommendation) || []
- list.push(c)
- byRec.set(c.recommendation, list)
- }
- console.log()
- success(`Total files changed: ${conflicts.length}`)
- console.log()
- // By type
- info("Changes by type:")
- for (const [type, files] of byType) {
- console.log(` ${type.padEnd(12)} ${files.length}`)
- }
- console.log()
- // By recommendation
- info("Changes by recommendation:")
- for (const [rec, files] of byRec) {
- const label =
- rec === "keep-ours"
- ? "Keep Kilo's"
- : rec === "codemod"
- ? "Auto-transform"
- : rec === "keep-theirs"
- ? "Take upstream"
- : "Manual review"
- console.log(` ${label.padEnd(16)} ${files.length}`)
- }
- // Show manual review files
- const manual = byRec.get("manual") || []
- if (manual.length > 0 && manual.length <= 20) {
- console.log()
- warn("Files requiring manual review:")
- list(manual.map((f) => f.path))
- }
- // Generate report
- const conflictReport: report.ConflictReport = {
- timestamp: new Date().toISOString(),
- upstreamVersion: target.version,
- upstreamCommit: target.commit,
- baseBranch: config.baseBranch,
- mergeBranch: "",
- totalConflicts: conflicts.length,
- conflicts,
- recommendations: [],
- }
- if (manual.length > 0) {
- conflictReport.recommendations.push(`${manual.length} files require manual review`)
- }
- const codemodFiles = byRec.get("codemod") || []
- if (codemodFiles.length > 0) {
- conflictReport.recommendations.push(`${codemodFiles.length} files will be auto-transformed`)
- }
- const keepOursFiles = byRec.get("keep-ours") || []
- if (keepOursFiles.length > 0) {
- conflictReport.recommendations.push(`${keepOursFiles.length} files will keep Kilo's version`)
- }
- // Save report
- const outputPath = options.output || `upstream-analysis-${target.version}.md`
- await report.saveReport(conflictReport, outputPath)
- divider()
- success(`Report saved to ${outputPath}`)
- console.log()
- info("Next steps:")
- info(" 1. Review the report")
- info(" 2. Run merge with: bun run script/upstream/merge.ts --version " + target.tag)
- }
- main().catch((err) => {
- error(`Error: ${err}`)
- process.exit(1)
- })
|