mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-19 01:51:21 +08:00
fix: skip removed packages during upstream merge
This commit is contained in:
@@ -76,6 +76,7 @@ The merge automation follows this process, applying **all transformations BEFORE
|
||||
- `<author>/opencode-<version>` - Transformed upstream branch
|
||||
|
||||
5. **Apply ALL transformations to upstream branch (PRE-MERGE)**:
|
||||
- Remove files that should not exist in Kilo (`skipFiles`)
|
||||
- Transform package names (opencode-ai -> @kilocode/cli)
|
||||
- Preserve Kilo's versions
|
||||
- Transform i18n files with Kilo branding
|
||||
@@ -161,15 +162,16 @@ Configuration is defined in `utils/config.ts`:
|
||||
|
||||
The following transforms are applied to the opencode branch before merging:
|
||||
|
||||
1. **Package names** - `opencode-ai` -> `@kilocode/cli`, etc.
|
||||
2. **Versions** - Preserve Kilo's version numbers
|
||||
3. **i18n files** - OpenCode -> Kilo in user-visible strings
|
||||
4. **Branding files** - UI components, configs with branding only
|
||||
5. **Tauri configs** - Desktop app identifiers, names
|
||||
6. **package.json** - Names, dependencies, Kilo injections
|
||||
7. **Scripts** - GitHub API references
|
||||
8. **Extensions** - Zed, etc.
|
||||
9. **Web/docs** - Documentation files
|
||||
1. **Skip files** - Remove upstream-only packages/files that should not exist in Kilo
|
||||
2. **Package names** - `opencode-ai` -> `@kilocode/cli`, etc.
|
||||
3. **Versions** - Preserve Kilo's version numbers
|
||||
4. **i18n files** - OpenCode -> Kilo in user-visible strings
|
||||
5. **Branding files** - UI components, configs with branding only
|
||||
6. **Tauri configs** - Desktop app identifiers, names
|
||||
7. **package.json** - Names, dependencies, Kilo injections
|
||||
8. **Scripts** - GitHub API references
|
||||
9. **Extensions** - Zed, etc.
|
||||
10. **Web/docs** - Documentation files
|
||||
|
||||
### Post-Merge Strategies
|
||||
|
||||
|
||||
@@ -405,6 +405,13 @@ async function main() {
|
||||
// This reduces conflicts by transforming upstream code to Kilo conventions BEFORE merging
|
||||
logger.step(6, 8, "Applying transformations to opencode branch (pre-merge)...")
|
||||
|
||||
logger.info("Removing files skipped in Kilo...")
|
||||
const skips = await skipFiles({ dryRun: false, verbose: options.verbose, force: true })
|
||||
const count = skips.filter((r) => r.action === "removed").length
|
||||
if (count > 0) {
|
||||
logger.success(`Removed ${count} skipped file(s) from opencode branch`)
|
||||
}
|
||||
|
||||
// 6a. Transform package names (opencode-ai -> @kilocode/cli)
|
||||
logger.info("Transforming package names...")
|
||||
const nameResults = await transformPackageNames({ dryRun: false, verbose: options.verbose })
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { shouldSkip } from "./skip-files"
|
||||
|
||||
test("matches hosted package glob paths", () => {
|
||||
expect(shouldSkip("packages/web/package.json", ["packages/web/**"])).toBe(true)
|
||||
expect(shouldSkip("packages/web/src/content/docs/ja/zen.mdx", ["packages/web/**"])).toBe(true)
|
||||
expect(shouldSkip("packages/console/app/package.json", ["packages/console/**"])).toBe(true)
|
||||
})
|
||||
|
||||
test("does not match sibling package glob paths", () => {
|
||||
expect(shouldSkip("packages/app/package.json", ["packages/web/**"])).toBe(false)
|
||||
})
|
||||
|
||||
test("matches extension glob paths", () => {
|
||||
expect(shouldSkip(".github/VOUCHED.td", [".github/VOUCHED.*"])).toBe(true)
|
||||
})
|
||||
@@ -13,6 +13,7 @@
|
||||
import { $ } from "bun"
|
||||
import { info, success, warn, debug } from "../utils/logger"
|
||||
import { defaultConfig } from "../utils/config"
|
||||
import { matches } from "../utils/match"
|
||||
|
||||
export interface SkipResult {
|
||||
file: string
|
||||
@@ -24,30 +25,14 @@ export interface SkipOptions {
|
||||
dryRun?: boolean
|
||||
verbose?: boolean
|
||||
patterns?: string[]
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
})
|
||||
return matches(filePath, patterns)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +66,21 @@ async function getUnmergedFiles(): Promise<string[]> {
|
||||
.filter((f) => f.length > 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tracked files from the current branch.
|
||||
*/
|
||||
async function getTrackedFiles(): Promise<string[]> {
|
||||
const result = await $`git ls-files`.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
|
||||
*/
|
||||
@@ -125,7 +125,8 @@ export async function skipFiles(options: SkipOptions = {}): Promise<SkipResult[]
|
||||
// Get all files involved in the merge
|
||||
const stagedFiles = await getUpstreamFiles()
|
||||
const unmergedFiles = await getUnmergedFiles()
|
||||
const allFiles = [...new Set([...stagedFiles, ...unmergedFiles])]
|
||||
const tracked = options.force ? await getTrackedFiles() : []
|
||||
const allFiles = [...new Set([...stagedFiles, ...unmergedFiles, ...tracked])]
|
||||
|
||||
if (allFiles.length === 0) {
|
||||
info("No files to process")
|
||||
@@ -138,7 +139,7 @@ export async function skipFiles(options: SkipOptions = {}): Promise<SkipResult[]
|
||||
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")
|
||||
const existedInKilo = options.force ? false : await fileExistsInRef(file, "HEAD")
|
||||
|
||||
if (existedInKilo) {
|
||||
debug(`Skipping ${file} - exists in Kilo, not removing`)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Match repository paths against exact, regex, or simple glob patterns.
|
||||
*/
|
||||
|
||||
function esc(text: string): string {
|
||||
return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&")
|
||||
}
|
||||
|
||||
function glob(pattern: string): RegExp {
|
||||
const source = pattern
|
||||
.split("**")
|
||||
.map((part) => part.split("*").map(esc).join("[^/]*"))
|
||||
.join(".*")
|
||||
|
||||
return new RegExp(`^${source}$`)
|
||||
}
|
||||
|
||||
export function match(path: string, pattern: string): boolean {
|
||||
if (path === pattern) return true
|
||||
|
||||
if (pattern.startsWith("^") || pattern.includes("\\")) {
|
||||
return new RegExp(pattern).test(path)
|
||||
}
|
||||
|
||||
if (pattern.includes("*")) {
|
||||
return glob(pattern).test(path)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function matches(path: string, patterns: string[]): boolean {
|
||||
return patterns.some((pattern) => match(path, pattern))
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { getRecommendation } from "./report"
|
||||
|
||||
test("recommends skip for hosted package globs", () => {
|
||||
expect(getRecommendation("packages/web/src/content/docs/ja/zen.mdx", [], ["packages/web/**"]).recommendation).toBe(
|
||||
"skip",
|
||||
)
|
||||
expect(getRecommendation("packages/console/app/package.json", [], ["packages/console/**"]).recommendation).toBe(
|
||||
"skip",
|
||||
)
|
||||
})
|
||||
|
||||
test("does not recommend skip for unrelated packages", () => {
|
||||
expect(getRecommendation("packages/app/package.json", [], ["packages/web/**"]).recommendation).toBe(
|
||||
"package-transform",
|
||||
)
|
||||
})
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { $ } from "bun"
|
||||
import { matches } from "./match"
|
||||
|
||||
export interface ConflictReport {
|
||||
timestamp: string
|
||||
@@ -119,7 +120,7 @@ export function classifyFile(path: string): ConflictFile["type"] {
|
||||
* Check if a file should be skipped (not added from upstream)
|
||||
*/
|
||||
function shouldSkipFile(path: string, skipPatterns: string[]): boolean {
|
||||
return skipPatterns.some((pattern) => path === pattern || path.includes(pattern))
|
||||
return matches(path, skipPatterns)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user