-
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcontinue.go
More file actions
173 lines (151 loc) · 5.2 KB
/
continue.go
File metadata and controls
173 lines (151 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// cmd/continue.go
package cmd
import (
"errors"
"fmt"
"os"
"github.com/boneskull/gh-stack/internal/config"
"github.com/boneskull/gh-stack/internal/git"
"github.com/boneskull/gh-stack/internal/state"
"github.com/boneskull/gh-stack/internal/style"
"github.com/boneskull/gh-stack/internal/tree"
"github.com/spf13/cobra"
)
var continueCmd = &cobra.Command{
Use: "continue",
Short: "Continue an operation after resolving conflicts",
Long: `Continue a restack, submit, or sync operation after resolving rebase conflicts.`,
RunE: runContinue,
}
func init() {
rootCmd.AddCommand(continueCmd)
}
func runContinue(cmd *cobra.Command, args []string) error {
s := style.New()
cwd, err := os.Getwd()
if err != nil {
return err
}
g := git.New(cwd)
// Check if operation in progress
st, err := state.Load(g.GetGitDir())
if err != nil {
return errors.New("no operation in progress")
}
// Determine the correct git instance for rebase operations.
// If the conflicting branch is in a linked worktree, operate there.
rebaseGit := g
wtPath := ""
if p, ok := st.Worktrees[st.Current]; ok && p != "" {
wtPath = p
rebaseGit = git.New(wtPath)
}
// Complete the in-progress rebase
if rebaseGit.IsRebaseInProgress() {
fmt.Println("Continuing rebase...")
if rebaseErr := rebaseGit.RebaseContinue(); rebaseErr != nil {
if wtPath != "" {
return fmt.Errorf("rebase --continue failed in worktree at %s for branch %s; resolve conflicts there first", wtPath, st.Current)
}
return errors.New("rebase --continue failed; resolve conflicts first")
}
}
fmt.Printf("%s Completed %s\n", s.SuccessIcon(), s.Branch(st.Current))
cfg, err := config.Load(cwd)
if err != nil {
return err
}
// Update fork point for the branch that just finished rebasing.
// The restack loop's fork point update only fires after a non-conflicting
// rebase, so branches that hit a conflict never get their fork point
// refreshed -- leaving it stale for future operations.
if parent, parentErr := cfg.GetParent(st.Current); parentErr == nil {
if parentTip, tipErr := g.GetTip(parent); tipErr == nil {
_ = cfg.SetForkPoint(st.Current, parentTip) //nolint:errcheck // best effort
}
}
// Build tree to get node objects
root, err := tree.Build(cfg)
if err != nil {
return err
}
// If there are more branches to restack, continue cascading
if len(st.Pending) > 0 {
var branches []*tree.Node
for _, name := range st.Pending {
if node := tree.FindNode(root, name); node != nil {
branches = append(branches, node)
}
}
// Remove state file before continuing (will be recreated if conflict)
_ = state.Remove(g.GetGitDir()) //nolint:errcheck // cleanup
if restackErr := doRestackWithState(g, cfg, branches, RestackOptions{
Operation: st.Operation,
UpdateOnly: st.UpdateOnly,
OpenWeb: st.Web,
PushOnly: st.PushOnly,
Branches: st.Branches,
StashRef: st.StashRef,
Worktrees: st.Worktrees,
}, s); restackErr != nil {
// Stash handling is done by doRestackWithState (conflict saves in state, errors restore)
if !errors.Is(restackErr, ErrConflict) && st.StashRef != "" {
fmt.Println("Restoring auto-stashed changes...")
if popErr := g.StashPop(st.StashRef); popErr != nil {
fmt.Printf("%s could not restore stashed changes (commit %s): %v\n", s.WarningIcon(), git.AbbrevSHA(st.StashRef), popErr)
}
}
return restackErr // Another conflict - state saved
}
} else {
// No more branches to restack - cleanup state
_ = state.Remove(g.GetGitDir()) //nolint:errcheck // cleanup
}
// If this was a submit operation, continue with push + PR phases
if st.Operation == state.OperationSubmit {
// Rebuild branches list from the original set of submit branches if available.
// Fall back to the current + pending branches for backward compatibility.
var branchNames []string
if len(st.Branches) > 0 {
branchNames = st.Branches
} else {
branchNames = append(branchNames, st.Current)
branchNames = append(branchNames, st.Pending...)
}
var allBranches []*tree.Node
for _, name := range branchNames {
node := tree.FindNode(root, name)
if node == nil {
// Preserve existing behaviour: fail fast if a branch from state
// cannot be found in the current tree.
if name == st.Current {
return fmt.Errorf("branch %q not found in tree", st.Current)
}
continue
}
allBranches = append(allBranches, node)
}
err = doSubmitPushAndPR(g, cfg, root, allBranches, SubmitOptions{
UpdateOnly: st.UpdateOnly,
OpenWeb: st.Web,
PushOnly: st.PushOnly,
}, s)
// Restore stash after submit completes
if st.StashRef != "" {
fmt.Println("Restoring auto-stashed changes...")
if popErr := g.StashPop(st.StashRef); popErr != nil {
fmt.Printf("%s could not restore stashed changes (commit %s): %v\n", s.WarningIcon(), git.AbbrevSHA(st.StashRef), popErr)
}
}
return err
}
// Restore stash after restack completes
if st.StashRef != "" {
fmt.Println("Restoring auto-stashed changes...")
if popErr := g.StashPop(st.StashRef); popErr != nil {
fmt.Printf("%s could not restore stashed changes (commit %s): %v\n", s.WarningIcon(), git.AbbrevSHA(st.StashRef), popErr)
}
}
fmt.Println(s.SuccessMessage("Restack complete!"))
return nil
}