Skip to content

Commit f9c9bbb

Browse files
committed
Allow dirty unaffected branches during adoption
Only require a clean worktree when adopting the checked-out branch with reset --hard. Non-current branch updates remain safe with unrelated working-tree changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 30bcbf2a-5ef1-4bdb-a0fc-618294ae8ded
1 parent 5880261 commit f9c9bbb

2 files changed

Lines changed: 55 additions & 6 deletions

File tree

cmd/utils.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -980,12 +980,21 @@ func adoptRemoteRebasedBranches(cfg *config.Config, s *stack.Stack, remote, curr
980980
remoteParent = remoteBranch
981981
}
982982

983-
dirty, err := git.HasUncommittedChanges()
984-
if err != nil {
985-
return nil, fmt.Errorf("could not determine whether the working tree is clean: %w", err)
983+
currentBranchWillMove := false
984+
for _, update := range updates {
985+
if update.branch == currentBranch {
986+
currentBranchWillMove = true
987+
break
988+
}
986989
}
987-
if dirty {
988-
return nil, errors.New("uncommitted changes prevent adopting the remote-rebased branches; commit or stash them first")
990+
if currentBranchWillMove {
991+
dirty, err := git.HasUncommittedChanges()
992+
if err != nil {
993+
return nil, fmt.Errorf("could not determine whether the working tree is clean: %w", err)
994+
}
995+
if dirty {
996+
return nil, errors.New("uncommitted changes prevent adopting the remote-rebased branches; commit or stash them first")
997+
}
989998
}
990999

9911000
var applied []remoteRebaseUpdate

cmd/utils_test.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,7 @@ func TestAdoptRemoteRebasedBranchesAllowsLocalOnlyUpstackBranch(t *testing.T) {
10331033
return ancestor == "main-old" && descendant == "b1", nil
10341034
},
10351035
RangeDiffEquivalentFn: func(string, string, string, string) (bool, error) { return true, nil },
1036-
HasUncommittedChangesFn: func() (bool, error) { return false, nil },
1036+
HasUncommittedChangesFn: func() (bool, error) { return true, nil },
10371037
UpdateBranchRefFn: func(branch, sha string) error {
10381038
refUpdates = append(refUpdates, [2]string{branch, sha})
10391039
return nil
@@ -1049,6 +1049,46 @@ func TestAdoptRemoteRebasedBranchesAllowsLocalOnlyUpstackBranch(t *testing.T) {
10491049
assert.Equal(t, [][2]string{{"b1", "b1-new"}}, refUpdates)
10501050
}
10511051

1052+
func TestAdoptRemoteRebasedBranchesRejectsDirtyCurrentBranch(t *testing.T) {
1053+
s := &stack.Stack{
1054+
Trunk: stack.BranchRef{Branch: "main"},
1055+
Branches: []stack.BranchRef{{Branch: "b1", Base: "main-old"}},
1056+
}
1057+
snapshots := map[string]branchTipSnapshot{
1058+
"b1": {localSHA: "b1-old", remoteSHA: "b1-old", hasRemote: true},
1059+
}
1060+
1061+
resetCalled := false
1062+
restore := git.SetOps(&git.MockOps{
1063+
RevParseFn: func(ref string) (string, error) {
1064+
if ref == "b1" {
1065+
return "b1-old", nil
1066+
}
1067+
return "b1-new", nil
1068+
},
1069+
IsAncestorFn: func(ancestor, descendant string) (bool, error) {
1070+
if ancestor == "origin/main" && descendant == "origin/b1" {
1071+
return true, nil
1072+
}
1073+
return ancestor == "main-old" && descendant == "b1", nil
1074+
},
1075+
RangeDiffEquivalentFn: func(string, string, string, string) (bool, error) { return true, nil },
1076+
HasUncommittedChangesFn: func() (bool, error) { return true, nil },
1077+
ResetHardFn: func(string) error {
1078+
resetCalled = true
1079+
return nil
1080+
},
1081+
})
1082+
defer restore()
1083+
1084+
cfg, _, _ := config.NewTestConfig()
1085+
_, err := adoptRemoteRebasedBranches(cfg, s, "origin", "b1", snapshots)
1086+
1087+
require.Error(t, err)
1088+
assert.Contains(t, err.Error(), "uncommitted changes")
1089+
assert.False(t, resetCalled)
1090+
}
1091+
10521092
func TestAdoptRemoteRebasedBranchesRejectsConcurrentChanges(t *testing.T) {
10531093
s := &stack.Stack{
10541094
Trunk: stack.BranchRef{Branch: "main"},

0 commit comments

Comments
 (0)