feat(upstream): record last merged tag in .opencode-version

Avoids the ls-remote + merge-base --is-ancestor walk in last() for the
common case by reading the recorded tag from .opencode-version. merge.ts
writes the file as part of the pre-merge compat commit so future runs of
fix-kilocode-markers, reset-to-upstream, and find-reset-candidates can
resolve the base tag instantly. Seeded with v1.14.33 (PR #9978).
This commit is contained in:
kiloconnect[bot]
2026-05-11 08:11:40 +00:00
parent 2d37f83daa
commit 8710e9d9de
4 changed files with 72 additions and 1 deletions
+1
View File
@@ -0,0 +1 @@
v1.14.33
+3 -1
View File
@@ -243,7 +243,9 @@ Options:
--dry-run Show what would change without writing the file
```
The command finds the newest upstream tag already merged into `HEAD`, reads that upstream version of the file, applies the same branding transforms used by upstream merge automation, strips existing `kilocode_change` markers from the current file, and adds fresh markers around the remaining lines that differ from upstream.
The command finds the newest upstream tag already merged into `HEAD` (read from `.opencode-version` at the repo root, falling back to an `ls-remote` + `merge-base --is-ancestor` walk), reads that upstream version of the file, applies the same branding transforms used by upstream merge automation, strips existing `kilocode_change` markers from the current file, and adds fresh markers around the remaining lines that differ from upstream.
The `.opencode-version` file is a single-line tag (e.g. `v1.14.33`) recorded by `merge.ts` after every successful upstream merge. Editing it by hand pins the "last merged" tag for the per-file commands above; delete it to fall back to the slower automatic discovery.
### reset-to-upstream.ts
+6
View File
@@ -38,6 +38,7 @@ import { transformConflictedScripts, transformAllScripts } from "./transforms/tr
import { transformConflictedExtensions, transformAllExtensions } from "./transforms/transform-extensions"
import { transformConflictedWeb, transformAllWeb } from "./transforms/transform-web"
import { resolveLockFileConflicts, regenerateLockFiles } from "./transforms/lock-files"
import { writeVersion } from "./utils/upstream"
interface MergeOptions {
version?: string
@@ -508,6 +509,11 @@ async function main() {
const keepOursResults = await resetToOurs(config.keepOurs, { dryRun: false, verbose: options.verbose })
logger.success(`Reset ${keepOursResults.length} files to Kilo's version`)
// 6k. Record the last merged upstream tag so future automation can find it
// without walking ls-remote + isAncestor for every tag.
const versionFile = await writeVersion(targetVersion.tag)
logger.success(`Recorded ${targetVersion.tag} in ${versionFile.split("/").pop()}`)
// Clean untracked build artifacts from Kilo-specific directories.
// These packages don't exist in upstream, so their .gitignore files are absent
// on the opencode branch. Artifacts like bin/, out/, .next/ etc. would otherwise
+62
View File
@@ -15,6 +15,12 @@ import { isAncestor } from "./git"
const url = "https://github.com/anomalyco/opencode.git"
const workflows = [".github/workflows/publish.yml", ".github/workflows/beta.yml"]
/**
* Repo-relative path of the file that records the last merged upstream tag.
* Single line containing the upstream tag (e.g. `v1.14.33`).
*/
export const versionFile = ".opencode-version"
export async function root() {
return (await $`git rev-parse --show-toplevel`.text()).trim()
}
@@ -39,6 +45,9 @@ export async function remote() {
}
export async function last(): Promise<VersionInfo> {
const recorded = await readVersionFile()
if (recorded) return recorded
const source = await remote()
info(`Fetching upstream tags from ${source}...`)
@@ -53,6 +62,59 @@ export async function last(): Promise<VersionInfo> {
throw new Error("Could not find a merged upstream tag in HEAD")
}
/**
* Read the recorded last-merged upstream tag from `.opencode-version`. Returns
* null if the file is missing/empty, or if the recorded tag cannot be resolved
* to a commit (e.g. tags have not been fetched yet). Falls back to the
* isAncestor-based discovery in `last()`.
*/
async function readVersionFile(): Promise<VersionInfo | null> {
const repo = await root()
const file = Bun.file(`${repo}/${versionFile}`)
if (!(await file.exists())) return null
const tag = (await file.text()).trim()
if (!tag) return null
const version = parseVersion(tag)
if (!version) {
warn(`${versionFile} contains '${tag}' which is not a valid version tag; ignoring`)
return null
}
const commit = await resolveTag(tag)
if (!commit) return null
return { version, tag, commit }
}
async function resolveTag(tag: string): Promise<string | null> {
const local = await $`git rev-parse --verify --quiet ${tag}^{commit}`.quiet().nothrow()
if (local.exitCode === 0) return local.stdout.toString().trim()
const source = await remote()
info(`Tag ${tag} not present locally; fetching from ${source}...`)
const fetch = await $`git fetch ${source} tag ${tag} --no-tags`.quiet().nothrow()
if (fetch.exitCode !== 0) {
warn(`Failed to fetch tag ${tag}: ${fetch.stderr.toString()}`)
return null
}
const after = await $`git rev-parse --verify --quiet ${tag}^{commit}`.quiet().nothrow()
return after.exitCode === 0 ? after.stdout.toString().trim() : null
}
/**
* Record the merged upstream tag in `.opencode-version` so subsequent runs of
* `last()` resolve instantly without an `ls-remote` walk.
*/
export async function writeVersion(tag: string): Promise<string> {
const repo = await root()
const path = `${repo}/${versionFile}`
await Bun.write(path, `${tag}\n`)
return path
}
export async function versions(source: string): Promise<VersionInfo[]> {
const result = await $`git ls-remote --tags ${source}`.quiet().nothrow()
if (result.exitCode !== 0) throw new Error(`Failed to list upstream tags: ${result.stderr.toString()}`)