Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

git: use custom giturl type to preserve original remote #4326

Merged
merged 2 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading