Skip to content

Conversation

schacon
Copy link
Member

@schacon schacon commented Sep 2, 2025

This PR adds the first testing version of many gitbutler cli workspace commands:

  • config
  • status
  • oplog
  • undo
  • restore
  • commit
  • new
  • describe
  • mark
  • unmark

This is the rebased, cleaned up and combined version of #9971 #9987 #9991 and #9993.

Copy link

vercel bot commented Sep 2, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
gitbutler-components Ready Ready Preview Comment Sep 2, 2025 11:12am
gitbutler-web Ready Ready Preview Comment Sep 2, 2025 11:12am

Adding a but config that by default shows some of the important config
Values like name and email.
Introduce an explicit "Undo" subcommand (alias: undo) and an "Oplog" subcommand (alias: oplog) in the CLI to expose operation history and restore functionality. Add the new gitbutler-oplog workspace, wire the new modules into main, and implement a new undo module that finds the previous snapshot, acquires exclusive worktree access, and restores the repository to that snapshot. This change was needed to replace a generic "revert" behavior with a clearer "but restore"/undo flow so users can inspect the oplog and reliably roll back the last operation.
Log: add --short option to display branch topology with commit counts

Add a --short (-s) flag to the Log subcommand to provide a concise view of the branch topology that shows only branch names and the number of commits (upstream vs local) instead of full commit details. This was needed to support a compact, high-level overview mode for users who only want topology and commit counts.

Changes:
- Add short: bool to the Log CLI subcommand.
- Extend commit_graph signature to accept a short flag and route Log to the new signature.
- Implement commit_graph_short to render branch topology with commit counts and simplified symbols, and return early when short is requested.
The status output now prints base information, then iterates stacks to show each branch as a section containing upstream/local commits and its assigned files, followed by an Unassigned Changes section for files not assigned to any stack. This reorganizes the previous single print_group into focused helpers (print_base_info, print_branch_sections, print_unassigned_section) and groups assignments by file so the UI matches the requested layout.
Make base/behind calculations and display conditional on the -b/--base 
flag so the status command only shows base branch and behind counts when
explicitly requested. This avoids unnecessary computation and clutter in
the default status output.

Also update status rendering to correctly connect stacked branches visually: compute prefixes and connectors, track nesting and branch counts, and print closing connectors so stacked branches appear as a connected graph rather than independent entries. Command argument parsing and call sites were adjusted to pass the new flag into the status worktree function.
Show assigned files only on top branch and fix graph gaps

Prevent assigned files from appearing on every stacked branch by only printing assignments for the first (topmost) branch in a stack. Files are associated with the stack rather than individual branches, so this change filters assignments by stack_id only for the first branch and avoids duplicating file listings across stacked branches.

Also simplify connector logic, tighten prefix/connector printing, and fix graph closing/spacing so connectors and nesting lines render correctly (fill gaps and ensure proper trailing lines between stacks).
Show stack-assigned files above commit list

Show assigned files for the stack before printing the branch commit list so that files (which are associated with the stack, not individual branches) appear above the commits for the topmost branch. This reorders the output: assigned files are displayed first (only for the stack's first branch) and commits (upstream then local) are shown afterwards, improving clarity of the status view.
Fix branch graph ASCII and show base commit

Adjust the branch graph printing to match the requested ASCII layout and include the repository base commit. Changes include: cloning project path where needed, passing project to print_branch_sections, correcting the final nesting line from └─╯ to ├─╯, and appending code to compute and print the 7-char base SHA as “● <sha> (base)”. These updates ensure the visual stack/tree output matches the prompt and that the graph explicitly shows the common merge base.
Fix broken graph rendering for second branch

Correct the stack-closing logic in status display to avoid incorrect nesting and a broken graph for the second branch. Remove unused mutable nesting, simplify the closure behavior for last vs non-last stacks, and ensure proper blank-line separation so the rendered graph lines (like "├─╯") appear correctly for the second branch.
Add a new branch subcommand and implementation to create virtual 
branches. 

This introduces a `Branch::New` clap subcommand (with optional
base id) and a new module that can create either an empty virtual branch
or a stacked branch based on an existing branch id.
This commit enhances the branch resolution logic in the 
`crates/but/src/branch/mod.rs` file. The changes include:

- First, it tries to resolve the provided ID as a CLI ID. If it matches 
  a single branch, it uses the branch name from the CLI ID.
- If the ID does not match any CLI ID, it treats it as a direct branch name and checks if the branch exists locally. If it does, it uses the provided name.
- If the ID does not match any branch, it returns an error.
- The commit also updates the log messages to reflect the new logic.
All unassigned files and all files assigned to the stack will be 
committed to the first stack. If there is more than one stack, list out 
the stacks and prompt the user which one they want to commit to. 

You cando `but commit -m 'my cache testing changes'` with a `-m` or 
`--message` to supply a message on the command line, or if you don't, it
will open $EDITOR or read the `core.editor` git config and launch that 
editor with a tempfile for you to supply the message.
Introduce a `new` subcommand and corresponding CLI parsing to allow 
inserting a blank commit before a specified commit or at the top of a 
stack when given a branch.
When displaying commit lists in but status and but log, blank commit 
messages were shown as empty lines, making output ambiguous. 

Introduce format_commit_message helper functions in both log and status 
modules that return the first line of the commit message or "(blank 
message)" when the first line is empty.
This commit adds the ability to restore the workspace to a previous state
based on an oplog snapshot. The key changes are:

- Implement the `restore_to_oplog` function in the `restore` module
- Add support for partial SHA matching when looking up the target snapshot
- Display information about the target snapshot before confirming the restore
- Acquire exclusive access to the worktree before performing the restore
- Provide a success message with the new snapshot ID after the restore

The restore functionality is an important feature to allow users to easily
revert their workspace to a known good state, which is useful for debugging
or undoing unwanted changes.
The next_help_heading attributes on Subcommands were unused; remove them to clean up args.rs. Replace the hardcoded grouped help text in print_grouped_help() with logic that builds groups from an array and pulls each subcommand's oneline description from the clap spec (via CommandFactory). This avoids duplicating descriptions, centralizes subcommand metadata, and makes group ordering explicit.
Group unspecified Clap subcommands into MISC

Avoid hardcoding a MISC list by automatically collecting and printing any
subcommands that aren't included in the explicit groups. This adds a
HashSet to track printed commands, filters out hidden commands, and
prints remaining commands under a MISC heading so help output stays
accurate and requires less manual maintenance.
Add a --files / -f option to the status command to list files modified 
in each commit, each annotated with a CliId shortcode for rubbing.

This change was needed so users can inspect per-commit file changes 
directly from `but status -f` and reference files by a stable shortcode.

Add 'st' alias for 'but status’ and `stf` for `but status -f`
Ensure all CliId representations start with only letters g–z by 
replacing the previous base-36 (0-9, a-z) encoding with a base-20 
alphabet of "ghijklmnopqrstuvwxyz". The second part of the ID can be the
full 36-char range. This is to ensure there is no confusion with a SHA.

Special-case unassigned area as "00"
Distinguish committed files from uncommitted/unassigned ones by adding a
CommittedFile variant to CliId, generating hashes that include the 
commit OID so identical paths in different commits get unique 
shortcodes. 

Update display in status to use the committed file IDs, and 
add handling in rub to reject or explicitly bail on unsupported 
operations involving committed files (extracting/moving between 
commits/branches) with clear error messages.

This was needed so files with the same path but different contexts 
(unassigned, assigned to a branch, or present in a specific commit) 
receive distinct short IDs and to prevent invalid rub operations against
committed files.
Add plumbing to allow uncommitting files from commits via the `rub` 
subcommand so the UI's "uncommit" action (alt-click) can call into the 
CLI.
Support identifying branches and commits by branch name (exact or 
partial) and by partial SHA prefixes in addition to the existing CliId 
lookup. 

This change adds helper functions to find branches by name and 
commits by SHA prefix, extends the CliId.from_str resolution to try 
branch-name and SHA matching before/alongside existing prefix/exact 
CliId matching, and de-duplicates results. 

It also improves ambiguity error messages in rub to include branch names
and suggest using longer SHAs or full branch names to disambiguate.
Clarify why a commit might not be found and suggest running 'but status'
to refresh state. 

Provide more context in errors for source/target lookups, and include 
the short commit id in squash/undo errors so users know which commit is 
affected.
Add simple colored status decorations to commits to indicate whether a 
commit is remote-only (R), pushed/both (P), or local-only (L). 

This makes it easier to visually distinguish commits that exist only 
upstream, those already pushed and present locally, and local-only 
commits.
schacon and others added 21 commits September 2, 2025 13:05
Introduce a new ‘unapply' branch subcommand and implement unapply_branch
to unapply a virtual branch (or branch by CLI ID/full name). 

This changeadds the CLI variant, wires the handler into main, and 
implements logic to resolve CLI IDs, locate the associated stack, call 
unapply_stack, and print user-facing messages.
This change implements creating a new virtual branch based on the 
target branch by locating the target stack, creating a 
BranchCreateRequest, and calling create_virtual_branch.

Note: proper 
stacking relationships and full integration with the stacking system 
remain TODO and will require further work.
When attempting to assign a file that is locked by hunks on other 
commits, the code previously printed a debug-style rejection list and 
then misleadingly reported success. 

Instead, fail fast with a user-friendly error that explains the file is
locked to other commit(s) and suggests using git to modify commits or 
move the changes. This prevents confusing output and makes the failure 
reason clear.
Add an -o/--only option to the commit command so only files assigned to 
the target stack are committed and unassigned files are excluded when 
requested. 

This was needed to provide finer control over commits (commit only 
assigned files) instead of always including unassigned files.

Changes:
- CLI: add --only (-o) boolean flag to commit args.
- Commit logic: respect the only flag and skip adding unassigned files when true.
- Main wiring: pass the only flag from CLI into commit handler.
Remove the --stack option and accept an optional branch ID or name to 
derive the stack to commit to. This lets users run commands like `but 
commit -m "message" x4` or `but commit my-branch-name`.

Changes:
- CLI: replace `--stack`/-s with a positional/optional `branch` argument.
- commit API: rename `stack_hint` to `branch_hint` and thread it through callers.
- Selection logic: update select_stack to accept a CommandContext, match exact branch names first, then attempt to parse CLI IDs and resolve branch CLI IDs to stacks, and improve error messages to reference branches.
- Main: pass `branch` through to the commit handler.
Committing to a stacked branch was unstacking it because the commit 
routine let the parent be auto-detected, which could detach the stack. 

This change finds the intended target branch (honoring an optional 
branch hint via exact name or CLI id parsing), uses that branch's tip as
the explicit parent for create_commit_simple, and thus preserves the 
stack relationship.

- Resolve target stack and branch selection: try branch hint first, fall back to stack HEAD.
- Parse CLI branch hints to match branch names when needed.
- Pass branch tip as parent_id to the simple commit engine to avoid unstacking.
Ensure each `but rub` command records an oplog entry by creating a 
snapshot before performing state-changing operations. 

The change imports oplog types and adds a helper create_snapshot that 
uses the project's exclusive worktree access and 
CommandContext::create_snapshot.

Snapshot creation calls (with appropriate OperationKind values) are 
added across all relevant match arms (moves, assigns, amends, undo, 
squash, etc.).
Ensure "but undo" only reverts the most recent non-restore command instead of stepping back multiple times. Previously the code always inspected the second snapshot which could point to a restore operation and cause undo to jump back further; now we fetch more snapshots, skip the current one, and select the first snapshot whose operation is not a RestoreFromSnapshot. If no such snapshot exists, we print a message and exit. This prevents undo from reverting more than the last actual user operation.
Undo: restore oplog head as undo target

Simplify the undo command to use the oplog head (the last operation before the current state) as the target to restore. The previous logic fetched many snapshots and attempted to skip restore operations to find a prior non-restore operation; this was more complex than necessary. Fetch only the last snapshot and treat it as the target, remove unused OperationKind import and the verbose current-operation printout. This makes undo behavior clearer and reduces snapshot traversal complexity.
Restore second-most recent snapshot instead of latest

The undo command was intended to restore the second-most recent 
operation (i.e., step back one more change), but it only listed one 
snapshot and selected the most recent. 

Change to request two snapshots and validate that at least two exist, 
then select snapshots[1] as the target. This ensures the command 
restores the previous oplog entry instead of the current head.
so if you have status:

❯ but st
╭  jy [my-stacked-branch4]
│  lz A t.md
│
● L rg 6f9a7dd hite
● L vy 31f83d6 test
● L qr 07f46fb move network info
│
│
● b2f17f4 (base)

00 Unassigned Changes
l9 M hello_world.rb
g6 A t1.md
rj A t2.md
ix A t3.md

you can run but rub g6-ix jy and it will rub all three of those files. 
or but rub g6,ix jy and it will only rub those two.
Allow commands to accept multiple source identifiers by parsing ranges 
(start-end), comma-separated lists, or single IDs. 

Previously the ids() function enforced exactly one source and returned 
a single CliId; now it returns a Vec<CliId> for sources and a single 
target.

This change adds parse_sources, parse_range, and parse_list helpers, 
uses status/committed lists to resolve ranges, and improves error 
messages for not-found or ambiguous items.
Use the same display order as the status command when resolving a 
start..end file range. 

The previous implementation searched an unordered file list (and fell 
back to committed files), which could omit files that appear between the
endpoints in the UI.

This change adds get_all_files_in_display_order() to build the file list
in the exact order shown by status (grouping assignments by file, 
iterating assigned files per stack, then unassigned files) and uses it 
to compute ranges. This ensures ranges include all files shown between 
the endpoints in the status view.
Allow the oplog command to return JSON when the json flag is set. 

Previously the json parameter was unused; this change wires the flag 
into show_oplog, prints an empty JSON array when no snapshots are found,
and emits pretty-printed JSON for the snapshots. 

When not in JSON mode, the existing human-readable, colorized output is 
preserved. Additionally, minor refactoring fixes variable indentation 
and ensures snapshot details are handled correctly for both output 
modes.
Add key/value handling to the Config subcommand so `but config <key> 
[value]` can get or set configuration values (e.g.,user.name / 
user.email). 

Implement config::handle to dispatch: setting writes to the
local repo config, getting uses existing lookup logic (including 
user.name/email helpers) and prints JSON when requested; fall back to 
the original show() behavior when no key is provided. Update CLI args to
accept optional key and value and wire the new handler into main.

This change was needed so that commands like `but config user.name 
<name>` actually set the name and `but config user.name` returns the 
effective value using the same lookup order as other config reads. It 
preserves existing behavior for showing all configuration and improves 
UX by supporting direct get/set operations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rust Pull requests that update Rust code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants