1package cmd
2
3import (
4 "fmt"
5 "strings"
6
7 gitm "github.com/aymanbagabas/git-module"
8 "github.com/charmbracelet/soft-serve/git"
9 "github.com/charmbracelet/soft-serve/pkg/backend"
10 "github.com/charmbracelet/soft-serve/pkg/proto"
11 "github.com/charmbracelet/soft-serve/pkg/webhook"
12 "github.com/spf13/cobra"
13)
14
15func branchCommand() *cobra.Command {
16 cmd := &cobra.Command{
17 Use: "branch",
18 Short: "Manage repository branches",
19 }
20
21 cmd.AddCommand(
22 branchListCommand(),
23 branchDefaultCommand(),
24 branchDeleteCommand(),
25 )
26
27 return cmd
28}
29
30func branchListCommand() *cobra.Command {
31 cmd := &cobra.Command{
32 Use: "list REPOSITORY",
33 Short: "List repository branches",
34 Args: cobra.ExactArgs(1),
35 PersistentPreRunE: checkIfReadable,
36 RunE: func(cmd *cobra.Command, args []string) error {
37 ctx := cmd.Context()
38 be := backend.FromContext(ctx)
39 rn := strings.TrimSuffix(args[0], ".git")
40 rr, err := be.Repository(ctx, rn)
41 if err != nil {
42 return err //nolint:wrapcheck
43 }
44
45 r, err := rr.Open()
46 if err != nil {
47 return err //nolint:wrapcheck
48 }
49
50 branches, _ := r.Branches()
51 for _, b := range branches {
52 cmd.Println(b)
53 }
54
55 return nil
56 },
57 }
58
59 return cmd
60}
61
62func branchDefaultCommand() *cobra.Command {
63 cmd := &cobra.Command{
64 Use: "default REPOSITORY [BRANCH]",
65 Short: "Set or get the default branch",
66 Args: cobra.RangeArgs(1, 2),
67 PersistentPreRunE: checkIfReadable,
68 RunE: func(cmd *cobra.Command, args []string) error {
69 ctx := cmd.Context()
70 be := backend.FromContext(ctx)
71 rn := strings.TrimSuffix(args[0], ".git")
72 switch len(args) {
73 case 1:
74 rr, err := be.Repository(ctx, rn)
75 if err != nil {
76 return err //nolint:wrapcheck
77 }
78
79 r, err := rr.Open()
80 if err != nil {
81 return err //nolint:wrapcheck
82 }
83
84 head, err := r.HEAD()
85 if err != nil {
86 return err //nolint:wrapcheck
87 }
88
89 cmd.Println(head.Name().Short())
90 case 2:
91 if err := checkIfCollab(cmd, args); err != nil {
92 return err
93 }
94
95 rr, err := be.Repository(ctx, rn)
96 if err != nil {
97 return err //nolint:wrapcheck
98 }
99
100 r, err := rr.Open()
101 if err != nil {
102 return err //nolint:wrapcheck
103 }
104
105 branch := args[1]
106 branches, _ := r.Branches()
107 var exists bool
108 for _, b := range branches {
109 if branch == b {
110 exists = true
111 break
112 }
113 }
114
115 if !exists {
116 return git.ErrReferenceNotExist
117 }
118
119 if _, err := r.SymbolicRef(git.HEAD, gitm.RefsHeads+branch, gitm.SymbolicRefOptions{
120 CommandOptions: gitm.CommandOptions{
121 Context: ctx,
122 },
123 }); err != nil {
124 return err //nolint:wrapcheck
125 }
126
127 // TODO: move this to backend?
128 user := proto.UserFromContext(ctx)
129 wh, err := webhook.NewRepositoryEvent(ctx, user, rr, webhook.RepositoryEventActionDefaultBranchChange)
130 if err != nil {
131 return err //nolint:wrapcheck
132 }
133
134 return webhook.SendEvent(ctx, wh)
135 }
136
137 return nil
138 },
139 }
140
141 return cmd
142}
143
144func branchDeleteCommand() *cobra.Command {
145 cmd := &cobra.Command{
146 Use: "delete REPOSITORY BRANCH",
147 Aliases: []string{"remove", "rm", "del"},
148 Short: "Delete a branch",
149 Args: cobra.ExactArgs(2),
150 PersistentPreRunE: checkIfReadableAndCollab,
151 RunE: func(cmd *cobra.Command, args []string) error {
152 ctx := cmd.Context()
153 be := backend.FromContext(ctx)
154 rn := strings.TrimSuffix(args[0], ".git")
155 rr, err := be.Repository(ctx, rn)
156 if err != nil {
157 return err //nolint:wrapcheck
158 }
159
160 r, err := rr.Open()
161 if err != nil {
162 return err //nolint:wrapcheck
163 }
164
165 branch := args[1]
166 branches, _ := r.Branches()
167 var exists bool
168 for _, b := range branches {
169 if branch == b {
170 exists = true
171 break
172 }
173 }
174
175 if !exists {
176 return git.ErrReferenceNotExist
177 }
178
179 head, err := r.HEAD()
180 if err != nil {
181 return err //nolint:wrapcheck
182 }
183
184 if head.Name().Short() == branch {
185 return fmt.Errorf("cannot delete the default branch")
186 }
187
188 branchCommit, err := r.BranchCommit(branch)
189 if err != nil {
190 return err //nolint:wrapcheck
191 }
192
193 if err := r.DeleteBranch(branch, gitm.DeleteBranchOptions{Force: true}); err != nil {
194 return err //nolint:wrapcheck
195 }
196
197 wh, err := webhook.NewBranchTagEvent(ctx, proto.UserFromContext(ctx), rr, git.RefsHeads+branch, branchCommit.ID.String(), git.ZeroID)
198 if err != nil {
199 return err //nolint:wrapcheck
200 }
201
202 return webhook.SendEvent(ctx, wh)
203 },
204 }
205
206 return cmd
207}