# Search: Accept Entire Mirror Remotes

`f72b778`→[main](/content/gh/entireio/cli/commits/main/index.html)·

pfleidi·1mo ago·2 files·+24 added/-46 removed

entire search derived owner/repo via a GitHub-only parser that required
the remote host to be github.com, so it rejected Entire mirror remotes
(entire://host/gh/owner/repo) with "remote is not a GitHub repository".

Delegate ParseGitHubRemote to the canonical gitremote.ParseURL, which
understands the entire:// scheme, and enforce the GitHub policy via
CanonicalHost() (gh -> github.com). This also fixes the same limitation
in the two dispatch callers that share the helper, and removes the
duplicated URL parsing.

## Sessions

13d8c9f7198fView transcript

[?\
Fix Search to Accept Entire Mirror RemotesClaude Code·Opus 4.8[1m]·1 step](/content/gh/entireio/cli/session/5a037a28-15c4-4121-a391-4f1be5d90488#timeline-13d8c9f7198f/index.html)

## Changes

2

- cmd/entire/cli/search

- Mgithub.go+13/-46

- Msearch_test.go+11

```
1 unmodified line

2
3
4
5
5
7
8
6
7
8
9
11
12
13
14
10
11
12
13
14
15
16
17
18
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
20
21
22
47
48
49
50
51
52
53
54
55
56
23
24

1 unmodified line

package search

import (
	"errors"
	"fmt"
	"net/url"
	"strings"

"github.com/entireio/cli/cmd/entire/cli/gitremote"
)

// ParseGitHubRemote extracts owner and repo from a GitHub remote URL.
// Supports SCP-style SSH (git@github.com:owner/repo.git),
// ssh:// URLs (ssh://git@github.com/owner/repo.git),
// and HTTPS (https://github.com/owner/repo.git).
// ParseGitHubRemote extracts owner and repo from a git remote URL that resolves
// to GitHub. It accepts direct GitHub remotes (SCP-style SSH, ssh://, and
// https://) as well as Entire mirror remotes (entire://host/gh/owner/repo),
// whose forge prefix maps back to github.com. Remotes resolving to any other
// host are rejected.
func ParseGitHubRemote(remoteURL string) (owner, repo string, err error) {
	remoteURL = strings.TrimSpace(remoteURL)
	if remoteURL == "" {
		return "", "", errors.New("empty remote URL")
	}
	info, err := gitremote.ParseURL(remoteURL)
	if err != nil {
		return "", "", fmt.Errorf("parsing remote URL: %w", err)
	}

var path string

// SCP-style SSH: git@github.com:owner/repo.git
	// Distinguished from ssh:// URLs by having no scheme and a colon before the path.
	if strings.HasPrefix(remoteURL, "git@") && !strings.Contains(remoteURL, "://") {
		idx := strings.Index(remoteURL, ":")
		if idx < 0 {
			return "", "", fmt.Errorf("invalid SSH remote URL: %s", remoteURL)
		}
		host := remoteURL[len("git@") : idx]
		if host != "github.com" {
			return "", "", fmt.Errorf("remote is not a GitHub repository (host: %s)", host)
		}
		path = remoteURL[idx+1:]
	} else {
		// URL format: https://, ssh://, git://
		u, parseErr := url.Parse(remoteURL)
		if parseErr != nil {
			return "", "", fmt.Errorf("parsing remote URL: %w", parseErr)
		}
		host := u.Hostname()
		if host != "github.com" {
			return "", "", fmt.Errorf("remote is not a GitHub repository (host: %s)", host)
		}
		path = strings.TrimPrefix(u.Path, "/")
	}
	if host := info.CanonicalHost(); host != "github.com" {
		return "", "", fmt.Errorf("remote is not a GitHub repository (host: %s)", host)
	}

// Remove .git suffix
	path = strings.TrimSuffix(path, ".git")

parts := strings.SplitN(path, "/", 3)
	if len(parts) < 2 || parts[0] == "" || parts[1] == "" {
		return "", "", fmt.Errorf("could not extract owner/repo from remote URL: %s", remoteURL)
	}

return parts[0], parts[1], nil
	return info.Owner, info.Repo, nil
}
```

Mcmd/entire/cli/search/github.go+13/-46

```
104 unmodified lines

105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

104 unmodified lines

}
}

func TestParseGitHubRemote_EntireMirror(t *testing.T) {
	t.Parallel()
	owner, repo, err := ParseGitHubRemote("entire://aws-us-east-2.entire.io/gh/entirehq/entire.io")
	if err != nil {
		t.Fatal(err)
	}
	if owner != testOwner || repo != testRepo {
		t.Errorf("got %s/%s, want %s/%s", owner, repo, testOwner, testRepo)
	}
}

// -- Search() tests --

func TestSearch_URLConstruction(t *testing.T) {
