ref: 32f84b9d89d34ab74f8843f939f8d1d811d403ad
parent: e4afe122f6e0c3159969402e7a3ec47f56d36abd
author: Runxi Yu <runxiyu@umich.edu>
date: Sun Mar 29 06:14:38 EDT 2026
object/id: Split files
--- a/format/commitgraph/read/layer_lookup.go
+++ b/format/commitgraph/read/layer_lookup.go
@@ -9,7 +9,7 @@
)
func layerLookup(layer *layer, oid objectid.ObjectID) (uint32, bool) {- hashSize := oid.Size()
+ hashSize := oid.Algorithm().Size()
first := int(oid.RawBytes()[0])
var lo uint32
--- a/object/commit/serialize.go
+++ b/object/commit/serialize.go
@@ -13,7 +13,7 @@
func (commit *Commit) SerializeWithoutHeader() ([]byte, error) {var buf bytes.Buffer
- if commit.Tree.Size() == 0 {+ if commit.Tree.Algorithm().Size() == 0 { return nil, errors.New("object: commit: missing tree id")}
--- /dev/null
+++ b/object/id/algorithm.go
@@ -1,0 +1,12 @@
+package objectid
+
+//#nosec gosec
+
+// Algorithm identifies the hash algorithm used for Git object IDs.
+type Algorithm uint8
+
+const (
+ AlgorithmUnknown Algorithm = iota
+ AlgorithmSHA1
+ AlgorithmSHA256
+)
--- /dev/null
+++ b/object/id/algorithm_details.go
@@ -1,0 +1,16 @@
+package objectid
+
+import "hash"
+
+type algorithmDetails struct {+ name string
+ size int
+ packHashID uint32
+ sum func([]byte) ObjectID
+ new func() hash.Hash
+ emptyTree ObjectID
+}
+
+func (algo Algorithm) info() algorithmDetails {+ return algorithmTable[algo]
+}
--- /dev/null
+++ b/object/id/algorithm_emptytree.go
@@ -1,0 +1,7 @@
+package objectid
+
+// EmptyTree returns the object ID of an empty tree ("tree 0\x00") for this+// algorithm.
+func (algo Algorithm) EmptyTree() ObjectID {+ return algo.info().emptyTree
+}
--- /dev/null
+++ b/object/id/algorithm_hexlen.go
@@ -1,0 +1,6 @@
+package objectid
+
+// HexLen returns the encoded hexadecimal length.
+func (algo Algorithm) HexLen() int {+ return algo.Size() * 2
+}
--- /dev/null
+++ b/object/id/algorithm_new.go
@@ -1,0 +1,13 @@
+package objectid
+
+import "hash"
+
+// New returns a new hash.Hash for this algorithm.
+func (algo Algorithm) New() (hash.Hash, error) {+ newFn := algo.info().new
+ if newFn == nil {+ return nil, ErrInvalidAlgorithm
+ }
+
+ return newFn(), nil
+}
--- /dev/null
+++ b/object/id/algorithm_packhashid.go
@@ -1,0 +1,8 @@
+package objectid
+
+// PackHashID returns the Git pack/rev hash-id encoding for this algorithm.
+//
+// Unknown algorithms return 0.
+func (algo Algorithm) PackHashID() uint32 {+ return algo.info().packHashID
+}
--- /dev/null
+++ b/object/id/algorithm_parse.go
@@ -1,0 +1,8 @@
+package objectid
+
+// ParseAlgorithm parses a canonical algorithm name (e.g. "sha1", "sha256").
+func ParseAlgorithm(s string) (Algorithm, bool) {+ algo, ok := algorithmByName[s]
+
+ return algo, ok
+}
--- /dev/null
+++ b/object/id/algorithm_size.go
@@ -1,0 +1,6 @@
+package objectid
+
+// Size returns the hash size in bytes.
+func (algo Algorithm) Size() int {+ return algo.info().size
+}
--- /dev/null
+++ b/object/id/algorithm_string.go
@@ -1,0 +1,11 @@
+package objectid
+
+// String returns the canonical algorithm name.
+func (algo Algorithm) String() string {+ inf := algo.info()
+ if inf.name == "" {+ return "unknown"
+ }
+
+ return inf.name
+}
--- /dev/null
+++ b/object/id/algorithm_sum.go
@@ -1,0 +1,6 @@
+package objectid
+
+// Sum computes an object ID from raw data using the selected algorithm.
+func (algo Algorithm) Sum(data []byte) ObjectID {+ return algo.info().sum(data)
+}
--- /dev/null
+++ b/object/id/algorithm_supported.go
@@ -1,0 +1,7 @@
+package objectid
+
+// SupportedAlgorithms returns all object ID algorithms supported by furgit.
+// Do not mutate.
+func SupportedAlgorithms() []Algorithm {+ return supportedAlgorithms
+}
--- /dev/null
+++ b/object/id/algorithm_tables.go
@@ -1,0 +1,63 @@
+package objectid
+
+import (
+ "crypto/sha1"
+ "crypto/sha256"
+)
+
+//nolint:gochecknoglobals
+var algorithmTable = [...]algorithmDetails{+ AlgorithmUnknown: {},+ AlgorithmSHA1: {+ name: "sha1",
+ size: sha1.Size,
+ packHashID: 1,
+ sum: func(data []byte) ObjectID {+ sum := sha1.Sum(data) //#nosec G401
+
+ var id ObjectID
+ copy(id.data[:], sum[:])
+ id.algo = AlgorithmSHA1
+
+ return id
+ },
+ new: sha1.New,
+ },
+ AlgorithmSHA256: {+ name: "sha256",
+ size: sha256.Size,
+ packHashID: 2,
+ sum: func(data []byte) ObjectID {+ sum := sha256.Sum256(data)
+
+ var id ObjectID
+ copy(id.data[:], sum[:])
+ id.algo = AlgorithmSHA256
+
+ return id
+ },
+ new: sha256.New,
+ },
+}
+
+var (
+ //nolint:gochecknoglobals
+ algorithmByName = map[string]Algorithm{}+ //nolint:gochecknoglobals
+ supportedAlgorithms []Algorithm
+)
+
+func init() { //nolint:gochecknoinits+ emptyTreeInput := []byte("tree 0\x00")+
+ for algo := Algorithm(0); int(algo) < len(algorithmTable); algo++ {+ info := &algorithmTable[algo]
+ if info.name == "" {+ continue
+ }
+
+ info.emptyTree = info.sum(emptyTreeInput)
+ algorithmByName[info.name] = algo
+ supportedAlgorithms = append(supportedAlgorithms, algo)
+ }
+}
--- a/object/id/algorithms.go
+++ /dev/null
@@ -1,150 +1,0 @@
-package objectid
-
-import (
- "crypto/sha1" //#nosec gosec
- "crypto/sha256"
- "hash"
-)
-
-// maxObjectIDSize MUST be >= the largest supported algorithm size.
-const maxObjectIDSize = sha256.Size
-
-// Algorithm identifies the hash algorithm used for Git object IDs.
-type Algorithm uint8
-
-const (
- AlgorithmUnknown Algorithm = iota
- AlgorithmSHA1
- AlgorithmSHA256
-)
-
-type algorithmDetails struct {- name string
- size int
- packHashID uint32
- sum func([]byte) ObjectID
- new func() hash.Hash
- emptyTree ObjectID
-}
-
-//nolint:gochecknoglobals
-var algorithmTable = [...]algorithmDetails{- AlgorithmUnknown: {},- AlgorithmSHA1: {- name: "sha1",
- size: sha1.Size,
- packHashID: 1,
- sum: func(data []byte) ObjectID {- sum := sha1.Sum(data) //#nosec G401
-
- var id ObjectID
- copy(id.data[:], sum[:])
- id.algo = AlgorithmSHA1
-
- return id
- },
- new: sha1.New,
- },
- AlgorithmSHA256: {- name: "sha256",
- size: sha256.Size,
- packHashID: 2,
- sum: func(data []byte) ObjectID {- sum := sha256.Sum256(data)
-
- var id ObjectID
- copy(id.data[:], sum[:])
- id.algo = AlgorithmSHA256
-
- return id
- },
- new: sha256.New,
- },
-}
-
-var (
- //nolint:gochecknoglobals
- algorithmByName = map[string]Algorithm{}- //nolint:gochecknoglobals
- supportedAlgorithms []Algorithm
-)
-
-func init() { //nolint:gochecknoinits- emptyTreeInput := []byte("tree 0\x00")-
- for algo := Algorithm(0); int(algo) < len(algorithmTable); algo++ {- info := &algorithmTable[algo]
- if info.name == "" {- continue
- }
-
- info.emptyTree = info.sum(emptyTreeInput)
- algorithmByName[info.name] = algo
- supportedAlgorithms = append(supportedAlgorithms, algo)
- }
-}
-
-// SupportedAlgorithms returns all object ID algorithms supported by furgit.
-// Do not mutate.
-func SupportedAlgorithms() []Algorithm {- return supportedAlgorithms
-}
-
-// ParseAlgorithm parses a canonical algorithm name (e.g. "sha1", "sha256").
-func ParseAlgorithm(s string) (Algorithm, bool) {- algo, ok := algorithmByName[s]
-
- return algo, ok
-}
-
-// Size returns the hash size in bytes.
-func (algo Algorithm) Size() int {- return algo.info().size
-}
-
-// String returns the canonical algorithm name.
-func (algo Algorithm) String() string {- inf := algo.info()
- if inf.name == "" {- return "unknown"
- }
-
- return inf.name
-}
-
-// HexLen returns the encoded hexadecimal length.
-func (algo Algorithm) HexLen() int {- return algo.Size() * 2
-}
-
-// PackHashID returns the Git pack/rev hash-id encoding for this algorithm.
-//
-// Unknown algorithms return 0.
-func (algo Algorithm) PackHashID() uint32 {- return algo.info().packHashID
-}
-
-// Sum computes an object ID from raw data using the selected algorithm.
-func (algo Algorithm) Sum(data []byte) ObjectID {- return algo.info().sum(data)
-}
-
-// New returns a new hash.Hash for this algorithm.
-func (algo Algorithm) New() (hash.Hash, error) {- newFn := algo.info().new
- if newFn == nil {- return nil, ErrInvalidAlgorithm
- }
-
- return newFn(), nil
-}
-
-// EmptyTree returns the object ID of an empty tree ("tree 0\x00") for this-// algorithm.
-func (algo Algorithm) EmptyTree() ObjectID {- return algo.info().emptyTree
-}
-
-func (algo Algorithm) info() algorithmDetails {- return algorithmTable[algo]
-}
--- /dev/null
+++ b/object/id/max_size.go
@@ -1,0 +1,6 @@
+package objectid
+
+import "crypto/sha256"
+
+// maxObjectIDSize MUST be >= the largest supported algorithm size.
+const maxObjectIDSize = sha256.Size
--- a/object/id/objectid.go
+++ b/object/id/objectid.go
@@ -1,13 +1,7 @@
package objectid
-import (
- //#nosec G505
+//#nosec G505
- "bytes"
- "encoding/hex"
- "fmt"
-)
-
// ObjectID represents a Git object ID.
//
//nolint:recvcheck
@@ -14,99 +8,4 @@
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
}
--- /dev/null
+++ b/object/id/objectid_algorithm.go
@@ -1,0 +1,6 @@
+package objectid
+
+// Algorithm returns the object ID's hash algorithm.
+func (id ObjectID) Algorithm() Algorithm {+ return id.algo
+}
--- /dev/null
+++ b/object/id/objectid_byte.go
@@ -1,0 +1,20 @@
+package objectid
+
+// Bytes returns a copy of the object ID bytes.
+func (id ObjectID) Bytes() []byte {+ size := id.Algorithm().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.Algorithm().Size()
+
+ return id.data[:size:size]
+}
--- /dev/null
+++ b/object/id/objectid_compare.go
@@ -1,0 +1,9 @@
+package objectid
+
+import "bytes"
+
+// Compare lexicographically compares two object IDs by their canonical byte
+// representation.
+func Compare(left, right ObjectID) int {+ return bytes.Compare(left.RawBytes(), right.RawBytes())
+}
--- /dev/null
+++ b/object/id/objectid_frombytes.go
@@ -1,0 +1,20 @@
+package objectid
+
+import "fmt"
+
+// 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
+}
--- /dev/null
+++ b/object/id/objectid_parse.go
@@ -1,0 +1,32 @@
+package objectid
+
+import (
+ "encoding/hex"
+ "fmt"
+)
+
+// 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
+}
--- /dev/null
+++ b/object/id/objectid_string.go
@@ -1,0 +1,10 @@
+package objectid
+
+import "encoding/hex"
+
+// String returns the canonical hex representation.
+func (id ObjectID) String() string {+ size := id.Algorithm().Size()
+
+ return hex.EncodeToString(id.data[:size])
+}
--- a/object/id/objectid_test.go
+++ b/object/id/objectid_test.go
@@ -44,7 +44,7 @@
t.Fatalf("String() = %q, want %q", got, hex)}
- if got := id.Size(); got != algo.Size() {+ if got := id.Algorithm().Size(); got != algo.Size() { t.Fatalf("Size() = %d, want %d", got, algo.Size())}
@@ -148,8 +148,8 @@
}
b := id.RawBytes()
- if len(b) != id.Size() {- t.Fatalf("RawBytes len = %d, want %d", len(b), id.Size())+ if len(b) != id.Algorithm().Size() {+ t.Fatalf("RawBytes len = %d, want %d", len(b), id.Algorithm().Size())}
if cap(b) != len(b) {@@ -169,12 +169,12 @@
t.Parallel()
id1 := objectid.AlgorithmSHA1.Sum([]byte("hello"))- if id1.Algorithm() != objectid.AlgorithmSHA1 || id1.Size() != objectid.AlgorithmSHA1.Size() {+ if id1.Algorithm() != objectid.AlgorithmSHA1 || id1.Algorithm().Size() != objectid.AlgorithmSHA1.Size() { t.Fatalf("sha1 sum produced invalid object id")}
id2 := objectid.AlgorithmSHA256.Sum([]byte("hello"))- if id2.Algorithm() != objectid.AlgorithmSHA256 || id2.Size() != objectid.AlgorithmSHA256.Size() {+ if id2.Algorithm() != objectid.AlgorithmSHA256 || id2.Algorithm().Size() != objectid.AlgorithmSHA256.Size() { t.Fatalf("sha256 sum produced invalid object id")}
--- /dev/null
+++ b/object/id/objectid_zero.go
@@ -1,0 +1,11 @@
+package objectid
+
+// 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
+}
--- a/object/tag/serialize.go
+++ b/object/tag/serialize.go
@@ -11,7 +11,7 @@
// SerializeWithoutHeader renders the raw tag body bytes.
func (tag *Tag) SerializeWithoutHeader() ([]byte, error) {- if tag.Target.Size() == 0 {+ if tag.Target.Algorithm().Size() == 0 { return nil, errors.New("object: tag: missing target id")}
--- a/object/tree/serialize.go
+++ b/object/tree/serialize.go
@@ -14,7 +14,7 @@
for _, entry := range tree.Entries {mode := strconv.FormatUint(uint64(entry.Mode), 8)
- bodyLen += len(mode) + 1 + len(entry.Name) + 1 + entry.ID.Size()
+ bodyLen += len(mode) + 1 + len(entry.Name) + 1 + entry.ID.Algorithm().Size()
}
body := make([]byte, bodyLen)
--- a/ref/store/files/resolve_list_test.go
+++ b/ref/store/files/resolve_list_test.go
@@ -236,7 +236,7 @@
t.Fatalf("Resolve(tag) type = %T, want ref.Detached", tagRef)}
- if tagDet.ID.Size() == 0 {+ if tagDet.ID.Algorithm().Size() == 0 { t.Fatal("Resolve(tag) returned zero object id")}
})
--- a/ref/store/files/update_validate.go
+++ b/ref/store/files/update_validate.go
@@ -21,7 +21,7 @@
return wrapUpdateError(op.name, &refstore.InvalidNameError{Err: err})}
- if op.newID.Size() == 0 {+ if op.newID.Algorithm().Size() == 0 { return wrapUpdateError(op.name, &refstore.InvalidValueError{Err: objectid.ErrInvalidAlgorithm})}
case updateDelete, updateVerify:
@@ -30,7 +30,7 @@
return wrapUpdateError(op.name, &refstore.InvalidNameError{Err: err})}
- if op.oldID.Size() == 0 {+ if op.oldID.Algorithm().Size() == 0 { return wrapUpdateError(op.name, &refstore.InvalidValueError{Err: objectid.ErrInvalidAlgorithm})}
case updateCreateSymbolic, updateReplaceSymbolic:
--
⑨