package external import ( "bytes" "fmt" "io" "os/exec" "strings" "git.hypertheory-labs.dev/loom/loom-cli/internal/config" ) // AddByName adopts a document named the way `list` prints it, against the // current context. // // loomctl external add loom/cart cart // // Nobody should have to type a host's raw-file route to adopt a document. The // route belongs to the host, so it lives in the context; the published // directory belongs to the convention, so it lives in the code; and what is // left — which repository, which document — is the only part a person knows. func AddByName(root, ownerRepo, name string, out io.Writer) error { cfg, err := config.Load() if err != nil { return err } ctx, err := cfg.Current() if err != nil { return err } if strings.Count(ownerRepo, "/") != 1 { return fmt.Errorf("want owner/repo, got %q", ownerRepo) } if !strings.HasSuffix(name, ".md") { name += ".md" } raw, resolved := ctx.RawURL(ownerRepo, PublishedDir+"/"+name) if raw == "" { return fmt.Errorf("context %q has flavor %q; known flavors are %s", cfg.CurrentContext, ctx.Flavor, config.Flavors()) } if !resolved { // The lock must record a resolved URL. A short form that stays short // follows whatever the default branch is at the time you ask, so a // branch rename would report as a change in the document. branch, err := defaultBranch(ctx.CloneURL(ownerRepo)) if err != nil { return fmt.Errorf("resolving the default branch of %s: %w", ownerRepo, err) } raw = fmt.Sprintf(raw, branch) } return Add(root, raw, "", out) } // ListByName enumerates a repository named against the current context. func ListByName(ownerRepo string, out io.Writer) error { cfg, err := config.Load() if err != nil { return err } ctx, err := cfg.Current() if err != nil { return err } return List(ctx.CloneURL(ownerRepo), out) } // defaultBranch asks the remote which branch HEAD points at, without cloning. // // Only hosts whose raw URLs do not redirect need this. Gitea resolves its own // short form, which is why adopting from gitea needs no git at all. func defaultBranch(cloneURL string) (string, error) { cmd := exec.Command("git", "ls-remote", "--symref", cloneURL, "HEAD") var stdout, stderr bytes.Buffer cmd.Stdout, cmd.Stderr = &stdout, &stderr if err := cmd.Run(); err != nil { return "", fmt.Errorf("%w: %s", err, strings.TrimSpace(stderr.String())) } for _, line := range strings.Split(stdout.String(), "\n") { if after, ok := strings.CutPrefix(line, "ref: refs/heads/"); ok { return strings.TrimSpace(strings.SplitN(after, "\t", 2)[0]), nil } } return "", fmt.Errorf("no symref in git ls-remote output") }