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