Add branchless trail creation · Entire

Add branchless trail creation

fc3274c·

dipree·3w ago·3 files·+221 added/-113 removed

Sessions

6604ab821878View transcript

Changes

3

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
    }