diff options
| -rw-r--r-- | hash.go | 43 | ||||
| -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.go | 16 | ||||
| -rw-r--r-- | loose.go | 24 | ||||
| -rw-r--r-- | obj.go | 24 | ||||
| -rw-r--r-- | obj_blob.go | 2 | ||||
| -rw-r--r-- | obj_commit.go | 16 | ||||
| -rw-r--r-- | obj_tag.go | 12 | ||||
| -rw-r--r-- | obj_tree.go | 18 | ||||
| -rw-r--r-- | objects_test.go | 34 | ||||
| -rw-r--r-- | pack_idx.go | 8 | ||||
| -rw-r--r-- | pack_midx.go | 4 | ||||
| -rw-r--r-- | pack_pack.go | 4 | ||||
| -rw-r--r-- | pack_test.go | 12 | ||||
| -rw-r--r-- | refs.go | 6 | ||||
| -rw-r--r-- | repo.go | 13 | ||||
| -rw-r--r-- | repo_test.go | 34 |
18 files changed, 151 insertions, 127 deletions
@@ -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") @@ -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 @@ -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 @@ -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) } @@ -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 } @@ -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) |
