mirror of https://github.com/go-gitea/gitea.git
Abstract hash function usage (#28138)
Refactor Hash interfaces and centralize hash function. This will allow easier introduction of different hash function later on. This forms the "no-op" part of the SHA256 enablement patch.pull/28392/head^2
parent
064f05204c
commit
cbf923e87b
@ -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)
|
||||||
|
}
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -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"))
|
||||||
|
}
|
||||||
@ -1,72 +0,0 @@
|
|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EmptySHA defines empty git SHA (undefined, non-existent)
|
|
||||||
const EmptySHA = "0000000000000000000000000000000000000000"
|
|
||||||
|
|
||||||
// EmptyTreeSHA is the SHA of an empty tree, the root of all git repositories
|
|
||||||
const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
|
||||||
|
|
||||||
// SHAFullLength is the full length of a git SHA
|
|
||||||
const SHAFullLength = 40
|
|
||||||
|
|
||||||
// SHAPattern can be used to determine if a string is an valid sha
|
|
||||||
var shaPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
|
|
||||||
|
|
||||||
// IsValidSHAPattern will check if the provided string matches the SHA Pattern
|
|
||||||
func IsValidSHAPattern(sha string) bool {
|
|
||||||
return shaPattern.MatchString(sha)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrInvalidSHA struct {
|
|
||||||
SHA string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrInvalidSHA) Error() string {
|
|
||||||
return fmt.Sprintf("invalid sha: %s", err.SHA)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustID always creates a new SHA1 from a [20]byte array with no validation of input.
|
|
||||||
func MustID(b []byte) SHA1 {
|
|
||||||
var id SHA1
|
|
||||||
copy(id[:], b)
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewID creates a new SHA1 from a [20]byte array.
|
|
||||||
func NewID(b []byte) (SHA1, error) {
|
|
||||||
if len(b) != 20 {
|
|
||||||
return SHA1{}, fmt.Errorf("Length must be 20: %v", b)
|
|
||||||
}
|
|
||||||
return MustID(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustIDFromString always creates a new sha from a ID with no validation of input.
|
|
||||||
func MustIDFromString(s string) SHA1 {
|
|
||||||
b, _ := hex.DecodeString(s)
|
|
||||||
return MustID(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIDFromString creates a new SHA1 from a ID string of length 40.
|
|
||||||
func NewIDFromString(s string) (SHA1, error) {
|
|
||||||
var id SHA1
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if len(s) != SHAFullLength {
|
|
||||||
return id, fmt.Errorf("Length must be 40: %s", s)
|
|
||||||
}
|
|
||||||
b, err := hex.DecodeString(s)
|
|
||||||
if err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
return NewID(b)
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SHA1 a git commit name
|
|
||||||
type SHA1 = plumbing.Hash
|
|
||||||
|
|
||||||
// ComputeBlobHash compute the hash for a given blob content
|
|
||||||
func ComputeBlobHash(content []byte) SHA1 {
|
|
||||||
return plumbing.ComputeHash(plumbing.BlobObject, content)
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build !gogit
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"hash"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SHA1 a git commit name
|
|
||||||
type SHA1 [20]byte
|
|
||||||
|
|
||||||
// String returns a string representation of the SHA
|
|
||||||
func (s SHA1) String() string {
|
|
||||||
return hex.EncodeToString(s[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsZero returns whether this SHA1 is all zeroes
|
|
||||||
func (s SHA1) IsZero() bool {
|
|
||||||
var empty SHA1
|
|
||||||
return s == empty
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComputeBlobHash compute the hash for a given blob content
|
|
||||||
func ComputeBlobHash(content []byte) SHA1 {
|
|
||||||
return ComputeHash(ObjectBlob, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComputeHash compute the hash for a given ObjectType and content
|
|
||||||
func ComputeHash(t ObjectType, content []byte) SHA1 {
|
|
||||||
h := NewHasher(t, int64(len(content)))
|
|
||||||
_, _ = h.Write(content)
|
|
||||||
return h.Sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher is a struct that will generate a SHA1
|
|
||||||
type Hasher struct {
|
|
||||||
hash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHasher takes an object type and size and creates a hasher to generate a SHA
|
|
||||||
func NewHasher(t ObjectType, size int64) Hasher {
|
|
||||||
h := Hasher{sha1.New()}
|
|
||||||
_, _ = h.Write(t.Bytes())
|
|
||||||
_, _ = h.Write([]byte(" "))
|
|
||||||
_, _ = h.Write([]byte(strconv.FormatInt(size, 10)))
|
|
||||||
_, _ = h.Write([]byte{0})
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum generates a SHA1 for the provided hash
|
|
||||||
func (h Hasher) Sum() (sha1 SHA1) {
|
|
||||||
copy(sha1[:], h.Hash.Sum(nil))
|
|
||||||
return sha1
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
// 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) {
|
|
||||||
assert.True(t, IsValidSHAPattern("fee1"))
|
|
||||||
assert.True(t, IsValidSHAPattern("abc000"))
|
|
||||||
assert.True(t, IsValidSHAPattern("9023902390239023902390239023902390239023"))
|
|
||||||
assert.False(t, IsValidSHAPattern("90239023902390239023902390239023902390239023"))
|
|
||||||
assert.False(t, IsValidSHAPattern("abc"))
|
|
||||||
assert.False(t, IsValidSHAPattern("123g"))
|
|
||||||
assert.False(t, IsValidSHAPattern("some random text"))
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue