vendor: update buildkit to v0.24.0-rc2
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+50
-11
@@ -247,9 +247,13 @@ const (
|
||||
// Formats that utilize SSH may need to supply credentials as a [GitOption].
|
||||
// You may need to check the source code for a full list of supported formats.
|
||||
//
|
||||
// Fragment can be used to pass ref:subdir format that can set in (old-style)
|
||||
// Docker Git URL format after # . This is provided for backwards compatibility.
|
||||
// It is recommended to leave it empty and call GitRef(), GitSubdir() options instead.
|
||||
//
|
||||
// By default the git repository is cloned with `--depth=1` to reduce the amount of data downloaded.
|
||||
// Additionally the ".git" directory is removed after the clone, you can keep ith with the [KeepGitDir] [GitOption].
|
||||
func Git(url, ref string, opts ...GitOption) State {
|
||||
func Git(url, fragment string, opts ...GitOption) State {
|
||||
remote, err := gitutil.ParseURL(url)
|
||||
if errors.Is(err, gitutil.ErrUnknownProtocol) {
|
||||
url = "https://" + url
|
||||
@@ -259,6 +263,20 @@ func Git(url, ref string, opts ...GitOption) State {
|
||||
url = remote.Remote
|
||||
}
|
||||
|
||||
gi := &GitInfo{
|
||||
AuthHeaderSecret: GitAuthHeaderKey,
|
||||
AuthTokenSecret: GitAuthTokenKey,
|
||||
}
|
||||
ref, subdir, ok := strings.Cut(fragment, ":")
|
||||
if ref != "" {
|
||||
GitRef(ref).SetGitOption(gi)
|
||||
}
|
||||
if ok && subdir != "" {
|
||||
GitSubDir(subdir).SetGitOption(gi)
|
||||
}
|
||||
for _, o := range opts {
|
||||
o.SetGitOption(gi)
|
||||
}
|
||||
var id string
|
||||
if err != nil {
|
||||
// If we can't parse the URL, just use the full URL as the ID. The git
|
||||
@@ -269,18 +287,13 @@ func Git(url, ref string, opts ...GitOption) State {
|
||||
// for different protocols (e.g. https and ssh) that have the same
|
||||
// host/path/fragment combination.
|
||||
id = remote.Host + path.Join("/", remote.Path)
|
||||
if ref != "" {
|
||||
id += "#" + ref
|
||||
if gi.Ref != "" || gi.SubDir != "" {
|
||||
id += "#" + gi.Ref
|
||||
if gi.SubDir != "" {
|
||||
id += ":" + gi.SubDir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gi := &GitInfo{
|
||||
AuthHeaderSecret: GitAuthHeaderKey,
|
||||
AuthTokenSecret: GitAuthTokenKey,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o.SetGitOption(gi)
|
||||
}
|
||||
attrs := map[string]string{}
|
||||
if gi.KeepGitDir {
|
||||
attrs[pb.AttrKeepGitDir] = "true"
|
||||
@@ -328,6 +341,11 @@ func Git(url, ref string, opts ...GitOption) State {
|
||||
addCap(&gi.Constraints, pb.CapSourceGitChecksum)
|
||||
}
|
||||
|
||||
if gi.SkipSubmodules {
|
||||
attrs[pb.AttrGitSkipSubmodules] = "true"
|
||||
addCap(&gi.Constraints, pb.CapSourceGitSkipSubmodules)
|
||||
}
|
||||
|
||||
addCap(&gi.Constraints, pb.CapSourceGit)
|
||||
|
||||
source := NewSource("git://"+id, attrs, gi.Constraints)
|
||||
@@ -352,6 +370,27 @@ type GitInfo struct {
|
||||
KnownSSHHosts string
|
||||
MountSSHSock string
|
||||
Checksum string
|
||||
Ref string
|
||||
SubDir string
|
||||
SkipSubmodules bool
|
||||
}
|
||||
|
||||
func GitRef(v string) GitOption {
|
||||
return gitOptionFunc(func(gi *GitInfo) {
|
||||
gi.Ref = v
|
||||
})
|
||||
}
|
||||
|
||||
func GitSubDir(v string) GitOption {
|
||||
return gitOptionFunc(func(gi *GitInfo) {
|
||||
gi.SubDir = v
|
||||
})
|
||||
}
|
||||
|
||||
func GitSkipSubmodules() GitOption {
|
||||
return gitOptionFunc(func(gi *GitInfo) {
|
||||
gi.SkipSubmodules = true
|
||||
})
|
||||
}
|
||||
|
||||
func KeepGitDir() GitOption {
|
||||
|
||||
+125
-17
@@ -3,6 +3,7 @@ package dfgitutil
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
@@ -23,9 +24,12 @@ type GitRef struct {
|
||||
// e.g., "bar" for "https://github.com/foo/bar.git"
|
||||
ShortName string
|
||||
|
||||
// Commit is a commit hash, a tag, or branch name.
|
||||
// Commit is optional.
|
||||
Commit string
|
||||
// Ref is a commit hash, a tag, or branch name.
|
||||
// Ref is optional.
|
||||
Ref string
|
||||
|
||||
// Checksum is a commit hash.
|
||||
Checksum string
|
||||
|
||||
// SubDir is a directory path inside the repo.
|
||||
// SubDir is optional.
|
||||
@@ -46,12 +50,16 @@ type GitRef struct {
|
||||
// Discouraged, although not deprecated.
|
||||
// Instead, consider using an encrypted TCP connection such as "git@github.com/foo/bar.git" or "https://github.com/foo/bar.git".
|
||||
UnencryptedTCP bool
|
||||
|
||||
// KeepGitDir is true for URL that controls whether to keep the .git directory.
|
||||
KeepGitDir *bool
|
||||
|
||||
// Submodules is true for URL that controls whether to fetch git submodules.
|
||||
Submodules *bool
|
||||
}
|
||||
|
||||
// var gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
|
||||
|
||||
// ParseGitRef parses a git ref.
|
||||
func ParseGitRef(ref string) (*GitRef, error) {
|
||||
func ParseGitRef(ref string) (*GitRef, bool, error) {
|
||||
res := &GitRef{}
|
||||
|
||||
var (
|
||||
@@ -60,21 +68,25 @@ func ParseGitRef(ref string) (*GitRef, error) {
|
||||
)
|
||||
|
||||
if strings.HasPrefix(ref, "./") || strings.HasPrefix(ref, "../") {
|
||||
return nil, cerrdefs.ErrInvalidArgument
|
||||
return nil, false, errors.WithStack(cerrdefs.ErrInvalidArgument)
|
||||
} else if strings.HasPrefix(ref, "github.com/") {
|
||||
res.IndistinguishableFromLocal = true // Deprecated
|
||||
remote = gitutil.FromURL(&url.URL{
|
||||
Scheme: "https",
|
||||
Host: "github.com",
|
||||
Path: strings.TrimPrefix(ref, "github.com/"),
|
||||
})
|
||||
u, err := url.Parse(ref)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
u.Scheme = "https"
|
||||
remote, err = gitutil.FromURL(u)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
} else {
|
||||
remote, err = gitutil.ParseURL(ref)
|
||||
if errors.Is(err, gitutil.ErrUnknownProtocol) {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
switch remote.Scheme {
|
||||
@@ -86,7 +98,7 @@ func ParseGitRef(ref string) (*GitRef, error) {
|
||||
// An HTTP(S) URL is considered to be a valid git ref only when it has the ".git[...]" suffix.
|
||||
case gitutil.HTTPProtocol, gitutil.HTTPSProtocol:
|
||||
if !strings.HasSuffix(remote.Path, ".git") {
|
||||
return nil, cerrdefs.ErrInvalidArgument
|
||||
return nil, false, errors.WithStack(cerrdefs.ErrInvalidArgument)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,11 +108,107 @@ func ParseGitRef(ref string) (*GitRef, error) {
|
||||
_, res.Remote, _ = strings.Cut(res.Remote, "://")
|
||||
}
|
||||
if remote.Opts != nil {
|
||||
res.Commit, res.SubDir = remote.Opts.Ref, remote.Opts.Subdir
|
||||
res.Ref, res.SubDir = remote.Opts.Ref, remote.Opts.Subdir
|
||||
}
|
||||
|
||||
repoSplitBySlash := strings.Split(res.Remote, "/")
|
||||
res.ShortName = strings.TrimSuffix(repoSplitBySlash[len(repoSplitBySlash)-1], ".git")
|
||||
|
||||
return res, nil
|
||||
if err := res.loadQuery(remote.Query); err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
return res, true, nil
|
||||
}
|
||||
|
||||
func (gf *GitRef) loadQuery(query url.Values) error {
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
var tag, branch string
|
||||
for k, v := range query {
|
||||
switch len(v) {
|
||||
case 0, 1:
|
||||
if len(v) == 0 || v[0] == "" {
|
||||
switch k {
|
||||
case "submodules", "keep-git-dir":
|
||||
v = nil
|
||||
default:
|
||||
return errors.Errorf("query %q has no value", k)
|
||||
}
|
||||
}
|
||||
// NOP
|
||||
default:
|
||||
return errors.Errorf("query %q has multiple values", k)
|
||||
}
|
||||
switch k {
|
||||
case "ref":
|
||||
if gf.Ref != "" && gf.Ref != v[0] {
|
||||
return errors.Errorf("ref conflicts: %q vs %q", gf.Ref, v[0])
|
||||
}
|
||||
gf.Ref = v[0]
|
||||
case "tag":
|
||||
tag = v[0]
|
||||
case "branch":
|
||||
branch = v[0]
|
||||
case "subdir":
|
||||
if gf.SubDir != "" && gf.SubDir != v[0] {
|
||||
return errors.Errorf("subdir conflicts: %q vs %q", gf.SubDir, v[0])
|
||||
}
|
||||
gf.SubDir = v[0]
|
||||
case "checksum", "commit":
|
||||
gf.Checksum = v[0]
|
||||
case "keep-git-dir":
|
||||
var vv bool
|
||||
if len(v) == 0 {
|
||||
vv = true
|
||||
} else {
|
||||
var err error
|
||||
vv, err = strconv.ParseBool(v[0])
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid keep-git-dir value: %q", v[0])
|
||||
}
|
||||
}
|
||||
gf.KeepGitDir = &vv
|
||||
case "submodules":
|
||||
var vv bool
|
||||
if len(v) == 0 {
|
||||
vv = true
|
||||
} else {
|
||||
var err error
|
||||
vv, err = strconv.ParseBool(v[0])
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid submodules value: %q", v[0])
|
||||
}
|
||||
}
|
||||
gf.Submodules = &vv
|
||||
default:
|
||||
return errors.Errorf("unexpected query %q", k)
|
||||
}
|
||||
}
|
||||
if tag != "" {
|
||||
const tagPrefix = "refs/tags/"
|
||||
if !strings.HasPrefix(tag, tagPrefix) {
|
||||
tag = tagPrefix + tag
|
||||
}
|
||||
if gf.Ref != "" && gf.Ref != tag {
|
||||
return errors.Errorf("ref conflicts: %q vs %q", gf.Ref, tag)
|
||||
}
|
||||
gf.Ref = tag
|
||||
}
|
||||
if branch != "" {
|
||||
if tag != "" {
|
||||
// TODO: consider allowing this, when the tag actually exists on the branch
|
||||
return errors.New("branch conflicts with tag")
|
||||
}
|
||||
const branchPrefix = "refs/heads/"
|
||||
if !strings.HasPrefix(branch, branchPrefix) {
|
||||
branch = branchPrefix + branch
|
||||
}
|
||||
if gf.Ref != "" && gf.Ref != branch {
|
||||
return errors.Errorf("ref conflicts: %q vs %q", gf.Ref, branch)
|
||||
}
|
||||
gf.Ref = branch
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+27
-13
@@ -69,11 +69,14 @@ func (bc *Client) initContext(ctx context.Context) (*buildContext, error) {
|
||||
bctx.dockerfileLocalName = v
|
||||
}
|
||||
|
||||
keepGit := false
|
||||
var keepGit *bool
|
||||
if v, err := strconv.ParseBool(opts[keyContextKeepGitDirArg]); err == nil {
|
||||
keepGit = v
|
||||
keepGit = &v
|
||||
}
|
||||
if st, ok := DetectGitContext(opts[localNameContext], keepGit); ok {
|
||||
if st, ok, err := DetectGitContext(opts[localNameContext], keepGit); ok {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bctx.context = st
|
||||
bctx.dockerfile = st
|
||||
} else if st, filename, ok := DetectHTTPContext(opts[localNameContext]); ok {
|
||||
@@ -140,22 +143,33 @@ func (bc *Client) initContext(ctx context.Context) (*buildContext, error) {
|
||||
return bctx, nil
|
||||
}
|
||||
|
||||
func DetectGitContext(ref string, keepGit bool) (*llb.State, bool) {
|
||||
g, err := dfgitutil.ParseGitRef(ref)
|
||||
func DetectGitContext(ref string, keepGit *bool) (*llb.State, bool, error) {
|
||||
g, isGit, err := dfgitutil.ParseGitRef(ref)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
return nil, isGit, err
|
||||
}
|
||||
commit := g.Commit
|
||||
if g.SubDir != "" {
|
||||
commit += ":" + g.SubDir
|
||||
gitOpts := []llb.GitOption{
|
||||
llb.GitRef(g.Ref),
|
||||
WithInternalName("load git source " + ref),
|
||||
}
|
||||
gitOpts := []llb.GitOption{WithInternalName("load git source " + ref)}
|
||||
if keepGit {
|
||||
if g.KeepGitDir != nil && *g.KeepGitDir {
|
||||
gitOpts = append(gitOpts, llb.KeepGitDir())
|
||||
}
|
||||
if keepGit != nil && *keepGit {
|
||||
gitOpts = append(gitOpts, llb.KeepGitDir())
|
||||
}
|
||||
if g.SubDir != "" {
|
||||
gitOpts = append(gitOpts, llb.GitSubDir(g.SubDir))
|
||||
}
|
||||
if g.Checksum != "" {
|
||||
gitOpts = append(gitOpts, llb.GitChecksum(g.Checksum))
|
||||
}
|
||||
if g.Submodules != nil && !*g.Submodules {
|
||||
gitOpts = append(gitOpts, llb.GitSkipSubmodules())
|
||||
}
|
||||
|
||||
st := llb.Git(g.Remote, commit, gitOpts...)
|
||||
return &st, true
|
||||
st := llb.Git(g.Remote, "", gitOpts...)
|
||||
return &st, true, nil
|
||||
}
|
||||
|
||||
func DetectHTTPContext(ref string) (*llb.State, string, bool) {
|
||||
|
||||
+8
-2
@@ -138,17 +138,23 @@ func (nc *NamedContext) load(ctx context.Context, count int) (*llb.State, *docke
|
||||
}
|
||||
return &st, &img, nil
|
||||
case "git":
|
||||
st, ok := DetectGitContext(nc.input, true)
|
||||
st, ok, err := DetectGitContext(nc.input, nil)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("invalid git context %s", nc.input)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return st, nil, nil
|
||||
case "http", "https":
|
||||
st, ok := DetectGitContext(nc.input, true)
|
||||
st, ok, err := DetectGitContext(nc.input, nil)
|
||||
if !ok {
|
||||
httpst := llb.HTTP(nc.input, llb.WithCustomName("[context "+nc.nameWithPlatform+"] "+nc.input))
|
||||
st = &httpst
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return st, nil, nil
|
||||
case "oci-layout":
|
||||
refSpec := strings.TrimPrefix(vv[1], "//")
|
||||
|
||||
+1
@@ -7,6 +7,7 @@ const AttrAuthTokenSecret = "git.authtokensecret"
|
||||
const AttrKnownSSHHosts = "git.knownsshhosts"
|
||||
const AttrMountSSHSock = "git.mountsshsock"
|
||||
const AttrGitChecksum = "git.checksum"
|
||||
const AttrGitSkipSubmodules = "git.skipsubmodules"
|
||||
|
||||
const AttrLocalSessionID = "local.session"
|
||||
const AttrLocalUniqueID = "local.unique"
|
||||
|
||||
+15
-8
@@ -23,14 +23,15 @@ const (
|
||||
CapSourceLocalDiffer apicaps.CapID = "source.local.differ"
|
||||
CapSourceMetadataTransfer apicaps.CapID = "source.local.metadatatransfer"
|
||||
|
||||
CapSourceGit apicaps.CapID = "source.git"
|
||||
CapSourceGitKeepDir apicaps.CapID = "source.git.keepgitdir"
|
||||
CapSourceGitFullURL apicaps.CapID = "source.git.fullurl"
|
||||
CapSourceGitHTTPAuth apicaps.CapID = "source.git.httpauth"
|
||||
CapSourceGitKnownSSHHosts apicaps.CapID = "source.git.knownsshhosts"
|
||||
CapSourceGitMountSSHSock apicaps.CapID = "source.git.mountsshsock"
|
||||
CapSourceGitSubdir apicaps.CapID = "source.git.subdir"
|
||||
CapSourceGitChecksum apicaps.CapID = "source.git.checksum"
|
||||
CapSourceGit apicaps.CapID = "source.git"
|
||||
CapSourceGitKeepDir apicaps.CapID = "source.git.keepgitdir"
|
||||
CapSourceGitFullURL apicaps.CapID = "source.git.fullurl"
|
||||
CapSourceGitHTTPAuth apicaps.CapID = "source.git.httpauth"
|
||||
CapSourceGitKnownSSHHosts apicaps.CapID = "source.git.knownsshhosts"
|
||||
CapSourceGitMountSSHSock apicaps.CapID = "source.git.mountsshsock"
|
||||
CapSourceGitSubdir apicaps.CapID = "source.git.subdir"
|
||||
CapSourceGitChecksum apicaps.CapID = "source.git.checksum"
|
||||
CapSourceGitSkipSubmodules apicaps.CapID = "source.git.skipsubmodules"
|
||||
|
||||
CapSourceHTTP apicaps.CapID = "source.http"
|
||||
CapSourceHTTPAuth apicaps.CapID = "source.http.auth"
|
||||
@@ -229,6 +230,12 @@ func init() {
|
||||
Status: apicaps.CapStatusExperimental,
|
||||
})
|
||||
|
||||
Caps.Init(apicaps.Cap{
|
||||
ID: CapSourceGitSkipSubmodules,
|
||||
Enabled: true,
|
||||
Status: apicaps.CapStatusExperimental,
|
||||
})
|
||||
|
||||
Caps.Init(apicaps.Cap{
|
||||
ID: CapSourceHTTP,
|
||||
Enabled: true,
|
||||
|
||||
+21
-8
@@ -47,16 +47,17 @@ type GitURL struct {
|
||||
Path string
|
||||
// User is the username/password to access the host
|
||||
User *url.Userinfo
|
||||
// Query is the query parameters for the URL
|
||||
Query url.Values
|
||||
// Opts can contain additional metadata
|
||||
Opts *GitURLOpts
|
||||
|
||||
// Remote is a valid URL remote to pass into the Git CLI tooling (i.e.
|
||||
// without the fragment metadata)
|
||||
Remote string
|
||||
}
|
||||
|
||||
// GitURLOpts is the buildkit-specific metadata extracted from the fragment
|
||||
// of a remote URL.
|
||||
// or the query of a remote URL.
|
||||
type GitURLOpts struct {
|
||||
// Ref is the git reference
|
||||
Ref string
|
||||
@@ -86,11 +87,11 @@ func ParseURL(remote string) (*GitURL, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FromURL(url), nil
|
||||
return FromURL(url)
|
||||
}
|
||||
|
||||
if url, err := sshutil.ParseSCPStyleURL(remote); err == nil {
|
||||
return fromSCPStyleURL(url), nil
|
||||
return fromSCPStyleURL(url)
|
||||
}
|
||||
|
||||
return nil, ErrUnknownProtocol
|
||||
@@ -105,28 +106,40 @@ func IsGitTransport(remote string) bool {
|
||||
return sshutil.IsImplicitSSHTransport(remote)
|
||||
}
|
||||
|
||||
func FromURL(url *url.URL) *GitURL {
|
||||
func FromURL(url *url.URL) (*GitURL, error) {
|
||||
withoutOpts := *url
|
||||
withoutOpts.Fragment = ""
|
||||
withoutOpts.RawQuery = ""
|
||||
q := url.Query()
|
||||
if len(q) == 0 {
|
||||
q = nil
|
||||
}
|
||||
return &GitURL{
|
||||
Scheme: url.Scheme,
|
||||
User: url.User,
|
||||
Host: url.Host,
|
||||
Path: url.Path,
|
||||
Query: q,
|
||||
Opts: parseOpts(url.Fragment),
|
||||
Remote: withoutOpts.String(),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func fromSCPStyleURL(url *sshutil.SCPStyleURL) *GitURL {
|
||||
func fromSCPStyleURL(url *sshutil.SCPStyleURL) (*GitURL, error) {
|
||||
withoutOpts := *url
|
||||
withoutOpts.Fragment = ""
|
||||
withoutOpts.Query = nil
|
||||
q := url.Query
|
||||
if len(q) == 0 {
|
||||
q = nil
|
||||
}
|
||||
return &GitURL{
|
||||
Scheme: SSHProtocol,
|
||||
User: url.User,
|
||||
Host: url.Host,
|
||||
Path: url.Path,
|
||||
Query: q,
|
||||
Opts: parseOpts(url.Fragment),
|
||||
Remote: withoutOpts.String(),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
+26
-8
@@ -1,13 +1,14 @@
|
||||
package sshutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var gitSSHRegex = regexp.MustCompile("^([a-zA-Z0-9-_]+)@([a-zA-Z0-9-.]+):(.*?)(?:#(.*))?$")
|
||||
var gitSSHRegex = regexp.MustCompile(`^([a-zA-Z0-9-_]+)@([a-zA-Z0-9-.]+):(.*?)(?:\?(.*?))?(?:#(.*))?$`)
|
||||
|
||||
func IsImplicitSSHTransport(s string) bool {
|
||||
return gitSSHRegex.MatchString(s)
|
||||
@@ -18,6 +19,7 @@ type SCPStyleURL struct {
|
||||
Host string
|
||||
|
||||
Path string
|
||||
Query url.Values
|
||||
Fragment string
|
||||
}
|
||||
|
||||
@@ -26,18 +28,34 @@ func ParseSCPStyleURL(raw string) (*SCPStyleURL, error) {
|
||||
if matches == nil {
|
||||
return nil, errors.New("invalid scp-style url")
|
||||
}
|
||||
|
||||
rawQuery := matches[4]
|
||||
vals := url.Values{}
|
||||
if rawQuery != "" {
|
||||
var err error
|
||||
vals, err = url.ParseQuery(rawQuery)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid query in scp-style url")
|
||||
}
|
||||
}
|
||||
|
||||
return &SCPStyleURL{
|
||||
User: url.User(matches[1]),
|
||||
Host: matches[2],
|
||||
Path: matches[3],
|
||||
Fragment: matches[4],
|
||||
Query: vals,
|
||||
Fragment: matches[5],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (url *SCPStyleURL) String() string {
|
||||
base := fmt.Sprintf("%s@%s:%s", url.User.String(), url.Host, url.Path)
|
||||
if url.Fragment == "" {
|
||||
return base
|
||||
func (u *SCPStyleURL) String() string {
|
||||
s := fmt.Sprintf("%s@%s:%s", u.User.String(), u.Host, u.Path)
|
||||
|
||||
if len(u.Query) > 0 {
|
||||
s += "?" + u.Query.Encode()
|
||||
}
|
||||
return base + "#" + url.Fragment
|
||||
if u.Fragment != "" {
|
||||
s += "#" + u.Fragment
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user