Skip to content

Commit b4d6ebe

Browse files
committed
handle renamed branches
1 parent eb39abc commit b4d6ebe

2 files changed

Lines changed: 85 additions & 16 deletions

File tree

internal/modify/apply.go

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -499,9 +499,9 @@ func ApplyPlan(
499499
// otherwise the nearest surviving branch.
500500
targetBranch := resolveCheckoutBranch(currentBranch, plan, snapshot, s)
501501
if err := git.CheckoutBranch(targetBranch); err == nil {
502-
if targetBranch != currentBranch {
503-
cfg.Printf("Switched to %s (original branch %s is no longer in the stack)", targetBranch, currentBranch)
504-
}
502+
if targetBranch != currentBranch {
503+
cfg.Printf("Switched to %s (original branch %s is no longer in the stack)", targetBranch, currentBranch)
504+
}
505505
}
506506

507507
// Update base SHAs
@@ -535,6 +535,24 @@ func resolveCheckoutBranch(originalBranch string, plan []Action, snapshot Snapsh
535535
return originalBranch
536536
}
537537

538+
// Build a rename map (old name → new name) so we can translate snapshot
539+
// neighbor names that may have been renamed in the same modify operation.
540+
renames := make(map[string]string)
541+
for _, a := range plan {
542+
if a.Type == "rename" && a.NewName != "" {
543+
renames[a.Branch] = a.NewName
544+
}
545+
}
546+
547+
// resolvedName returns the post-rename name for a branch, or the
548+
// original name if it wasn't renamed.
549+
resolvedName := func(name string) string {
550+
if newName, ok := renames[name]; ok {
551+
return newName
552+
}
553+
return name
554+
}
555+
538556
// Scan the plan for an action that targeted the original branch.
539557
for _, a := range plan {
540558
if a.Branch != originalBranch {
@@ -550,24 +568,26 @@ func resolveCheckoutBranch(originalBranch string, plan []Action, snapshot Snapsh
550568
case "fold_down":
551569
// Fold-down merges into the branch below in the original order.
552570
if target := adjacentSnapshotBranch(snapshot, originalBranch, -1); target != "" {
553-
if s.IndexOf(target) >= 0 {
554-
return target
571+
resolved := resolvedName(target)
572+
if s.IndexOf(resolved) >= 0 {
573+
return resolved
555574
}
556575
}
557576

558577
case "fold_up":
559578
// Fold-up merges into the branch above in the original order.
560579
if target := adjacentSnapshotBranch(snapshot, originalBranch, +1); target != "" {
561-
if s.IndexOf(target) >= 0 {
562-
return target
580+
resolved := resolvedName(target)
581+
if s.IndexOf(resolved) >= 0 {
582+
return resolved
563583
}
564584
}
565585

566586
case "drop":
567587
// Prefer the branch that was directly above in the original order,
568588
// then fall back to the one below.
569-
if above := nearestSurvivingBranch(snapshot, originalBranch, s); above != "" {
570-
return above
589+
if nearest := nearestSurvivingBranch(snapshot, originalBranch, s, resolvedName); nearest != "" {
590+
return nearest
571591
}
572592
}
573593
}
@@ -596,7 +616,8 @@ func adjacentSnapshotBranch(snapshot Snapshot, target string, direction int) str
596616

597617
// nearestSurvivingBranch finds the closest branch to the dropped branch that
598618
// still exists in the stack. Prefers the branch above (higher index), then below.
599-
func nearestSurvivingBranch(snapshot Snapshot, dropped string, s *stack.Stack) string {
619+
// resolvedName translates snapshot names through any renames from the same operation.
620+
func nearestSurvivingBranch(snapshot Snapshot, dropped string, s *stack.Stack, resolvedName func(string) string) string {
600621
pos := -1
601622
for i, bs := range snapshot.Branches {
602623
if bs.Name == dropped {
@@ -610,14 +631,16 @@ func nearestSurvivingBranch(snapshot Snapshot, dropped string, s *stack.Stack) s
610631

611632
// Search above first (higher indices = away from trunk)
612633
for i := pos + 1; i < len(snapshot.Branches); i++ {
613-
if s.IndexOf(snapshot.Branches[i].Name) >= 0 {
614-
return snapshot.Branches[i].Name
634+
name := resolvedName(snapshot.Branches[i].Name)
635+
if s.IndexOf(name) >= 0 {
636+
return name
615637
}
616638
}
617639
// Then below (lower indices = toward trunk)
618640
for i := pos - 1; i >= 0; i-- {
619-
if s.IndexOf(snapshot.Branches[i].Name) >= 0 {
620-
return snapshot.Branches[i].Name
641+
name := resolvedName(snapshot.Branches[i].Name)
642+
if s.IndexOf(name) >= 0 {
643+
return name
621644
}
622645
}
623646
return ""
@@ -764,8 +787,8 @@ func ContinueApply(
764787
if state.OriginalBranch != "" {
765788
targetBranch := resolveCheckoutBranch(state.OriginalBranch, state.Plan, state.Snapshot, s)
766789
if err := git.CheckoutBranch(targetBranch); err == nil {
767-
if targetBranch != state.OriginalBranch {
768-
cfg.Printf("Switched to %s (original branch %s is no longer in the stack)", targetBranch, state.OriginalBranch)
790+
if targetBranch != state.OriginalBranch {
791+
cfg.Printf("Switched to %s (original branch %s is no longer in the stack)", targetBranch, state.OriginalBranch)
769792
}
770793
}
771794
}

internal/modify/apply_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,52 @@ func TestResolveCheckoutBranch_Fallback_TopBranch(t *testing.T) {
15031503
assert.Equal(t, "Y", result)
15041504
}
15051505

1506+
func TestResolveCheckoutBranch_FoldDown_TargetRenamed(t *testing.T) {
1507+
// B is folded down into A, and A is renamed to new-A in the same operation.
1508+
// After apply, stack has [new-A, C].
1509+
s := &stack.Stack{
1510+
Trunk: stack.BranchRef{Branch: "main"},
1511+
Branches: []stack.BranchRef{{Branch: "new-A"}, {Branch: "C"}},
1512+
}
1513+
snapshot := Snapshot{
1514+
Branches: []BranchSnapshot{
1515+
{Name: "A", Position: 0},
1516+
{Name: "B", Position: 1},
1517+
{Name: "C", Position: 2},
1518+
},
1519+
}
1520+
plan := []Action{
1521+
{Type: "rename", Branch: "A", NewName: "new-A"},
1522+
{Type: "fold_down", Branch: "B"},
1523+
}
1524+
1525+
result := resolveCheckoutBranch("B", plan, snapshot, s)
1526+
assert.Equal(t, "new-A", result)
1527+
}
1528+
1529+
func TestResolveCheckoutBranch_Dropped_NeighborRenamed(t *testing.T) {
1530+
// B is dropped, and C (above) is renamed to new-C in the same operation.
1531+
// After apply, stack has [A, new-C].
1532+
s := &stack.Stack{
1533+
Trunk: stack.BranchRef{Branch: "main"},
1534+
Branches: []stack.BranchRef{{Branch: "A"}, {Branch: "new-C"}},
1535+
}
1536+
snapshot := Snapshot{
1537+
Branches: []BranchSnapshot{
1538+
{Name: "A", Position: 0},
1539+
{Name: "B", Position: 1},
1540+
{Name: "C", Position: 2},
1541+
},
1542+
}
1543+
plan := []Action{
1544+
{Type: "rename", Branch: "C", NewName: "new-C"},
1545+
{Type: "drop", Branch: "B"},
1546+
}
1547+
1548+
result := resolveCheckoutBranch("B", plan, snapshot, s)
1549+
assert.Equal(t, "new-C", result)
1550+
}
1551+
15061552
// ─── ApplyPlan: Checkout behavior after drop ────────────────────────────────
15071553

15081554
func TestApplyPlan_Drop_ChecksOutNearestBranch(t *testing.T) {

0 commit comments

Comments
 (0)