| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| // Copyright 2023 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package git | ||
|
|
||
| import ( | ||
| "crypto/sha1" | ||
| "fmt" | ||
| "regexp" | ||
| "strings" | ||
| ) | ||
|
|
||
| type ObjectFormatID int | ||
|
|
||
| const ( | ||
| Sha1 ObjectFormatID = iota | ||
| ) | ||
|
|
||
| // sha1Pattern can be used to determine if a string is an valid sha | ||
| var sha1Pattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`) | ||
|
|
||
| type ObjectFormat interface { | ||
| ID() ObjectFormatID | ||
| String() string | ||
|
|
||
| // Empty is the hash of empty git | ||
| Empty() ObjectID | ||
| // EmptyTree is the hash of an empty tree | ||
| EmptyTree() ObjectID | ||
| // FullLength is the length of the hash's hex string | ||
| FullLength() int | ||
|
|
||
| IsValid(input string) bool | ||
| MustID(b []byte) ObjectID | ||
| MustIDFromString(s string) ObjectID | ||
| NewID(b []byte) (ObjectID, error) | ||
| NewIDFromString(s string) (ObjectID, error) | ||
| NewEmptyID() ObjectID | ||
|
|
||
| NewHasher() HasherInterface | ||
| } | ||
|
|
||
| /* SHA1 Type */ | ||
| type Sha1ObjectFormat struct{} | ||
|
|
||
| func (*Sha1ObjectFormat) ID() ObjectFormatID { return Sha1 } | ||
| func (*Sha1ObjectFormat) String() string { return "sha1" } | ||
| func (*Sha1ObjectFormat) Empty() ObjectID { return &Sha1Hash{} } | ||
| func (*Sha1ObjectFormat) EmptyTree() ObjectID { | ||
| return &Sha1Hash{ | ||
| 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, | ||
| 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04, | ||
| } | ||
| } | ||
| func (*Sha1ObjectFormat) FullLength() int { return 40 } | ||
| func (*Sha1ObjectFormat) IsValid(input string) bool { | ||
| return sha1Pattern.MatchString(input) | ||
| } | ||
|
|
||
| func (*Sha1ObjectFormat) MustID(b []byte) ObjectID { | ||
| var id Sha1Hash | ||
| copy(id[0:20], b) | ||
| return &id | ||
| } | ||
|
|
||
| func (h *Sha1ObjectFormat) MustIDFromString(s string) ObjectID { | ||
| return MustIDFromString(h, s) | ||
| } | ||
|
|
||
| func (h *Sha1ObjectFormat) NewID(b []byte) (ObjectID, error) { | ||
| return IDFromRaw(h, b) | ||
| } | ||
|
|
||
| func (h *Sha1ObjectFormat) NewIDFromString(s string) (ObjectID, error) { | ||
| return genericIDFromString(h, s) | ||
| } | ||
|
|
||
| func (*Sha1ObjectFormat) NewEmptyID() ObjectID { | ||
| return NewSha1() | ||
| } | ||
|
|
||
| func (h *Sha1ObjectFormat) NewHasher() HasherInterface { | ||
| return &Sha1Hasher{sha1.New()} | ||
| } | ||
|
|
||
| // utils | ||
| func ObjectFormatFromID(id ObjectFormatID) ObjectFormat { | ||
| switch id { | ||
| case Sha1: | ||
| return &Sha1ObjectFormat{} | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func ObjectFormatFromString(hash string) (ObjectFormat, error) { | ||
| switch strings.ToLower(hash) { | ||
| case "sha1": | ||
| return &Sha1ObjectFormat{}, nil | ||
| } | ||
|
|
||
| return nil, fmt.Errorf("unknown hash type: %s", hash) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| // Copyright 2023 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package git | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/hex" | ||
| "errors" | ||
| "fmt" | ||
| "hash" | ||
| "strconv" | ||
| "strings" | ||
| ) | ||
|
|
||
| type ObjectID interface { | ||
| String() string | ||
| IsZero() bool | ||
| RawValue() []byte | ||
| Type() ObjectFormat | ||
| } | ||
|
|
||
| /* SHA1 */ | ||
| type Sha1Hash [20]byte | ||
|
|
||
| func (h *Sha1Hash) String() string { | ||
| return hex.EncodeToString(h[:]) | ||
| } | ||
|
|
||
| func (h *Sha1Hash) IsZero() bool { | ||
| empty := Sha1Hash{} | ||
| return bytes.Equal(empty[:], h[:]) | ||
| } | ||
| func (h *Sha1Hash) RawValue() []byte { return h[:] } | ||
| func (*Sha1Hash) Type() ObjectFormat { return &Sha1ObjectFormat{} } | ||
|
|
||
| func NewSha1() *Sha1Hash { | ||
| return &Sha1Hash{} | ||
| } | ||
|
|
||
| // generic implementations | ||
| func NewHash(hash string) (ObjectID, error) { | ||
| hash = strings.ToLower(hash) | ||
| switch hash { | ||
| case "sha1": | ||
| return &Sha1Hash{}, nil | ||
| } | ||
|
|
||
| return nil, errors.New("unsupported hash type") | ||
| } | ||
|
|
||
| func IDFromRaw(h ObjectFormat, b []byte) (ObjectID, error) { | ||
| if len(b) != h.FullLength()/2 { | ||
| return h.Empty(), fmt.Errorf("length must be %d: %v", h.FullLength(), b) | ||
| } | ||
| return h.MustID(b), nil | ||
| } | ||
|
|
||
| func MustIDFromString(h ObjectFormat, s string) ObjectID { | ||
| b, _ := hex.DecodeString(s) | ||
| return h.MustID(b) | ||
| } | ||
|
|
||
| func genericIDFromString(h ObjectFormat, s string) (ObjectID, error) { | ||
| s = strings.TrimSpace(s) | ||
| if len(s) != h.FullLength() { | ||
| return h.Empty(), fmt.Errorf("length must be %d: %s", h.FullLength(), s) | ||
| } | ||
| b, err := hex.DecodeString(s) | ||
| if err != nil { | ||
| return h.Empty(), err | ||
| } | ||
| return h.NewID(b) | ||
| } | ||
|
|
||
| // utils | ||
| func IDFromString(hexHash string) (ObjectID, error) { | ||
| switch len(hexHash) { | ||
| case 40: | ||
| hashType := Sha1ObjectFormat{} | ||
| h, err := hashType.NewIDFromString(hexHash) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return h, nil | ||
| } | ||
|
|
||
| return nil, fmt.Errorf("invalid hash hex string: '%s' len: %d", hexHash, len(hexHash)) | ||
| } | ||
|
|
||
| func IsEmptyCommitID(commitID string) bool { | ||
| if commitID == "" { | ||
| return true | ||
| } | ||
|
|
||
| id, err := IDFromString(commitID) | ||
| if err != nil { | ||
| return false | ||
| } | ||
|
|
||
| return id.IsZero() | ||
| } | ||
|
|
||
| // HashInterface is a struct that will generate a Hash | ||
| type HasherInterface interface { | ||
| hash.Hash | ||
|
|
||
| HashSum() ObjectID | ||
| } | ||
|
|
||
| type Sha1Hasher struct { | ||
| hash.Hash | ||
| } | ||
|
|
||
| // ComputeBlobHash compute the hash for a given blob content | ||
| func ComputeBlobHash(hashType ObjectFormat, content []byte) ObjectID { | ||
| return ComputeHash(hashType, ObjectBlob, content) | ||
| } | ||
|
|
||
| // ComputeHash compute the hash for a given ObjectType and content | ||
| func ComputeHash(hashType ObjectFormat, t ObjectType, content []byte) ObjectID { | ||
| h := hashType.NewHasher() | ||
| _, _ = h.Write(t.Bytes()) | ||
| _, _ = h.Write([]byte(" ")) | ||
| _, _ = h.Write([]byte(strconv.FormatInt(int64(len(content)), 10))) | ||
| _, _ = h.Write([]byte{0}) | ||
| return h.HashSum() | ||
|
Contributor
There was a problem hiding this comment. Here and below, a regression ..... the "content" is not written into the hasher 😭 -> Fix incorrect object id hash function #30708 |
||
| } | ||
|
|
||
| // Sum generates a SHA1 for the provided hash | ||
| func (h *Sha1Hasher) HashSum() ObjectID { | ||
| var sha1 Sha1Hash | ||
| copy(sha1[:], h.Hash.Sum(nil)) | ||
| return &sha1 | ||
| } | ||
|
|
||
| type ErrInvalidSHA struct { | ||
| SHA string | ||
| } | ||
|
|
||
| func (err ErrInvalidSHA) Error() string { | ||
| return fmt.Sprintf("invalid sha: %s", err.SHA) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Copyright 2023 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
| //go:build gogit | ||
|
|
||
| package git | ||
|
|
||
| import ( | ||
| "github.com/go-git/go-git/v5/plumbing" | ||
| "github.com/go-git/go-git/v5/plumbing/hash" | ||
| ) | ||
|
|
||
| func ParseGogitHash(h plumbing.Hash) ObjectID { | ||
| switch hash.Size { | ||
| case 20: | ||
| return ObjectFormatFromID(Sha1).MustID(h[:]) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func ParseGogitHashArray(objectIDs []plumbing.Hash) []ObjectID { | ||
| ret := make([]ObjectID, len(objectIDs)) | ||
| for i, h := range objectIDs { | ||
| ret[i] = ParseGogitHash(h) | ||
| } | ||
|
|
||
| return ret | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package git | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestIsValidSHAPattern(t *testing.T) { | ||
| h := NewSha1().Type() | ||
| assert.True(t, h.IsValid("fee1")) | ||
| assert.True(t, h.IsValid("abc000")) | ||
| assert.True(t, h.IsValid("9023902390239023902390239023902390239023")) | ||
| assert.False(t, h.IsValid("90239023902390239023902390239023902390239023")) | ||
| assert.False(t, h.IsValid("abc")) | ||
| assert.False(t, h.IsValid("123g")) | ||
| assert.False(t, h.IsValid("some random text")) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it needs "integer" number?
I think
type ObjectFormat stringandSha1 = "sha1"work better?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is another function that does this from
stringalready, at the bottom of the file.git.ObjectFormatFromString()Basically, there is no need for this to be int or string in particular, but I wanted to move at least temporarily away from string to catch errors like passing "SHA1" vs. "sha1".
Finally, maybe my prejudice shows, coming from C and C++ world shows, where enums are ints 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#28469