aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-01-17 22:56:53 +0800
committerGravatar Runxi Yu2026-01-17 22:56:53 +0800
commit7a0ab5f77917a36a87945f6a88b036b9b6ba88ee (patch)
tree06947107af26e45bb5006276daa4b4d611f5dee2
parentREADME: Clarify that the sha1 build tag is for testing only (diff)
signatureNo signature
hash: Key by algorithm, not size
-rw-r--r--hash.go61
-rw-r--r--loose.go4
-rw-r--r--obj_tree.go7
-rw-r--r--pack_idx.go4
-rw-r--r--pack_pack.go4
-rw-r--r--repo.go24
6 files changed, 75 insertions, 29 deletions
diff --git a/hash.go b/hash.go
index 3edb6079..1a0053c2 100644
--- a/hash.go
+++ b/hash.go
@@ -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()
}
diff --git a/loose.go b/loose.go
index 137f0c41..5932f92f 100644
--- a/loose.go
+++ b/loose.go
@@ -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)
diff --git a/repo.go b/repo.go
index da39cb22..5576c465 100644
--- 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 @@ 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)