fix(trail): require branch push to origin before creating a trail · Entire

fix(trail): require branch push to origin before creating a trail

dc40fb6→main· dipree·4w ago·2 files·+37 added/-18 removed

Make delivering the branch to origin a precondition for 'entire trail create'. Previously the push was gated on the CLI having created the branch locally, so 'git checkout -b foo && entire trail create --branch foo' bound the trail without ever pushing foo. The server then had no remote branch to anchor to and backfilled it at the base tip, producing local/remote/work drift while still printing 'Created trail'.

Now the branch is always pushed (hard-failing on auth/non-fast-forward errors) and the create request sends branch_action=link so the server attaches the delivered branch instead of backfilling at base.

Sessions

e0ad760f2e8eView transcript

Changes

2

// TrailCreateRequest is the body for POST /api/v1/trails/:host/:owner/:repo.
type TrailCreateRequest struct {
    Title      string   `json:"title"`
    Body       string   `json:"body,omitempty"`
    BranchName string   `json:"branch_name"`
    Base       string   `json:"base,omitempty"`
    Status     string   `json:"status,omitempty"`
    Assignees  []string `json:"assignees,omitempty"`
    Labels     []string `json:"labels,omitempty"`
    Priority   string   `json:"priority,omitempty"`
    Type       string   `json:"type,omitempty"`
    // BranchAction tells the server how to bind BranchName. The CLI always
    // pushes the branch to the remote before creating the trail, so it sends
    // "link" to attach the already-delivered branch. This prevents the server
    // from backfilling a backing branch at the base tip when the branch was
    // never delivered (which would silently anchor the trail to empty/diverged
    // content). Valid values: "create" (default, server-side) or "link".
    BranchAction string   `json:"branch_action,omitempty"`
}

Mcmd/entire/cli/api/trail_types.go+16/-9

641 unmodified lines
if needsCreation {
    // needsCreation is a local-only signal, so the branch may still exist on
    // origin (e.g. a teammate pushed it and we never fetched). In that case the
    // remote branch is not ours, and our push may merely fast-forward it; we
    // must not delete it during cleanup. Only treat the remote branch as
    // created-by-us when it did not already exist before our push.
    // Delivering the branch to origin is a PRECONDITION for trail creation, not
    // an optional follow-up. A trail binds to a *remote* branch; if the branch
    // never reaches origin the server has nothing to anchor to and backfills it
    // at the base tip, producing a trail whose backing branch does not reflect
    // the user's work (local branch, remote branch, and actual work all differ).
    // We therefore always push and hard-fail on error, regardless of whether we
    // created the branch locally. (Previously this was gated on needsCreation,
    // so `git checkout -b foo && entire trail create --branch foo` created the
    // trail without ever pushing foo.)
    {
        // branchExistsOnOrigin tells us whether origin already has this branch
        // (e.g. a teammate pushed it, or we pushed it on a previous run). In that
        // case our push may merely fast-forward it and we must not delete it
        // during cleanup. Only treat the remote branch as created-by-us when it
        // did not already exist before our push.
        existedOnOrigin, existErr := branchExistsOnOrigin(branch)
        if existErr != nil {
            // Be conservative: if we cannot tell, do not delete the remote branch.
        }
        if err := pushBranchToOrigin(branch); err != nil {
            cleanupCreatedTrailBranch(repo, branch, localBranchCreated, false, errW)
            return fmt.Errorf("failed to push branch %q: %w", branch, err)
        }
    }
}

Mcmd/entire/cli/trail_cmd.go+21/-9