Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ When -m is omitted but -A or -u is used, your editor opens for the
commit message. When -m is provided without an explicit branch name,
the branch name is auto-generated based on the commit message and
stack prefix.`,
Example: ` # Add a new named branch to the stack
$ gh stack add my-feature

# Add a branch and commit staged changes
$ gh stack add -Am "Add user authentication" my-feature

# Auto-generate branch name from the commit message
$ gh stack add -m "Fix login bug"

# Add a branch and open editor to write commit message
$ gh stack add -A my-feature`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runAdd(cfg, opts, args)
Expand Down
8 changes: 8 additions & 0 deletions cmd/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ func AliasCmd(cfg *config.Config) *cobra.Command {
This installs a small wrapper script into ~/.local/bin/ that forwards all
arguments to "gh stack". The default alias name is "gs", but you can choose
any name by passing it as an argument.`,
Example: ` # Create the default 'gs' alias
$ gh stack alias

# Create a custom alias
$ gh stack alias gst

# Remove alias
$ gh stack alias --remove`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := defaultAliasName
Expand Down
10 changes: 10 additions & 0 deletions cmd/checkout.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ locally tracked stacks only.

When run without arguments, shows a menu of all locally available
stacks to choose from.`,
Example: ` # Check out a stack by PR number
$ gh stack checkout 42

# Check out a stack by branch name
$ gh stack checkout feat/api-routes

# Show a menu of all locally tracked stacks
$ gh stack checkout`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
Expand Down Expand Up @@ -114,6 +122,8 @@ func runCheckout(cfg *config.Config, opts *checkoutOptions) error {

cfg.Successf("Switched to %s", targetBranch)
cfg.Printf("Stack: %s", s.DisplayChain())
cfg.Printf("Run `%s` to see the full stack",
cfg.ColorCyan("gh stack view"))
return nil
}

Expand Down
5 changes: 5 additions & 0 deletions cmd/feedback.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ func FeedbackCmd(cfg *config.Config) *cobra.Command {
Use: "feedback [title]",
Short: "Submit feedback for gh-stack",
Long: "Opens a GitHub Discussion in the gh-stack repository to submit feedback. Optionally provide a title for the discussion post.",
Example: ` # Open the feedback form in your browser
$ gh stack feedback

# Open with a pre-filled title
$ gh stack feedback "My feature request"`,
RunE: func(cmd *cobra.Command, args []string) error {
return runFeedback(cfg, args)
},
Expand Down
8 changes: 8 additions & 0 deletions cmd/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ automatically with the correct base branch chaining.
If the PRs are not yet in a stack, a new stack is created. If some of
the PRs are already in a stack, the existing stack is updated to include
the new PRs (existing PRs are never removed).`,
Example: ` # Link branches into a stack (bottom to top)
$ gh stack link auth-layer api-routes ui-components

# Link existing PRs by number
$ gh stack link 41 42 43

# Specify a custom base branch for stack
$ gh stack link --base develop auth-layer api-routes`,
Args: cobra.MinimumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return runLink(cfg, opts, args)
Expand Down
14 changes: 11 additions & 3 deletions cmd/modify.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ Operations available:

All changes are staged in the TUI and applied together when you press Ctrl+S.
After applying, run 'gh stack submit' to push changes and recreate the stack on GitHub.`,
Example: ` # Open the interactive TUI to restructure the stack
$ gh stack modify

# Abort a modify session and restore the stack
$ gh stack modify --abort

# Continue after resolving conflicts from a modify
$ gh stack modify --continue`,
RunE: func(cmd *cobra.Command, args []string) error {
if opts.abort {
return runModifyAbort(cfg)
Expand Down Expand Up @@ -202,9 +210,9 @@ func runModifyAbort(cfg *config.Config) error {
if err := modify.UnwindFromStateFile(cfg, gitDir); err != nil {
cfg.Errorf("recovery failed: %s", err)
cfg.Printf("The stack may be in an inconsistent state.")
cfg.Printf("Try `%s` to fix, or `%s` + `%s` to recreate.",
cfg.ColorCyan("gh stack rebase"), cfg.ColorCyan("gh stack unstack --local"),
cfg.ColorCyan("gh stack init --adopt"))
cfg.Printf("Try `%s` to fix, or `%s` + `%s` to recreate.",
cfg.ColorCyan("gh stack rebase"), cfg.ColorCyan("gh stack unstack --local"),
cfg.ColorCyan("gh stack init --adopt"))
return ErrSilent
}
cfg.Successf("Stack restored successfully")
Expand Down
26 changes: 24 additions & 2 deletions cmd/navigate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ func UpCmd(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "up [n]",
Short: "Check out a branch further up in the stack (further from the trunk)",
Args: cobra.MaximumNArgs(1),
Long: `Check out a branch further up in the stack (further from the trunk).
Merged branches are automatically skipped.`,
Example: ` # Move one branch up
$ gh stack up

# Move three branches up
$ gh stack up 3`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
n := 1
if len(args) > 0 {
Expand All @@ -32,7 +39,14 @@ func DownCmd(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "down [n]",
Short: "Check out a branch further down in the stack (closer to the trunk)",
Args: cobra.MaximumNArgs(1),
Long: `Check out a branch further down in the stack (closer to the trunk).
Merged branches are automatically skipped.`,
Example: ` # Move one branch down
$ gh stack down

# Move two branches down
$ gh stack down 2`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
n := 1
if len(args) > 0 {
Expand All @@ -52,6 +66,10 @@ func TopCmd(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "top",
Short: "Check out the top branch of the stack (furthest from the trunk)",
Long: `Check out the top branch of the stack (furthest from the trunk).
Merged branches are automatically skipped.`,
Example: ` # Jump to the top of the stack
$ gh stack top`,
RunE: func(cmd *cobra.Command, args []string) error {
return runNavigateToEnd(cfg, true)
},
Expand All @@ -62,6 +80,10 @@ func BottomCmd(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "bottom",
Short: "Check out the bottom branch of the stack (closest to the trunk)",
Long: `Check out the bottom branch of the stack (closest to the trunk).
Merged branches are automatically skipped.`,
Example: ` # Jump to the bottom of the stack
$ gh stack bottom`,
RunE: func(cmd *cobra.Command, args []string) error {
return runNavigateToEnd(cfg, false)
},
Expand Down
12 changes: 12 additions & 0 deletions cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ func PushCmd(cfg *config.Config) *cobra.Command {
cmd := &cobra.Command{
Use: "push",
Short: "Push all branches in the current stack to the remote",
Long: `Push all branches in the current stack to the remote.

Uses --force-with-lease and --atomic to ensure safe, all-or-nothing pushes.
Merged and queued branches are automatically skipped. This command is safe to
run repeatedly — it will only update branches that have changed.`,
Example: ` # Push all stack branches to the default remote
$ gh stack push

# Push to a specific remote
$ gh stack push --remote upstream`,
RunE: func(cmd *cobra.Command, args []string) error {
return runPush(cfg, opts)
},
Expand Down Expand Up @@ -123,6 +133,8 @@ func runPush(cfg *config.Config, opts *pushOptions) error {
if hasBranchWithoutPR {
cfg.Printf("To create PRs for this stack, run `%s`",
cfg.ColorCyan("gh stack submit"))
} else {
cfg.Printf("Run `%s` to see your stack of PRs", cfg.ColorCyan("gh stack view"))
}
return nil
}
Expand Down
14 changes: 14 additions & 0 deletions cmd/rebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ func RebaseCmd(cfg *config.Config) *cobra.Command {

Ensures that each branch in the stack has the tip of the previous
layer in its commit history, rebasing if necessary.`,
Example: ` # Rebase the entire stack
$ gh stack rebase

# Only rebase from trunk to the current branch
$ gh stack rebase --downstack

# Only rebase from current branch to the top
$ gh stack rebase --upstack

# Continue after resolving conflicts
$ gh stack rebase --continue

# Abort and restore all branches
$ gh stack rebase --abort`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
Expand Down
140 changes: 112 additions & 28 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,25 @@ func RootCmd() *cobra.Command {
cfg := config.New()

root := &cobra.Command{
Use: "stack <command>",
Short: "Manage stacked branches and pull requests",
Long: "Create, navigate, and manage stacks of branches and pull requests.",
Use: "stack <command>",
Short: "Manage stacked branches and pull requests",
Long: `Stacked PRs let you break a large change into a chain of pull requests
that build on each other. Use ` + "`gh stack`" + ` to create and manage your stack
locally, then push to GitHub to create your stack of PRs.`,
Example: ` # Start a new stack targeting your default branch
$ gh stack init

# Or turn an existing set of branches into a stack
$ gh stack init --adopt branch1 branch2 branch3

# Make changes and commit, then add a branch to the stack
$ gh stack add branch4

# Push all branches and create/update PRs on GitHub
$ gh stack submit

# Keep your local in sync with remote
$ gh stack sync`,
Version: Version,
SilenceUsage: true,
SilenceErrors: true,
Expand All @@ -26,36 +42,104 @@ func RootCmd() *cobra.Command {
root.SetOut(cfg.Out)
root.SetErr(cfg.Err)

// Local operations
root.AddCommand(InitCmd(cfg))
root.AddCommand(AddCmd(cfg))
root.AddGroup(
&cobra.Group{ID: "stack", Title: "Stack management:"},
&cobra.Group{ID: "remote", Title: "Remote operations:"},
&cobra.Group{ID: "nav", Title: "Navigation:"},
&cobra.Group{ID: "utils", Title: "Utilities:"},
)

defaultHelp := root.HelpFunc()
root.SetHelpFunc(func(cmd *cobra.Command, args []string) {
defaultHelp(cmd, args)
if cmd.Name() == "stack" {
out := cmd.OutOrStderr()
fmt.Fprintln(out)
fmt.Fprintln(out, "Learn more:")
fmt.Fprintln(out, " Documentation: https://gh.io/stacks")
fmt.Fprintln(out, " Feedback: https://gh.io/stacks-feedback")
}
})

// Remote operations
root.AddCommand(CheckoutCmd(cfg))
root.AddCommand(PushCmd(cfg))
root.AddCommand(SubmitCmd(cfg))
root.AddCommand(SyncCmd(cfg))
root.AddCommand(UnstackCmd(cfg))
root.AddCommand(MergeCmd(cfg))
root.AddCommand(LinkCmd(cfg))
// Stack management commands
initCmd := InitCmd(cfg)
initCmd.GroupID = "stack"
root.AddCommand(initCmd)

// Helper commands
root.AddCommand(ViewCmd(cfg))
root.AddCommand(RebaseCmd(cfg))
root.AddCommand(ModifyCmd(cfg))
addCmd := AddCmd(cfg)
addCmd.GroupID = "stack"
root.AddCommand(addCmd)

// Navigation commands
root.AddCommand(UpCmd(cfg))
root.AddCommand(DownCmd(cfg))
root.AddCommand(TopCmd(cfg))
root.AddCommand(BottomCmd(cfg))
root.AddCommand(SwitchCmd(cfg))
viewCmd := ViewCmd(cfg)
viewCmd.GroupID = "stack"
root.AddCommand(viewCmd)

checkoutCmd := CheckoutCmd(cfg)
checkoutCmd.GroupID = "stack"
root.AddCommand(checkoutCmd)

modifyCmd := ModifyCmd(cfg)
modifyCmd.GroupID = "stack"
root.AddCommand(modifyCmd)

// Alias
root.AddCommand(AliasCmd(cfg))
unstackCmd := UnstackCmd(cfg)
unstackCmd.GroupID = "stack"
root.AddCommand(unstackCmd)

// Feedback
root.AddCommand(FeedbackCmd(cfg))
// Remote operations commands
submitCmd := SubmitCmd(cfg)
submitCmd.GroupID = "remote"
root.AddCommand(submitCmd)

syncCmd := SyncCmd(cfg)
syncCmd.GroupID = "remote"
root.AddCommand(syncCmd)

rebaseCmd := RebaseCmd(cfg)
rebaseCmd.GroupID = "remote"
root.AddCommand(rebaseCmd)

pushCmd := PushCmd(cfg)
pushCmd.GroupID = "remote"
root.AddCommand(pushCmd)

linkCmd := LinkCmd(cfg)
linkCmd.GroupID = "remote"
root.AddCommand(linkCmd)

mergeCmd := MergeCmd(cfg)
mergeCmd.GroupID = "remote"
root.AddCommand(mergeCmd)

// Navigation commands
switchCmd := SwitchCmd(cfg)
switchCmd.GroupID = "nav"
root.AddCommand(switchCmd)

upCmd := UpCmd(cfg)
upCmd.GroupID = "nav"
root.AddCommand(upCmd)

downCmd := DownCmd(cfg)
downCmd.GroupID = "nav"
root.AddCommand(downCmd)

topCmd := TopCmd(cfg)
topCmd.GroupID = "nav"
root.AddCommand(topCmd)

bottomCmd := BottomCmd(cfg)
bottomCmd.GroupID = "nav"
root.AddCommand(bottomCmd)

// Utility commands
aliasCmd := AliasCmd(cfg)
aliasCmd.GroupID = "utils"
root.AddCommand(aliasCmd)

feedbackCmd := FeedbackCmd(cfg)
feedbackCmd.GroupID = "utils"
root.AddCommand(feedbackCmd)

return root
}
Expand Down
Loading
Loading