branch.go

  1package cmd
  2
  3import (
  4	"fmt"
  5	"strings"
  6
  7	"github.com/charmbracelet/soft-serve/git"
  8	"github.com/charmbracelet/soft-serve/pkg/backend"
  9	"github.com/charmbracelet/soft-serve/pkg/proto"
 10	"github.com/charmbracelet/soft-serve/pkg/webhook"
 11	gitm "github.com/gogs/git-module"
 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
 43			}
 44
 45			r, err := rr.Open()
 46			if err != nil {
 47				return err
 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		RunE: func(cmd *cobra.Command, args []string) error {
 68			ctx := cmd.Context()
 69			be := backend.FromContext(ctx)
 70			rn := strings.TrimSuffix(args[0], ".git")
 71			switch len(args) {
 72			case 1:
 73				if err := checkIfReadable(cmd, args); err != nil {
 74					return err
 75				}
 76				rr, err := be.Repository(ctx, rn)
 77				if err != nil {
 78					return err
 79				}
 80
 81				r, err := rr.Open()
 82				if err != nil {
 83					return err
 84				}
 85
 86				head, err := r.HEAD()
 87				if err != nil {
 88					return err
 89				}
 90
 91				cmd.Println(head.Name().Short())
 92			case 2:
 93				if err := checkIfCollab(cmd, args); err != nil {
 94					return err
 95				}
 96
 97				rr, err := be.Repository(ctx, rn)
 98				if err != nil {
 99					return err
100				}
101
102				r, err := rr.Open()
103				if err != nil {
104					return err
105				}
106
107				branch := args[1]
108				branches, _ := r.Branches()
109				var exists bool
110				for _, b := range branches {
111					if branch == b {
112						exists = true
113						break
114					}
115				}
116
117				if !exists {
118					return git.ErrReferenceNotExist
119				}
120
121				if _, err := r.SymbolicRef(git.HEAD, gitm.RefsHeads+branch, gitm.SymbolicRefOptions{
122					CommandOptions: gitm.CommandOptions{
123						Context: ctx,
124					},
125				}); err != nil {
126					return err
127				}
128
129				// TODO: move this to backend?
130				user := proto.UserFromContext(ctx)
131				wh, err := webhook.NewRepositoryEvent(ctx, user, rr, webhook.RepositoryEventActionDefaultBranchChange)
132				if err != nil {
133					return err
134				}
135
136				return webhook.SendEvent(ctx, wh)
137			}
138
139			return nil
140		},
141	}
142
143	return cmd
144}
145
146func branchDeleteCommand() *cobra.Command {
147	cmd := &cobra.Command{
148		Use:               "delete REPOSITORY BRANCH",
149		Aliases:           []string{"remove", "rm", "del"},
150		Short:             "Delete a branch",
151		PersistentPreRunE: checkIfCollab,
152		RunE: func(cmd *cobra.Command, args []string) error {
153			ctx := cmd.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			r, err := rr.Open()
162			if err != nil {
163				return err
164			}
165
166			branch := args[1]
167			branches, _ := r.Branches()
168			var exists bool
169			for _, b := range branches {
170				if branch == b {
171					exists = true
172					break
173				}
174			}
175
176			if !exists {
177				return git.ErrReferenceNotExist
178			}
179
180			head, err := r.HEAD()
181			if err != nil {
182				return err
183			}
184
185			if head.Name().Short() == branch {
186				return fmt.Errorf("cannot delete the default branch")
187			}
188
189			branchCommit, err := r.BranchCommit(branch)
190			if err != nil {
191				return err
192			}
193
194			if err := r.DeleteBranch(branch, gitm.DeleteBranchOptions{Force: true}); err != nil {
195				return err
196			}
197
198			wh, err := webhook.NewBranchTagEvent(ctx, proto.UserFromContext(ctx), rr, git.RefsHeads+branch, branchCommit.ID.String(), git.ZeroID)
199			if err != nil {
200				return err
201			}
202
203			return webhook.SendEvent(ctx, wh)
204		},
205	}
206
207	return cmd
208}