Skip to content

Commit

Permalink
git: use custom giturl type to preserve original remote
Browse files Browse the repository at this point in the history
This resolves a regression introduced in
50e75e3. In this previous patch, I'd
incorrectly assumed that scp-like URLs can express a subset of
"standard"-URLs and so we can always safely convert them for
consistency. This isn't true - the URL "[email protected]:foo" should be
resolved to the home directory of the host, however, the converted URL
"ssh://[email protected]/foo" will be resolved to the root of the host.

To resolve this, we need to not perform this conversion. However, we
also need preserve the behaviour of firm distinction between SCP and
normal URL types (so as to keep proper port parsing).

To do this, we add a new GitURL type to the gitutil package. This new
type contains all useful fields shared in common between the standard
libraries url package and our custom scp-style url parsing package. This
keeps the previous property of a single clean interface to all GitURLs,
while also ensuring that we preserve the original URL to pass to the Git
CLI (making sure we strip fragments out, which are used as
buildkit-level metadata).

As a side-effect of this, the client-side calling code for parsing
git urls is simplified (so we don't have to do fragment wrangling at
every call point).

Signed-off-by: Justin Chadwell <[email protected]>
  • Loading branch information
jedevc committed Oct 12, 2023
1 parent 938560b commit 9a51bb0
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 115 deletions.
3 changes: 1 addition & 2 deletions client/llb/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,7 @@ func Git(url, ref string, opts ...GitOption) State {
remote, err = gitutil.ParseURL(url)
}
if remote != nil {
remote.Fragment = ""
url = remote.String()
url = remote.Remote
}

var id string
Expand Down
10 changes: 5 additions & 5 deletions source/git/identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ func NewGitIdentifier(remoteURL string) (*GitIdentifier, error) {
return nil, err
}

repo := GitIdentifier{}
repo.Ref, repo.Subdir = gitutil.SplitGitFragment(u.Fragment)
u.Fragment = ""
repo.Remote = u.String()

repo := GitIdentifier{Remote: u.Remote}
if u.Fragment != nil {
repo.Ref = u.Fragment.Ref
repo.Subdir = u.Fragment.Subdir
}
if sd := path.Clean(repo.Subdir); sd == "/" || sd == "." {
repo.Subdir = ""
}
Expand Down
6 changes: 3 additions & 3 deletions source/git/identifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestNewGitIdentifier(t *testing.T) {
{
url: "[email protected]:moby/buildkit.git",
expected: GitIdentifier{
Remote: "ssh://[email protected]/moby/buildkit.git",
Remote: "[email protected]:moby/buildkit.git",
},
},
{
Expand Down Expand Up @@ -75,13 +75,13 @@ func TestNewGitIdentifier(t *testing.T) {
{
url: "[email protected]:user/repo.git",
expected: GitIdentifier{
Remote: "ssh://[email protected]/user/repo.git",
Remote: "[email protected]:user/repo.git",
},
},
{
url: "[email protected]:user/repo.git#mybranch:mydir/mysubdir/",
expected: GitIdentifier{
Remote: "ssh://[email protected]/user/repo.git",
Remote: "[email protected]:user/repo.git",
Ref: "mybranch",
Subdir: "mydir/mysubdir/",
},
Expand Down
14 changes: 7 additions & 7 deletions util/gitutil/git_ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,17 @@ func ParseGitRef(ref string) (*GitRef, error) {
res := &GitRef{}

var (
remote *url.URL
remote *GitURL
err error
)

if strings.HasPrefix(ref, "github.com/") {
res.IndistinguishableFromLocal = true // Deprecated
remote = &url.URL{
remote = fromURL(&url.URL{
Scheme: "https",
Host: "github.com",
Path: strings.TrimPrefix(ref, "github.com/"),
}
})
} else {
remote, err = ParseURL(ref)
if errors.Is(err, ErrUnknownProtocol) {
Expand All @@ -87,13 +87,13 @@ func ParseGitRef(ref string) (*GitRef, error) {
}
}

res.Commit, res.SubDir = SplitGitFragment(remote.Fragment)
remote.Fragment = ""

res.Remote = remote.String()
res.Remote = remote.Remote
if res.IndistinguishableFromLocal {
_, res.Remote, _ = strings.Cut(res.Remote, "://")
}
if remote.Fragment != nil {
res.Commit, res.SubDir = remote.Fragment.Ref, remote.Fragment.Subdir
}

repoSplitBySlash := strings.Split(res.Remote, "/")
res.ShortName = strings.TrimSuffix(repoSplitBySlash[len(repoSplitBySlash)-1], ".git")
Expand Down
6 changes: 3 additions & 3 deletions util/gitutil/git_ref_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,21 @@ func TestParseGitRef(t *testing.T) {
{
ref: "[email protected]:moby/buildkit",
expected: &GitRef{
Remote: "ssh://[email protected]/moby/buildkit",
Remote: "[email protected]:moby/buildkit",
ShortName: "buildkit",
},
},
{
ref: "[email protected]:moby/buildkit.git",
expected: &GitRef{
Remote: "ssh://[email protected]/moby/buildkit.git",
Remote: "[email protected]:moby/buildkit.git",
ShortName: "buildkit",
},
},
{
ref: "[email protected]:atlassianlabs/atlassian-docker.git",
expected: &GitRef{
Remote: "ssh://[email protected]/atlassianlabs/atlassian-docker.git",
Remote: "[email protected]:atlassianlabs/atlassian-docker.git",
ShortName: "atlassian-docker",
},
},
Expand Down
102 changes: 77 additions & 25 deletions util/gitutil/git_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,42 +30,94 @@ var supportedProtos = map[string]struct{}{

var protoRegexp = regexp.MustCompile(`^[a-zA-Z0-9]+://`)

// ParseURL parses a git URL and returns a parsed URL object.
// URL is a custom URL type that points to a remote Git repository.
//
// ParseURL understands implicit ssh URLs such as "git@host:repo", and
// returns the same response as if the URL were "ssh://git@host/repo".
func ParseURL(remote string) (*url.URL, error) {
// URLs can be parsed from both standard URLs (e.g.
// "https://github.com/moby/buildkit.git"), as well as SCP-like URLs (e.g.
// "[email protected]:moby/buildkit.git").
//
// See https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols
type GitURL struct {
// Scheme is the protocol over which the git repo can be accessed
Scheme string

// Host is the remote host that hosts the git repo
Host string
// Path is the path on the host to access the repo
Path string
// User is the username/password to access the host
User *url.Userinfo
// Fragment can contain additional metadata
Fragment *GitURLFragment

// Remote is a valid URL remote to pass into the Git CLI tooling (i.e.
// without the fragment metadata)
Remote string
}

// GitURLFragment is the buildkit-specific metadata extracted from the fragment
// of a remote URL.
type GitURLFragment struct {
// Ref is the git reference
Ref string
// Subdir is the sub-directory inside the git repository to use
Subdir string
}

// splitGitFragment splits a git URL fragment into its respective git
// reference and subdirectory components.
func splitGitFragment(fragment string) *GitURLFragment {
if fragment == "" {
return nil
}
ref, subdir, _ := strings.Cut(fragment, ":")
return &GitURLFragment{Ref: ref, Subdir: subdir}
}

// ParseURL parses a BuildKit-style Git URL (that may contain additional
// fragment metadata) and returns a parsed GitURL object.
func ParseURL(remote string) (*GitURL, error) {
if proto := protoRegexp.FindString(remote); proto != "" {
proto = strings.ToLower(strings.TrimSuffix(proto, "://"))
if _, ok := supportedProtos[proto]; !ok {
return nil, errors.Wrap(ErrInvalidProtocol, proto)
}

return url.Parse(remote)
url, err := url.Parse(remote)
if err != nil {
return nil, err
}
return fromURL(url), nil
}

if sshutil.IsImplicitSSHTransport(remote) {
remote, fragment, _ := strings.Cut(remote, "#")
remote, path, _ := strings.Cut(remote, ":")
user, host, _ := strings.Cut(remote, "@")
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
return &url.URL{
Scheme: SSHProtocol,
User: url.User(user),
Host: host,
Path: path,
Fragment: fragment,
}, nil
if url, err := sshutil.ParseSCPStyleURL(remote); err == nil {
return fromSCPStyleURL(url), nil
}

return nil, ErrUnknownProtocol
}

// SplitGitFragments splits a git URL fragment into its respective git
// reference and subdirectory components.
func SplitGitFragment(fragment string) (ref string, subdir string) {
ref, subdir, _ = strings.Cut(fragment, ":")
return ref, subdir
func fromURL(url *url.URL) *GitURL {
withoutFragment := *url
withoutFragment.Fragment = ""
return &GitURL{
Scheme: url.Scheme,
User: url.User,
Host: url.Host,
Path: url.Path,
Fragment: splitGitFragment(url.Fragment),
Remote: withoutFragment.String(),
}
}

func fromSCPStyleURL(url *sshutil.SCPStyleURL) *GitURL {
withoutFragment := *url
withoutFragment.Fragment = ""
return &GitURL{
Scheme: SSHProtocol,
User: url.User,
Host: url.Host,
Path: url.Path,
Fragment: splitGitFragment(url.Fragment),
Remote: withoutFragment.String(),
}
}
Loading

0 comments on commit 9a51bb0

Please sign in to comment.