1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
package git
import (
"bytes"
"context"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage"
"gitlab.com/gitlab-org/gitaly/v16/internal/helper/text"
"gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
"gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
)
var (
// ObjectHashSHA1 is the implementation of an object ID via SHA1.
ObjectHashSHA1 = ObjectHash{
Hash: sha1.New,
EmptyTreeOID: ObjectID("4b825dc642cb6eb9a060e54bf8d69288fbee4904"),
ZeroOID: ObjectID("0000000000000000000000000000000000000000"),
Format: "sha1",
ProtoFormat: gitalypb.ObjectFormat_OBJECT_FORMAT_SHA1,
}
// ObjectHashSHA256 is the implementation of an object ID via SHA256.
ObjectHashSHA256 = ObjectHash{
Hash: sha256.New,
EmptyTreeOID: ObjectID("6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321"),
ZeroOID: ObjectID("0000000000000000000000000000000000000000000000000000000000000000"),
Format: "sha256",
ProtoFormat: gitalypb.ObjectFormat_OBJECT_FORMAT_SHA256,
}
)
// InvalidObjectIDLengthError is returned when an object ID's string
// representation is not the required length.
type InvalidObjectIDLengthError struct {
OID string
CorrectLength int
Length int
}
func (e InvalidObjectIDLengthError) Error() string {
return fmt.Sprintf("invalid object ID: %q, expected length %v, got %v", e.OID, e.CorrectLength, e.Length)
}
// InvalidObjectIDCharError is returned when an object ID's string
// representation contains an invalid hexadecimal digit.
type InvalidObjectIDCharError struct {
OID string
BadChar rune
}
func (e InvalidObjectIDCharError) Error() string {
return fmt.Sprintf("invalid object ID: %q, invalid character %q", e.OID, e.BadChar)
}
// ObjectHash is a hash-function specific implementation of an object ID.
type ObjectHash struct {
// Hash is the hashing function used to hash objects.
Hash func() hash.Hash
// EmptyTreeOID is the object ID of the tree object that has no directory entries.
EmptyTreeOID ObjectID
// ZeroOID is the special value that Git uses to signal a ref or object does not exist
ZeroOID ObjectID
// Format is the name of the object hash.
Format string
// ProtoFormat is the Protobuf representation of the object format.
ProtoFormat gitalypb.ObjectFormat
}
// ObjectHashByFormat looks up the ObjectHash by its format name.
func ObjectHashByFormat(format string) (ObjectHash, error) {
switch format {
case ObjectHashSHA1.Format:
return ObjectHashSHA1, nil
case ObjectHashSHA256.Format:
return ObjectHashSHA256, nil
default:
return ObjectHash{}, fmt.Errorf("unknown object format: %q", format)
}
}
// ObjectHashByProto looks up the ObjectHash by its Protobuf representation `gitalypb.ObjectFormat`.
// Returns an error in case the object format is not known.
func ObjectHashByProto(format gitalypb.ObjectFormat) (ObjectHash, error) {
switch format {
case gitalypb.ObjectFormat_OBJECT_FORMAT_UNSPECIFIED, gitalypb.ObjectFormat_OBJECT_FORMAT_SHA1:
return ObjectHashSHA1, nil
case gitalypb.ObjectFormat_OBJECT_FORMAT_SHA256:
return ObjectHashSHA256, nil
default:
return ObjectHash{}, fmt.Errorf("unknown object format: %q", format)
}
}
// DetectObjectHash detects the object-hash used by the given repository.
func DetectObjectHash(ctx context.Context, gitCmdFactory CommandFactory, repository storage.Repository) (ObjectHash, error) {
var stdout, stderr bytes.Buffer
revParseCmd, err := gitCmdFactory.New(ctx, repository, Command{
Name: "rev-parse",
Flags: []Option{
Flag{"--show-object-format"},
},
}, WithStdout(&stdout), WithStderr(&stderr))
if err != nil {
return ObjectHash{}, fmt.Errorf("spawning rev-parse: %w", err)
}
if err := revParseCmd.Wait(); err != nil {
return ObjectHash{}, structerr.New("reading object format: %w", err).WithMetadata("stderr", stderr.String())
}
return ObjectHashByFormat(text.ChompBytes(stdout.Bytes()))
}
// EncodedLen returns the length of the hex-encoded string of a full object ID.
func (h ObjectHash) EncodedLen() int {
return hex.EncodedLen(h.Hash().Size())
}
// FromHex constructs a new ObjectID from the given hex representation of the object ID. Returns
// ErrInvalidObjectID if the given object ID is not valid.
func (h ObjectHash) FromHex(hex string) (ObjectID, error) {
if err := h.ValidateHex(hex); err != nil {
return "", err
}
return ObjectID(hex), nil
}
// ValidateHex checks if `hex` is a syntactically correct object ID for the given hash. Abbreviated
// object IDs are not deemed to be valid. Returns an `ErrInvalidObjectID` if the `hex` is not valid.
func (h ObjectHash) ValidateHex(hex string) error {
if len(hex) != h.EncodedLen() {
return InvalidObjectIDLengthError{OID: hex, CorrectLength: h.EncodedLen(), Length: len(hex)}
}
for _, c := range hex {
switch {
case '0' <= c && c <= '9':
case 'a' <= c && c <= 'f':
default:
return InvalidObjectIDCharError{OID: hex, BadChar: c}
}
}
return nil
}
// IsZeroOID checks whether the given object ID is the all-zeroes object ID for the given hash.
func (h ObjectHash) IsZeroOID(oid ObjectID) bool {
return string(oid) == string(h.ZeroOID)
}
// ObjectID represents an object ID.
type ObjectID string
// String returns the hex representation of the ObjectID.
func (oid ObjectID) String() string {
return string(oid)
}
// Bytes returns the byte representation of the ObjectID.
func (oid ObjectID) Bytes() ([]byte, error) {
decoded, err := hex.DecodeString(string(oid))
if err != nil {
return nil, err
}
return decoded, nil
}
// Revision returns a revision of the ObjectID. This directly returns the hex
// representation as every object ID is a valid revision.
func (oid ObjectID) Revision() Revision {
return Revision(oid.String())
}
|