fix(upstream): preserve compatibility tree overlays

This commit is contained in:
marius-kilocode
2026-07-22 17:20:05 +02:00
parent a51864cd75
commit f1e22ebf21
3 changed files with 91 additions and 2 deletions
+9 -2
View File
@@ -551,10 +551,17 @@ async function main() {
await git.stageAll()
const compatMessage = `refactor: kilo compat for ${targetVersion.tag}`
if (prior) {
const tree = await git.writeTree()
const transformed = await git.writeTree()
const tree = await git.overlayCompatTree({
previous: prior.commit,
upstream: prior.upstream,
target: targetVersion.commit,
transformed,
extra: [".opencode-version"],
})
const commit = await git.createCommit(tree, compatMessage, prior.commit)
await git.updateBranch(opencodeBranch, commit)
await git.checkout(opencodeBranch)
await $`git reset --hard ${commit}`.quiet()
} else {
await git.commit(compatMessage)
}
+40
View File
@@ -9,6 +9,7 @@ import {
getCommitHash,
getCommitParents,
isAncestor,
overlayCompatTree,
recordAncestor,
updateBranch,
writeTree,
@@ -108,3 +109,42 @@ test("finds previous compatibility commit when upstream tags diverge", async ()
expect(found?.upstream).toBe(side)
expect(found?.commit).not.toBe(ancient)
})
test("compatibility tree preserves Kilo paths unchanged upstream", async () => {
await Bun.write("shared.txt", "opencode A\n")
await Bun.write("unchanged.txt", "opencode unchanged\n")
await Bun.write("removed.txt", "remove me\n")
const old = await commit("release: v1.0.0")
await Bun.write("shared.txt", "opencode B\n")
await Bun.write("added.txt", "opencode added\n")
await rm("removed.txt")
const target = await commit("release: v1.0.1")
await $`git checkout -b main ${old}`.quiet()
await Bun.write("shared.txt", "kilo A\n")
await Bun.write("unchanged.txt", "kilo marker\n")
await Bun.write("kilo-only.txt", "keep me\n")
const previous = await commit("refactor: kilo compat for v1.0.0")
await $`git checkout --detach ${target}`.quiet()
await Bun.write("shared.txt", "kilo B\n")
await Bun.write("added.txt", "kilo added\n")
await Bun.write(".opencode-version", "v1.0.1\n")
await $`git add -A`.quiet()
const transformed = await writeTree()
const tree = await overlayCompatTree({
previous,
upstream: old,
target,
transformed,
extra: [".opencode-version"],
})
expect(await $`git show ${`${tree}:shared.txt`}`.text()).toBe("kilo B\n")
expect(await $`git show ${`${tree}:added.txt`}`.text()).toBe("kilo added\n")
expect(await $`git show ${`${tree}:unchanged.txt`}`.text()).toBe("kilo marker\n")
expect(await $`git show ${`${tree}:kilo-only.txt`}`.text()).toBe("keep me\n")
expect(await $`git show ${`${tree}:.opencode-version`}`.text()).toBe("v1.0.1\n")
expect((await $`git cat-file -e ${`${tree}:removed.txt`}`.quiet().nothrow()).exitCode).not.toBe(0)
})
+42
View File
@@ -206,6 +206,48 @@ export async function writeTree(): Promise<string> {
return result.trim()
}
function chunks<T>(items: T[], size = 200): T[][] {
return Array.from({ length: Math.ceil(items.length / size) }, (_, idx) => items.slice(idx * size, (idx + 1) * size))
}
/**
* Overlay transformed upstream changes onto the previous compatibility tree.
* Paths unchanged upstream keep their prior Kilo content, including Kilo-only
* files and marker-bearing shared files.
*/
export async function overlayCompatTree(input: {
previous: string
upstream: string
target: string
transformed: string
extra?: string[]
}): Promise<string> {
const diff = await $`git diff --name-only -z ${input.upstream} ${input.target}`.quiet().nothrow()
if (diff.exitCode !== 0) throw new Error(`Failed to list upstream changes: ${diff.stderr.toString()}`)
const paths = Array.from(
new Set([
...diff.stdout
.toString()
.split("\0")
.filter((path) => path.length > 0),
...(input.extra ?? []),
]),
)
const keep: string[] = []
const remove: string[] = []
for (const path of paths) {
const result = await $`git cat-file -e ${`${input.transformed}:${path}`}`.quiet().nothrow()
if (result.exitCode === 0) keep.push(path)
else remove.push(path)
}
await $`git read-tree ${input.previous}`.quiet()
for (const batch of chunks(keep)) await $`git checkout ${input.transformed} -- ${batch}`.quiet()
for (const batch of chunks(remove)) await $`git update-index --force-remove -- ${batch}`.quiet()
return writeTree()
}
export async function createCommit(tree: string, message: string, parent: string): Promise<string> {
const result = await $`git commit-tree ${tree} -p ${parent} -m ${message}`.text()
return result.trim()