-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathbranch_onto.go
131 lines (115 loc) · 3.71 KB
/
branch_onto.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
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
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/spice/state"
"go.abhg.dev/gs/internal/text"
"go.abhg.dev/gs/internal/ui"
)
type branchOntoCmd struct {
Branch string `help:"Branch to move" placeholder:"NAME" predictor:"trackedBranches"`
Onto string `arg:"" optional:"" help:"Destination branch" predictor:"trackedBranches"`
}
func (*branchOntoCmd) Help() string {
return text.Dedent(`
The commits of the current branch are transplanted onto another
branch.
Branches upstack are moved to point to its original base.
Use --branch to move a different branch than the current one.
A prompt will allow selecting the new base.
Provide the new base name as an argument to skip the prompt.
For example, given the following stack with B checked out,
running 'gs branch onto main' will move B onto main
and leave C on top of A.
gs branch onto main
┌── C ┌── B ◀
┌─┴ B ◀ │ ┌── C
┌─┴ A ├─┴ A
trunk trunk
`)
}
func (cmd *branchOntoCmd) Run(
ctx context.Context,
log *log.Logger,
view ui.View,
repo *git.Repository,
store *state.Store,
svc *spice.Service,
) error {
if cmd.Branch == "" {
currentBranch, err := repo.CurrentBranch(ctx)
if err != nil {
return fmt.Errorf("get current branch: %w", err)
}
cmd.Branch = currentBranch
}
if cmd.Branch == store.Trunk() {
return fmt.Errorf("cannot move trunk")
}
branch, err := svc.LookupBranch(ctx, cmd.Branch)
if err != nil {
if errors.Is(err, state.ErrNotExist) {
return fmt.Errorf("branch not tracked: %s", cmd.Branch)
}
return fmt.Errorf("get branch: %w", err)
}
if cmd.Onto == "" {
if !ui.Interactive(view) {
return fmt.Errorf("cannot proceed without a destination branch: %w", errNoPrompt)
}
cmd.Onto, err = (&branchPrompt{
Disabled: func(b git.LocalBranch) bool {
return b.Name == cmd.Branch
},
TrackedOnly: true,
Default: branch.Base,
Title: "Select a branch to move onto",
Description: fmt.Sprintf("Moving %s onto another branch", cmd.Branch),
}).Run(ctx, view, repo, store)
if err != nil {
return fmt.Errorf("select branch: %w", err)
}
}
aboves, err := svc.ListAbove(ctx, cmd.Branch)
if err != nil {
return fmt.Errorf("list branches above %s: %w", cmd.Branch, err)
}
// As long as there are any branches above this one,
// they need to be grafted onto this branch's original base.
// However, this move operation will be an 'upstack onto'
// as for each of these branches, we want to keep *their* upstacks.
for _, above := range aboves {
if err := (&upstackOntoCmd{
Branch: above,
Onto: branch.Base,
}).Run(ctx, log, view, repo, store, svc); err != nil {
return svc.RebaseRescue(ctx, spice.RebaseRescueRequest{
Err: err,
Command: []string{"branch", "onto", cmd.Onto},
Branch: cmd.Branch,
Message: fmt.Sprintf("interrupted: %s: branch onto %s", cmd.Branch, cmd.Onto),
})
}
}
// Only after the upstacks have been moved
// will we move the branch itself and update its internal state.
if err := svc.BranchOnto(ctx, &spice.BranchOntoRequest{
Branch: cmd.Branch,
Onto: cmd.Onto,
}); err != nil {
// If the rebase is interrupted,
// we'll just re-run this command again later.
return svc.RebaseRescue(ctx, spice.RebaseRescueRequest{
Err: err,
Command: []string{"branch", "onto", cmd.Onto},
Branch: cmd.Branch,
Message: fmt.Sprintf("interrupted: %s: branch onto %s", cmd.Branch, cmd.Onto),
})
}
log.Infof("%s: moved onto %s", cmd.Branch, cmd.Onto)
return repo.Checkout(ctx, cmd.Branch)
}