Welcome to mirror list, hosted at ThFree Co, Russian Federation.

object_id.go « git « internal - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 69bb58b8b4cf27c9bc2b598dfb9d804dd60f0b75 (plain)
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
package git

import (
	"bytes"
	"context"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"hash"

	"gitlab.com/gitlab-org/gitaly/v15/internal/helper/text"
	"gitlab.com/gitlab-org/gitaly/v15/internal/structerr"
	"gitlab.com/gitlab-org/gitaly/v15/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, repoExecutor RepositoryExecutor) (ObjectHash, error) {
	var stdout, stderr bytes.Buffer

	if err := repoExecutor.ExecAndWait(ctx, Command{
		Name: "rev-parse",
		Flags: []Option{
			Flag{"--show-object-format"},
		},
	}, WithStdout(&stdout), WithStderr(&stderr)); 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())
}