-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathcmd_merge.go
More file actions
149 lines (124 loc) · 4.18 KB
/
cmd_merge.go
File metadata and controls
149 lines (124 loc) · 4.18 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
package main
import (
"fmt"
"io"
"os/exec"
"github.com/gastownhall/wasteland/internal/commons"
"github.com/gastownhall/wasteland/internal/federation"
"github.com/gastownhall/wasteland/internal/style"
"github.com/spf13/cobra"
)
func newMergeCmd(stdout, stderr io.Writer) *cobra.Command {
var (
noPush bool
keepBranch bool
)
cmd := &cobra.Command{
Use: "merge <branch>",
Short: "Merge a reviewed branch into main",
Long: `Merge a wl/* branch into main after review.
Performs a Dolt merge, pushes main to upstream and origin, and deletes
the branch (unless --keep-branch is set).
Examples:
wl merge wl/my-rig/w-abc123
wl merge wl/my-rig/w-abc123 --keep-branch
wl merge wl/my-rig/w-abc123 --no-push`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runMerge(cmd, stdout, stderr, args[0], noPush, keepBranch)
},
}
cmd.Flags().BoolVar(&noPush, "no-push", false, "Skip pushing to remotes")
cmd.Flags().BoolVar(&keepBranch, "keep-branch", false, "Don't delete branch after merge")
cmd.ValidArgsFunction = completeBranchNames
return cmd
}
func runMerge(cmd *cobra.Command, stdout, _ io.Writer, branch string, noPush, keepBranch bool) error {
cfg, err := resolveWasteland(cmd)
if err != nil {
return hintWrap(err)
}
// Remote mode: use RemoteDB.MergeBranch via the write API.
if cfg.ResolveBackend() != federation.BackendLocal {
if noPush {
return fmt.Errorf("--no-push is not supported in remote mode (remote merges are immediate)")
}
return runMergeRemote(stdout, cfg, branch, keepBranch)
}
exists, err := commons.BranchExists(cfg.LocalDir, branch)
if err != nil {
return fmt.Errorf("checking branch: %w", err)
}
if !exists {
return fmt.Errorf("branch %q does not exist", branch)
}
// Best-effort: check PR approval status before merging.
if cfg.IsGitHub() {
if ghPath, err := exec.LookPath("gh"); err == nil {
client := newGHClient(ghPath)
hasApproval, hasChangesRequested := prApprovalStatus(client, cfg.Upstream, cfg.ForkOrg, branch)
if msg := mergeApprovalWarning(hasApproval, hasChangesRequested); msg != "" {
fmt.Fprintf(stdout, " %s %s\n", style.Warning.Render("⚠"), msg)
}
}
}
if err := commons.CheckoutMain(cfg.LocalDir); err != nil {
return fmt.Errorf("checking out main: %w", err)
}
if err := commons.MergeBranch(cfg.LocalDir, branch); err != nil {
return err
}
fmt.Fprintf(stdout, "%s Merged %s into main\n", style.Bold.Render("✓"), branch)
if !keepBranch {
if err := commons.DeleteBranch(cfg.LocalDir, branch); err != nil {
fmt.Fprintf(stdout, " warning: failed to delete branch %s: %v\n", branch, err)
} else {
fmt.Fprintf(stdout, " Branch %s deleted\n", branch)
}
}
if !noPush {
if err := commons.PushAllRemotes(cfg.LocalDir, stdout); err != nil {
fmt.Fprintf(stdout, "\n %s %s\n", style.Warning.Render(style.IconWarn),
"Push failed — merge saved locally. Run 'wl sync' to retry.")
}
}
// Best-effort: auto-close the corresponding GitHub PR shell.
if cfg.IsGitHub() {
if ghPath, err := exec.LookPath("gh"); err == nil {
closeGitHubPR(newGHClient(ghPath), cfg.Upstream, cfg.ForkOrg, cfg.ForkDB, branch, stdout)
}
}
return nil
}
func runMergeRemote(stdout io.Writer, cfg *federation.Config, branch string, keepBranch bool) error {
db, err := openDBFromConfig(cfg)
if err != nil {
return err
}
sp := style.StartSpinner(stdout, "Merging branch via API...")
err = db.MergeBranch(branch)
sp.Stop()
if err != nil {
return fmt.Errorf("merging branch: %w", err)
}
fmt.Fprintf(stdout, "%s Merged %s into main\n", style.Bold.Render("✓"), branch)
if !keepBranch {
if err := db.DeleteBranch(branch); err != nil {
fmt.Fprintf(stdout, " warning: failed to delete branch %s: %v\n", branch, err)
} else {
fmt.Fprintf(stdout, " Branch %s deleted\n", branch)
}
}
return nil
}
// mergeApprovalWarning returns a warning message based on PR approval state.
// Returns "" if the PR is approved with no outstanding change requests.
func mergeApprovalWarning(hasApproval, hasChangesRequested bool) string {
if hasChangesRequested {
return "PR has outstanding change requests"
}
if !hasApproval {
return "PR has no approvals"
}
return ""
}