Add branchless trail creation · Entire
Add branchless trail creation
fc3274c·
dipree·3w ago·3 files·+221 added/-113 removed
Sessions
6604ab821878View transcript
Changes
3
cmd/entire/cli
api
Mtrail_types.go+3/-2
Mtrail_cmd.go+202/-111
Mtrail_cmd_test.go+16
77 unmodified lines
78
79
80
81
81
82
83
83
84
85
86
87
77 unmodified lines
type TrailCreateRequest struct {
Title string `json:"title"`
Body string `json:"body,omitempty"`
BranchName string `json:"branch_name"`
BranchName string `json:"branch_name,omitempty"`
// BranchAction is "create" (default) or "link". The CLI sends "link" to
// attach the already-pushed branch instead of backfilling it at base.
// attach an already-pushed branch instead of backfilling it at base. Omit
// both branch_name and branch_action to create a branchless trail.
BranchAction string `json:"branch_action,omitempty"`
Base string `json:"base,omitempty"`
Status string `json:"status,omitempty"`
}
func newTrailCreateCmd() *cobra.Command {
var title, body, base, branch, status string
var checkout bool
var checkout, noBranch bool
cmd := &cobra.Command{
Use: "create",
Short: "Create a trail for the current or a new branch",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
return runTrailCreate(cmd, title, body, base, branch, status, checkout)
},
}
cmd.Flags().StringVar(&branch, "branch", "", "Branch for the trail (defaults to current branch)")
cmd.Flags().StringVar(&status, "status", "", "Initial status (defaults to open)")
cmd.Flags().BoolVar(&checkout, "checkout", false, "Check out the branch after creating it")
cmd.Flags().BoolVar(&noBranch, "no-branch", false, "Create a branchless trail")
return cmd
}
func runTrailCreate(cmd *cobra.Command, title, body, base, branch, statusStr string, checkout bool) error {
ctx := cmd.Context()
w := cmd.OutOrStdout()
errW := cmd.ErrOrStderr()
if err := validateTrailCreateFlagCombos(cmd, checkout, noBranch); err != nil {
return err
}
repo, err := strategy.OpenRepository(ctx)
if err != nil {
return fmt.Errorf("failed to open repository: %w", err)
}
defer repo.Close()
// Determine base branch.
if base == "" {
base = strategy.GetDefaultBranchName(repo)
if base == "" {
base = defaultBaseBranch
}
}
base = resolveTrailCreateBase(repo, base)
_, currentBranch, _ := isOnDefaultBranchRepo(repo) //nolint:errcheck // best-effort; reuse the open repo for the current branch name
title, body, base, branch, statusStr, err = resolveTrailCreateFields(cmd, w, title, body, base, branch, statusStr, currentBranch, noBranch)
if err != nil {
return err
}
if err := validateTrailCreateFields(ctx, title, branch, statusStr, noBranch); err != nil {
return err
}
client, err := NewAuthenticatedAPIClient(ctx, trailInsecureHTTP(cmd))
if err != nil {
return renderDataAPIAuthError(cmd.ErrOrStderr(), err)
}
forge, owner, repoName, err := resolveTrailRemote(ctx)
if err != nil {
return err
}
branchState, err := prepareTrailCreateBranch(w, errW, repo, branch, currentBranch, noBranch)
if err != nil {
return err
}
createResp, err := postTrailCreate(ctx, client, forge, owner, repoName, title, body, branch, base, statusStr)
if err != nil {
cleanupCreatedTrailBranch(repo, branch, branchState.LocalCreated, branchState.RemotePushed, errW)
return err
}
printTrailCreateResult(w, createResp.Trail)
return maybeCheckoutTrailCreateBranch(ctx, cmd, w, branch, currentBranch, checkout, branchState.NeedsCreation)
}
type trailCreateBranchState struct {
NeedsCreation bool
LocalCreated bool
RemotePushed bool
}
func validateTrailCreateFlagCombos(cmd *cobra.Command, checkout, noBranch bool) error {
if noBranch && cmd.Flags().Changed("branch") {
return errors.New("cannot combine --no-branch with --branch")
}
if noBranch && checkout {
return errors.New("cannot combine --no-branch with --checkout")
}
return nil
}
Test Cases
func TestNewTrailCreateRequestCanBeBranchless(t *testing.T) {
req := newTrailCreateRequest("title", "body", "", "main", "open")
require.Equal(t, api.TrailCreateRequest{
Title: "title",
Body: "body",
Base: "main",
Status: "open",
}, req)
encoded, err := json.Marshal(req)
require.NoError(t, err)
require.NotContains(t, string(encoded), "branch_name")
require.NotContains(t, string(encoded), "branch_action")
}
func TestCleanupCreatedTrailBranch(t *testing.T) {
cases := []struct {
name string
}