198 lines
5.5 KiB
TypeScript
198 lines
5.5 KiB
TypeScript
#!/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 git@github.com: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)
|
|
})
|