aboutsummaryrefslogtreecommitdiff
path: root/objectid/objectid.go
blob: 8eb82969cd318dc92997e239ad9d2a9a90d9f51f (about) (plain) (blame)
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
// Package objectid provides utilities around object IDs and hash algorithms.
package objectid

import (
	//#nosec G505

	"bytes"
	"encoding/hex"
	"fmt"
)

// ObjectID represents a Git object ID.
//
//nolint:recvcheck
type ObjectID struct {
	algo Algorithm
	data [maxObjectIDSize]byte
}

// Algorithm returns the object ID's hash algorithm.
func (id ObjectID) Algorithm() Algorithm {
	return id.algo
}

// Size returns the object ID size in bytes.
func (id ObjectID) Size() int {
	return id.algo.Size()
}

// String returns the canonical hex representation.
func (id ObjectID) String() string {
	size := id.Size()

	return hex.EncodeToString(id.data[:size])
}

// Bytes returns a copy of the object ID bytes.
func (id ObjectID) Bytes() []byte {
	size := id.Size()

	return append([]byte(nil), id.data[:size]...)
}

// RawBytes returns a direct byte slice view of the object ID bytes.
//
// The returned slice aliases the object ID's internal storage. Callers MUST
// treat it as read-only and MUST NOT modify its contents.
//
// Use Bytes when an independent copy is required.
func (id *ObjectID) RawBytes() []byte {
	size := id.Size()

	return id.data[:size:size]
}

// Compare lexicographically compares two object IDs by their canonical byte
// representation.
func Compare(left, right ObjectID) int {
	return bytes.Compare(left.RawBytes(), right.RawBytes())
}

// Zero returns the all-zero object ID for the specified algorithm.
func Zero(algo Algorithm) ObjectID {
	id, err := FromBytes(algo, make([]byte, algo.Size()))
	if err != nil {
		panic(err)
	}

	return id
}

// ParseHex parses an object ID from hex for the specified algorithm.
func ParseHex(algo Algorithm, s string) (ObjectID, error) {
	var id ObjectID
	if algo.Size() == 0 {
		return id, ErrInvalidAlgorithm
	}

	if len(s)%2 != 0 {
		return id, fmt.Errorf("%w: odd hex length %d", ErrInvalidObjectID, len(s))
	}

	if len(s) != algo.HexLen() {
		return id, fmt.Errorf("%w: got %d chars, expected %d", ErrInvalidObjectID, len(s), algo.HexLen())
	}

	decoded, err := hex.DecodeString(s)
	if err != nil {
		return id, fmt.Errorf("%w: decode: %w", ErrInvalidObjectID, err)
	}

	copy(id.data[:], decoded)
	id.algo = algo

	return id, nil
}

// FromBytes builds an object ID from raw bytes for the specified algorithm.
func FromBytes(algo Algorithm, b []byte) (ObjectID, error) {
	var id ObjectID
	if algo.Size() == 0 {
		return id, ErrInvalidAlgorithm
	}

	if len(b) != algo.Size() {
		return id, fmt.Errorf("%w: got %d bytes, expected %d", ErrInvalidObjectID, len(b), algo.Size())
	}

	copy(id.data[:], b)
	id.algo = algo

	return id, nil
}