branch.go

  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}