Skip to content

Commit 7f33691

Browse files
skarimCopilot
andcommitted
Fix 7 nit issues from code review
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>
1 parent 9aab998 commit 7f33691

6 files changed

Lines changed: 79 additions & 17 deletions

File tree

internal/modify/preconditions.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ func CheckStackLinearity(cfg *config.Config, s *stack.Stack) error {
3838
if i == 0 {
3939
parentBranch = s.Trunk.Branch
4040
} else {
41-
parentBranch = s.Branches[i-1].Branch
42-
}
43-
44-
if i > 0 && s.Branches[i-1].IsMerged() {
4541
parentBranch = s.ActiveBaseBranch(b.Branch)
4642
}
4743

internal/modify/state.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ import (
1111

1212
const stateFileName = "gh-stack-modify-state"
1313

14+
const (
15+
PhaseApplying = "applying"
16+
PhaseConflict = "conflict"
17+
PhasePendingSubmit = "pending_submit"
18+
)
19+
1420
// StateFile holds the state of an in-progress or pending-submit modify operation.
1521
// It is stored at .git/gh-stack-modify-state.
1622
type StateFile struct {
@@ -117,10 +123,10 @@ func CheckStateGuard(gitDir string) error {
117123
if state == nil {
118124
return nil
119125
}
120-
if state.Phase == "applying" {
126+
if state.Phase == PhaseApplying {
121127
return fmt.Errorf("a modify session was interrupted — run `gh stack modify --abort` to restore your stack")
122128
}
123-
if state.Phase == "conflict" {
129+
if state.Phase == PhaseConflict {
124130
return fmt.Errorf("a modify has unresolved conflicts — run `gh stack modify --continue` or `gh stack modify --abort`")
125131
}
126132
return nil

internal/stack/stack.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,10 +317,12 @@ func Save(gitDir string, sf *StackFile) error {
317317
}
318318

319319
// SaveWithLock writes the stack file while the caller already holds the lock.
320-
// This avoids double-locking when the caller needs to hold the lock across
321-
// multiple operations (e.g. modify apply). The staleness check is skipped
322-
// because the caller owns the lock and has been mutating the StackFile.
323-
func SaveWithLock(gitDir string, sf *StackFile, _ *FileLock) error {
320+
// The caller is responsible for acquiring and releasing the lock.
321+
// Panics if lock is nil to catch programming errors.
322+
func SaveWithLock(gitDir string, sf *StackFile, lock *FileLock) error {
323+
if lock == nil {
324+
panic("SaveWithLock called with nil lock")
325+
}
324326
return writeStackFile(gitDir, sf)
325327
}
326328

internal/tui/modifyview/model.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/github/gh-stack/internal/git"
1212
"github.com/github/gh-stack/internal/stack"
1313
"github.com/github/gh-stack/internal/tui/shared"
14-
"github.com/github/gh-stack/internal/tui/stackview"
1514
)
1615

1716
// modifyKeyMap defines key bindings for the modify view.
@@ -767,7 +766,7 @@ func (m *Model) undoLast() {
767766

768767
case ActionRename:
769768
for i := range m.nodes {
770-
if m.nodes[i].Ref.Branch == action.BranchName || (m.nodes[i].PendingAction != nil && m.nodes[i].PendingAction.Type == ActionRename) {
769+
if m.nodes[i].Ref.Branch == action.BranchName || (m.nodes[i].PendingAction != nil && m.nodes[i].PendingAction.Type == ActionRename && m.nodes[i].PendingAction.NewName == action.NewName) {
771770
m.nodes[i].PendingAction = nil
772771
break
773772
}
@@ -999,7 +998,7 @@ func (m Model) View() string {
999998
// Count fixed bottom lines (always visible, not scrollable).
1000999
// The bottom section always has 2 lines: one for contextual info
10011000
// (rename prompt or error, blank when neither) and one for the status bar.
1002-
bottomLines := 2 // post-scroll newline + context line + status bar
1001+
bottomLines := 2 // error/status line + status bar (post-scroll newline is inline)
10031002

10041003
// Scrolling — reserve space for header and fixed bottom
10051004
reservedLines := bottomLines
@@ -1090,6 +1089,3 @@ func (m Model) buildHeaderConfig() shared.HeaderConfig {
10901089

10911090
// Ensure Model satisfies the tea.Model interface.
10921091
var _ tea.Model = Model{}
1093-
1094-
// Ensure stackview import is used (BranchNode is embedded in ModifyBranchNode).
1095-
var _ = stackview.BranchNode{}

internal/tui/modifyview/model_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,3 +697,62 @@ func TestPluralize(t *testing.T) {
697697
assert.Equal(t, "drops", pluralize(2, "drop", "drops"))
698698
assert.Equal(t, "drops", pluralize(0, "drop", "drops"))
699699
}
700+
701+
func TestUndoRename(t *testing.T) {
702+
nodes := []ModifyBranchNode{
703+
makeNode("a", false, 0),
704+
makeNode("b", true, 1),
705+
makeNode("c", false, 2),
706+
}
707+
m := New(nodes, testTrunk, "1.0.0")
708+
709+
// Simulate a rename on node at index 1 (bypass git validation)
710+
m.nodes[1].PendingAction = &PendingAction{Type: ActionRename, NewName: "b-renamed"}
711+
m.actionStack = append(m.actionStack, StagedAction{
712+
Type: ActionRename,
713+
BranchName: "b",
714+
OriginalName: "b",
715+
NewName: "b-renamed",
716+
})
717+
718+
require.NotNil(t, m.nodes[1].PendingAction)
719+
assert.Equal(t, ActionRename, m.nodes[1].PendingAction.Type)
720+
assert.Equal(t, "b-renamed", m.nodes[1].PendingAction.NewName)
721+
722+
// Undo
723+
m = sendKey(t, m, runeKey('z'))
724+
assert.Nil(t, m.nodes[1].PendingAction, "PendingAction should be cleared after undo")
725+
}
726+
727+
func TestUndoRename_DoesNotAffectOtherRenames(t *testing.T) {
728+
nodes := []ModifyBranchNode{
729+
makeNode("a", false, 0),
730+
makeNode("b", true, 1),
731+
makeNode("c", false, 2),
732+
}
733+
m := New(nodes, testTrunk, "1.0.0")
734+
735+
// Simulate rename on node 0 (first rename)
736+
m.nodes[0].PendingAction = &PendingAction{Type: ActionRename, NewName: "a-renamed"}
737+
m.actionStack = append(m.actionStack, StagedAction{
738+
Type: ActionRename,
739+
BranchName: "a",
740+
OriginalName: "a",
741+
NewName: "a-renamed",
742+
})
743+
744+
// Simulate rename on node 2 (second rename)
745+
m.nodes[2].PendingAction = &PendingAction{Type: ActionRename, NewName: "c-renamed"}
746+
m.actionStack = append(m.actionStack, StagedAction{
747+
Type: ActionRename,
748+
BranchName: "c",
749+
OriginalName: "c",
750+
NewName: "c-renamed",
751+
})
752+
753+
// Undo the second rename
754+
m = sendKey(t, m, runeKey('z'))
755+
assert.Nil(t, m.nodes[2].PendingAction, "second rename should be undone")
756+
require.NotNil(t, m.nodes[0].PendingAction, "first rename should still be intact")
757+
assert.Equal(t, "a-renamed", m.nodes[0].PendingAction.NewName, "first rename new name should be unchanged")
758+
}

internal/tui/shared/header.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ const MinWidthForShortcuts = 65
1818
// MinWidthForHeader is the minimum width to show the header at all.
1919
const MinWidthForHeader = 50
2020

21+
// MinWidthForArt is the minimum width to show ASCII art in the header.
22+
const MinWidthForArt = 88
23+
2124
// ShortcutEntry represents a keyboard shortcut for the header.
2225
type ShortcutEntry struct {
2326
Key string
@@ -173,7 +176,7 @@ func RenderHeader(b *strings.Builder, cfg HeaderConfig, width, height int) {
173176
showInfo := true
174177

175178
// Hide art when viewport is too narrow for art + info + shortcuts
176-
if showArt && width < 88 {
179+
if showArt && width < MinWidthForArt {
177180
showArt = false
178181
}
179182

0 commit comments

Comments
 (0)