rickub/clipublic Fork
⑂ d68b124
Commits
⬇ Clone ▾
git clone https://git.rickub.com/rickub/cli.git
git clone ssh://git@rickub.com/rickub/cli.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

repo clone: derive a git URL the host actually servesUnverified

The web host does not speak smart HTTP, so <host>/<owner>/<repo>.git
404s for every repo. rickub serves git over SSH on the site hostname
and over HTTPS on git.<host>; clone now defaults to the SSH form and
takes --protocol https, in which case the token authenticates the clone
through GIT_CONFIG_* (never argv, never .git/config).

Fixes #1

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgkcT3UPxwxukBKojB5eg4
Olivier Girardot committed 2026-09-03T07:26:56+02:00 Browse files
d68b124 parent: 900561e
modified cmd/repo.go +62 -2
@@ -4,7 +4,10 @@ import (
44 "encoding/base64"
55 "fmt"
66 "io"
7+ "net/url"
8+ "os"
79 "os/exec"
10+ "strconv"
811 "strings"
912
1013 "rickub.com/rickub/cli/internal/api"
@@ -96,11 +99,14 @@ By default repositories are private; pass --public to create a public one.`,
9699 Short: "Clone a repository with git",
97100 Long: `Clone a repository by shelling out to git.
98101
99-The clone URL is derived from the configured host as <host>/<owner>/<repo>.git.
102+The clone URL is derived from the configured host: ssh://git@<host>/<owner>/<repo>.git
103+by default (your SSH keys authenticate), or https://git.<host>/<owner>/<repo>.git
104+with --protocol https, where your token authenticates this clone only.
100105 Extra arguments after -- are passed through to git clone.`,
101106 Args: cobra.MinimumNArgs(1),
102107 RunE: runRepoClone,
103108 }
109+ cloneCmd.Flags().StringVar(&repoCloneProtocol, "protocol", "ssh", "ssh | https")
104110
105111 // files (list dir)
106112 filesCmd := &cobra.Command{
@@ -311,7 +317,10 @@ func runRepoClone(cmd *cobra.Command, args []string) error {
311317 return err
312318 }
313319 host := hostFor(cfg)
314- cloneURL := fmt.Sprintf("%s/%s/%s.git", host, owner, repo)
320+ cloneURL, err := cloneURLFor(host, owner, repo, repoCloneProtocol)
321+ if err != nil {
322+ return err
323+ }
315324 gitArgs := []string{"clone", cloneURL}
316325 gitArgs = append(gitArgs, args[1:]...)
317326 fmt.Fprintf(cmd.OutOrStdout(), "Cloning %s…\n", cloneURL)
@@ -319,9 +328,58 @@ func runRepoClone(cmd *cobra.Command, args []string) error {
319328 g.Stdout = cmd.OutOrStdout()
320329 g.Stderr = cmd.ErrOrStderr()
321330 g.Stdin = cmd.InOrStdin()
331+ g.Env = os.Environ()
332+ if repoCloneProtocol == "https" {
333+ // Authenticate this clone with the token, handed to git through its
334+ // environment-config mechanism: never on the command line (visible in
335+ // ps) and never written into the clone's .git/config.
336+ if token := config.ResolveToken(flagToken, cfg, host); token != "" {
337+ config.WarnIfInsecure(cmd.ErrOrStderr(), cloneURL)
338+ g.Env = append(g.Env, gitAuthEnv(token)...)
339+ }
340+ }
322341 return g.Run()
323342 }
324343
344+// cloneURLFor builds the git URL for owner/repo on the API host. rickub serves
345+// git over SSH on the site's own hostname and over HTTPS on the git.<host>
346+// subdomain; the web host itself does not speak smart HTTP, so the API host is
347+// never used verbatim.
348+func cloneURLFor(host, owner, repo, protocol string) (string, error) {
349+ u, err := url.Parse(config.NormalizeHost(host))
350+ if err != nil || u.Hostname() == "" {
351+ return "", fmt.Errorf("cannot derive a clone URL from host %q", host)
352+ }
353+ switch protocol {
354+ case "", "ssh":
355+ return fmt.Sprintf("ssh://git@%s/%s/%s.git", u.Hostname(), owner, repo), nil
356+ case "https":
357+ scheme := u.Scheme
358+ if scheme == "" {
359+ scheme = "https"
360+ }
361+ return fmt.Sprintf("%s://git.%s/%s/%s.git", scheme, u.Host, owner, repo), nil
362+ default:
363+ return "", fmt.Errorf("unknown --protocol %q (want ssh or https)", protocol)
364+ }
365+}
366+
367+// gitAuthEnv returns the GIT_CONFIG_* variables that make git send the token as
368+// a bearer header, appended after any GIT_CONFIG_COUNT entries already present.
369+func gitAuthEnv(token string) []string {
370+ n := 0
371+ if v := os.Getenv("GIT_CONFIG_COUNT"); v != "" {
372+ if parsed, err := strconv.Atoi(v); err == nil && parsed > 0 {
373+ n = parsed
374+ }
375+ }
376+ return []string{
377+ fmt.Sprintf("GIT_CONFIG_COUNT=%d", n+1),
378+ fmt.Sprintf("GIT_CONFIG_KEY_%d=http.extraHeader", n),
379+ fmt.Sprintf("GIT_CONFIG_VALUE_%d=Authorization: Bearer %s", n, token),
380+ }
381+}
382+
325383 func runRepoFiles(cmd *cobra.Command, args []string) error {
326384 client, err := newClient()
327385 if err != nil {
@@ -514,6 +572,8 @@ func defaultBranch(cmd *cobra.Command, client *api.Client, owner, repo string) (
514572 }
515573
516574 // hostFor returns the effective host from flags/env/config.
575+var repoCloneProtocol string
576+
517577 func hostFor(cfg *config.Config) string {
518578 return config.ResolveHost(flagHost, cfg)
519579 }
@@ -4,7 +4,10 @@ import (
4 "encoding/base64"4 "encoding/base64"
5 "fmt"5 "fmt"
6 "io"6 "io"
7+ "net/url"
8+ "os"
7 "os/exec"9 "os/exec"
10+ "strconv"
8 "strings"11 "strings"
9 12
10 "rickub.com/rickub/cli/internal/api"13 "rickub.com/rickub/cli/internal/api"
@@ -96,11 +99,14 @@ By default repositories are private; pass --public to create a public one.`,
96 Short: "Clone a repository with git",99 Short: "Clone a repository with git",
97 Long: `Clone a repository by shelling out to git.100 Long: `Clone a repository by shelling out to git.
98 101
99-The clone URL is derived from the configured host as <host>/<owner>/<repo>.git.102+The clone URL is derived from the configured host: ssh://git@<host>/<owner>/<repo>.git
103+by default (your SSH keys authenticate), or https://git.<host>/<owner>/<repo>.git
104+with --protocol https, where your token authenticates this clone only.
100 Extra arguments after -- are passed through to git clone.`,105 Extra arguments after -- are passed through to git clone.`,
101 Args: cobra.MinimumNArgs(1),106 Args: cobra.MinimumNArgs(1),
102 RunE: runRepoClone,107 RunE: runRepoClone,
103 }108 }
109+ cloneCmd.Flags().StringVar(&repoCloneProtocol, "protocol", "ssh", "ssh | https")
104 110
105 // files (list dir)111 // files (list dir)
106 filesCmd := &cobra.Command{112 filesCmd := &cobra.Command{
@@ -311,7 +317,10 @@ func runRepoClone(cmd *cobra.Command, args []string) error {
311 return err317 return err
312 }318 }
313 host := hostFor(cfg)319 host := hostFor(cfg)
314- cloneURL := fmt.Sprintf("%s/%s/%s.git", host, owner, repo)320+ cloneURL, err := cloneURLFor(host, owner, repo, repoCloneProtocol)
321+ if err != nil {
322+ return err
323+ }
315 gitArgs := []string{"clone", cloneURL}324 gitArgs := []string{"clone", cloneURL}
316 gitArgs = append(gitArgs, args[1:]...)325 gitArgs = append(gitArgs, args[1:]...)
317 fmt.Fprintf(cmd.OutOrStdout(), "Cloning %s…\n", cloneURL)326 fmt.Fprintf(cmd.OutOrStdout(), "Cloning %s…\n", cloneURL)
@@ -319,9 +328,58 @@ func runRepoClone(cmd *cobra.Command, args []string) error {
319 g.Stdout = cmd.OutOrStdout()328 g.Stdout = cmd.OutOrStdout()
320 g.Stderr = cmd.ErrOrStderr()329 g.Stderr = cmd.ErrOrStderr()
321 g.Stdin = cmd.InOrStdin()330 g.Stdin = cmd.InOrStdin()
331+ g.Env = os.Environ()
332+ if repoCloneProtocol == "https" {
333+ // Authenticate this clone with the token, handed to git through its
334+ // environment-config mechanism: never on the command line (visible in
335+ // ps) and never written into the clone's .git/config.
336+ if token := config.ResolveToken(flagToken, cfg, host); token != "" {
337+ config.WarnIfInsecure(cmd.ErrOrStderr(), cloneURL)
338+ g.Env = append(g.Env, gitAuthEnv(token)...)
339+ }
340+ }
322 return g.Run()341 return g.Run()
323 }342 }
324 343
344+// cloneURLFor builds the git URL for owner/repo on the API host. rickub serves
345+// git over SSH on the site's own hostname and over HTTPS on the git.<host>
346+// subdomain; the web host itself does not speak smart HTTP, so the API host is
347+// never used verbatim.
348+func cloneURLFor(host, owner, repo, protocol string) (string, error) {
349+ u, err := url.Parse(config.NormalizeHost(host))
350+ if err != nil || u.Hostname() == "" {
351+ return "", fmt.Errorf("cannot derive a clone URL from host %q", host)
352+ }
353+ switch protocol {
354+ case "", "ssh":
355+ return fmt.Sprintf("ssh://git@%s/%s/%s.git", u.Hostname(), owner, repo), nil
356+ case "https":
357+ scheme := u.Scheme
358+ if scheme == "" {
359+ scheme = "https"
360+ }
361+ return fmt.Sprintf("%s://git.%s/%s/%s.git", scheme, u.Host, owner, repo), nil
362+ default:
363+ return "", fmt.Errorf("unknown --protocol %q (want ssh or https)", protocol)
364+ }
365+}
366+
367+// gitAuthEnv returns the GIT_CONFIG_* variables that make git send the token as
368+// a bearer header, appended after any GIT_CONFIG_COUNT entries already present.
369+func gitAuthEnv(token string) []string {
370+ n := 0
371+ if v := os.Getenv("GIT_CONFIG_COUNT"); v != "" {
372+ if parsed, err := strconv.Atoi(v); err == nil && parsed > 0 {
373+ n = parsed
374+ }
375+ }
376+ return []string{
377+ fmt.Sprintf("GIT_CONFIG_COUNT=%d", n+1),
378+ fmt.Sprintf("GIT_CONFIG_KEY_%d=http.extraHeader", n),
379+ fmt.Sprintf("GIT_CONFIG_VALUE_%d=Authorization: Bearer %s", n, token),
380+ }
381+}
382+
325 func runRepoFiles(cmd *cobra.Command, args []string) error {383 func runRepoFiles(cmd *cobra.Command, args []string) error {
326 client, err := newClient()384 client, err := newClient()
327 if err != nil {385 if err != nil {
@@ -514,6 +572,8 @@ func defaultBranch(cmd *cobra.Command, client *api.Client, owner, repo string) (
514 }572 }
515 573
516 // hostFor returns the effective host from flags/env/config.574 // hostFor returns the effective host from flags/env/config.
575+var repoCloneProtocol string
576+
517 func hostFor(cfg *config.Config) string {577 func hostFor(cfg *config.Config) string {
518 return config.ResolveHost(flagHost, cfg)578 return config.ResolveHost(flagHost, cfg)
519 }579 }