branch.go

  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}