repo create: stamp a usable entire:// remote in the JSON output · Entire

repo create: stamp a usable entire:// remote in the JSON output

7de7dcemain·

stiak·1mo ago·2 files·+210 added/-1 removed

entire repo create echoed the raw Repo wire JSON, which has clusterHost and path but no ready-to-use clone/remote URL. Synthesize a remote field of the form entire:/// (the form git clone and git-remote-entire accept), merged into the create output.

The path the API returns carries a leading slash, so trim it before joining to avoid a doubled separator. Omit the field entirely when either coordinate is unresolved (e.g. a still-provisioning repo) rather than emit a half-formed URL. The synthesis only fills a gap: if the wire object already carries a remote (a future first-class field, or one arriving via additional properties) it's left untouched so the server value wins.

Repo carries a custom marshaler plus additional properties, so the repo is round-tripped through its own encoder and the remote merged in rather than embedded in a wrapper struct.

Sessions

Changes

2

import (
    "context"
    "encoding/json"
    "fmt"
    "strings"

"github.com/spf13/cobra"
)

// repoRemoteURL synthesizes the entire:// clone/remote URL for a repo from
// its resolved cluster host and path — the form `git clone` and
// `git remote add` accept, which git-remote-entire reads back as the repo
// slug from the URL path. Returns "" when either coordinate is missing (a
// still-provisioning repo may not have them yet); a half-formed URL is worse
// than none.
func repoRemoteURL(r coreapi.Repo) string {
    host := strings.TrimSpace(r.ClusterHost.Or(""))
    path := strings.TrimSpace(r.Path.Or(""))
    if host == "" || path == "" {
        return ""
    }
    return "entire://" + host + "/" + strings.TrimPrefix(path, "/")
}

// repoCreateOutput renders a created repo as JSON with a synthesized `remote`
// field merged in — the entire:// URL callers paste into `git clone` or
// `git remote add`. The repo carries a custom marshaler plus arbitrary
// additional properties, so it can't simply be embedded in a wrapper struct;
// instead it's round-tripped through its own encoder and the remote is merged
// into the resulting object. The synthesis only fills a gap: if the wire
// object already carries a `remote` (a future first-class field, or one
// arriving via additional properties) it's left untouched, so the
// server-provided value always wins. The field is omitted when the clone
// coordinates aren't resolvable yet rather than emitted half-formed.
func repoCreateOutput(r *coreapi.Repo) (any, error) {
    raw, err := json.Marshal(r)
    if err != nil {
        return nil, fmt.Errorf("encode repo: %w", err)
    }
    var obj map[string]json.RawMessage
    if err := json.Unmarshal(raw, &obj); err != nil {
        return nil, fmt.Errorf("decode repo: %w", err)
    }
    if _, ok := obj["remote"]; !ok {
        if remote := repoRemoteURL(*r); remote != "" {
            encoded, err := json.Marshal(remote)
            if err != nil {
                return nil, fmt.Errorf("encode remote: %w", err)
            }
            obj["remote"] = encoded
        }
    }
    return obj, nil
}

func TestRepoRemoteURL(t *testing.T) {
    t.Parallel()
    tests := []struct {
        name string
        repo coreapi.Repo
        want string
    }{
        {
            name: "host and path produce an entire:// URL",
            repo: coreapi.Repo{
                ClusterHost: coreapi.NewOptString("aws-us-east-2.entire.io"),
                Path:        coreapi.NewOptString("acme/web"),
            },
            want: "entire://aws-us-east-2.entire.io/acme/web",
        },
        {
            name: "leading slash on path is not doubled",
            repo: coreapi.Repo{
                ClusterHost: coreapi.NewOptString("aws-us-east-2.entire.io"),
                Path:        coreapi.NewOptString("/acme/web"),
            },
            want: "entire://aws-us-east-2.entire.io/acme/web",
        },
        {
            name: "missing host yields no URL",
            repo: coreapi.Repo{Path: coreapi.NewOptString("acme/web")},
            want: "",
        },
        {
            name: "missing path yields no URL",
            repo: coreapi.Repo{ClusterHost: coreapi.NewOptString("aws-us-east-2.entire.io")},
            want: "",
        },
        {
            name: "blank coordinates yield no URL",
            repo: coreapi.Repo{
                ClusterHost: coreapi.NewOptString("  "),
                Path:        coreapi.NewOptString(""),
            },
            want: "",
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            if got := repoRemoteURL(tt.repo); got != tt.want {
                t.Errorf("repoRemoteURL() = %q, want %q", got, tt.want)
            }
        })
    }
}