-
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Normalizes and validates git URLs before accepting for processi…
…ng (#38) Signed-off-by: John McBride <john@opensauced.pizza>
- Loading branch information
Showing
3 changed files
with
168 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package common | ||
|
||
import ( | ||
"fmt" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/go-git/go-git/v5" | ||
"github.com/go-git/go-git/v5/config" | ||
"github.com/go-git/go-git/v5/storage/memory" | ||
) | ||
|
||
// IsValidGitRepo returns true if the provided git repo URL is a valid and reachable | ||
// git repository. This is equivalent to running "git ls-remote" on the provided | ||
// URL string. This may result in some unexpected "authentication required" or | ||
// "repository not found" errors which is standard for git to return in these | ||
// situations. | ||
func IsValidGitRepo(repoURL string) (bool, error) { | ||
remoteConfig := &config.RemoteConfig{ | ||
Name: "source", | ||
URLs: []string{ | ||
repoURL, | ||
}, | ||
} | ||
|
||
remote := git.NewRemote(memory.NewStorage(), remoteConfig) | ||
|
||
_, err := remote.List(&git.ListOptions{}) | ||
if err != nil { | ||
return false, fmt.Errorf("could not list remote repository: %s", err.Error()) | ||
} | ||
|
||
return true, nil | ||
} | ||
|
||
// NormalizeGitURL attempts to take a raw git repo URL and ensure it is normalized | ||
// before being validated or entered into the database | ||
func NormalizeGitURL(repoURL string) (string, error) { | ||
parsedURL, err := url.Parse(repoURL) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
// Check if it has a valid protocol specified (e.g., https, ssh, git) | ||
if parsedURL.Scheme != "git" && parsedURL.Scheme != "https" && parsedURL.Scheme != "file" { | ||
return "", fmt.Errorf("repo URL missing valid protocol scheme (https, git, file): %s", repoURL) | ||
} | ||
|
||
// Trim trailing slashes | ||
// Example: https://github.com/open-sauced/pizza/ to https://github.com/open-sauced/pizza | ||
trimmedPath := strings.TrimSuffix(parsedURL.Path, "/") | ||
|
||
// Remove .git suffix if present | ||
// Example: https://github.com/open-sauced/pizza.git to https://github.com/open-sauced/pizza | ||
trimmedPath = strings.TrimSuffix(trimmedPath, ".git") | ||
|
||
parsedURL.Path = trimmedPath | ||
|
||
return parsedURL.String(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package common | ||
|
||
import "testing" | ||
|
||
func TestNormalizeGitURL(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := []struct { | ||
name string | ||
url string | ||
expected string | ||
}{ | ||
{ | ||
name: "Fully normalizes", | ||
url: "https://github.com/user/repo.git/", | ||
expected: "https://github.com/user/repo", | ||
}, | ||
{ | ||
name: "Removes trailing .git", | ||
url: "https://github.com/user/repo.git", | ||
expected: "https://github.com/user/repo", | ||
}, | ||
{ | ||
name: "Removes trailing slash", | ||
url: "https://github.com/user/repo/", | ||
expected: "https://github.com/user/repo", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
normalizedURL, err := NormalizeGitURL(tt.url) | ||
if err != nil { | ||
t.Fatalf("unexpected error: %s", err.Error()) | ||
} | ||
|
||
if normalizedURL != tt.expected { | ||
t.Fatalf("normalized URL: %s is not expected: %s", normalizedURL, tt.expected) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestNormalizeGitURLError(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := []struct { | ||
name string | ||
url string | ||
}{ | ||
{ | ||
name: "Missing protocol fails", | ||
url: "github.com/user/repo", | ||
}, | ||
{ | ||
name: "Malformed protocol fails", | ||
url: "ht:/github.com/user/repo", | ||
}, | ||
{ | ||
name: "Unusable protocol fails", | ||
url: "ssh://github.com/user/repo", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
normalizedURL, err := NormalizeGitURL(tt.url) | ||
if err == nil { | ||
t.Fatalf("expected error, got none: %s", normalizedURL) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters