Skip to content

Commit

Permalink
EC-651 - Add sub-directory checkout functionality.
Browse files Browse the repository at this point in the history
This commit adds the ability to specify a sub-directory within a git repo
that should be checked out. This process checks out the repository,
verifies that the sub-directory exists within the repo worktree, copies
the sub-directory to the provided destination and removes the
checked-out repository.

Signed-off-by: Rob Nester <rnester@redhat.com>
  • Loading branch information
robnester-rh committed May 31, 2024
1 parent ba2c370 commit 6b5aa05
Show file tree
Hide file tree
Showing 23 changed files with 784 additions and 92 deletions.
25 changes: 22 additions & 3 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (t URIType) String() string {
return [...]string{"GitURI", "HTTPURI", "FileURI", "Unknown"}[t]
}

// expandTilde expands a leading tilde in the file path to the user's home directory
// ExpandTilde expands a leading tilde in the file path to the user's home directory
func ExpandTilde(path string) string {
if strings.HasPrefix(path, "~/") {
homeDir, err := GetHomeDir()
Expand All @@ -51,8 +51,12 @@ func ClassifyURI(input string) (URIType, error) {
return HTTPURI, nil
}

if strings.HasPrefix(input, "github.com") || strings.HasPrefix(input, "gitlab.com") {
return GitURI, nil
}

// Regular expression for Git URIs
gitURIPattern := regexp.MustCompile(`^(git@[\w\.\-]+:[\w\.\-]+/[\w\.\-]+(\.git)?|https?://[\w\.\-]+/[\w\.\-]+/[\w\.\-]+(\.git)?|git://[\w\.\-]+/[\w\.\-]+/[\w\.\-]+(\.git)?|[\w\.\-]+/[\w\.\-]+/[\w\.\-]+//.*|file://.*\.git)$`)
gitURIPattern := regexp.MustCompile(`^(git@[\w\.\-]+:[\w\.\-]+/[\w\.\-]+(\.git)?|https?://[\w\.\-]+/[\w\.\-]+/[\w\.\-]+(\.git)?|git://[\w\.\-]+/[\w\.\-]+/[\w\.\-]+(\.git)?|[\w\.\-]+/[\w\.\-]+/[\w\.\-]+//.*|file://.*\.git|[\w\.\-]+/[\w\.\-]+(\.git)?)$`)
// Regular expression for HTTP URIs (with or without protocol)
httpURIPattern := regexp.MustCompile(`^((http://|https://)[\w\-]+(\.[\w\-]+)+.*)$`)
// Regular expression for file paths
Expand Down Expand Up @@ -91,8 +95,23 @@ func ClassifyURI(input string) (URIType, error) {

// Check if the input contains a dot but lacks a valid scheme
if strings.Contains(input, ".") {
return Unknown, fmt.Errorf("HTTP(S) URIs require a scheme (http:// or https://)")
return Unknown, fmt.Errorf("got %s. HTTP(S) URIs require a scheme (http:// or https://)", input)
}

return Unknown, nil
}

// ValidateFileDestination validates the d1estination path for saving files
func ValidateFileDestination(destination string) error {
// Expand the tilde in the file path if it exists
destination = ExpandTilde(destination)
// Check if the destination file exists.
_, err := os.Stat(destination)
if err == nil {
return fmt.Errorf("destination file already exists: %s", destination)
}
if os.IsNotExist(err) {
return nil
}
return nil
}
62 changes: 62 additions & 0 deletions common_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package gogather

import (
"errors"
"fmt"
"os"
"path/filepath"
"testing"
)

Expand Down Expand Up @@ -76,6 +79,8 @@ func TestClassifyURI(t *testing.T) {
{input: "/home/user/file.git", expected: GitURI},
{input: "https://example.com", expected: HTTPURI},
{input: "ftpexamplecom", expected: Unknown},
{input: "github.com/user/repo.git", expected: GitURI},
{input: "gitlab.com/user/repo.git", expected: GitURI},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -106,3 +111,60 @@ func TestClassifyURI_errors(t *testing.T) {
}
}
}

// TestValidateFileDestination tests the ValidateFileDestination function.
func TestValidateFileDestination(t *testing.T) {
testCases := []struct {
destination string
}{
{destination: "/path/to/file.txt"},
{destination: "/path/to/directory/"},
}

for _, tc := range testCases {
err := ValidateFileDestination(tc.destination)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
}

// TestValidateFileDestination_errors tests the ValidateFileDestination function with invalid destinations.
func TestValidateFileDestination_errors(t *testing.T) {
dir, _ := os.MkdirTemp("", "path")
err := os.WriteFile(filepath.Join(dir, "file.text"), []byte("test"), 0600)
if err != nil {
t.Fatalf("Failed to create file: %v", err)
}
defer os.RemoveAll(dir)

testCases := []struct {
destination string
errExpected bool
expectedErr error
}{
{
destination: filepath.Join(dir, "file.text"),
errExpected: true,
expectedErr: errors.New("destination is a file"),
},
{
destination: filepath.Join(dir, "file2.text"),
errExpected: false,
expectedErr: nil,
},
}

for _, tc := range testCases {
err := ValidateFileDestination(tc.destination)
if tc.errExpected && err == nil {
t.Errorf("Expected an error: %s,\n but got nil", tc.expectedErr)
}
if !tc.errExpected && err != nil {
t.Errorf("Expected no error, but got: %s", err)
}
}
t.Cleanup(func() {
os.RemoveAll(dir)
})
}
3 changes: 2 additions & 1 deletion gather/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type FileGatherer struct{}
func (f *FileGatherer) Gather(ctx context.Context, source, destination string) (metadata.Metadata, error) {
// Parse the source URI
src, err := url.Parse(source)

if err != nil {
return nil, fmt.Errorf("failed to parse source URI: %w", err)
}
Expand Down Expand Up @@ -126,7 +127,7 @@ func (f *FileGatherer) copyFile(ctx context.Context, source, destination string)
func (f *FileGatherer) copyDirectory(ctx context.Context, source, destination string) (metadata.Metadata, error) {
src, err := url.Parse(source)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse source URI: %w", err)
}
dst, err := url.Parse(destination)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion gather/file/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func TestFileGatherer_copyDirectory_Source_URIParseError(t *testing.T) {
if err == nil {
t.Error("expected an error, but got nil")
}
if err.Error() != "parse \":\": missing protocol scheme" {
if err.Error() != "failed to parse source URI: parse \":\": missing protocol scheme" {
t.Logf("Expected: %s, Got: %s", "parse :: missing protocol scheme", err.Error())
t.Fail()
}
Expand Down
8 changes: 4 additions & 4 deletions gather/file/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ module github.com/enterprise-contract/go-gather/gather/file
go 1.21.9

require (
github.com/enterprise-contract/go-gather/metadata v0.0.0-20240521080222-0648e998132b
github.com/enterprise-contract/go-gather/metadata/file v0.0.0-20240521080222-0648e998132b
github.com/enterprise-contract/go-gather/saver v0.0.0-20240521080222-0648e998132b
github.com/enterprise-contract/go-gather/metadata v0.0.0-20240523073727-ba2c37023242
github.com/enterprise-contract/go-gather/metadata/file v0.0.0-20240523073727-ba2c37023242
github.com/enterprise-contract/go-gather/saver v0.0.0-20240523073727-ba2c37023242
)

require github.com/enterprise-contract/go-gather/saver/file v0.0.0-20240521080222-0648e998132b // indirect
require github.com/enterprise-contract/go-gather/saver/file v0.0.0-20240523073727-ba2c37023242 // indirect
16 changes: 8 additions & 8 deletions gather/file/go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
github.com/enterprise-contract/go-gather/metadata v0.0.0-20240521080222-0648e998132b h1:TeQa9c1daZ/EtMslTDBtsk4IO1hP0rPA3E7PpSk/GGI=
github.com/enterprise-contract/go-gather/metadata v0.0.0-20240521080222-0648e998132b/go.mod h1:m2HxByQBWZyc99HDs/Lqy7QzU9+XQ2tU0X/mzkCPgPw=
github.com/enterprise-contract/go-gather/metadata/file v0.0.0-20240521080222-0648e998132b h1:/7DlESAdE4CW1cHOWpnZbAhBEzX1P/U8O5ZnL9Ftm0Y=
github.com/enterprise-contract/go-gather/metadata/file v0.0.0-20240521080222-0648e998132b/go.mod h1:4PckwLejZstUEBp2QUAdQYQ0O+h5tijrs48j+7OY4OY=
github.com/enterprise-contract/go-gather/saver v0.0.0-20240521080222-0648e998132b h1:kUSMsZ5q+Bj4OdOphfWwxdcfoMHMKO9t1AUZD9l9WtA=
github.com/enterprise-contract/go-gather/saver v0.0.0-20240521080222-0648e998132b/go.mod h1:iZsEDpzcJSXdm0xaQqucU0xu/h1hZXsY60z5A0R/4VY=
github.com/enterprise-contract/go-gather/saver/file v0.0.0-20240521080222-0648e998132b h1:2nWimrcrKVSpdHl9bjTzLKhvVbH7pYr/XFKUkqpdsSE=
github.com/enterprise-contract/go-gather/saver/file v0.0.0-20240521080222-0648e998132b/go.mod h1:qnNStNDYPJGjJunKANv6jq93ynndcfxmUoeYeBEnZEY=
github.com/enterprise-contract/go-gather/metadata v0.0.0-20240523073727-ba2c37023242 h1:bRMpqsF+NbPf6R514yzo9fVL+8QqOkFoMpMdYjoPynw=
github.com/enterprise-contract/go-gather/metadata v0.0.0-20240523073727-ba2c37023242/go.mod h1:m2HxByQBWZyc99HDs/Lqy7QzU9+XQ2tU0X/mzkCPgPw=
github.com/enterprise-contract/go-gather/metadata/file v0.0.0-20240523073727-ba2c37023242 h1:zA8jD+54i4Yybivs7eI74I2hbrzZzW/ifGoBR+Q4T7U=
github.com/enterprise-contract/go-gather/metadata/file v0.0.0-20240523073727-ba2c37023242/go.mod h1:4PckwLejZstUEBp2QUAdQYQ0O+h5tijrs48j+7OY4OY=
github.com/enterprise-contract/go-gather/saver v0.0.0-20240523073727-ba2c37023242 h1:rqsKA4myaeXVRiqfHaL5bL7FLjNhuRWmSGHQZz0djEk=
github.com/enterprise-contract/go-gather/saver v0.0.0-20240523073727-ba2c37023242/go.mod h1:uOt8X/CztOGi0YC5jERopBQpjXqkU6UPUqPellgBBG8=
github.com/enterprise-contract/go-gather/saver/file v0.0.0-20240523073727-ba2c37023242 h1:BaaoL22+5U4G51WlxjNR5Vxzla3irRZ9zvglf3JYgrA=
github.com/enterprise-contract/go-gather/saver/file v0.0.0-20240523073727-ba2c37023242/go.mod h1:qnNStNDYPJGjJunKANv6jq93ynndcfxmUoeYeBEnZEY=
6 changes: 3 additions & 3 deletions gather/gather.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ type Gatherer interface {
// protocolHandlers maps URL schemes to their corresponding Gatherer implementations.
var protocolHandlers = map[string]Gatherer{
"FileURI": &file.FileGatherer{},
"GitURI": &git.GitGatherer{},
"HTTPURI": &http.HTTPGatherer{},
"GitURI": &git.GitGatherer{},
"HTTPURI": &http.HTTPGatherer{},
}

// Gather determines the protocol from the source URI and uses the appropriate Gatherer to perform the operation.
// It returns the gathered metadata and an error, if any.
func Gather(ctx context.Context, source, destination string) (metadata.Metadata, error) {
srcProtocol, err := gogather.ClassifyURI(source)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to classify source URI: %w", err)
}

if gatherer, ok := protocolHandlers[srcProtocol.String()]; ok {
Expand Down
11 changes: 6 additions & 5 deletions gather/gather_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"path/filepath"
"testing"

"github.com/enterprise-contract/go-gather"
gogather "github.com/enterprise-contract/go-gather"
"github.com/enterprise-contract/go-gather/metadata"
"github.com/enterprise-contract/go-gather/metadata/git"
)
Expand All @@ -48,30 +48,31 @@ func TestGather(t *testing.T) {
t.Run("UnsupportedProtocol", func(t *testing.T) {
source := "ftp://example.com/file.txt"
destination := "/path/to/destination"
defer os.RemoveAll(destination)

_, err := Gather(ctx, source, destination)
if err == nil {
t.Error("expected an error, but got nil")
}

expectedErrorMessage := "unsupported source protocol: ftp"
expectedErrorMessage := "failed to classify source URI: unsupported source protocol: ftp"
if err.Error() != expectedErrorMessage {
t.Errorf("expected error message: %s, but got: %s", expectedErrorMessage, err.Error())
}
t.Cleanup(func() {
os.RemoveAll(destination)
})
})

t.Run("SupportedProtocol_git", func(t *testing.T) {
source := "git::https://github.com/git-fixtures/basic.git"
destination := "/tmp/path/to/destination"
defer os.RemoveAll(destination)

// Add your test logic here
// BEGIN: SupportedProtocolTest
_, err := Gather(ctx, source, destination)
if err != nil {
t.Errorf("expected no error, but got: %s", err.Error())
}
// END: SupportedProtocolTest
t.Cleanup(func() {
os.RemoveAll(destination)
})
Expand Down
Loading

0 comments on commit 6b5aa05

Please sign in to comment.