| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- #!/usr/bin/env bun
- /**
- * Skip files transform - handles files that should be completely skipped during merge
- *
- * These are files that exist in upstream but should NOT exist in Kilo fork.
- * Examples: README.*.md (translated READMEs), STATS.md, etc.
- *
- * During merge, these files will be:
- * - Removed if they were added from upstream
- * - Kept deleted if they don't exist in Kilo
- */
- import { $ } from "bun"
- import { info, success, warn, debug } from "../utils/logger"
- import { defaultConfig } from "../utils/config"
- export interface SkipResult {
- file: string
- action: "removed" | "skipped" | "not-found"
- dryRun: boolean
- }
- export interface SkipOptions {
- dryRun?: boolean
- verbose?: boolean
- patterns?: string[]
- }
- /**
- * Check if a file matches any skip patterns
- */
- export function shouldSkip(filePath: string, patterns: string[]): boolean {
- return patterns.some((pattern) => {
- // Exact match
- if (filePath === pattern) return true
- // Regex pattern (e.g., README\.[a-z]+\.md)
- if (pattern.startsWith("^") || pattern.includes("\\")) {
- const regex = new RegExp(pattern)
- return regex.test(filePath)
- }
- // Glob-style pattern
- if (pattern.includes("*")) {
- const regex = new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$")
- return regex.test(filePath)
- }
- return false
- })
- }
- /**
- * Get list of files that were added/modified from upstream during merge
- */
- async function getUpstreamFiles(): Promise<string[]> {
- // Get files that are staged (after merge)
- const result = await $`git diff --cached --name-only`.quiet().nothrow()
- if (result.exitCode !== 0) return []
- return result.stdout
- .toString()
- .trim()
- .split("\n")
- .filter((f) => f.length > 0)
- }
- /**
- * Get list of unmerged (conflicted) files
- */
- async function getUnmergedFiles(): Promise<string[]> {
- const result = await $`git diff --name-only --diff-filter=U`.quiet().nothrow()
- if (result.exitCode !== 0) return []
- return result.stdout
- .toString()
- .trim()
- .split("\n")
- .filter((f) => f.length > 0)
- }
- /**
- * Check if a file exists in a specific git ref
- */
- async function fileExistsInRef(file: string, ref: string): Promise<boolean> {
- const result = await $`git cat-file -e ${ref}:${file}`.quiet().nothrow()
- return result.exitCode === 0
- }
- /**
- * Remove a file from the merge (git rm)
- */
- async function removeFile(file: string): Promise<boolean> {
- const result = await $`git rm -f ${file}`.quiet().nothrow()
- return result.exitCode === 0
- }
- /**
- * Skip files that shouldn't exist in Kilo fork
- *
- * This function handles files that:
- * 1. Match skip patterns (like README.*.md)
- * 2. Were added from upstream during merge
- * 3. Don't exist in Kilo's version (HEAD before merge)
- */
- export async function skipFiles(options: SkipOptions = {}): Promise<SkipResult[]> {
- const results: SkipResult[] = []
- const patterns = options.patterns || defaultConfig.skipFiles
- if (!patterns || patterns.length === 0) {
- info("No skip patterns configured")
- return results
- }
- // Get all files involved in the merge
- const stagedFiles = await getUpstreamFiles()
- const unmergedFiles = await getUnmergedFiles()
- const allFiles = [...new Set([...stagedFiles, ...unmergedFiles])]
- if (allFiles.length === 0) {
- info("No files to process")
- return results
- }
- debug(`Checking ${allFiles.length} files against ${patterns.length} skip patterns`)
- for (const file of allFiles) {
- if (!shouldSkip(file, patterns)) continue
- // Check if file existed in Kilo before merge (HEAD~1 or the merge base)
- const existedInKilo = await fileExistsInRef(file, "HEAD")
- if (existedInKilo) {
- debug(`Skipping ${file} - exists in Kilo, not removing`)
- results.push({ file, action: "skipped", dryRun: options.dryRun ?? false })
- continue
- }
- // File doesn't exist in Kilo - should be removed
- if (options.dryRun) {
- info(`[DRY-RUN] Would remove: ${file}`)
- results.push({ file, action: "removed", dryRun: true })
- } else {
- const removed = await removeFile(file)
- if (removed) {
- success(`Removed: ${file}`)
- results.push({ file, action: "removed", dryRun: false })
- } else {
- warn(`Failed to remove: ${file}`)
- results.push({ file, action: "not-found", dryRun: false })
- }
- }
- }
- return results
- }
- /**
- * Skip files from a specific list (used during conflict resolution)
- */
- export async function skipSpecificFiles(files: string[], options: SkipOptions = {}): Promise<SkipResult[]> {
- const results: SkipResult[] = []
- for (const file of files) {
- if (options.dryRun) {
- info(`[DRY-RUN] Would remove: ${file}`)
- results.push({ file, action: "removed", dryRun: true })
- } else {
- const removed = await removeFile(file)
- if (removed) {
- success(`Removed: ${file}`)
- results.push({ file, action: "removed", dryRun: false })
- } else {
- warn(`Failed to remove: ${file}`)
- results.push({ file, action: "not-found", 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 skipSpecificFiles(files, { dryRun, verbose }) : await skipFiles({ dryRun, verbose })
- const removed = results.filter((r) => r.action === "removed")
- console.log()
- success(`Removed ${removed.length} files`)
- if (dryRun) {
- info("Run without --dry-run to apply changes")
- }
- }
|