ref: 7a0ab5f77917a36a87945f6a88b036b9b6ba88ee
parent: c1592fb8ba14a443ac7e575dbd41f3aa358f885d
author: Runxi Yu <runxiyu@umich.edu>
date: Sat Jan 17 17:56:53 EST 2026
hash: Key by algorithm, not size
--- a/hash.go
+++ b/hash.go
@@ -8,9 +8,42 @@
const maxHashSize = 32
+// hashAlgorithm identifies the hash algorithm used for Git object IDs.
+type hashAlgorithm uint8
+
+const (
+ hashAlgoUnknown hashAlgorithm = iota
+ hashAlgoSHA1
+ hashAlgoSHA256
+)
+
+// size returns the hash size in bytes.
+func (algo hashAlgorithm) size() int {+ switch algo {+ case hashAlgoSHA1:
+ return sha1.Size
+ case hashAlgoSHA256:
+ return sha256.Size
+ default:
+ return 0
+ }
+}
+
+// String returns the canonical name of the hash algorithm.
+func (algo hashAlgorithm) String() string {+ switch algo {+ case hashAlgoSHA1:
+ return "sha1"
+ case hashAlgoSHA256:
+ return "sha256"
+ default:
+ return "unknown"
+ }
+}
+
// Hash represents a Git object ID.
type Hash struct {- size int
+ algo hashAlgorithm
data [maxHashSize]byte
}
@@ -17,20 +50,20 @@
// hashFunc is a function that computes a hash from input data.
type hashFunc func([]byte) Hash
-// hashFuncs maps hash size to hash function.
-var hashFuncs = map[int]hashFunc{- sha1.Size: func(data []byte) Hash {+// hashFuncs maps hash algorithm to hash function.
+var hashFuncs = map[hashAlgorithm]hashFunc{+ hashAlgoSHA1: func(data []byte) Hash {sum := sha1.Sum(data)
var h Hash
copy(h.data[:], sum[:])
- h.size = sha1.Size
+ h.algo = hashAlgoSHA1
return h
},
- sha256.Size: func(data []byte) Hash {+ hashAlgoSHA256: func(data []byte) Hash {sum := sha256.Sum256(data)
var h Hash
copy(h.data[:], sum[:])
- h.size = sha256.Size
+ h.algo = hashAlgoSHA256
return h
},
}
@@ -37,15 +70,23 @@
// String returns a hexadecimal string representation of the hash.
func (hash Hash) String() string {- return hex.EncodeToString(hash.data[:hash.size])
+ size := hash.algo.size()
+ if size == 0 {+ return ""
+ }
+ return hex.EncodeToString(hash.data[:size])
}
// Bytes returns a copy of the hash's bytes.
func (hash Hash) Bytes() []byte {- return append([]byte(nil), hash.data[:hash.size]...)
+ size := hash.algo.size()
+ if size == 0 {+ return nil
+ }
+ return append([]byte(nil), hash.data[:size]...)
}
// Size returns the hash size.
func (hash Hash) Size() int {- return hash.size
+ return hash.algo.size()
}
--- a/loose.go
+++ b/loose.go
@@ -17,8 +17,8 @@
// loosePath returns the path for a loose object, validating hash size.
func (repo *Repository) loosePath(id Hash) (string, error) {- if id.size != repo.hashSize {- return "", fmt.Errorf("furgit: hash size mismatch: got %d, expected %d", id.size, repo.hashSize)+ if id.algo != repo.hashAlgo {+ return "", fmt.Errorf("furgit: hash algorithm mismatch: got %s, expected %s", id.algo.String(), repo.hashAlgo.String())}
hex := id.String()
return filepath.Join("objects", hex[:2], hex[2:]), nil--- a/obj_tree.go
+++ b/obj_tree.go
@@ -83,7 +83,7 @@
}
var child Hash
copy(child.data[:], body[i:i+repo.hashSize])
- child.size = repo.hashSize
+ child.algo = repo.hashAlgo
i += repo.hashSize
mode, err := strconv.ParseUint(string(modeBytes), 8, 32)
@@ -112,7 +112,7 @@
var bodyLen int
for _, e := range tree.Entries {mode := strconv.FormatUint(uint64(e.Mode), 8)
- bodyLen += len(mode) + 1 + len(e.Name) + 1 + e.ID.size
+ bodyLen += len(mode) + 1 + len(e.Name) + 1 + e.ID.Size()
}
body := make([]byte, bodyLen)
@@ -125,7 +125,8 @@
pos += copy(body[pos:], e.Name)
body[pos] = 0
pos++
- pos += copy(body[pos:], e.ID.data[:e.ID.size])
+ size := e.ID.Size()
+ pos += copy(body[pos:], e.ID.data[:size])
}
return body
--- a/pack_idx.go
+++ b/pack_idx.go
@@ -244,8 +244,8 @@
if err != nil { return packlocation{}, err}
- if id.size != pi.repo.hashSize {- return packlocation{}, fmt.Errorf("furgit: hash size mismatch: got %d, expected %d", id.size, pi.repo.hashSize)+ if id.algo != pi.repo.hashAlgo {+ return packlocation{}, fmt.Errorf("furgit: hash algorithm mismatch: got %s, expected %s", id.algo.String(), pi.repo.hashAlgo.String())}
first := int(id.data[0])
var lo int
--- a/pack_pack.go
+++ b/pack_pack.go
@@ -182,7 +182,7 @@
}
var base Hash
copy(base.data[:], pf.data[dataStart:hashEnd])
- base.size = repo.hashSize
+ base.algo = repo.hashAlgo
loc, err := repo.packIndexFind(base)
if err == nil {pf, err = repo.packFile(loc.PackPath)
@@ -279,7 +279,7 @@
}
var base Hash
copy(base.data[:], pf.data[dataStart:hashEnd])
- base.size = repo.hashSize
+ base.algo = repo.hashAlgo
delta, err := packSectionInflate(pf, hashEnd, 0)
if err != nil {return fail(err)
--- a/repo.go
+++ b/repo.go
@@ -1,8 +1,6 @@
package furgit
import (
- "crypto/sha1"
- "crypto/sha256"
"encoding/hex"
"fmt"
"os"
@@ -21,6 +19,7 @@
// has been closed.
type Repository struct {rootPath string
+ hashAlgo hashAlgorithm
hashSize int
packIdxOnce sync.Once
@@ -65,22 +64,27 @@
algo = "sha1"
}
- var hashSize int
+ var hashAlgo hashAlgorithm
switch algo {case "sha1":
- hashSize = sha1.Size
+ hashAlgo = hashAlgoSHA1
case "sha256":
- hashSize = sha256.Size
+ hashAlgo = hashAlgoSHA256
default:
return nil, fmt.Errorf("furgit: unsupported hash algorithm %q", algo)}
- if _, ok := hashFuncs[hashSize]; !ok {+ hashSize := hashAlgo.size()
+ if hashSize == 0 {+ return nil, fmt.Errorf("furgit: unsupported hash algorithm %q", algo)+ }
+ if _, ok := hashFuncs[hashAlgo]; !ok { return nil, fmt.Errorf("furgit: hash algorithm %q is not supported by the hash functions provided by this build", algo)}
return &Repository{rootPath: path,
+ hashAlgo: hashAlgo,
hashSize: hashSize,
packFiles: make(map[string]*packFile),
}, nil
@@ -138,19 +142,19 @@
return id, fmt.Errorf("furgit: decode hash: %w", err)}
copy(id.data[:], data)
- id.size = len(s) / 2
+ id.algo = repo.hashAlgo
return id, nil
}
// computeRawHash computes a hash from raw data using the repository's hash algorithm.
func (repo *Repository) computeRawHash(data []byte) Hash {- hashFunc := hashFuncs[repo.hashSize]
+ hashFunc := hashFuncs[repo.hashAlgo]
return hashFunc(data)
}
// verifyRawObject verifies a raw object against its expected hash.
func (repo *Repository) verifyRawObject(buf []byte, want Hash) bool { //nolint:unused- if want.size != repo.hashSize {+ if want.algo != repo.hashAlgo {return false
}
return repo.computeRawHash(buf) == want
@@ -158,7 +162,7 @@
// verifyTypedObject verifies a typed object against its expected hash.
func (repo *Repository) verifyTypedObject(ty ObjectType, body []byte, want Hash) bool { //nolint:unused- if want.size != repo.hashSize {+ if want.algo != repo.hashAlgo {return false
}
header, err := headerForType(ty, body)
--
⑨