crates/WORKSPACE_MODEL.md
Use this when changing, reviewing, or investigating Rust code under crates/ that derives GitButler graph, workspace, branch, stack, commit, push, or rebase relationships, reachability, dependencies, ordering, operation targets, or Git graph/history/ref-placement changes.
This document captures intended direction. Prefer nearby code patterns for small legacy fixes, but avoid expanding legacy abstractions when adding new behavior.
GitButler is moving away from stacks as a primary internal abstraction.
Stacks are useful for users and UI, but Git itself has commits, refs, parent edges, and reachable subgraphs. New logic should generally model behavior in terms of Git-representable concepts and graph relationships:
When an operation needs a handle inside a live graph editor, use operation-local selectors. Do not treat selectors as durable Git objects; they point into one editor revision and are derived from commits or refs inside that editor.
UI code may still present stacks, lanes, or buckets. Core read/query paths and mutations should avoid depending on those presentation buckets as source of truth.
This applies both when changing state and when asking questions about stacks, branches, commits, or dependencies. The compressed/projection views are convenient, but they are not the best internal source of truth for graph-shaped questions.
| Task | Prefer | Avoid |
|---|---|---|
| Rewrite, move, drop, insert commits or ref placements | graph editor (but_rebase::graph_rebase::Editor) where an editor model exists | old linear rebase engine (but_rebase::Rebase) |
| Ask graph/dependency/state questions about stacks, branches, commits, or ordering | but_graph::Graph | workspace projection / refinfo as source of truth |
| Render UI or caller state | workspace projection (but_graph::Workspace) and refinfo (but_workspace::RefInfo) | raw mutation structures directly |
| API operation targets | commit IDs and refs at the boundary; selectors inside a live editor operation | stack IDs unless legacy boundary requires it |
| Lower-level operations | explicit repo/db/meta/workspace handles | taking full but_ctx::Context |
If code is shaping data for an existing UI/API contract, projection/refinfo is appropriate. If code decides what may be mutated, pushed, rebased, hidden, ordered, or considered dependent/reachable, derive that from but_graph::Graph or the graph editor first and convert afterward. Existing workspace-shaped legacy boundaries may still use projection; avoid expanding that shape into new core logic.
but_ctx::Context is a broad provider for repository, database, cache, and workspace access. It is useful near API/composition boundaries, but lower-level logic should receive narrower dependencies.
Prefer acquiring permissions/handles once at the boundary, then passing explicit dependencies inward:
but_core::RefMetadata)_with_permAvoid lower-level helpers reacquiring Context or repository write guards while another guard is held. Hidden reacquisition makes lock ordering unclear and can cause deadlocks.
Workspace metadata is exposed through but_core::RefMetadata and but_core::ref_metadata::Workspace.
Important concepts:
origin/main; the branch/ref used as the workflow frame of reference.Treat target ref and target commit as frame-of-reference metadata, not automatically as mutation commands.
Use target ref/commit differently depending on the operation:
| Situation | Role |
|---|---|
| Workspace/query traversal | target commit/ref can bound or extend the traversal context |
| Display/caller state | target ref names the integration frame |
| Rebase/push/action target | the selected commit/ref is the operation target, unless the command explicitly updates workspace target metadata |
Do not conflate:
foo vs remote-tracking branch origin/foofoo relative to target ref origin/mainThose answer different questions. This matters especially for normal Git / single-branch workflows.
but_graph::Graphbut_graph::Graph is the current best graph-shaped model of repository/workspace state. Prefer starting from it when internal code needs to understand relationships between stacks, branches, commits, refs, or dependencies.
Use it for state/query questions such as:
Caveats:
but_graph::Workspace and but_workspace::RefInfo are derived, interpreted, compressed views. They are useful for presentation, compatibility, and existing workspace-shaped boundaries, but they are lossy. The warning is broader than mutations: do not use these views as the main internal source when asking substantive new questions about stacks, branches, commits, ordering, dependencies, or reachability.
Use them for:
Avoid using them as source of truth for:
If code needs graph topology or accurate relationships, go back to but_graph::Graph or the graph editor before converting into projection/refinfo.
Treat the old rebase engine (but_rebase::Rebase) as legacy. It is linear: base plus ordered rebase steps. That assumes a meaningful contiguous range, which breaks down for graph-shaped histories, merge commits, normal Git branches, and single-branch mode.
Use the graph editor (but_rebase::graph_rebase::Editor) for new Git graph/history/ref-placement mutation logic where an editor model exists. Use existing metadata, database, worktree, checkout, hunk-assignment, and API bookkeeping APIs for non-graph state changes.
Important graph editor concepts:
but_rebase::graph_rebase::Pick) — materialize a commit.but_rebase::graph_rebase::Step::Reference) — place or move a ref.but_rebase::graph_rebase::Step::None) — placeholder after removing a pick/ref.Editor::rebase() — materializes the edited graph back into Git objects and ref edits.The graph editor is not merely “a rebase command.” It is the in-memory graph mutation layer for history and ref-placement rewrites. It is currently created from a mutable workspace projection, so projection may be involved in editor setup even when the mutation decision should be graph-shaped.
Push should be graph-based, not stack-based.
Current legacy push code still has stack-shaped boundaries, such as gitbutler_branch_actions::push_stack. Avoid copying that shape into new code unless maintaining the legacy boundary.
For new push/dependency logic, ask graph questions:
Push is not itself a mutation, so using the graph editor only for ordering can be conceptually awkward. Prefer but_graph::Graph for dependency and ordering questions.
Upstream integration can be a useful conceptual pattern: reason over related subgraphs under workspace heads, even if existing code still uses stack terminology.
“Single branch mode” is a product/workflow concept, not one clean Rust type.
When touching code that should work in normal Git mode:
gitbutler/workspace is checked out;Ask this for both read/query code and mutation code:
but_graph::Graph?but_rebase::graph_rebase::Editor where an editor model exists?but_ctx::Context into lower layers?but_graph::Graph for dependency, reachability, membership, ordering, and topology questions.but_rebase::graph_rebase::Editor for Git graph/history/ref-placement rewrites where an editor model exists.StackId when commit/ref targets would do.but_ctx::Context and reacquires locks.crates/but-graph/tests/graph/init/with_workspace.rs, especially workspace_with_stack_and_local_target() and workspace_projection_with_advanced_stack_tip(), shows Graph::from_head(), validated(), into_workspace(), and snapshot-backed graph/projection expectations.crates/but-graph/tests/graph/workspace/resolved_target_commit_id.rs, especially prefers_target_commit_over_target_ref() and returns_none_with_only_extra_target(), shows cases where target commit metadata, target refs, and extra traversal targets intentionally differ.crates/but-rebase/tests/rebase/graph_rebase/replace.rs and crates/but-rebase/tests/rebase/graph_rebase/insert.rs show selecting commits, replacing/inserting steps, checking overlayed_graph(), and materializing once.crates/but-workspace/tests/workspace/commit/move_commit.rs shows creating an editor, calling but_workspace::commit::move_commit, materializing, refreshing workspace state, and asserting ref movement.crates/but-workspace/tests/workspace/ref_info/mod.rs, especially single_branch() and single_branch_multiple_segments(), shows unmanaged/non-workspace RefInfo behavior and legacy stack compatibility expectations.