Skip to content

Commit f711af5

Browse files
authored
fix: resolve SCP-like submodule URLs via transport.NewEndpoint (#514)
1 parent d6ec3d5 commit f711af5

2 files changed

Lines changed: 35 additions & 35 deletions

File tree

git/git.go

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -509,32 +509,23 @@ func RedactURL(u string) string {
509509
}
510510

511511
// ResolveSubmoduleURL resolves a potentially relative submodule URL against a parent repository URL.
512-
//
513-
// Limitation: SCP-like URLs (e.g., git@github.com:org/repo.git) are not supported as parent URLs
514-
// when the submodule uses a relative path. This is a known limitation.
515-
// See: https://github.com/coder/envbuilder/issues/492
516512
func ResolveSubmoduleURL(parentURL, submoduleURL string) (string, error) {
517513
// If the submodule URL is absolute (contains ://) or doesn't start with ./ or ../, return it as-is
518514
if strings.Contains(submoduleURL, "://") || (!strings.HasPrefix(submoduleURL, "../") && !strings.HasPrefix(submoduleURL, "./")) {
519515
return submoduleURL, nil
520516
}
521517

522-
// Check if parent URL is SCP-like (e.g., git@github.com:org/repo.git)
523-
// These cannot be properly parsed by net/url and relative submodule resolution is not supported.
524-
if scpLikeURLRegex.MatchString(parentURL) {
525-
return "", fmt.Errorf("relative submodule URL %q cannot be resolved: parent URL %q uses SCP-like syntax which is not supported for relative submodule resolution (see https://github.com/coder/envbuilder/issues/492)", submoduleURL, RedactURL(parentURL))
526-
}
527-
528-
// Parse the parent URL
529-
parentParsed, err := url.Parse(parentURL)
518+
// Parse the parent URL using go-git's endpoint parser, which handles
519+
// SCP-like URLs (git@host:path) in addition to standard URLs.
520+
parentEP, err := transport.NewEndpoint(parentURL)
530521
if err != nil {
531522
return "", fmt.Errorf("parse parent URL: %w", err)
532523
}
533524

534-
// For relative URLs, we need to resolve them against the parent's path
535-
// The parent path represents a repository (like a file in filesystem terms)
536-
// So ../something means "sibling repository"
537-
parentPath := strings.TrimSuffix(parentParsed.Path, "/")
525+
// For relative URLs, we need to resolve them against the parent's path.
526+
// The parent path represents a repository (like a file in filesystem terms),
527+
// so ../something means "sibling repository".
528+
parentPath := strings.TrimSuffix(parentEP.Path, "/")
538529

539530
// Split the submodule URL into components
540531
// and manually walk up the directory tree for each ../
@@ -554,18 +545,9 @@ func ResolveSubmoduleURL(parentURL, submoduleURL string) (string, error) {
554545
}
555546
}
556547

557-
// Clean the final path
558-
resolvedPath := path.Clean(currentPath)
559-
560-
// Construct the absolute URL
561-
resolvedParsed := &url.URL{
562-
Scheme: parentParsed.Scheme,
563-
User: parentParsed.User,
564-
Host: parentParsed.Host,
565-
Path: resolvedPath,
566-
}
567-
568-
return resolvedParsed.String(), nil
548+
// Reconstruct the URL with the resolved path.
549+
parentEP.Path = path.Clean(currentPath)
550+
return parentEP.String(), nil
569551
}
570552

571553
// initSubmodules recursively initializes and updates all submodules in the repository.

git/git_test.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -807,16 +807,34 @@ func TestResolveSubmoduleURL(t *testing.T) {
807807
expect: "https://example.com/org/main.git/extras/tool.git",
808808
},
809809
{
810-
name: "badParent",
811-
parentURL: "://bad",
812-
subURL: "./child",
813-
expectErr: "parse parent URL",
810+
name: "scpRelativeSibling",
811+
parentURL: "git@github.com:org/main.git",
812+
subURL: "../deps/lib.git",
813+
expect: "ssh://git@github.com/org/deps/lib.git",
814814
},
815815
{
816-
name: "scpParentWithRelativeSubmodule",
816+
name: "scpRelativeChild",
817817
parentURL: "git@github.com:org/main.git",
818-
subURL: "../other/submodule.git",
819-
expectErr: "SCP-like syntax which is not supported",
818+
subURL: "./extras/tool.git",
819+
expect: "ssh://git@github.com/org/main.git/extras/tool.git",
820+
},
821+
{
822+
name: "scpMultiLevelUp",
823+
parentURL: "git@github.com:a/b/c/repo.git",
824+
subURL: "../../other/lib.git",
825+
expect: "ssh://git@github.com/a/b/other/lib.git",
826+
},
827+
{
828+
name: "scpWithPort",
829+
parentURL: "git@github.com:2222:org/main.git",
830+
subURL: "../deps/lib.git",
831+
expect: "ssh://git@github.com:2222/org/deps/lib.git",
832+
},
833+
{
834+
name: "httpsMultiLevelUp",
835+
parentURL: "https://example.com/a/b/c/repo.git",
836+
subURL: "../../other/lib.git",
837+
expect: "https://example.com/a/b/other/lib.git",
820838
},
821839
{
822840
name: "scpParentWithAbsoluteSubmodule",

0 commit comments

Comments
 (0)