Větve a slučování
Dokumentace/Práce s nativním klientem
Větve a slučování11 min čtení

Branches and conflict resolution

Zdrojová dokumentace je zatím dostupná v angličtině. Ovládání čtečky odpovídá zvolenému jazyku.

Na této stránce

A branch is a named line of development within a repository. Use ordinary add, edit, delete, move, and submit commands to work on it, then merge its changes into another branch. main is the protected default branch: it cannot be renamed. Branch deletion and history rewriting are not supported.

Creating a branch shares immutable metadata snapshots and existing asset objects. Renaming a branch preserves its identity, history, and workspace bindings. Locks apply to a path across the entire repository, including other branches. The accepted locks.rules in .scribe/config.toml on main govern every branch; changing a feature branch's rules proposes a policy change but does not activate it. Workspace profiles use the selected branch's config instead. See mandatory lock rules. Change numbers come from one repository-wide sequence, while each branch has its own head revision.

A merge compares the source and target with a common ancestor. Non-overlapping text edits can merge automatically; binary files and overlapping edits need a choice or manual editing. scribe resolve records those decisions, and scribe submit publishes the merge. Unresolved conflicts survive restarts and prevent submission. Merge ancestry records the source revision even if the merge changes no file bytes, so later merges can identify shared history.

Locks are decided before a merge or cherry-pick writes anything. Planning lists the current holders of the paths the result would change. A path another workspace holds refuses the plan, naming up to five holders, and leaves every working file untouched; wait for the holder to release it, or release it explicitly, then run scribe merge --continue. For paths the active policy requires a lock for, the integration acquires the lock for this workspace before applying, recording the claim first, so scribe merge --abort releases exactly those acquisitions. Keeping the target's version writes nothing, needs no lock, and never blocks. A successful merge submission releases its submitted locks by default. Use scribe submit --keep-locks to retain them; scribe lock release releases them explicitly.

Commands

Run these commands inside a workspace. For an older repository, follow the offline migration procedure before using branches.

sh
scribe branch list
scribe branch create environment --from main       # snapshot current source head
scribe branch create experiment --from main --revision 42
scribe branch rename experiment lighting           # identity/history stay the same
scribe branch --continue                               # recover a lost branch-operation reply
scribe switch environment                         # resume with switch or sync if interrupted

# Work on a branch with the usual add/edit/delete/submit commands.
scribe edit Source/lighting.cpp
scribe submit -m "Update lighting"

scribe switch main
scribe merge environment                          # pin source and prepare local results
scribe status
scribe resolve                                    # inspect current conflict resolution
scribe resolve --export /tmp/lighting-conflict     # base/, ours/, theirs/ for external tools
scribe resolve --theirs Assets/level.umap
# Or edit the working file using an editor, then acknowledge that result:
scribe resolve --working Source/lighting.cpp
scribe submit -m "Merge environment"

ours is the target workspace's version; theirs is the source branch (or latest server version when resolving stale work). Missing sides represent file deletion. --ours, --theirs, and --working take paths or --all. --all chooses that side for every path in the plan, including automatic merges; use individual paths to retain other automatic results.

Keeping the target's version for a path the source changed writes no file, but it is still a decision the submission has to carry. scribe status shows those paths opened for keep; scribe revert on one only retires the record. A keep submits no bytes and changes no tree, so a merge that only declines the source still lists those paths in its change set.

scribe merge --continue resumes without changing the pinned source. A merge still needs submit to become shared history. scribe merge --abort (also resolve --abort) restores the target states of the affected paths and retains displaced local files in recovery. Extra opened paths outside the plan stay open.

After a repository restore, branch --continue still resolves recorded outcomes and snapshot intents retained in the backup. If the server refuses an unknown old intent because its incarnation changed, the client archives it in .scribe/_state/recovery/branch-<operation>.pending. Sync and explicitly select the branch operation again; a reused revision number never silently selects new history.

If a restore invalidates an active resolution, resolve --abort archives its marker and resolution records in .scribe/_state/recovery/resolution-<workflow-id>.pending and clears the active workflow without downloading old targets. Working files, opened edits and baselines remain intact. Locks the merge acquired are not released along that path, because the restored repository's lock table is not this workflow's to interpret; review scribe lock list after syncing. Run scribe sync, then scribe resolve --update and select the files to keep before submitting again.

For ordinary stale submissions, run scribe resolve --update, resolve any conflicts, and submit again. Finish or abort pending moves before updating their source revisions. The same command updates a completed, ungrouped merge plan in a full-view workspace if another client advanced the target before submission. Unrelated target commits do not invalidate an integration: the server validates source paths, submitted paths, move origins, and their collision dependencies against the current head, then accepts with that head as its parent. This also preserves partial-view plans, rename groups, and manual choices. If those dependencies actually changed, partial-view integrations and merges with rename or collision groups must be aborted and restarted against the new target; preserve and reapply recovered manual edits as needed. Resolve existing conflicts first. If all stale local edits are discarded in favor of the server, the workflow finishes without creating an empty ordinary submission.

Switching branches

A branch switch first updates the workspace registration, then changes the local branch and downloads its files. If registration is refused before the local transition, the client cancels the pending switch and keeps the original branch, files, and sync watermark intact. This also releases an older stuck switch when resumed. A transport failure retains pending intent because the registration may have succeeded; a refusal after local materialization starts retains the recovery state as well. Resume those switches after restoring connectivity or access.

Repository access does not grant ownership of another account's working copy. Sign in as the workspace owner, or create a separate workspace under your account. Workspaces registered without an authenticated owner cannot be claimed by signing in later. The macOS permission notice offers sign-in and workspace creation in the app; creating a new working copy leaves the existing local files intact.

Verified merge coverage

A merge submission pins the merge base it was planned against alongside the target head and the source revision. Before taking the publication gate the server recomputes the merge base from its own history and refuses a base branch that disagrees with it (base_mismatch). It then walks every path the source changed between that base and the pinned source revision, skipping paths the source did not really change and paths the target already holds identically, and requires each remaining path to appear in the change set — as an add, edit, delete, or keep. A path whose live target spelling was renamed is covered by the submitted new spelling, which the server maps back through recorded file lineage.

A submission that omits part of the source delta is rejected with merge_incomplete, naming up to five uncovered paths, and records no history: without this check a planning defect would mark the source merged while its changes stayed invisible from the target, and the next merge would report already_merged. The verification uses the same budget as the client planner (100,000 paths / 32 MiB of path names) and reports limit_exceeded beyond it. Cherry-picks carry no base; their base is the picked commit's parent, which the server derives itself.

Requirements and limits

  • Merge supports full and partial views, with no opened files or unfinished sync. See partial-view integration for excluded content and conflicts. It verifies affected tracked files, preserving unrelated local edits. A paged prerequisite delta updates the workspace to the pinned target first; files already at that repository state are not reopened or replaced.
  • Merge and cherry-pick planning reads the active lock policy once and lists the repository's locks in bounded pages, stopping at the last planned path. Stale opened work updated with resolve --update keeps the locks its edit acquired and takes none: only an integration writes paths this workspace never opened.
  • Planning reads divergent first-parent change indexes and queries only the candidate paths at base/target/source. Historical folded-path lookups detect case and file/directory collisions without downloading a complete tree. Resolve uses the same targeted queries. Already-merged sources return before any tracked-file verification or tree lookup.
  • Planning is bounded to 100,000 candidate paths, 32 MiB of path names and 128 MiB of encoded resolution rows. Larger plans fail before merge writes; merge an earlier source revision first. Pages contain at most 1,024 changed paths or examine 128 commits. Work grows with divergent history and affected paths, including conflicting descendants; it is not constant time. Each connected rename/collision group is limited to 1,024 paths and 32 MiB of encoded resolution records so its choice fits one bounded transaction.
  • Branch creation stores the source snapshot roots and transfers no asset content. It performs no per-file copying, including on nested forks. Branch name and ancestry validation still have their own costs; switching transfers missing chunks.
  • Text merging is conservative: valid UTF-8, no NUL bytes, at most 4 MiB per input and 8 million line-comparison cells. Larger or ambiguous edits require an explicit choice or an external tool. No binary merge is attempted.
  • Per-file logs follow fork origins and merge parents, retaining each source revision's branch attribution and deduplicating shared ancestors. Use scribe log <path> --first-parent to omit merge-source ancestry. A source revision is historical context, not proof that conflict resolution chose its bytes. History reads use one metadata snapshot, exact-path ranges and the sparse repository-wide merge-parent index; they skip unrelated ordinary commits. Work is capped at 100,000 branch, path-row and merge-row visits per request; exceeding the budget returns limit_exceeded, never partial history. scribe log <path> --all-branches additionally follows indexed file lineage into unmerged branches at a pinned repository snapshot. Filename reuse starts a separate file incarnation. Its in_ancestry field describes commit ancestry, not whether a merge selected that revision's bytes. See cross-branch history for pagination.
  • scribe move records an add/delete pair with its exact predecessor revision. File history follows those records, including moves on a merged source branch. Merge planning follows this lineage through chained and folder moves. A rename on one side and edit on the other merges at the renamed destination; matching renames use their agreed destination. Competing names, rename/delete, and occupied destinations form explicit resolution groups. Selecting a member selects the whole group. See rename-aware merging for content choices, layout choices, and --working. There is no heuristic rename detection for moves made outside Scribe.
  • Path-case, normalization, and file/directory conflicts require a compatible layout for the complete collision group. Conflicting choices are not applied or submitted.
  • Multiple best merge bases require an explicit --base <change> after review. Multiple base-file ancestors for one file are also refused; abort and select an appropriate earlier explicit base before replanning. Origin reads use the lineage index, with a 100,000-visit / 32 MiB budget per query batch. Source revisions must be on the source's first-parent history and at or after its creation. Historical tree queries before a branch existed are refused.
  • main cannot be renamed. There is no branch deletion, pruning, rebase, or history rewrite. There is no separate push step: submit publishes immediately. A creation that never activated is not history: an operator can release its reserved name offline with scribe-server staging-branches --abandon.
  • Merge requires repository schema 3 with snapshot profile 3 and workspace schema 2. Pre-snapshot development repositories are incompatible with this build; do not force their profile markers. Older clients are rejected.

Measuring merge planning

Merge planning queries changed paths and their historical states. Its work grows with divergent history and affected paths. Branch creation shares the source snapshot without enumerating its files. Snapshot regression tests also count visited pages and inserted rows on generated metadata; large asset repositories are unnecessary for checking those growth properties.

Developers can run the opt-in metadata benchmark from the repository root:

sh
cargo test --release --locked -p scribe-metadata qualify_merge_index_scale -- --ignored --nocapture

This measures index queries on generated repository metadata. Complete merge time also includes hashing affected files, downloading missing chunks, writing local files, and durable flushes. Use a representative workspace to measure those costs.

Developer reference: branch publication

Serving requires repository schema 3 and TCP v2, or authenticated protocol v4. Ordinary commits use format 1; merge commits use format 2 with a second parent. The repository stores branch heads, merge parents, operation outcomes, pending snapshots, historical path-collision indexes, exact move predecessors, and the bidirectional file_lineage index. Submit accepts history and its derived indexes in the same durable metadata transaction.

Branch reservation pins the source revision's exact-path and Unicode-collision radix roots in one durable transaction. It creates no per-file history anchors and no snapshot pages. The branch remains invisible until a second durable transaction records its head and operation outcome. Subsequent submits replace only pages along changed paths; unchanged subtrees remain shared. Branch operation IDs and exact intents persist before transmission. Switch intent likewise persists before changing server registration or local state; interrupted sync resumes to the pinned destination. See the format specification for records and durability and recovery for ordering.

Staging lifecycle

A branch record is written in the staging state as soon as its name is reserved, and stays invisible: it has no head, no serving query returns it, and only a retry of the same operation id can activate it. The reservation is what makes a name unique, so it also survives the client that made it. A workspace that still holds the intent finishes it with scribe branch --continue; if that client is gone — its .scribe/_state/ lost, its workspace discarded — nothing can complete or replace the reservation, and create keeps reporting that the name is already reserved.

Releasing such a name is offline maintenance, not a branch deletion: a staging branch was never history. With the server stopped, scribe-server staging-branches lists the unfinished snapshots (name, branch id, pending operation id, staged rows, pinned change) and --abandon discards one, deleting its single staged snapshot-root row and removing the reservation last in bounded durable transactions. An activated branch is refused. See operations.

Zdroj docs/branches.mdVerze 2b8dcccb