simplify unstack to only target the active stack by skarim · Pull Request #74 · github/gh-stack · GitHub
Skip to content
Merged
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
13 changes: 4 additions & 9 deletions README.md
14 changes: 5 additions & 9 deletions cmd/unstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,19 @@ import (
)

type unstackOptions struct {
target string
local bool
local bool
}

func UnstackCmd(cfg *config.Config) *cobra.Command {
opts := &unstackOptions{}

cmd := &cobra.Command{
Use: "unstack [branch]",
Use: "unstack",
Aliases: []string{"delete"},
Short: "Delete a stack locally and on GitHub",
Long: "Remove a stack from local tracking and delete it on GitHub. Use --local to only remove local tracking.",
Args: cobra.MaximumNArgs(1),
Long: "Remove the current active stack from local tracking and delete it on GitHub. Use --local to only remove local tracking.",
Args: cobra.NoArgs,
Comment thread
skarim marked this conversation as resolved.
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
opts.target = args[0]
}
return runUnstack(cfg, opts)
},
}
Expand All @@ -38,7 +34,7 @@ func UnstackCmd(cfg *config.Config) *cobra.Command {
}

func runUnstack(cfg *config.Config, opts *unstackOptions) error {
result, err := loadStack(cfg, opts.target)
result, err := loadStack(cfg, "")
if err != nil {
return ErrNotInStack
}
Expand Down
35 changes: 2 additions & 33 deletions cmd/unstack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,37 +94,6 @@ func TestUnstack_Local(t *testing.T) {
assert.Empty(t, sf.Stacks)
}

func TestUnstack_WithTarget(t *testing.T) {
gitDir := t.TempDir()
restore := git.SetOps(&git.MockOps{
GitDirFn: func() (string, error) { return gitDir, nil },
CurrentBranchFn: func() (string, error) { return "unrelated", nil },
})
defer restore()

s1 := stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{{Branch: "b1"}, {Branch: "b2"}},
}
s2 := stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{{Branch: "b3"}, {Branch: "b4"}},
}
writeTwoStacks(t, gitDir, s1, s2)

cfg, outR, errR := config.NewTestConfig()
err := runUnstack(cfg, &unstackOptions{target: "b3", local: true})
output := collectOutput(cfg, outR, errR)

require.NoError(t, err)
assert.Contains(t, output, "Stack removed")

sf, err := stack.Load(gitDir)
require.NoError(t, err)
require.Len(t, sf.Stacks, 1)
assert.Equal(t, []string{"b1", "b2"}, sf.Stacks[0].BranchNames())
}

func TestUnstack_NoStackID_WarnsAndSkipsAPI(t *testing.T) {
gitDir := t.TempDir()
restore := git.SetOps(&git.MockOps{
Expand Down Expand Up @@ -225,7 +194,7 @@ func TestUnstack_API409_ShowsErrorAndStopsLocalDeletion(t *testing.T) {
}

func TestUnstack_RemovesCorrectStackByPointer(t *testing.T) {
// Two stacks share the same trunk "main". Targeting "b3" should remove
// Two stacks share the same trunk "main". Current branch "b3" should remove
// only the second stack (b3,b4), leaving the first (b1,b2) intact.
// This verifies pointer-based removal instead of branch-name-based.
gitDir := t.TempDir()
Expand All @@ -246,7 +215,7 @@ func TestUnstack_RemovesCorrectStackByPointer(t *testing.T) {
writeTwoStacks(t, gitDir, s1, s2)

cfg, outR, errR := config.NewTestConfig()
err := runUnstack(cfg, &unstackOptions{target: "b3", local: true})
err := runUnstack(cfg, &unstackOptions{local: true})
output := collectOutput(cfg, outR, errR)

require.NoError(t, err)
Expand Down
13 changes: 4 additions & 9 deletions docs/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,21 +350,19 @@ gh stack push --remote upstream
Remove a stack from local tracking and delete it on GitHub. Also available as `gh stack delete`.

```sh
gh stack unstack [flags] [branch]
gh stack unstack [flags]
```

Deletes the stack on GitHub first, then removes it from local tracking. If the remote deletion fails, the local state is left untouched so you can retry. Use `--local` to skip the remote deletion and only remove local tracking.
You must have a branch from the stack checked out locally. The command targets the active stack — the one that contains the currently checked out branch.

Deletes the stack on GitHub first, if it exists, then removes it from local tracking. If the remote deletion fails, the local state is left untouched so you can retry. Use `--local` to skip the remote deletion and only remove local tracking.

This is useful when you need to restructure a stack — remove a branch, reorder branches, rename branches, or make other large changes. After unstacking, use `gh stack init --adopt` to re-create the stack with the desired structure.

| Flag | Description |
|------|-------------|
| `--local` | Only delete the stack locally (keep it on GitHub) |

| Argument | Description |
|----------|-------------|
| `[branch]` | A branch in the stack to identify which stack to delete (defaults to the current branch) |

**Examples:**

```sh
Expand All @@ -373,9 +371,6 @@ gh stack unstack

# Only remove local tracking
gh stack unstack --local

# Specify a branch to identify which stack
gh stack unstack feature-auth
```

### `gh stack link`
Expand Down
11 changes: 3 additions & 8 deletions skills/gh-stack/SKILL.md
Loading