Files
2026-02-05 13:31:15 -03:00

136 lines
3.8 KiB
TypeScript

#!/usr/bin/env bun
/**
* Keep Kilo's version of specific files during merge
*
* This script handles files that should always keep Kilo's version
* and not be overwritten by upstream changes.
*/
import { $ } from "bun"
import { info, success, warn, error, debug } from "../utils/logger"
import { defaultConfig } from "../utils/config"
import { checkoutOurs, stageFiles, getConflictedFiles } from "../utils/git"
export interface KeepOursResult {
file: string
action: "kept" | "skipped" | "not-conflicted"
dryRun: boolean
}
export interface KeepOursOptions {
dryRun?: boolean
verbose?: boolean
files?: string[]
}
/**
* Check if a file matches any keep-ours patterns
*/
export function shouldKeepOurs(filePath: string, patterns: string[]): boolean {
return patterns.some((pattern) => {
// Exact match
if (filePath === pattern) return true
// Pattern match (simple glob)
if (pattern.includes("*")) {
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$")
return regex.test(filePath)
}
// Contains match
return filePath.includes(pattern)
})
}
/**
* Keep Kilo's version of conflicted files
*/
export async function keepOursFiles(options: KeepOursOptions = {}): Promise<KeepOursResult[]> {
const results: KeepOursResult[] = []
// Get list of conflicted files
const conflicted = await getConflictedFiles()
if (conflicted.length === 0) {
info("No conflicted files found")
return results
}
info(`Found ${conflicted.length} conflicted files`)
const patterns = options.files || defaultConfig.keepOurs
for (const file of conflicted) {
if (shouldKeepOurs(file, patterns)) {
if (options.dryRun) {
info(`[DRY-RUN] Would keep ours: ${file}`)
results.push({ file, action: "kept", dryRun: true })
} else {
try {
await checkoutOurs([file])
await stageFiles([file])
success(`Kept ours: ${file}`)
results.push({ file, action: "kept", dryRun: false })
} catch (err) {
error(`Failed to keep ours for ${file}: ${err}`)
results.push({ file, action: "skipped", dryRun: false })
}
}
} else {
debug(`Skipping ${file} (not in keep-ours list)`)
results.push({ file, action: "not-conflicted", dryRun: options.dryRun ?? false })
}
}
return results
}
/**
* Reset specific files to Kilo's version (even if not conflicted)
*/
export async function resetToOurs(files: string[], options: KeepOursOptions = {}): Promise<KeepOursResult[]> {
const results: KeepOursResult[] = []
for (const file of files) {
if (options.dryRun) {
info(`[DRY-RUN] Would reset to ours: ${file}`)
results.push({ file, action: "kept", dryRun: true })
} else {
try {
// Get the file from HEAD (Kilo's version)
await $`git checkout HEAD -- ${file}`
success(`Reset to ours: ${file}`)
results.push({ file, action: "kept", dryRun: false })
} catch (err) {
warn(`Could not reset ${file}: ${err}`)
results.push({ file, action: "skipped", dryRun: false })
}
}
}
return results
}
// CLI entry point
if (import.meta.main) {
const args = process.argv.slice(2)
const dryRun = args.includes("--dry-run")
const verbose = args.includes("--verbose")
// Get specific files if provided
const files = args.filter((a) => !a.startsWith("--"))
if (dryRun) {
info("Running in dry-run mode (no files will be modified)")
}
const results =
files.length > 0 ? await resetToOurs(files, { dryRun, verbose }) : await keepOursFiles({ dryRun, verbose })
const kept = results.filter((r) => r.action === "kept")
console.log()
success(`Kept Kilo's version for ${kept.length} files`)
if (dryRun) {
info("Run without --dry-run to apply changes")
}
}