forked from abhinav/git-spice
-
Notifications
You must be signed in to change notification settings - Fork 0
/
upstack_restack.go
100 lines (87 loc) · 2.64 KB
/
upstack_restack.go
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
package main
import (
"context"
"errors"
"fmt"
"github.com/charmbracelet/log"
"go.abhg.dev/gs/internal/git"
"go.abhg.dev/gs/internal/spice"
"go.abhg.dev/gs/internal/text"
)
type upstackRestackCmd struct {
Branch string `help:"Branch to restack the upstack of" placeholder:"NAME" predictor:"trackedBranches"`
SkipStart bool `help:"Do not restack the starting branch"`
}
func (*upstackRestackCmd) Help() string {
return text.Dedent(`
The current branch and all branches above it
are rebased on top of their respective bases,
ensuring a linear history.
Use --branch to start at a different branch.
Use --skip-start to skip the starting branch,
but still rebase all branches above it.
The target branch defaults to the current branch.
If run from the trunk branch,
all managed branches will be restacked.
`)
}
func (cmd *upstackRestackCmd) Run(ctx context.Context, log *log.Logger, opts *globalOptions) error {
repo, store, svc, err := openRepo(ctx, log, opts)
if err != nil {
return err
}
if cmd.Branch == "" {
currentBranch, err := repo.CurrentBranch(ctx)
if err != nil {
return fmt.Errorf("get current branch: %w", err)
}
cmd.Branch = currentBranch
}
upstacks, err := svc.ListUpstack(ctx, cmd.Branch)
if err != nil {
return fmt.Errorf("get upstack branches: %w", err)
}
if cmd.SkipStart && len(upstacks) > 0 && upstacks[0] == cmd.Branch {
upstacks = upstacks[1:]
if len(upstacks) == 0 {
return nil
}
}
loop:
for _, upstack := range upstacks {
// Trunk never needs to be restacked.
if upstack == store.Trunk() {
continue loop
}
res, err := svc.Restack(ctx, upstack)
if err != nil {
var rebaseErr *git.RebaseInterruptError
switch {
case errors.As(err, &rebaseErr):
// If the rebase is interrupted by a conflict,
// we'll resume by re-running this command.
return svc.RebaseRescue(ctx, spice.RebaseRescueRequest{
Err: rebaseErr,
Command: []string{"upstack", "restack"},
Branch: cmd.Branch,
Message: fmt.Sprintf("interrupted: restack upstack of %v", cmd.Branch),
})
case errors.Is(err, spice.ErrAlreadyRestacked):
// Log the "does not need to be restacked" message
// only for branches that are not the base branch.
if upstack != cmd.Branch {
log.Infof("%v: branch does not need to be restacked.", upstack)
}
continue loop
default:
return fmt.Errorf("restack branch: %w", err)
}
}
log.Infof("%v: restacked on %v", upstack, res.Base)
}
// On success, check out the original branch.
if err := repo.Checkout(ctx, cmd.Branch); err != nil {
return fmt.Errorf("checkout branch %v: %w", cmd.Branch, err)
}
return nil
}