aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hash.go43
-rw-r--r--hash_sha1_test.go (renamed from hash_sha1.go)4
-rw-r--r--hash_sha256_test.go (renamed from hash_sha256.go)4
-rw-r--r--hash_test.go16
-rw-r--r--loose.go24
-rw-r--r--obj.go24
-rw-r--r--obj_blob.go2
-rw-r--r--obj_commit.go16
-rw-r--r--obj_tag.go12
-rw-r--r--obj_tree.go18
-rw-r--r--objects_test.go34
-rw-r--r--pack_idx.go8
-rw-r--r--pack_midx.go4
-rw-r--r--pack_pack.go4
-rw-r--r--pack_test.go12
-rw-r--r--refs.go6
-rw-r--r--repo.go13
-rw-r--r--repo_test.go34
18 files changed, 151 insertions, 127 deletions
diff --git a/hash.go b/hash.go
index 3c6ff6ac..336d5322 100644
--- a/hash.go
+++ b/hash.go
@@ -1,17 +1,40 @@
package furgit
import (
+ "crypto/sha1"
+ "crypto/sha256"
"encoding/hex"
"fmt"
)
+const maxHashSize = 32
+
// Hash represents a Git object identifier.
-type Hash [HashSize]byte
+type Hash [maxHashSize]byte
+
+// hashFunc is a function that computes a hash from input data.
+type hashFunc func([]byte) [maxHashSize]byte
+
+// hashFuncs maps hash size to hash function.
+var hashFuncs = map[int]hashFunc{
+ sha1.Size: func(data []byte) [maxHashSize]byte {
+ var result [maxHashSize]byte
+ sum := sha1.Sum(data)
+ copy(result[:], sum[:])
+ return result
+ },
+ sha256.Size: func(data []byte) [maxHashSize]byte {
+ var result [maxHashSize]byte
+ sum := sha256.Sum256(data)
+ copy(result[:], sum[:])
+ return result
+ },
+}
-// ParseHash converts a hex string into an Hash.
-func ParseHash(s string) (Hash, error) {
+// ParseHashWithSize converts a hex string into a Hash for a given hash size.
+func ParseHashWithSize(s string, hashSize int) (Hash, error) {
var id Hash
- if len(s) != HashSize*2 {
+ if len(s) != hashSize*2 {
return id, fmt.Errorf("furgit: invalid hash length %d", len(s))
}
data, err := hex.DecodeString(s)
@@ -22,12 +45,12 @@ func ParseHash(s string) (Hash, error) {
return id, nil
}
-// String renders the ID as hex.
-func (id Hash) String() string {
- return hex.EncodeToString(id[:])
+// StringWithSize returns the ID as hex for a given hash size.
+func (id Hash) StringWithSize(hashSize int) string {
+ return hex.EncodeToString(id[:hashSize])
}
-// Bytes returns a mutable copy of the underlying bytes.
-func (id Hash) Bytes() []byte {
- return append([]byte(nil), id[:]...)
+// BytesWithSize returns a mutable copy of the underlying bytes for a given hash size.
+func (id Hash) BytesWithSize(hashSize int) []byte {
+ return append([]byte(nil), id[:hashSize]...)
}
diff --git a/hash_sha1.go b/hash_sha1_test.go
index ac2f52b5..9f3137b9 100644
--- a/hash_sha1.go
+++ b/hash_sha1_test.go
@@ -6,6 +6,4 @@ import (
"crypto/sha1"
)
-const HashSize = sha1.Size
-
-var newHash = sha1.Sum
+const testHashSize = sha1.Size
diff --git a/hash_sha256.go b/hash_sha256_test.go
index e35eab34..0b735f0a 100644
--- a/hash_sha256.go
+++ b/hash_sha256_test.go
@@ -6,6 +6,4 @@ import (
"crypto/sha256"
)
-const HashSize = sha256.Size
-
-var newHash = sha256.Sum256
+const testHashSize = sha256.Size
diff --git a/hash_test.go b/hash_test.go
index e09eadfb..4b359c4a 100644
--- a/hash_test.go
+++ b/hash_test.go
@@ -7,24 +7,24 @@ import (
func TestParseHashValidAndInvalid(t *testing.T) {
pattern := "0123456789abcdef"
- repeats := (HashSize*2 + len(pattern) - 1) / len(pattern)
- hexStr := strings.Repeat(pattern, repeats)[:HashSize*2]
+ repeats := (testHashSize*2 + len(pattern) - 1) / len(pattern)
+ hexStr := strings.Repeat(pattern, repeats)[:testHashSize*2]
- id, err := ParseHash(hexStr)
+ id, err := ParseHashWithSize(hexStr, testHashSize)
if err != nil {
t.Fatalf("ParseHash returned error: %v", err)
}
- if got := id.String(); got != hexStr {
+ if got := id.StringWithSize(testHashSize); got != hexStr {
t.Fatalf("unexpected String result: %q", got)
}
- if _, err := ParseHash("abcd"); err == nil {
+ if _, err := ParseHashWithSize("abcd", testHashSize); err == nil {
t.Fatal("expected error for short hash")
}
- badHex := strings.Repeat("z", HashSize*2)
- if _, err := ParseHash(badHex); err == nil {
+ badHex := strings.Repeat("z", testHashSize*2)
+ if _, err := ParseHashWithSize(badHex, testHashSize); err == nil {
t.Fatal("expected error for non-hex input")
}
}
@@ -34,7 +34,7 @@ func TestHashBytesCopiesUnderlyingData(t *testing.T) {
for i := range id {
id[i] = byte(i)
}
- orig := id.Bytes()
+ orig := id.BytesWithSize(testHashSize)
orig[0] ^= 0xff
if id[0] == orig[0] {
t.Fatal("Bytes should return a copy")
diff --git a/loose.go b/loose.go
index 0951bc5f..c1371991 100644
--- a/loose.go
+++ b/loose.go
@@ -12,8 +12,8 @@ import (
const looseHeaderLimit = 4096
-func loosePath(id Hash) string {
- hex := id.String()
+func loosePath(id Hash, hashSize int) string {
+ hex := id.StringWithSize(hashSize)
return filepath.Join("objects", hex[:2], hex[2:])
}
@@ -22,11 +22,11 @@ func (repo *Repository) looseRead(id Hash) (Object, error) {
if err != nil {
return nil, err
}
- return parseObjectBody(ty, id, body)
+ return parseObjectBody(ty, id, body, repo.HashSize)
}
func (repo *Repository) looseReadTyped(id Hash) (ObjType, []byte, error) {
- path := repo.repoPath(loosePath(id))
+ path := repo.repoPath(loosePath(id, repo.HashSize))
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
@@ -62,7 +62,7 @@ func (repo *Repository) looseReadTyped(id Hash) (ObjType, []byte, error) {
if declaredSize != int64(len(body)) {
return ObjInvalid, nil, ErrInvalidObject
}
- if !verifyRawObject(raw, id) {
+ if !verifyRawObject(raw, id, repo.HashSize) {
return ObjInvalid, nil, ErrInvalidObject
}
@@ -71,7 +71,7 @@ func (repo *Repository) looseReadTyped(id Hash) (ObjType, []byte, error) {
}
func (repo *Repository) looseTypeSize(id Hash) (ObjType, int64, error) {
- path := repo.repoPath(loosePath(id))
+ path := repo.repoPath(loosePath(id, repo.HashSize))
// #nosec G304
f, err := os.Open(path)
if err != nil {
@@ -161,13 +161,13 @@ func (repo *Repository) WriteLooseObject(obj Object) (Hash, error) {
switch o := obj.(type) {
case *Blob:
- raw, err = o.Serialize()
+ raw, err = o.Serialize(repo.HashSize)
case *Tree:
- raw, err = o.Serialize()
+ raw, err = o.Serialize(repo.HashSize)
case *Commit:
- raw, err = o.Serialize()
+ raw, err = o.Serialize(repo.HashSize)
case *Tag:
- raw, err = o.Serialize()
+ raw, err = o.Serialize(repo.HashSize)
default:
return Hash{}, fmt.Errorf("furgit: unsupported object type for writing: %T", obj)
}
@@ -177,8 +177,8 @@ func (repo *Repository) WriteLooseObject(obj Object) (Hash, error) {
return Hash{}, err
}
- id := computeRawHash(raw)
- path := repo.repoPath(loosePath(id))
+ id := computeRawHash(raw, repo.HashSize)
+ path := repo.repoPath(loosePath(id, repo.HashSize))
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return Hash{}, err
diff --git a/obj.go b/obj.go
index 38f18b55..ce3d0258 100644
--- a/obj.go
+++ b/obj.go
@@ -33,11 +33,9 @@ type Object interface {
ObjType() ObjType
}
-func computeRawHash(data []byte) Hash {
- var id Hash
- sum := newHash(data)
- copy(id[:], sum[:])
- return id
+func computeRawHash(data []byte, hashSize int) Hash {
+ hashFunc := hashFuncs[hashSize]
+ return hashFunc(data)
}
func headerForType(ty ObjType, body []byte) ([]byte, error) {
@@ -66,11 +64,11 @@ func headerForType(ty ObjType, body []byte) ([]byte, error) {
return buf.Bytes(), nil
}
-func verifyRawObject(buf []byte, want Hash) bool {
- return computeRawHash(buf) == want
+func verifyRawObject(buf []byte, want Hash, hashSize int) bool {
+ return computeRawHash(buf, hashSize) == want
}
-func verifyTypedObject(ty ObjType, body []byte, want Hash) bool {
+func verifyTypedObject(ty ObjType, body []byte, want Hash, hashSize int) bool {
header, err := headerForType(ty, body)
if err != nil {
return false
@@ -78,19 +76,19 @@ func verifyTypedObject(ty ObjType, body []byte, want Hash) bool {
raw := make([]byte, len(header)+len(body))
copy(raw, header)
copy(raw[len(header):], body)
- return computeRawHash(raw) == want
+ return computeRawHash(raw, hashSize) == want
}
-func parseObjectBody(ty ObjType, id Hash, body []byte) (Object, error) {
+func parseObjectBody(ty ObjType, id Hash, body []byte, hashSize int) (Object, error) {
switch ty {
case ObjBlob:
return parseBlob(id, body)
case ObjTree:
- return parseTree(id, body)
+ return parseTree(id, body, hashSize)
case ObjCommit:
- return parseCommit(id, body)
+ return parseCommit(id, body, hashSize)
case ObjTag:
- return parseTag(id, body)
+ return parseTag(id, body, hashSize)
case ObjInvalid, ObjFuture, ObjOfsDelta, ObjRefDelta:
return nil, fmt.Errorf("furgit: object: unsupported type %d", ty)
default:
diff --git a/obj_blob.go b/obj_blob.go
index 5ae0c40e..9edad0a9 100644
--- a/obj_blob.go
+++ b/obj_blob.go
@@ -21,7 +21,7 @@ func parseBlob(id Hash, body []byte) (*Blob, error) {
}
// Serialize renders the full "blob size\\0body" representation.
-func (b *Blob) Serialize() ([]byte, error) {
+func (b *Blob) Serialize(hashSize int) ([]byte, error) {
header, err := headerForType(ObjBlob, b.Data)
if err != nil {
return nil, err
diff --git a/obj_commit.go b/obj_commit.go
index d40ae784..84de2c41 100644
--- a/obj_commit.go
+++ b/obj_commit.go
@@ -22,7 +22,7 @@ func (*Commit) ObjType() ObjType {
return ObjCommit
}
-func parseCommit(id Hash, body []byte) (*Commit, error) {
+func parseCommit(id Hash, body []byte, hashSize int) (*Commit, error) {
c := new(Commit)
c.Hash = id
i := 0
@@ -39,13 +39,13 @@ func parseCommit(id Hash, body []byte) (*Commit, error) {
switch {
case bytes.HasPrefix(line, []byte("tree ")):
- treeID, err := ParseHash(string(line[5:]))
+ treeID, err := ParseHashWithSize(string(line[5:]), hashSize)
if err != nil {
return nil, fmt.Errorf("furgit: commit: tree: %w", err)
}
c.Tree = treeID
case bytes.HasPrefix(line, []byte("parent ")):
- parent, err := ParseHash(string(line[7:]))
+ parent, err := ParseHashWithSize(string(line[7:]), hashSize)
if err != nil {
return nil, fmt.Errorf("furgit: commit: parent: %w", err)
}
@@ -91,11 +91,11 @@ func parseCommit(id Hash, body []byte) (*Commit, error) {
return c, nil
}
-func commitBody(c *Commit) []byte {
+func commitBody(c *Commit, hashSize int) []byte {
var buf bytes.Buffer
- fmt.Fprintf(&buf, "tree %s\n", c.Tree.String())
+ fmt.Fprintf(&buf, "tree %s\n", c.Tree.StringWithSize(hashSize))
for _, p := range c.Parents {
- fmt.Fprintf(&buf, "parent %s\n", p.String())
+ fmt.Fprintf(&buf, "parent %s\n", p.StringWithSize(hashSize))
}
buf.WriteString("author ")
buf.Write(c.Author.Serialize())
@@ -110,8 +110,8 @@ func commitBody(c *Commit) []byte {
}
// Serialize renders a Commit into canonical Git format.
-func (c *Commit) Serialize() ([]byte, error) {
- body := commitBody(c)
+func (c *Commit) Serialize(hashSize int) ([]byte, error) {
+ body := commitBody(c, hashSize)
header, err := headerForType(ObjCommit, body)
if err != nil {
return nil, err
diff --git a/obj_tag.go b/obj_tag.go
index 6d26fe80..bf9ee97d 100644
--- a/obj_tag.go
+++ b/obj_tag.go
@@ -22,7 +22,7 @@ func (*Tag) ObjType() ObjType {
}
// parseTag parses a tag object body.
-func parseTag(id Hash, body []byte) (*Tag, error) {
+func parseTag(id Hash, body []byte, hashSize int) (*Tag, error) {
t := new(Tag)
t.Hash = id
i := 0
@@ -41,7 +41,7 @@ func parseTag(id Hash, body []byte) (*Tag, error) {
switch {
case bytes.HasPrefix(line, []byte("object ")):
- hash, err := ParseHash(string(line[7:]))
+ hash, err := ParseHashWithSize(string(line[7:]), hashSize)
if err != nil {
return nil, fmt.Errorf("furgit: tag: object: %w", err)
}
@@ -94,9 +94,9 @@ func parseTag(id Hash, body []byte) (*Tag, error) {
return t, nil
}
-func tagBody(t *Tag) ([]byte, error) {
+func tagBody(t *Tag, hashSize int) ([]byte, error) {
var buf bytes.Buffer
- fmt.Fprintf(&buf, "object %s\n", t.Target.String())
+ fmt.Fprintf(&buf, "object %s\n", t.Target.StringWithSize(hashSize))
buf.WriteString("type ")
switch t.TargetType {
case ObjCommit:
@@ -128,8 +128,8 @@ func tagBody(t *Tag) ([]byte, error) {
}
// Serialize renders a Tag into canonical Git format.
-func (t *Tag) Serialize() ([]byte, error) {
- body, err := tagBody(t)
+func (t *Tag) Serialize(hashSize int) ([]byte, error) {
+ body, err := tagBody(t, hashSize)
if err != nil {
return nil, err
}
diff --git a/obj_tree.go b/obj_tree.go
index abf23adc..55a27a08 100644
--- a/obj_tree.go
+++ b/obj_tree.go
@@ -26,7 +26,7 @@ func (*Tree) ObjType() ObjType {
}
// parseTree decodes a tree body.
-func parseTree(id Hash, body []byte) (*Tree, error) {
+func parseTree(id Hash, body []byte, hashSize int) (*Tree, error) {
var entries []TreeEntry
i := 0
for i < len(body) {
@@ -44,12 +44,12 @@ func parseTree(id Hash, body []byte) (*Tree, error) {
nameBytes := body[i : i+nul]
i += nul + 1
- if i+HashSize > len(body) {
+ if i+hashSize > len(body) {
return nil, errors.New("furgit: tree: truncated child hash")
}
var child Hash
- copy(child[:], body[i:i+HashSize])
- i += HashSize
+ copy(child[:], body[i:i+hashSize])
+ i += hashSize
mode, err := strconv.ParseUint(string(modeBytes), 8, 32)
if err != nil {
@@ -71,11 +71,11 @@ func parseTree(id Hash, body []byte) (*Tree, error) {
}
// treeBody builds the entry list for a tree without the Git header.
-func treeBody(t *Tree) []byte {
+func treeBody(t *Tree, hashSize int) []byte {
var bodyLen int
for _, e := range t.Entries {
mode := strconv.FormatUint(uint64(e.Mode), 8)
- bodyLen += len(mode) + 1 + len(e.Name) + 1 + HashSize
+ bodyLen += len(mode) + 1 + len(e.Name) + 1 + hashSize
}
body := make([]byte, bodyLen)
@@ -88,15 +88,15 @@ func treeBody(t *Tree) []byte {
pos += copy(body[pos:], e.Name)
body[pos] = 0
pos++
- pos += copy(body[pos:], e.ID[:])
+ pos += copy(body[pos:], e.ID[:hashSize])
}
return body
}
// Serialize renders a Tree into canonical Git format.
-func (t *Tree) Serialize() ([]byte, error) {
- body := treeBody(t)
+func (t *Tree) Serialize(hashSize int) ([]byte, error) {
+ body := treeBody(t, hashSize)
header, err := headerForType(ObjTree, body)
if err != nil {
return nil, err
diff --git a/objects_test.go b/objects_test.go
index 2bb0d486..3a3e641f 100644
--- a/objects_test.go
+++ b/objects_test.go
@@ -9,7 +9,7 @@ import (
)
func mustHash(t *testing.T, hex string) Hash {
- id, err := ParseHash(hex)
+ id, err := ParseHashWithSize(hex, testHashSize)
if err != nil {
t.Fatalf("ParseHash failed: %v", err)
}
@@ -18,7 +18,7 @@ func mustHash(t *testing.T, hex string) Hash {
func hashWithByte(fill byte) Hash {
var h Hash
- for i := range h {
+ for i := 0; i < testHashSize; i++ {
h[i] = fill
fill++
}
@@ -27,11 +27,11 @@ func hashWithByte(fill byte) Hash {
func TestLoosePathUsesExpectedLayout(t *testing.T) {
pattern := "0123456789abcdef"
- repeats := (HashSize*2 + len(pattern) - 1) / len(pattern)
- hexStr := strings.Repeat(pattern, repeats)[:HashSize*2]
+ repeats := (testHashSize*2 + len(pattern) - 1) / len(pattern)
+ hexStr := strings.Repeat(pattern, repeats)[:testHashSize*2]
id := mustHash(t, hexStr)
expect := filepath.Join("objects", hexStr[:2], hexStr[2:])
- if got := loosePath(id); got != expect {
+ if got := loosePath(id, testHashSize); got != expect {
t.Fatalf("unexpected loose path: %q", got)
}
}
@@ -49,7 +49,7 @@ func TestParseBlobAndSerialize(t *testing.T) {
if blob.Hash != id {
t.Fatalf("blob hash mismatch: %v", blob.Hash)
}
- raw, err := blob.Serialize()
+ raw, err := blob.Serialize(testHashSize)
if err != nil {
t.Fatalf("Serialize error: %v", err)
}
@@ -68,9 +68,9 @@ func TestParseTreeAndSerialize(t *testing.T) {
{Mode: 0100644, Name: []byte("file.txt"), ID: hashWithByte(0x20)},
{Mode: 040000, Name: []byte("subdir"), ID: hashWithByte(0x30)},
}
- body := treeBody(&Tree{Entries: entries})
+ body := treeBody(&Tree{Entries: entries}, testHashSize)
id := hashWithByte(0x40)
- tree, err := parseTree(id, body)
+ tree, err := parseTree(id, body, testHashSize)
if err != nil {
t.Fatalf("parseTree error: %v", err)
}
@@ -82,7 +82,7 @@ func TestParseTreeAndSerialize(t *testing.T) {
t.Fatalf("entry %d mismatch", i)
}
}
- serialized, err := (&Tree{Entries: entries}).Serialize()
+ serialized, err := (&Tree{Entries: entries}).Serialize(testHashSize)
if err != nil {
t.Fatalf("Serialize error: %v", err)
}
@@ -103,8 +103,8 @@ func TestParseCommitWithExtraHeader(t *testing.T) {
OffsetMinutes: -420,
}
var buf bytes.Buffer
- fmt.Fprintf(&buf, "tree %s\n", treeID.String())
- fmt.Fprintf(&buf, "parent %s\n", parent.String())
+ fmt.Fprintf(&buf, "tree %s\n", treeID.StringWithSize(testHashSize))
+ fmt.Fprintf(&buf, "parent %s\n", parent.StringWithSize(testHashSize))
buf.WriteString("author ")
buf.Write(ident.Serialize())
buf.WriteByte('\n')
@@ -112,7 +112,7 @@ func TestParseCommitWithExtraHeader(t *testing.T) {
buf.Write(ident.Serialize())
buf.WriteByte('\n')
buf.WriteString("extra data\n\nMessage body\n")
- commit, err := parseCommit(hashWithByte(0x70), buf.Bytes())
+ commit, err := parseCommit(hashWithByte(0x70), buf.Bytes(), testHashSize)
if err != nil {
t.Fatalf("parseCommit error: %v", err)
}
@@ -136,11 +136,11 @@ func TestParseCommitWithExtraHeader(t *testing.T) {
Committer: ident,
Message: []byte("Message body\n"),
}
- raw, err := roundTrip.Serialize()
+ raw, err := roundTrip.Serialize(testHashSize)
if err != nil {
t.Fatalf("Serialize error: %v", err)
}
- if !strings.Contains(string(raw), "tree "+treeID.String()) {
+ if !strings.Contains(string(raw), "tree "+treeID.StringWithSize(testHashSize)) {
t.Fatalf("serialized commit missing tree header")
}
}
@@ -155,7 +155,7 @@ func TestParseTagAndSerialize(t *testing.T) {
}
var buf bytes.Buffer
buf.WriteString("object ")
- buf.WriteString(target.String())
+ buf.WriteString(target.StringWithSize(testHashSize))
buf.WriteByte('\n')
buf.WriteString("type commit\n")
buf.WriteString("tag v1.0\n")
@@ -163,7 +163,7 @@ func TestParseTagAndSerialize(t *testing.T) {
buf.Write(tagger.Serialize())
buf.WriteString("\n\nannotated tag\n")
body := append([]byte(nil), buf.Bytes()...)
- tag, err := parseTag(hashWithByte(0x90), body)
+ tag, err := parseTag(hashWithByte(0x90), body, testHashSize)
if err != nil {
t.Fatalf("parseTag error: %v", err)
}
@@ -179,7 +179,7 @@ func TestParseTagAndSerialize(t *testing.T) {
if string(tag.Name) != "v1.0" {
t.Fatalf("tag name mismatch: %q", tag.Name)
}
- serialized, err := tag.Serialize()
+ serialized, err := tag.Serialize(testHashSize)
if err != nil {
t.Fatalf("Serialize error: %v", err)
}
diff --git a/pack_idx.go b/pack_idx.go
index ede8c2e6..9ca28193 100644
--- a/pack_idx.go
+++ b/pack_idx.go
@@ -162,7 +162,7 @@ func (pi *packIndex) parse(buf []byte) error {
nobj := int(readBE32(pi.fanout[len(pi.fanout)-4:]))
namesStart := fanoutEnd
- namesEnd := namesStart + nobj*HashSize
+ namesEnd := namesStart + nobj*pi.repo.HashSize
if namesEnd > len(buf) {
return ErrInvalidObject
}
@@ -182,7 +182,7 @@ func (pi *packIndex) parse(buf []byte) error {
pi.offset32 = buf[off32Start:off32End]
off64Start := off32End
- trailerStart := len(buf) - 2*HashSize
+ trailerStart := len(buf) - 2*pi.repo.HashSize
if trailerStart < off64Start {
return ErrInvalidObject
}
@@ -249,7 +249,7 @@ func (pi *packIndex) lookup(id Hash) (packlocation, error) {
lo = int(pi.fanoutEntry(first - 1))
}
hi := int(pi.fanoutEntry(first))
- idx, found := bsearchHash(pi.names, HashSize, lo, hi, id)
+ idx, found := bsearchHash(pi.names, pi.repo.HashSize, lo, hi, id)
if !found {
return packlocation{}, ErrNotFound
}
@@ -266,7 +266,7 @@ func (pi *packIndex) lookup(id Hash) (packlocation, error) {
func bsearchHash(names []byte, stride, lo, hi int, want Hash) (int, bool) {
for lo < hi {
mid := lo + (hi-lo)/2
- cmp := compareHash(names, stride, mid, want[:])
+ cmp := compareHash(names, stride, mid, want[:stride])
if cmp == 0 {
return mid, true
}
diff --git a/pack_midx.go b/pack_midx.go
index f37badbf..7f31e565 100644
--- a/pack_midx.go
+++ b/pack_midx.go
@@ -197,7 +197,7 @@ func (midx *multiPackIndex) parse(buf []byte) error {
if !ok {
return ErrInvalidObject
}
- oidlSize := int64(numObjects) * int64(HashSize)
+ oidlSize := int64(numObjects) * int64(midx.repo.HashSize)
if oidlOffset < 0 || oidlOffset+oidlSize > int64(len(buf)) {
return ErrInvalidObject
}
@@ -252,7 +252,7 @@ func (midx *multiPackIndex) lookup(id Hash) (packlocation, error) {
}
hi := int(readBE32(midx.fanout[first*4 : (first+1)*4]))
- idx, found := bsearchHash(midx.oids, HashSize, lo, hi, id)
+ idx, found := bsearchHash(midx.oids, midx.repo.HashSize, lo, hi, id)
if !found {
return packlocation{}, ErrNotFound
}
diff --git a/pack_pack.go b/pack_pack.go
index eed3a4af..757c5c02 100644
--- a/pack_pack.go
+++ b/pack_pack.go
@@ -69,11 +69,11 @@ func (repo *Repository) packReadAt(loc packlocation, want Hash) (Object, error)
return nil, err
}
data := body.Bytes()
- if !verifyTypedObject(ty, data, want) {
+ if !verifyTypedObject(ty, data, want, repo.HashSize) {
body.Release()
return nil, ErrInvalidObject
}
- obj, err := parseObjectBody(ty, want, data)
+ obj, err := parseObjectBody(ty, want, data, repo.HashSize)
body.Release()
return obj, err
}
diff --git a/pack_test.go b/pack_test.go
index 1dae5d3a..48f4d604 100644
--- a/pack_test.go
+++ b/pack_test.go
@@ -153,12 +153,12 @@ func TestPackDeltaReadOfsDistance(t *testing.T) {
func TestBsearchHash(t *testing.T) {
h1 := hashWithByte(0x01)
h2 := hashWithByte(0x03)
- names := append(append([]byte(nil), h1[:]...), h2[:]...)
- idx, found := bsearchHash(names, HashSize, 0, 2, h2)
+ names := append(append([]byte(nil), h1[:testHashSize]...), h2[:testHashSize]...)
+ idx, found := bsearchHash(names, testHashSize, 0, 2, h2)
if !found || idx != 1 {
t.Fatalf("expected to find second hash, idx=%d found=%v", idx, found)
}
- _, found = bsearchHash(names, HashSize, 0, 2, hashWithByte(0x05))
+ _, found = bsearchHash(names, testHashSize, 0, 2, hashWithByte(0x05))
if found {
t.Fatalf("did not expect to find unknown hash")
}
@@ -178,19 +178,19 @@ func buildTestPackIndexBuffer(hash Hash, offset uint32) []byte {
_ = binary.Write(&buf, binary.BigEndian, uint32(idxMagic))
_ = binary.Write(&buf, binary.BigEndian, uint32(idxVersion2))
buf.Write(fanout)
- buf.Write(hash[:])
+ buf.Write(hash[:testHashSize])
buf.Write(make([]byte, 4))
off32 := make([]byte, 4)
binary.BigEndian.PutUint32(off32, offset)
buf.Write(off32)
- buf.Write(make([]byte, 2*HashSize))
+ buf.Write(make([]byte, 2*testHashSize))
return buf.Bytes()
}
func TestPackIndexParse(t *testing.T) {
h := hashWithByte(0x11)
data := buildTestPackIndexBuffer(h, 0x12345678)
- pi := &packIndex{}
+ pi := &packIndex{repo: &Repository{HashSize: testHashSize}}
if err := pi.parse(data); err != nil {
t.Fatalf("parse error: %v", err)
}
diff --git a/refs.go b/refs.go
index df3a8303..b543a842 100644
--- a/refs.go
+++ b/refs.go
@@ -29,7 +29,7 @@ func (repo *Repository) resolveLooseRef(refname string) (Hash, error) {
return Hash{}, err
}
line := strings.TrimSpace(string(data))
- id, err := ParseHash(line)
+ id, err := ParseHashWithSize(line, repo.HashSize)
if err != nil {
return Hash{}, err
}
@@ -55,13 +55,13 @@ func (repo *Repository) resolvePackedRef(refname string) (Hash, error) {
continue
}
sp := bytes.IndexByte(line, ' ')
- if sp != HashSize*2 {
+ if sp != repo.HashSize*2 {
continue
}
name := line[sp+1:]
if bytes.Equal(name, want) {
hex := string(line[:sp])
- id, err := ParseHash(hex)
+ id, err := ParseHashWithSize(hex, repo.HashSize)
if err != nil {
return Hash{}, err
}
diff --git a/repo.go b/repo.go
index c0d056bc..fb835edb 100644
--- a/repo.go
+++ b/repo.go
@@ -1,6 +1,7 @@
package furgit
import (
+ "fmt"
"os"
"path/filepath"
"sync"
@@ -9,6 +10,7 @@ import (
// Repository represents the root of a Git repository.
type Repository struct {
rootPath string
+ HashSize int
packIdxOnce sync.Once
packIdx []*packIndex
@@ -22,8 +24,10 @@ type Repository struct {
closeOnce sync.Once
}
-// OpenRepository opens the repository at the provided path.
-func OpenRepository(path string) (*Repository, error) {
+// OpenRepository opens the repository at the provided path with the specified hash size.
+// This will be replaced later with a function that auto-detects the hash size based
+// on the git configuration.
+func OpenRepository(path string, hashSize int) (*Repository, error) {
fi, err := os.Stat(path)
if err != nil {
return nil, err
@@ -31,7 +35,10 @@ func OpenRepository(path string) (*Repository, error) {
if !fi.IsDir() {
return nil, ErrInvalidObject
}
- return &Repository{rootPath: path}, nil
+ if _, ok := hashFuncs[hashSize]; !ok {
+ return nil, fmt.Errorf("furgit: unsupported hash size %d", hashSize)
+ }
+ return &Repository{rootPath: path, HashSize: hashSize}, nil
}
func (r *Repository) Close() error {
diff --git a/repo_test.go b/repo_test.go
index 4b46d49a..d09d2642 100644
--- a/repo_test.go
+++ b/repo_test.go
@@ -23,7 +23,7 @@ func writeLooseBlob(t *testing.T, repo *Repository, data []byte) Hash {
func TestOpenRepositoryAndLooseRead(t *testing.T) {
root := t.TempDir()
- repo, err := OpenRepository(root)
+ repo, err := OpenRepository(root, testHashSize)
if err != nil {
t.Fatalf("OpenRepository error: %v", err)
}
@@ -45,7 +45,7 @@ func TestOpenRepositoryAndLooseRead(t *testing.T) {
func TestResolveRefLooseAndPacked(t *testing.T) {
root := t.TempDir()
- repo, err := OpenRepository(root)
+ repo, err := OpenRepository(root, testHashSize)
if err != nil {
t.Fatalf("OpenRepository error: %v", err)
}
@@ -56,7 +56,7 @@ func TestResolveRefLooseAndPacked(t *testing.T) {
if err := os.MkdirAll(loosePath, 0o755); err != nil {
t.Fatalf("mkdir refs: %v", err)
}
- if err := os.WriteFile(filepath.Join(loosePath, "master"), []byte(looseID.String()+"\n"), 0o644); err != nil {
+ if err := os.WriteFile(filepath.Join(loosePath, "master"), []byte(looseID.StringWithSize(testHashSize)+"\n"), 0o644); err != nil {
t.Fatalf("write ref: %v", err)
}
id, err := repo.ResolveRef("refs/heads/master")
@@ -65,7 +65,7 @@ func TestResolveRefLooseAndPacked(t *testing.T) {
}
packedID := hashWithByte(0xb0)
- packed := fmt.Sprintf("%s refs/tags/v1\n", packedID.String())
+ packed := fmt.Sprintf("%s refs/tags/v1\n", packedID.StringWithSize(testHashSize))
if err := os.WriteFile(filepath.Join(root, "packed-refs"), []byte(packed), 0o644); err != nil {
t.Fatalf("write packed refs: %v", err)
}
@@ -85,7 +85,7 @@ func TestResolveRefLooseAndPacked(t *testing.T) {
func TestResolveHEAD(t *testing.T) {
root := t.TempDir()
- repo, err := OpenRepository(root)
+ repo, err := OpenRepository(root, testHashSize)
if err != nil {
t.Fatalf("OpenRepository error: %v", err)
}
@@ -110,7 +110,7 @@ func TestResolveHEAD(t *testing.T) {
func TestReadObjectTypeSizeLoose(t *testing.T) {
t.Parallel()
root := t.TempDir()
- repo, err := OpenRepository(root)
+ repo, err := OpenRepository(root, testHashSize)
if err != nil {
t.Fatalf("OpenRepository error: %v", err)
}
@@ -142,7 +142,7 @@ func TestReadObjectTypeSizePackedObjects(t *testing.T) {
}
ids := writeTestPack(t, root, "pack-basic", objs)
- repo, err := OpenRepository(root)
+ repo, err := OpenRepository(root, testHashSize)
if err != nil {
t.Fatalf("OpenRepository error: %v", err)
}
@@ -169,7 +169,7 @@ func TestReadObjectTypeSizePackRefDeltaLooseBase(t *testing.T) {
t.Parallel()
root := t.TempDir()
- repo, err := OpenRepository(root)
+ repo, err := OpenRepository(root, testHashSize)
if err != nil {
t.Fatalf("OpenRepository error: %v", err)
}
@@ -200,7 +200,7 @@ func TestReadObjectTypeSizePackRefDeltaLooseBase(t *testing.T) {
func TestWriteLooseObjectAllTypes(t *testing.T) {
root := t.TempDir()
- repo, err := OpenRepository(root)
+ repo, err := OpenRepository(root, testHashSize)
if err != nil {
t.Fatalf("OpenRepository error: %v", err)
}
@@ -358,7 +358,7 @@ func writeTestPack(t *testing.T, root, name string, objs []testPackObject) []Has
raw := make([]byte, len(header)+len(obj.body))
copy(raw, header)
copy(raw[len(header):], obj.body)
- ids[i] = computeRawHash(raw)
+ ids[i] = computeRawHash(raw, testHashSize)
switch obj.encoding {
case packEncodingFull:
@@ -392,8 +392,8 @@ func writeTestPack(t *testing.T, root, name string, objs []testPackObject) []Has
}
packContent := append([]byte(nil), buf.Bytes()...)
- packChecksum := newHash(packContent)
- buf.Write(packChecksum[:])
+ packChecksum := computeRawHash(packContent, testHashSize)
+ buf.Write(packChecksum[:testHashSize])
packBytes := buf.Bytes()
packPath := filepath.Join(packDir, name+".pack")
@@ -406,7 +406,7 @@ func writeTestPack(t *testing.T, root, name string, objs []testPackObject) []Has
return ids
}
-func writeTestPackIndex(t *testing.T, packDir, name string, ids []Hash, offsets []uint64, packChecksum [HashSize]byte) {
+func writeTestPackIndex(t *testing.T, packDir, name string, ids []Hash, offsets []uint64, packChecksum Hash) {
t.Helper()
type idxEntry struct {
id Hash
@@ -445,7 +445,7 @@ func writeTestPackIndex(t *testing.T, packDir, name string, ids []Hash, offsets
}
for _, entry := range entries {
- buf.Write(entry.id[:])
+ buf.Write(entry.id[:testHashSize])
}
buf.Write(make([]byte, len(entries)*4))
@@ -460,9 +460,9 @@ func writeTestPackIndex(t *testing.T, packDir, name string, ids []Hash, offsets
}
idxData := append([]byte(nil), buf.Bytes()...)
- idxChecksum := newHash(idxData)
- buf.Write(packChecksum[:])
- buf.Write(idxChecksum[:])
+ idxChecksum := computeRawHash(idxData, testHashSize)
+ buf.Write(packChecksum[:testHashSize])
+ buf.Write(idxChecksum[:testHashSize])
idxPath := filepath.Join(packDir, name+".idx")
err = os.WriteFile(idxPath, buf.Bytes(), 0o600)