diff options
| author | 2026-01-17 22:56:53 +0800 | |
|---|---|---|
| committer | 2026-01-17 22:56:53 +0800 | |
| commit | 7a0ab5f77917a36a87945f6a88b036b9b6ba88ee (patch) | |
| tree | 06947107af26e45bb5006276daa4b4d611f5dee2 | |
| parent | README: Clarify that the sha1 build tag is for testing only (diff) | |
| signature | No signature | |
hash: Key by algorithm, not size
| -rw-r--r-- | hash.go | 61 | ||||
| -rw-r--r-- | loose.go | 4 | ||||
| -rw-r--r-- | obj_tree.go | 7 | ||||
| -rw-r--r-- | pack_idx.go | 4 | ||||
| -rw-r--r-- | pack_pack.go | 4 | ||||
| -rw-r--r-- | repo.go | 24 |
6 files changed, 75 insertions, 29 deletions
@@ -8,44 +8,85 @@ import ( 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 } // 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 }, } // 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() } @@ -17,8 +17,8 @@ const looseHeaderLimit = 4096 // 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 diff --git a/obj_tree.go b/obj_tree.go index ccbea694..0ae8b3ac 100644 --- a/obj_tree.go +++ b/obj_tree.go @@ -83,7 +83,7 @@ func parseTree(id Hash, body []byte, repo *Repository) (*StoredTree, error) { } 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 @@ func (tree *Tree) serialize() []byte { 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 @@ func (tree *Tree) serialize() []byte { 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 diff --git a/pack_idx.go b/pack_idx.go index 68170ad3..d84bb5ec 100644 --- a/pack_idx.go +++ b/pack_idx.go @@ -244,8 +244,8 @@ func (pi *packIndex) lookup(id Hash) (packlocation, error) { 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 diff --git a/pack_pack.go b/pack_pack.go index 8d0bb11f..6b8b356d 100644 --- a/pack_pack.go +++ b/pack_pack.go @@ -182,7 +182,7 @@ func (repo *Repository) packTypeSizeWithin(pf *packFile, ofs uint64, seen map[pa } 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 @@ func (repo *Repository) packBodyResolveWithin(pf *packFile, ofs uint64) (ObjectT } 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) @@ -1,8 +1,6 @@ package furgit import ( - "crypto/sha1" - "crypto/sha256" "encoding/hex" "fmt" "os" @@ -21,6 +19,7 @@ import ( // has been closed. type Repository struct { rootPath string + hashAlgo hashAlgorithm hashSize int packIdxOnce sync.Once @@ -65,22 +64,27 @@ func OpenRepository(path string) (*Repository, error) { 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 @@ func (repo *Repository) ParseHash(s string) (Hash, error) { 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 @@ func (repo *Repository) verifyRawObject(buf []byte, want Hash) bool { //nolint:u // 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) |
