diff --git a/cmd/bazel-git/fetch.go b/cmd/bazel-git/fetch.go index bf62efb9960a9aca9a0d12e152dd2bd95fc88d23..8f37a6d6357f729164d70d2ff8ed3000207b9661 100644 --- a/cmd/bazel-git/fetch.go +++ b/cmd/bazel-git/fetch.go @@ -2,11 +2,13 @@ package main import ( "log/slog" + + "gitlab.arm.com/bazel/git/v1/reference" ) type FetchCommand struct { - RecurseSubmodules bool `long:"recurse-submodules" description:"Recursively fetch submodule objects."` - References []string `long:"reference" value-name:"" description:"References to use to retrieve objects from when a single SHA cannot be retrieved."` + RecurseSubmodules bool `long:"recurse-submodules" description:"Recursively fetch submodule objects."` + References []reference.Name `long:"reference" value-name:"" description:"References to use to retrieve objects from when a single SHA cannot be retrieved."` Args struct { Remote string `positional-arg-name:"repository" description:"The remote to retrieve objects from."` Committish []string `positional-arg-name:"refspec" description:"The committish to retrieve from the server. Usually a commit SHA but can be a resolvable reference."` diff --git a/go.mod b/go.mod index 18111c2b355236d82e870bbc0b926f11617c43fd..90472f38388770ac2f669966be420e74b556073c 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,10 @@ module gitlab.arm.com/bazel/git/v1 go 1.21.3 require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/jessevdk/go-flags v1.5.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index df313636297131251bab78ecc99cb0c0af3add19..573e5ec400a83b4592539f2c4ef58096a2c47807 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,13 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/object/id.go b/object/id.go new file mode 100644 index 0000000000000000000000000000000000000000..b516c1023e3b47c04875f3742b8c250949a19197 --- /dev/null +++ b/object/id.go @@ -0,0 +1,55 @@ +package object + +import ( + "errors" +) + +const ( + sha1 = 40 + sha256 = 64 +) + +type Id string + +func isHex(value string) bool { + for _, char := range value { + if !(('a' <= char && char <= 'f') || ('0' <= char && char <= '9')) { + return false + } + } + return true +} + +const ( + Zero = Id("0000000000000000000000000000000000000000") +) + +func NewId(hex string) (id Id, err error) { + length := len(hex) + if !(length == sha1 || length == sha256) || !isHex(hex) { + return Id(""), errors.New("object ID must be SHA{1,256}: " + hex) + } + + return Id(hex), nil +} + +func (i Id) String() string { + return string(i) +} + +func (i Id) IsSha1() bool { + return len(i) == sha1 +} + +func (i Id) IsSha256() bool { + return len(i) == sha256 +} + +func (i *Id) UnmarshalFlag(value string) error { + id, err := NewId(value) + if err != nil { + return err + } + *i = id + return nil +} diff --git a/object/id_test.go b/object/id_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8a275613a1d9a826d9e6ae52bec13c1acffcb9a8 --- /dev/null +++ b/object/id_test.go @@ -0,0 +1,67 @@ +package object + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewIdNotAllowed(t *testing.T) { + assert := assert.New(t) + _, err := NewId("not allowed") + assert.NotNil(err) +} + +func TestNewIdShortHex(t *testing.T) { + assert := assert.New(t) + _, err := NewId("abcd1234") + assert.NotNil(err) +} + +func TestNewIdSha1NonHex(t *testing.T) { + assert := assert.New(t) + _, err := NewId("b2d203c3ef75fe97cb3898debbad36c58f94361!") + assert.NotNil(err) +} + +func ExampleSha1String() { + id, err := NewId("b2d203c3ef75fe97cb3898debbad36c58f94361a") + if err == nil { + fmt.Println(id) + fmt.Println(id.IsSha1()) + fmt.Println(id.IsSha256()) + } + // Output: + // b2d203c3ef75fe97cb3898debbad36c58f94361a + // true + // false +} + +func TestNewIdSha256NonHex(t *testing.T) { + assert := assert.New(t) + _, err := NewId("e959c1422a45922496e7123dd7f3ea79dce1ee67c5d014bbb6ec73a0cd9110d!") + assert.NotNil(err) +} + +func ExampleSha256String() { + id, err := NewId("e959c1422a45922496e7123dd7f3ea79dce1ee67c5d014bbb6ec73a0cd9110db") + if err == nil { + fmt.Println(id) + fmt.Println(id.IsSha1()) + fmt.Println(id.IsSha256()) + } + // Output: + // e959c1422a45922496e7123dd7f3ea79dce1ee67c5d014bbb6ec73a0cd9110db + // false + // true +} + +func TestUnmarshalFlag(t *testing.T) { + assert := assert.New(t) + id := Zero + var err error + err = id.UnmarshalFlag("b2d203c3ef75fe97cb3898debbad36c58f94361a") + assert.Nil(err) + err = id.UnmarshalFlag("b2d203c3ef75fe97cb3898debbad36c58f94361!") + assert.NotNil(err) +} diff --git a/reference/name.go b/reference/name.go new file mode 100644 index 0000000000000000000000000000000000000000..aebdee954fd34ffeec950e4404d963f00ab018db --- /dev/null +++ b/reference/name.go @@ -0,0 +1,69 @@ +package reference + +import ( + "errors" + "strings" +) + +type Name string + +func isSymbolic(value string) bool { + if len(value) == 0 { + return false + } + + if value[0] == '_' { + return false + } + + for _, char := range value { + if !('_' == char || ('A' <= char && char <= 'Z')) { + return false + } + } + + return true +} + +func NewName(value string) (name Name, err error) { + if strings.HasPrefix(value, "refs/heads/") { + return Name(value), nil + } else if strings.HasPrefix(value, "refs/tags/") { + return Name(value), nil + } else if strings.HasPrefix(value, "remotes/") { + return Name(value), nil + } else if isSymbolic(value) { + return Name(value), nil + } else { + return Name(""), errors.New("unsupported reference name: " + value) + } +} + +func (n Name) String() string { + return string(n) +} + +func (n Name) IsHead() bool { + return strings.HasPrefix(string(n), "refs/heads/") +} + +func (n Name) IsTag() bool { + return strings.HasPrefix(string(n), "refs/tags/") +} + +func (n Name) IsRemote() bool { + return strings.HasPrefix(string(n), "remotes/") +} + +func (n Name) IsSymbolic() bool { + return isSymbolic(string(n)) +} + +func (n *Name) UnmarshalFlag(value string) error { + name, err := NewName(value) + if err != nil { + return err + } + *n = name + return nil +} diff --git a/reference/name_test.go b/reference/name_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f396ed7578c68ae89d45682e248aa0efe48bda8e --- /dev/null +++ b/reference/name_test.go @@ -0,0 +1,103 @@ +package reference + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNameInvalid(t *testing.T) { + assert := assert.New(t) + _, err := NewName("not allowed") + assert.NotNil(err) +} + +func TestNameEmpty(t *testing.T) { + assert := assert.New(t) + _, err := NewName("") + assert.NotNil(err) +} + +func TestNameUnderscore(t *testing.T) { + assert := assert.New(t) + _, err := NewName("_") + assert.NotNil(err) +} + +func TestUnmarshalFlag(t *testing.T) { + assert := assert.New(t) + name := Name("") + var err error + err = name.UnmarshalFlag("refs/heads/something") + assert.Nil(err) + err = name.UnmarshalFlag("not allowed") + assert.NotNil(err) +} + +func ExampleHead() { + ref, err := NewName("refs/heads/something") + if err == nil { + fmt.Println(ref) + fmt.Println(ref.IsHead()) + fmt.Println(ref.IsTag()) + fmt.Println(ref.IsRemote()) + fmt.Println(ref.IsSymbolic()) + } + // Output: + // refs/heads/something + // true + // false + // false + // false +} + +func ExampleTag() { + ref, err := NewName("refs/tags/something") + if err == nil { + fmt.Println(ref) + fmt.Println(ref.IsHead()) + fmt.Println(ref.IsTag()) + fmt.Println(ref.IsRemote()) + fmt.Println(ref.IsSymbolic()) + } + // Output: + // refs/tags/something + // false + // true + // false + // false +} + +func ExampleRemote() { + ref, err := NewName("remotes/origin/something") + if err == nil { + fmt.Println(ref) + fmt.Println(ref.IsHead()) + fmt.Println(ref.IsTag()) + fmt.Println(ref.IsRemote()) + fmt.Println(ref.IsSymbolic()) + } + // Output: + // remotes/origin/something + // false + // false + // true + // false +} + +func ExampleSymbolic() { + ref, err := NewName("HEAD") + if err == nil { + fmt.Println(ref) + fmt.Println(ref.IsHead()) + fmt.Println(ref.IsTag()) + fmt.Println(ref.IsRemote()) + fmt.Println(ref.IsSymbolic()) + } + // Output: + // HEAD + // false + // false + // false + // true +}