# Branches and conflict resolution

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](/docs/client-usage.md#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](/docs/migration-backup-restore.md#scribe-server-migrate)
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](/docs/partial-view-merging.md) 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](/docs/client-usage.md#cross-branch-file-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](/docs/client-usage.md#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`](/docs/branches.md#staging-lifecycle).
- 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](/docs/on-disk-format.md) for
records and [durability and recovery](/docs/durability-and-recovery.md) 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](/docs/operations.md#unfinished-branch-creations).
