From f725b0477a74a150326b1bf85905a73ad5b5cf44 Mon Sep 17 00:00:00 2001 From: Brendan Clement Date: Fri, 9 Oct 2026 09:18:15 -0700 Subject: [PATCH] docs: document branch diff and cherry-pick as the SDK implements them The compare-and-merge section documented branches.merge, mergeable, mergeBlockers and merged/rejected statuses, none of which the pinned SDK has, and read TypeScript's branches() as a property. The SDK exposes diff and cherry_pick (cherryPick in TypeScript, reached through await table.branches()), returns camelCase results whose diff lists errors, answers a refused cherry-pick with status "failed" rather than an exception, and refuses both calls on local tables. The section now documents those calls, the diff and result fields, dry-run behaviour, statuses, error codes, async Python and the Rust names, under its existing anchors. The Python examples were run from the page against a mocked server and a local table, and the TypeScript examples were type-checked against the SDK's declarations and run the same way. No deployment was contacted. --- docs/web/tables/branching.mdx | 137 +++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 59 deletions(-) diff --git a/docs/web/tables/branching.mdx b/docs/web/tables/branching.mdx index 7af1faaa6..25defcd38 100644 --- a/docs/web/tables/branching.mdx +++ b/docs/web/tables/branching.mdx @@ -210,7 +210,7 @@ below. ## Apply branch-tested changes to `main` {#apply-branch-tested-changes-to-main} -A branch has its own writable history. Outside of the [diff and merge +A branch has its own writable history. Outside of the [diff and cherry-pick APIs](#compare-and-merge-a-branch-into-main) — which are available on LanceDB Enterprise and promote added columns only — LanceDB does not reconcile one branch's history with another or detect conflicts between them. To carry @@ -221,8 +221,8 @@ explicitly write selected results to it. How you apply a validated change depends on the type of work: - **Added columns (Enterprise):** review the branch with `diff`, then promote - the new columns onto `main` with `merge`. See - [Compare and merge a branch into `main`](#compare-and-merge-a-branch-into-main). + the new columns onto `main` with `cherry_pick` (`cherryPick` in TypeScript). See + [Compare and promote a branch into `main`](#compare-and-merge-a-branch-into-main). - **Backfill or transformation:** rerun the validated job against `main`. - **Schema change:** apply the same reviewed schema operation to `main`. - **Index change:** build the index on `main` using the configuration validated @@ -268,103 +268,122 @@ newer values with the same key. Read and upsert the whole branch only when that overwrite is intentional; otherwise, filter the branch read to the rows you intend to apply. -## Compare and merge a branch into `main` {#compare-and-merge-a-branch-into-main} +## Compare and promote a branch into `main` {#compare-and-merge-a-branch-into-main} Enterprise -On LanceDB Enterprise, branches expose two review-and-land calls that let you -inspect what a branch has changed relative to `main` and then promote its new -columns onto `main` in place — without reissuing the branch's writes. +On LanceDB Enterprise, branches expose two review-and-land calls: `diff` +inspects what a branch has changed relative to `main`, and `cherry_pick` +(`cherryPick` in TypeScript) promotes the branch's new columns onto `main` in +place, without reissuing the branch's writes. -`diff` and `merge` are available on Enterprise (remote) tables only. On local -tables both calls raise `NotSupported`. `merge` currently promotes added -columns; use the [upsert](#upsert-selected-branch-rows-into-main) or rerun -patterns above for row and index changes. +`diff` and `cherry_pick` work on Enterprise (remote) tables only. On a local +table they fail as not supported: Python raises `NotImplementedError`, and +TypeScript and Rust return an error. A cherry-pick promotes added columns; use +the [upsert](#upsert-selected-branch-rows-into-main) or rerun patterns above for +row and index changes. ### Diff a branch {#diff-a-branch} -`diff` reads the branch and `main`, and returns a summary of what has changed: -which columns were added, removed, or altered; which indexes were added or -removed; row-count deltas; and — most importantly — a list of merge blockers -explaining why the branch cannot currently be merged, if any. +`diff` reads the branch and `main` without modifying either, and returns what +has changed: columns added, removed or changed, indexes added or removed, row +counts, and a list of `errors` -- the reasons a cherry-pick of the branch could +not land now. An empty list means it could. ```python Python icon="python" diff = table.branches.diff("exp") -print(diff["addedColumns"]) # columns the branch introduced -print(diff["mergeable"]) # True when there are no blockers -print(diff["mergeBlockers"]) # list of {"code", "message"} entries +print([c["name"] for c in diff["addedColumns"]]) # columns the branch introduced +print(diff["baseMoved"]) # True if main has moved on since the branch's fork +for error in diff["errors"]: # empty when a cherry-pick could land + print(error["code"], error["message"]) ``` ```typescript TypeScript icon="square-js" -const diff = await table.branches.diff("exp"); +const branches = await table.branches(); +const diff = await branches.diff("exp"); -console.log(diff.addedColumns); // columns the branch introduced -console.log(diff.mergeable); // true when there are no blockers -console.log(diff.mergeBlockers); // array of { code, message } entries +console.log(diff.addedColumns.map((c) => c.name)); // columns the branch introduced +console.log(diff.baseMoved); // true if main has moved on since the branch's fork +for (const error of diff.errors) { // empty when a cherry-pick could land + console.log(error.code, error.message); +} ``` -Common merge blocker codes include `BaseMoved` (the branch's parent no longer -matches `main`'s latest), `RowsChanged`, `ColumnRemoved`, `ColumnChanged`, -`NoMergeableChanges`, `NoColumnChanges`, `InputColumnDependency`, and -`ParentNotMain`. Newer server codes surface as `Unknown` so older clients -keep working. +The result also carries the versions it compared (`parentVersion`, +`mainVersion`, `branchVersion`), `rowCountMain` and `rowCountBranch`, a +`rowSummary`, and `removedColumns`, `changedColumns`, `addedIndexes` and +`removedIndexes`. In Python the result is a dictionary with the same camelCase +keys. -### Merge a branch {#merge-a-branch} +The error codes are `baseMoved` (`main` has moved on since the version the +branch was forked from), `rowCountMismatch`, `rowsChanged`, `columnRemoved`, +`columnChanged`, `nothingToApply`, `noColumnChanges`, `inputColumnDependency` +and `parentNotMain`. A code the client does not know surfaces as `unknown`, so +older clients keep working. -`merge` promotes a branch's added columns onto `main`. It is a review-and-land -operation: the server re-evaluates the diff at request time, and either lands -the promotion or rejects it with the same blockers `diff` would report. A -rejected merge is not an exception — it resolves with `status="rejected"` so -you can inspect the blockers and decide what to do next. +### Promote a branch's columns {#merge-a-branch} -Set `dry_run=True` (Python) or `dryRun: true` (TypeScript) to preview the -merge without landing it. The result includes a `preview.promoted_columns` -list showing which columns the merge would (or did) promote. +`cherry_pick` promotes the columns a branch added onto `main`. The server +re-evaluates the diff when the request arrives and either lands the change or +refuses it for the reasons `diff` would report. A refusal is not an exception: +the call returns a result whose `status` is `"failed"`, with the reasons in +`diff.errors`, so you can decide what to do next. Cherry-picks are not retried. + +Pass `dry_run=True` in Python, or `true` as the second argument in TypeScript, +to check without landing anything. `preview.promotedColumns` lists the columns +the cherry-pick would promote, or did. ```python Python icon="python" -# Preview first — this does not modify main. -preview = table.branches.merge("exp", dry_run=True) -print(preview["status"]) # "ready" | "rejected" | ... +# Check first; this does not modify main. +preview = table.branches.cherry_pick("exp", dry_run=True) +print(preview["status"]) # "ready" when it would land print(preview["preview"]["promotedColumns"]) -# Land the merge. -result = table.branches.merge("exp") -if result["status"] == "merged": +# Land it. +result = table.branches.cherry_pick("exp") +if result["status"] == "cherryPicked": print("landed at main version", result["mainVersionAfter"]) -elif result["status"] == "rejected": - for blocker in result["diff"]["mergeBlockers"]: - print(blocker["code"], blocker["message"]) +elif result["status"] == "failed": + for error in result["diff"]["errors"]: + print(error["code"], error["message"]) ``` ```typescript TypeScript icon="square-js" -// Preview first — this does not modify main. -const preview = await table.branches.merge("exp", true); -console.log(preview.status); // "ready" | "rejected" | ... +const branches = await table.branches(); + +// Check first; this does not modify main. +const preview = await branches.cherryPick("exp", true); +console.log(preview.status); // "ready" when it would land console.log(preview.preview.promotedColumns); -// Land the merge. -const result = await table.branches.merge("exp"); -if (result.status === "merged") { +// Land it. +const result = await branches.cherryPick("exp"); +if (result.status === "cherryPicked") { console.log("landed at main version", result.mainVersionAfter); -} else if (result.status === "rejected") { - for (const blocker of result.diff.mergeBlockers) { - console.log(blocker.code, blocker.message); +} else if (result.status === "failed") { + for (const error of result.diff.errors) { + console.log(error.code, error.message); } } ``` -Possible `status` values are `ready` (returned by `dry_run` when the merge -would land), `merged` (a real merge that landed), `rejected` (server declined; -see `diff.mergeBlockers`), `notImplemented`, and `unknown` for forward -compatibility. Merge requests are not retried on rejection — the response -carries everything you need to decide next steps. +`status` is `ready` (a dry run that would land), `cherryPicked` (it landed, and +`mainVersionAfter` is the new version of `main`), `failed` (refused, or for a +dry run, would be refused; see `diff.errors`), `notImplemented`, or `unknown` +for a status the client does not know. A branch that does not exist raises a +not-found error instead of returning a status. + +In async Python, await the same calls on an `AsyncTable`: +`await table.branches.diff("exp")` and +`await table.branches.cherry_pick("exp", dry_run=True)`. In Rust they are +`Table::diff_branch` and `Table::cherry_pick`. ## Build indexes on a branch {#build-indexes-on-a-branch}