Skip to content

modify command#72

Open
skarim wants to merge 25 commits intomainfrom
skarim/modify
Open

modify command#72
skarim wants to merge 25 commits intomainfrom
skarim/modify

Conversation

@skarim
Copy link
Copy Markdown
Collaborator

@skarim skarim commented May 4, 2026

Screenshot 2026-05-03 at 10 50 54 PM

Summary

Adds a new interactive modify command that lets users restructure an existing stack — drop, fold, rename, and reorder branches — then apply all changes as a single atomic operation with cascading rebases. Includes full conflict recovery (--continue / --abort), submit integration for recreating the stack on GitHub, and comprehensive documentation.

Motivation

Today, restructuring a stack requires manually unstacking branches, reordering them, and restacking — a tedious and error-prone workflow. The modify command provides a safe, interactive way to perform these operations with undo support, conflict recovery, and automatic cascading rebases.

What's included

New command: gh stack modify

An interactive TUI (built on Bubble Tea) for staging structural changes to a stack:

  • Drop (x) — Remove a branch from the stack. The branch is deleted locally; if it had an open PR, the user is reminded to close it manually.
  • Fold down (d) — Merge a branch's commits into the branch below via cherry-pick. Useful for combining related work.
  • Fold up (u) — Merge a branch's commits into the branch above by adjusting rebase boundaries so the cascading rebase replays both sets of commits.
  • Rename (r) — Rename a branch locally. The rename prompt replaces the bottom status bar, showing the original name for reference.
  • Reorder (Shift+↑/↓) — Move a branch up or down in the stack. Reorder and structural changes (drop/fold/rename) are mutually exclusive to prevent ambiguous operations.
  • Undo (z) — Revert the last staged action.

All changes are staged visually in the TUI and applied together when the user presses Ctrl+S. The TUI shows annotations for each pending change (red strikethrough for drops, yellow for folds, magenta for moves, cyan for renames).

Conflict recovery

  • --continue — Resume after resolving rebase or cherry-pick conflicts. The state file tracks conflict type, branch context, and remaining work so the cascading rebase picks up exactly where it left off.
  • --abort — Restore the stack to its exact pre-modify state using the snapshot saved before any changes were applied. All branch tips, names, and stack metadata are rolled back.

State is persisted to .git/gh-stack-modify-state with phases (applyingconflictpending_submit) and a full snapshot for recovery.

Submit integration

After modifying a stack that exists on GitHub, gh stack submit detects the pending modifications and:

  1. Confirms with the user before overwriting the remote stack
  2. Deletes the old remote stack (to prevent GitHub auto-merge detection)
  3. Force-pushes each branch sequentially (not atomically, to avoid race conditions)
  4. Updates PR base branches to reflect the new stack order
  5. Recreates the remote stack via the stack API

Shared TUI components

The modify TUI shares a common rendering foundation with the existing view command:

  • internal/tui/shared/ — Extracted styles, node rendering, scroll math, header with progressive disclosure, and mouse handling into a shared package
  • The view command was refactored to use these shared components (no behavioral changes)
  • The modify command extends the shared base with action annotations, keyboard shortcuts, and the rename prompt

Precondition guards

  • Other stack commands (add, push, sync, rebase, unstack) check for an in-progress modify session and block with a helpful message
  • Modify itself checks for: clean working tree, no active rebase, no merge-queue PRs, stack linearity (no diverged branches)

Architecture

internal/modify/
├── state.go          # State file types, I/O, phase constants
├── apply.go          # Apply engine: BuildSnapshot, BuildPlan, ApplyPlan, Unwind, ContinueApply
└── preconditions.go  # CheckNoMergeQueuePRs, CheckStackLinearity

internal/tui/
├── shared/           # Shared rendering, styles, scroll, header, mouse handling
├── modifyview/       # Modify TUI model, types, styles, status bar, help overlay
└── stackview/        # View TUI (refactored to use shared/)

cmd/modify.go         # Command entry, preconditions, --continue/--abort flags

Key design decisions

  • Fold-down uses cherry-pick (commits don't exist in target's ancestry). Fold-up adjusts originalParentTips so the cascading rebase naturally replays both commit sets.
  • Sequential push in submit (not atomic) to prevent GitHub from detecting auto-merges when multiple branches update simultaneously.
  • Stack identified by index in the state file, not trunk name — multiple stacks commonly share the same trunk (e.g., main).
  • Action mode exclusivity — reorder and structural changes can't be mixed, enforced in the TUI with grayed-out shortcuts.

Testing

  • internal/modify/apply_test.go — tests covering drop, fold-down, fold-up, rename, reorder, mixed operations, rebase conflicts, cherry-pick conflicts, continue/abort, multi-stack identification, unwind
  • internal/tui/modifyview/model_test.go — TUI tests covering keyboard navigation, all action types, undo, mutual exclusivity, fold validation, help toggle, mouse support
  • cmd/modify_test.go — command-level tests covering preconditions, state guards, continue/abort flows
  • cmd/submit_test.go — tests for submit with pending modifications

Copilot AI review requested due to automatic review settings May 4, 2026 03:02
@skarim skarim linked an issue May 4, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new interactive gh stack modify workflow for restructuring a local stack (drop/fold/rename/reorder) with recovery support, while refactoring TUI rendering into shared helpers and adjusting submit behavior to better support post-modify stack recreation.

Changes:

  • Introduces gh stack modify command, TUI, apply engine (with snapshot/state for conflict recovery), and docs.
  • Centralizes TUI styles/rendering/header/scroll/click logic in internal/tui/shared and updates stackview to use it.
  • Updates submit to push branches sequentially and to handle “pending modify” remote stack recreation.
Show a summary per file
File Description
internal/tui/stackview/styles.go Removes stackview-specific styles (moved to shared).
internal/tui/stackview/model_test.go Updates header text expectations and shortcut visibility behavior.
internal/tui/stackview/model.go Uses shared header/node rendering and shared click/scroll helpers.
internal/tui/shared/styles.go Adds shared lipgloss styles/icons for TUI views.
internal/tui/shared/scroll.go Adds shared scroll clamp/ensure-visible and scroll slicing helper.
internal/tui/shared/render.go Adds shared branch node rendering + mouse hit-testing + browser open helpers.
internal/tui/shared/header.go Adds shared header rendering with progressive disclosure and shortcut layout.
internal/tui/modifyview/types.go Defines modify-specific action and node state types.
internal/tui/modifyview/styles.go Adds modify-specific badges/overrides and overlay/status styles.
internal/tui/modifyview/status.go Adds pending-change summary and bottom status bar rendering.
internal/tui/modifyview/model_test.go Adds comprehensive TUI behavior tests for modify view.
internal/tui/modifyview/model.go Implements modify TUI interactions, rendering, and state tracking.
internal/tui/modifyview/help.go Adds help overlay content and centering logic.
internal/stack/stack.go Adds SaveWithLock for saving while a lock is already held.
internal/modify/state.go Implements on-disk modify session state (apply/conflict/pending_submit).
internal/modify/preconditions.go Adds modify precondition checks (merge queue + linearity).
internal/modify/apply.go Implements apply engine (snapshot, plan, renames/folds/drops/reorder/rebase, unwind/continue).
internal/git/mock_ops.go Extends git mock ops with rename/cherry-pick/uncommitted/merge-log helpers.
internal/git/gitops.go Extends git ops interface + default implementations for new modify needs.
internal/git/git.go Adds package-level wrappers for the new git ops methods.
go.sum Adds new indirect dependency checksums.
go.mod Adds indirect dependency required by new functionality.
docs/src/content/docs/reference/cli.md Documents gh stack modify command and behavior.
docs/src/content/docs/guides/workflows.md Updates workflow guidance to use gh stack modify.
docs/src/content/docs/guides/modify.md Adds dedicated guide for restructuring stacks via modify.
docs/astro.config.mjs Adds docs navigation entry for the modify guide.
cmd/utils.go Adds new exit code for modify recovery.
cmd/unstack.go Blocks unstack when modify is mid-apply/conflict via state guard.
cmd/sync.go Blocks sync when modify is mid-apply/conflict via state guard.
cmd/submit_test.go Updates push expectations + adds pending-modify/submit integration tests.
cmd/submit.go Adds pending-modify handling and switches to sequential per-branch push flow.
cmd/root.go Registers the new modify command.
cmd/rebase.go Blocks rebase when modify is mid-apply/conflict; refactors conflict printing helper.
cmd/push.go Blocks push when modify is mid-apply/conflict via state guard.
cmd/modify_test.go Adds command-layer tests for modify state/preconditions/builders.
cmd/modify.go Implements gh stack modify, including --continue/--abort and preconditions.
cmd/add.go Blocks add when modify is mid-apply/conflict via state guard.
README.md Documents gh stack modify usage, keys, and preconditions.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 38/40 changed files
  • Comments generated: 5

Comment thread cmd/submit.go
Comment thread internal/modify/apply.go
Comment thread internal/modify/apply.go Outdated
Comment thread internal/modify/state.go
Comment on lines +999 to +1016
// Count fixed bottom lines (always visible, not scrollable).
// The bottom section always has 2 lines: one for contextual info
// (rename prompt or error, blank when neither) and one for the status bar.
bottomLines := 2 // post-scroll newline + context line + status bar

// Scrolling — reserve space for header and fixed bottom
reservedLines := bottomLines
if showHeader {
reservedLines += shared.HeaderHeight
}
viewHeight := m.height - reservedLines
if viewHeight < 1 {
viewHeight = 1
}

out.WriteString(shared.ApplyScrollToContent(b.String(), m.scrollOffset, viewHeight))
out.WriteString("\n")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional — the extra line ensures the status bar is pinned to the very bottom of the viewport.

skarim and others added 3 commits May 4, 2026 01:20
Bug 1: Move RevParseMap error check before using originalRefs.
The error from git.RevParseMap() was deferred past iteration of
originalRefs, which could panic on a nil map.

Bug 2: Differentiate cherry-pick vs rebase conflicts in modify.
Cherry-pick conflicts don't save state as 'conflict' phase, so
--continue won't work. Now prints --abort-only instructions for
cherry-pick conflicts.

Bug 3: Unwind now cleans up branches created by renames.
After restoring snapshot branches, Unwind deletes renamed branch
names that don't belong to the original snapshot.

Bug 4: Simplify push message in submit command.
Changed from 'Pushing N branches to remote...' to 'Pushing to
remote...' since individual branches may fail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
11: Add named constants for phase strings (PhaseApplying, PhaseConflict,
PhasePendingSubmit) in state.go; replace remaining raw literals in
state.go CheckStateGuard.

14: Fix bottomLines comment mismatch — listed 3 items but value is 2.

15: Extract magic number 88 to MinWidthForArt constant in header.go.

16: Remove unused stackview import anchor in model.go — the import
is used via types.go where BranchNode is embedded.

17: Simplify CheckStackLinearity parent resolution — ActiveBaseBranch
already handles skipping merged branches.

18: Fix rename undo matching any rename — add NewName check so only
the specific rename being undone is matched.

20: Add TestUndoRename and TestUndoRename_DoesNotAffectOtherRenames
to validate rename undo behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously, cherry-pick conflicts during fold-down operations could only
be resolved with --abort. Now they save full conflict state (phase,
conflict type, fold branch/target, remaining branches) to the state file,
enabling recovery via 'gh stack modify --continue'.

Changes:
- Add ConflictType field to StateFile (rebase or cherry_pick)
- Add FoldBranch/FoldTarget fields for cherry-pick context
- Add CherryPickContinue to git package (cherry-pick --continue)
- Save cherry-pick conflict state in ApplyPlan with remaining branches
- ContinueApply handles both rebase and cherry-pick conflicts
- Unified conflict messaging in cmd/modify.go (both types show --continue)
- Updated test to verify cherry-pick conflict state is saved correctly
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support excluding/removing specific branches from stacks

2 participants