-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat: add git-spice integration via new Stacks pane #5219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
josephschmitt
wants to merge
49
commits into
jesseduffield:master
Choose a base branch
from
josephschmitt:spice-stack-review
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
feat: add git-spice integration via new Stacks pane #5219
josephschmitt
wants to merge
49
commits into
jesseduffield:master
from
josephschmitt:spice-stack-review
+2,903
−23
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Implements a new "Stacks" tab in the branches window that displays git-spice managed branch stacks when the `gs` CLI is available and the repo is initialized with git-spice. Features: - Displays branch stacks in a tree structure with proper indentation - Shows branch status (current, needs restack, needs push) - Displays PR information (number, status) - Keybindings for stack operations (restack, submit, navigate) - Integrates with lazygit's refresh and context system - Only appears when git-spice is available New files: - pkg/commands/models/spice.go: Data models for spice stacks - pkg/commands/git_commands/spice.go: Git-spice CLI commands - pkg/commands/git_commands/spice_loader.go: Data loader and tree builder - pkg/gui/presentation/spice_stacks.go: Display string formatting - pkg/gui/context/spice_stacks_context.go: Context definition - pkg/gui/controllers/spice_stacks_controller.go: Controller with keybindings Modified files: - pkg/commands/git.go: Added Spice commands and loader - pkg/gui/context/context.go: Added SPICE_STACKS_CONTEXT_KEY - pkg/gui/context/setup.go: Added SpiceStacksContext initialization - pkg/gui/controllers.go: Registered SpiceStacksController - pkg/gui/controllers/helpers/refresh_helper.go: Added refresh logic - pkg/gui/gui.go: Added "Stacks" tab to branches window - pkg/gui/types/common.go: Added SpiceStackItems to Model - pkg/gui/types/refresh.go: Added SPICE_STACKS refresh type - pkg/gui/types/views.go: Added SpiceStacks view - pkg/gui/views.go: Added SpiceStacks view configuration and titles
Prevent git-spice commands from running on startup by implementing lazy loading and caching: - Cache IsInitialized() result to avoid repeated command execution - Only check IsAvailable() (PATH lookup) on startup, not IsInitialized() - Add GetOnFocus() handler with hasRefreshed flag to load data only when Stacks tab is first focused - Fix incorrect initialization command (gs repo status -> gs log short) - Fix Refresh() return value handling in controller methods - Remove unused imports (oscommands, fmt) - Add debug logging to spice_loader for troubleshooting This ensures git-spice has zero performance impact unless the user actually switches to the Stacks tab. Co-Authored-By: Claude <noreply@anthropic.com>
Align keybindings with the local branches pane for consistency: - Use Universal.Select (space), Universal.New (n), Universal.Remove (d) - Use Branches.RebaseBranch (r) and Branches.CreatePullRequest (o) - Add branch creation, deletion, and stack reordering commands - Use ctrl modifiers for stack navigation to avoid conflicts New commands: - gs branch create (n) - gs branch delete (d) - gs branch up/down (ctrl-j/k) for reordering within stack Co-Authored-By: Claude <noreply@anthropic.com>
After creating a branch, deleting, or performing other operations, the Stacks view was not updating because: 1. Refresh calls didn't include SPICE_STACKS in the scope 2. The hasRefreshed flag wasn't being reset Additionally, checkout was switching to the branches pane due to using CheckoutRef helper instead of Git().Branch.Checkout directly. Changes: - All operations now reset hasRefreshed flag and explicitly refresh SPICE_STACKS scope - Checkout uses Git().Branch.Checkout to stay in Stacks pane - Added git_commands import for CheckoutOptions type Co-Authored-By: Claude <noreply@anthropic.com>
Implement a keybinding (l) and config option to switch between 'gs log short' and 'gs log long' formats for displaying git-spice stacks. The default is short format, which shows basic branch hierarchy. Long format includes: - PR information (number, URL, status) - Sync status (ahead/behind, needs-push) - Commits array with SHA and subject for each commit in the branch Features: - Press 'l' in the Stacks tab to toggle between short and long format - Format choice is persisted in config at git.spice.logFormat - Commits are displayed as nested, non-interactive items under each branch in long format, with proper indentation and styling (cyan SHA + subject) - Navigation (j/k) skips over commits and only selects branch items - All branch operations (checkout, restack, submit, delete, move) skip commit items to prevent invalid operations Implementation details: - Added SpiceConfig struct with LogFormat field to user_config.go - Modified GetStackBranches() to accept format parameter - Extended SpiceStackItem and SpiceBranchJSON models with commit fields - Updated spice_loader to include commits in long format - Added buildCommitPrefix() to presentation layer for commit display - Overrode HandleNextLine/HandlePrevLine in controller to skip commits - Added ensureValidSelection() to guarantee starting on a branch Co-Authored-By: Claude <noreply@anthropic.com>
Add SPICE_STACKS to refresh scopes in multiple locations: - background.go: Include in background fetch refresh - files_controller.go: Include when fetching/pulling - refs_helper.go: Include when checking out branches Also removed the hasRefreshed flag that was preventing re-fetches. Now the spice stacks view will automatically update when: - New commits are created - Branches are checked out - Background fetches occur - Git push/pull operations complete This fixes the issue where the spice stacks view showed stale commit data even after making new commits. Co-Authored-By: Claude <noreply@anthropic.com>
The SpiceStacksController defines custom HandlePrevLine/HandleNextLine methods to skip commit items in long mode. However, the automatic ListController attachment was overriding these with default navigation, preventing upward (k key) navigation from working properly. Fixed by excluding SpiceStacks from automatic ListController attachment and adding explicit navigation keybindings to SpiceStacksController. Co-Authored-By: Claude <noreply@anthropic.com>
In long mode, the stack view displays commits under each branch, but only branches are selectable. The footer "X of Y" was counting all items including commits, which was confusing. Added custom footer logic to count only branches and calculate which branch number is currently selected. Also overrode HandleFocus, HandleRender, and FocusLine to ensure the custom footer is always displayed correctly. Co-Authored-By: Claude <noreply@anthropic.com>
Changed the tree traversal to start from leaves (top of stack) and traverse
down to the base, instead of starting from roots (base) and going up.
This matches the gs log output where the top of the stack appears first
and the base branch appears at the bottom:
Before (incorrect):
master
└─ claude/add-spice-stacks-tab-x1jBG
└─ spice-stacks-long-desc
After (correct):
spice-stacks-long-desc
└─ claude/add-spice-stacks-tab-x1jBG
└─ master
Changes:
- Find leaves (branches with no Ups) instead of roots
- DFS traverses down (to parent) instead of up (to children)
- Depth increases as we go down the stack
- Removed unused children map
Co-Authored-By: Claude <noreply@anthropic.com>
The hasRefreshed field was accidentally removed during rebase but is still referenced throughout the controller. Re-add it to fix build errors. Co-Authored-By: Claude <noreply@anthropic.com>
Replace ├ (Box Drawings Light Vertical and Right) with ┌ (Light Down and Right) at the branch level in git-spice stack rendering for cleaner visual appearance. Co-Authored-By: Claude <noreply@anthropic.com>
Remove bullet points from commit display so commit SHAs align directly with branch names for cleaner visual hierarchy. Co-Authored-By: Claude <noreply@anthropic.com>
Changed the tree traversal to start from leaves (top of stack) and traverse
down to the base, instead of starting from roots (base) and going up.
This matches the gs log output where the top of the stack appears first
and the base branch appears at the bottom:
Before (incorrect):
master
└─ claude/add-spice-stacks-tab-x1jBG
└─ spice-stacks-long-desc
After (correct):
spice-stacks-long-desc
└─ claude/add-spice-stacks-tab-x1jBG
└─ master
Changes:
- Find leaves (branches with no Ups) instead of roots
- DFS traverses down (to parent) instead of up (to children)
- Depth increases as we go down the stack
- Removed unused children map
Co-Authored-By: Claude <noreply@anthropic.com>
- Copy git-spice's fliptree rendering approach (children before parents) - Add SiblingIndex field to track sibling position for proper connectors - Use lighter weight box-drawing characters (│ ┌ ├ ┴ ─) - Add branch indicators: ◯ for regular branches, ● for current branch - Fix commit ordering in long format (commits after their branch) - Ensure branch names align with commit hashes in long format - Add comprehensive unit tests for tree prefix rendering The rendering now matches gs log output with correct: - Vertical pipe placement based on ancestor sibling indices - Branch connectors (┌─ for first sibling, ├─ for others) - Horizontal-up connector (┴) when branch has children - Consistent spacing between indicators and branch names Co-Authored-By: Claude <noreply@anthropic.com>
… mode In long stack log mode, sibling branches were incorrectly showing the ┴ connector (indicating they have children) when they didn't. This happened because hasItemsAtDepthBefore was counting commits from sibling branches as children. Fix by filtering out commits and only counting actual branches. Co-Authored-By: Claude <noreply@anthropic.com>
When a branch has children, its prefix includes the ┴ character making it one character wider. The commit prefix now accounts for this by adding an extra space when the parent branch has children. Co-Authored-By: Claude <noreply@anthropic.com>
The hasItemsAtDepthBefore() function was checking for any branch at depth+1 before the current index, but this incorrectly included children of sibling branches. Now it iterates backwards and stops when encountering a sibling, ensuring only actual children are counted. Co-Authored-By: Claude <noreply@anthropic.com>
Combine branch name and status decorations into a single column so decorations (✓, ⟳ restack, ↑ push, ↓N) appear immediately after each branch name instead of being aligned in a separate padded column. Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unused hasRefreshed field and all assignments (dead code) - Fix incorrect status messages: use SpiceRestackingStatus and SpiceSubmittingStatus instead of DeletingStatus - Add i18n translations for all hard-coded UI strings - Add Enabled config option (git.spice.enabled) to disable integration - Create spice_integration.go with isSpiceEnabled() helper Co-Authored-By: Claude <noreply@anthropic.com>
The Stacks pane wasn't refreshing when operations like commits, branch creation, or rebases occurred because SPICE_STACKS was missing from the default refresh scope. Co-Authored-By: Claude <noreply@anthropic.com>
Removes custom navigation that skipped commits, allowing users to select both branches and commits. Adds gs commit fixup integration for amending staged changes into selected commits. Uses disabled reasons to properly restrict branch-only commands. Co-Authored-By: Claude <noreply@anthropic.com>
The commit command handlers had several issues: 1. Hash lookup used exact equality (==) comparing 7-char short hashes against 40-char full hashes, so commits were never found. Fixed by using strings.HasPrefix for prefix matching. 2. Rebase operations didn't wrap errors with CheckMergeOrRebase, so conflicts weren't handled properly. Added the wrapper to all rebase operations (squash, fixup, drop, edit, reword, amend). 3. commitCopy passed LocalCommits context to CopyRange, but the user is in SpiceStacks context, so the selection range was wrong. Fixed by directly manipulating cherry-pick data for the specific commit. Also replaced the spice-specific commitFixup with standard commit commands that mirror the local commits view behavior. Co-Authored-By: Claude <noreply@anthropic.com>
Enter on a branch row opens SubCommits view for that branch. Enter on a commit row opens CommitFiles view showing the diff. Escape returns to spice stacks via parent context tracking. Co-Authored-By: Claude <noreply@anthropic.com>
Consolidate duplicate Universal.Select bindings into a single binding with a unified press handler that routes to commitCheckout or checkout based on item type. Previously, having two bindings for the same key caused the first one's disabled reason to show when on a branch. Co-Authored-By: Claude <noreply@anthropic.com>
When navigating the stacks view, the main window now updates based on the selected row - showing commit log for branches and diff/patch for commits, matching the behavior of branches and commits views. Co-Authored-By: Claude <noreply@anthropic.com>
Reorganize keybindings in the Stacks pane to reduce conflicts with standard lazygit commands by consolidating operations into two menus: - S: Stack Operations (restack, submit, create, delete, move branch) - G: Stack Navigation (up, down, top, bottom) This frees up keys like n, r, o, and ctrl+u/d/j/k for their standard lazygit functions. Also changes log toggle from 'l' to '~' to avoid conflict with panel navigation. Co-Authored-By: Claude <noreply@anthropic.com>
- ctrl-j/k now works on both branches and commits: - Branch selected: moves branch up/down in stack order (gs branch up/down) - Commit selected: moves commit up/down via interactive rebase - Changed log format toggle from ~ to V (Shift+V) - Removed "Move branch up/down" from Stack Operations menu since ctrl-j/k is now a direct keybinding Co-Authored-By: Claude <noreply@anthropic.com>
Use DescriptionFunc to show "Move branch down/up in stack" when a branch is selected, or "Move commit down/up" when a commit is selected. Co-Authored-By: Claude <noreply@anthropic.com>
Add SubmitOpts to support --no-publish and --update-only flags for git-spice submit commands. The Stack Operations menu now has: - Direct submit items (s/S) for default behavior - Options submenus (o/O) with no-publish, update-only, and default Co-Authored-By: Claude <noreply@anthropic.com>
When a commit is selected, pressing 'y' opens a menu to copy commit attributes: hash, subject, message, URL, diff, or author. The binding is disabled when a branch is selected. Co-Authored-By: Claude <noreply@anthropic.com>
- n: Create new branch with selected branch as target (or parent branch if commit selected) - N: Create new commit using gs commit create on current branch Co-Authored-By: Claude <noreply@anthropic.com>
When git-spice is available but not initialized, the stacks pane now shows an instructional message prompting the user to press ENTER. Pressing ENTER opens a branch picker to select the trunk branch, then runs `gs repo init --trunk <branch>` to initialize git-spice. Co-Authored-By: Claude <noreply@anthropic.com>
Replace toggle with a menu (V key) offering Short/Long/Default options. The selection persists in state.yml under spice.logFormat, with precedence: AppState override > UserConfig default > "short". Co-Authored-By: Claude <noreply@anthropic.com>
- Remove getEffectiveLogFormat() - duplicate of function in spice_loader.go - Remove branchSelected() - never called, branchSelectedReason() is used instead Co-Authored-By: Claude <noreply@anthropic.com>
These functions were implemented but never used in the codebase. Co-Authored-By: Claude <noreply@anthropic.com>
- Remove SpiceCommitFixup and SpiceFixupStatus from TranslationSet - These were unused after removing CommitFixup function Co-Authored-By: Claude <noreply@anthropic.com>
- Remove IsLast field from SpiceStackItem model - Remove PRURL field from SpiceStackItem model - Remove isLast parameter from dfs function in spice_loader.go - These fields were set but never read Co-Authored-By: Claude <noreply@anthropic.com>
The continuing map was passed to buildTreePrefix and buildCommitPrefix but never actually used within those functions. Co-Authored-By: Claude <noreply@anthropic.com>
- Remove section divider comments from spice_stacks_controller.go - Remove redundant doc comments from spice.go - Remove redundant comments from spice_stacks_context.go Co-Authored-By: Claude <noreply@anthropic.com>
Migrate all hardcoded strings in spice_stacks_controller.go to use translation keys from the TranslationSet. This includes: - Menu titles and labels - Navigation tooltips - Error messages - Status descriptions Co-Authored-By: Claude <noreply@anthropic.com>
- Add findCommitOrError() helper that combines findCommitByHash with error handling - Add refreshSpiceStacksOnly() helper for spice-only view refresh - Add refreshAllViews() helper for full view refresh - Update all callers to use the new helper methods Co-Authored-By: Claude <noreply@anthropic.com>
Change the status display from showing "↑ push" to showing the actual commit count like "↑3" when there are unpushed commits. This provides more useful information about how many commits are ahead. - Update formatStatus() to use Ahead count instead of NeedsPush boolean - Remove unused NeedsPush field from SpiceStackItem model Co-Authored-By: Claude <noreply@anthropic.com>
- Add SpiceStacks() view helper to integration test framework - Add spiceStacks to cheatsheet context title map - Create 6 integration test files for spice stacks feature: - basic_display.go: View shows stacks when initialized - checkout_branch.go: Checkout branch from stacks view - create_branch.go: Create new branch (n key) - delete_branch.go: Delete branch from stack - navigation_menu.go: Stack navigation menu (G key) - stack_operations_menu.go: Stack operations menu (S key) - log_format_toggle.go: Toggle between short/long format (V key) Note: Tests are skipped by default as they require git-spice to be installed Also fix compilation error: use = instead of := for err reassignment Co-Authored-By: Claude <noreply@anthropic.com>
- Remove continuing parameter from test file to match function signature - Fix gofumpt formatting issues - Use _ for unused buildCommitPrefix item parameter Co-Authored-By: Claude <noreply@anthropic.com>
Nodes without children were indented 1 character less than nodes with children, causing siblings to not line up properly. This adds a horizontal line (─) when nodes don't have children, making all joints consistently 3 characters wide so siblings align correctly. Co-Authored-By: Claude <noreply@anthropic.com>
Add `gs repo sync` command to the Stack Operations menu (S key) with two options: sync (y) and sync with restack (Y). This pulls latest changes from remote and deletes branches with merged PRs. Co-Authored-By: Claude <noreply@anthropic.com>
The Sync function was incorrectly using NewGitCmd("gs") which prepends "git" to the command, resulting in "git gs repo sync" instead of "gs repo sync". This caused the error: "git: 'gs' is not a git command".
Changed to use a plain slice []string{"gs", "repo", "sync"} to match the pattern used by all other spice commands in the file.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The gs log command was missing the -S flag which requests Change Request status information. Without it, the status field in the JSON output was empty, causing PR numbers to display in white instead of their status-based colors (green for open, magenta for merged, red for closed). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
PR Description
A note on scope
I want to acknowledge upfront that this PR introduces an external integration with https://github.com/abhinav/git-spice, a tool that extends Git with stacked branch workflows. This isn't core Git functionality, and I completely understand if that puts it outside the scope of what lazygit wants to support. If you decide this doesn't belong in this project, I'll be disappointed, but understand completely.
That said, after using git-spice alongside lazygit for the past year, I found myself constantly context-switching between the terminal and lazygit. The experience of managing stacked PRs felt incomplete. I looked for a plugin system to contribute this without touching the main codebase, but couldn't find one: so here I am with a PR instead.
I also want to acknowledge that I've implemented support for git-spice specifically, rather than building a generic "stacked branch tool" abstraction. This was a practical decision: git-spice is the tool I know and use daily, and trying to abstract across multiple stacking tools (graphite, ghstack, etc.) would have significantly increased complexity without even knowing if you'd accept this feature. If maintainers feel a more generic approach would be preferable, I'm open to discussing that.
What is git-spice?
https://github.com/abhinav/git-spice is a CLI tool that helps manage stacked pull requests, a workflow where you break large changes into a chain of dependent, reviewable PRs:
Instead of one massive 2000-line PR, you get three focused, reviewable PRs that build on each other. git-spice handles the complexity of:
This workflow is increasingly popular (see Graphite, ghstack, Stacked Git) because it leads to faster reviews, cleaner history, and more incremental development.
The irony is not lost on me that I'm claiming to be a proponent of smaller, more focused PR's while myself trying to contribute a massive PR. However I actually built this feature using stacked PR's (see the screenshots), just figured it'd be easier to have this reviewed in one shot vs... 10
What this PR adds
A new Stacks pane (tab alongside Branches, Remotes, Tags) that provides native git-spice integration:
Visual tree display:
Full commit operations - squash, fixup, reword, drop, edit, amend, reset (same keybindings as the Commits pane)
Branch operations - checkout, create, delete, move up/down in stack
Stack operations menu (S key):
Navigation menu (G key): - Navigate up/down/top/bottom of stack
Two display formats - Short (hierarchy only) or Long (with PR status, sync indicators, individual commits)
Diff integration - Select any branch or commit to see its diff in the main panel
Following
lazygitconventionsI've tried hard to make this feel native to lazygit:
Minimal blast radius
This feature is designed to be completely optional:
Testing
Configuration
I'm happy to address any feedback, split this into smaller PRs if helpful, or discuss alternative approaches. Thanks for considering this! I really hope I don't have to maintain a fork of this project just to keep this feature 😅.
Please check if the PR fulfills these requirements
go generate ./...)