From 38cc0297a873392da449a50ad6281b302b9d5bab Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Tue, 28 Apr 2026 11:46:43 +0200 Subject: [PATCH] feat(cli): show which upstream versions are already merged in list-versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses 'git merge-base --is-ancestor' per tag (parallelized) to mark upstream versions reachable from HEAD with a '✓ merged' annotation. Kilo's own package version no longer tracks upstream, so semver comparison isn't reliable; the ancestor check is the source of truth and handles non-linear merge history correctly. --- script/upstream/list-versions.ts | 12 ++++++++---- script/upstream/utils/git.ts | 10 ++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/script/upstream/list-versions.ts b/script/upstream/list-versions.ts index e1576cfea1..0ab8e87ab5 100644 --- a/script/upstream/list-versions.ts +++ b/script/upstream/list-versions.ts @@ -7,7 +7,7 @@ */ import { getAvailableUpstreamVersions, getCurrentKiloVersion } from "./utils/version" -import { fetchUpstream, hasUpstreamRemote } from "./utils/git" +import { fetchUpstream, hasUpstreamRemote, isAncestor } from "./utils/git" import { header, info, success, warn, error } from "./utils/logger" async function main() { @@ -34,11 +34,15 @@ async function main() { console.log() const limit = process.argv.includes("--all") ? versions.length : 20 + const shown = versions.slice(0, Math.min(limit, versions.length)) - for (let i = 0; i < Math.min(limit, versions.length); i++) { - const v = versions[i] + // Check merge status in parallel — fast because is-ancestor short-circuits. + const merged = await Promise.all(shown.map((v) => isAncestor(v.commit, "HEAD"))) + + for (let i = 0; i < shown.length; i++) { + const v = shown[i] if (!v) continue - const marker = i === 0 ? " (latest)" : "" + const marker = merged[i] ? " ✓ merged" : i === 0 ? " (latest)" : "" console.log(` ${v.tag.padEnd(12)} ${v.commit.slice(0, 8)}${marker}`) } diff --git a/script/upstream/utils/git.ts b/script/upstream/utils/git.ts index f55e731a78..b91107724e 100644 --- a/script/upstream/utils/git.ts +++ b/script/upstream/utils/git.ts @@ -172,6 +172,16 @@ export async function getCommitHash(ref: string): Promise { return result.trim() } +/** + * Check if `commit` is an ancestor of `ref` (i.e. reachable from `ref`). + * Uses `git merge-base --is-ancestor`, which exits 0 for yes, 1 for no. + * Any other exit code (e.g. unknown commit) is treated as "not an ancestor". + */ +export async function isAncestor(commit: string, ref = "HEAD"): Promise { + const result = await $`git merge-base --is-ancestor ${commit} ${ref}`.quiet().nothrow() + return result.exitCode === 0 +} + export async function getTagsForCommit(commit: string): Promise { const result = await $`git tag --points-at ${commit}`.text() return result