git/odb/pack: promote IndexVersion to interface
This commit is contained in:
parent
735619d3b3
commit
069035c105
@ -1,6 +1,7 @@
|
||||
package pack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/git-lfs/git-lfs/errors"
|
||||
@ -71,17 +72,16 @@ func (i *Index) Entry(name []byte) (*IndexEntry, error) {
|
||||
// Find the midpoint between the upper and lower bounds.
|
||||
mid := bounds.Left() + ((bounds.Right() - bounds.Left()) / 2)
|
||||
|
||||
// Search for the given object at that midpoint.
|
||||
entry, cmp, err := i.version.Search(i, name, mid)
|
||||
got, err := i.version.Name(i, mid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cmp == 0 {
|
||||
if cmp := bytes.Compare(name, got); cmp == 0 {
|
||||
// If "cmp" is zero, that means the object at that index
|
||||
// "at" had a SHA equal to the one given by name, and we
|
||||
// are done.
|
||||
return entry, nil
|
||||
return i.version.Entry(i, mid)
|
||||
} else if cmp < 0 {
|
||||
// If the comparison is less than 0, we searched past
|
||||
// the desired object, so limit the upper bound of the
|
||||
|
@ -93,24 +93,26 @@ func DecodeIndex(r io.ReaderAt) (*Index, error) {
|
||||
func decodeIndexHeader(r io.ReaderAt) (IndexVersion, error) {
|
||||
hdr := make([]byte, 4)
|
||||
if _, err := r.ReadAt(hdr, 0); err != nil {
|
||||
return VersionUnknown, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Equal(hdr, indexHeader) {
|
||||
vb := make([]byte, 4)
|
||||
if _, err := r.ReadAt(vb, 4); err != nil {
|
||||
return VersionUnknown, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version := IndexVersion(binary.BigEndian.Uint32(vb))
|
||||
version := binary.BigEndian.Uint32(vb)
|
||||
switch version {
|
||||
case V1, V2:
|
||||
return version, nil
|
||||
case 1:
|
||||
return new(V1), nil
|
||||
case 2:
|
||||
return new(V2), nil
|
||||
}
|
||||
|
||||
return version, &UnsupportedVersionErr{uint32(version)}
|
||||
return nil, &UnsupportedVersionErr{uint32(version)}
|
||||
}
|
||||
return V1, nil
|
||||
return new(V1), nil
|
||||
}
|
||||
|
||||
// decodeIndexFanout decodes the fanout table given by "r" and beginning at the
|
||||
|
@ -31,7 +31,6 @@ func TestDecodeIndexV2(t *testing.T) {
|
||||
idx, err := DecodeIndex(bytes.NewReader(buf))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, V2, idx.version)
|
||||
assert.EqualValues(t, 3, idx.Count())
|
||||
}
|
||||
|
||||
@ -51,7 +50,6 @@ func TestDecodeIndexV1(t *testing.T) {
|
||||
idx, err := DecodeIndex(bytes.NewReader(make([]byte, indexFanoutWidth)))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, V1, idx.version)
|
||||
assert.EqualValues(t, 0, idx.Count())
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ func init() {
|
||||
fanout: fanout,
|
||||
// version is unimportant here, use V2 since it's more common in
|
||||
// the wild.
|
||||
version: V2,
|
||||
version: new(V2),
|
||||
|
||||
// *bytes.Buffer does not implement io.ReaderAt, but
|
||||
// *bytes.Reader does.
|
||||
|
@ -1,36 +1,40 @@
|
||||
package pack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
// V1 is an instance of IndexVersion corresponding to the V1 index file
|
||||
// format.
|
||||
V1 IndexVersion = 1
|
||||
)
|
||||
// V1 implements IndexVersion for v1 packfiles.
|
||||
type V1 struct{}
|
||||
|
||||
// v1Search implements the IndexVersion.Search method for V1 packfiles.
|
||||
func v1Search(idx *Index, name []byte, at int64) (*IndexEntry, int, error) {
|
||||
// Name implements IndexVersion.Name by returning the 20 byte SHA-1 object name
|
||||
// for the given entry at offset "at" in the v1 index file "idx".
|
||||
func (v *V1) Name(idx *Index, at int64) ([]byte, error) {
|
||||
var sha [20]byte
|
||||
if _, err := idx.readAt(sha[:], v1ShaOffset(at)); err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmp := bytes.Compare(name, sha[:])
|
||||
if cmp != 0 {
|
||||
return nil, cmp, nil
|
||||
}
|
||||
return sha[:], nil
|
||||
}
|
||||
|
||||
// Entry implements IndexVersion.Entry for v1 packfiles by parsing and returning
|
||||
// the IndexEntry specified at the offset "at" in the given index file.
|
||||
func (v *V1) Entry(idx *Index, at int64) (*IndexEntry, error) {
|
||||
var offs [4]byte
|
||||
if _, err := idx.readAt(offs[:], v1EntryOffset(at)); err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IndexEntry{
|
||||
PackOffset: uint64(binary.BigEndian.Uint32(offs[:])),
|
||||
}, 0, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Width implements IndexVersion.Width() by returning the number of bytes that
|
||||
// v1 packfile index header occupy.
|
||||
func (v *V1) Width() int64 {
|
||||
return indexV1Width
|
||||
}
|
||||
|
||||
// v1ShaOffset returns the location of the SHA1 of an object given at "at".
|
||||
|
@ -37,40 +37,19 @@ var (
|
||||
|
||||
V1Index = &Index{
|
||||
fanout: V1IndexFanout,
|
||||
version: V1,
|
||||
version: new(V1),
|
||||
}
|
||||
)
|
||||
|
||||
func TestIndexV1SearchExact(t *testing.T) {
|
||||
e, cmp, err := V1.Search(V1Index, V1IndexMediumSha, 1)
|
||||
e, err := new(V1).Entry(V1Index, 1)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, cmp)
|
||||
assert.EqualValues(t, 2, e.PackOffset)
|
||||
}
|
||||
|
||||
func TestIndexV1SearchSmall(t *testing.T) {
|
||||
e, cmp, err := V1.Search(V1Index, V1IndexMediumSha, 0)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, cmp)
|
||||
assert.Nil(t, e)
|
||||
}
|
||||
|
||||
func TestIndexV1SearchBig(t *testing.T) {
|
||||
e, cmp, err := V1.Search(V1Index, V1IndexMediumSha, 2)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, -1, cmp)
|
||||
assert.Nil(t, e)
|
||||
}
|
||||
|
||||
func TestIndexV1SearchOutOfBounds(t *testing.T) {
|
||||
e, cmp, err := V1.Search(V1Index, V1IndexMediumSha, 10)
|
||||
|
||||
assert.Nil(t, e)
|
||||
assert.Equal(t, 0, cmp)
|
||||
assert.Equal(t, ErrIndexOutOfBounds, err)
|
||||
func TestIndexVersionWidthV1(t *testing.T) {
|
||||
assert.EqualValues(t, 0, new(V1).Width())
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -1,31 +1,29 @@
|
||||
package pack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
// V2 is an instance of IndexVersion corresponding to the V2 index file
|
||||
// format.
|
||||
V2 IndexVersion = 2
|
||||
)
|
||||
// V2 implements IndexVersion for v2 packfiles.
|
||||
type V2 struct{}
|
||||
|
||||
// v2Search implements the IndexVersion.Search method for V2 packfiles.
|
||||
func v2Search(idx *Index, name []byte, at int64) (*IndexEntry, int, error) {
|
||||
// Name implements IndexVersion.Name by returning the 20 byte SHA-1 object name
|
||||
// for the given entry at offset "at" in the v2 index file "idx".
|
||||
func (v *V2) Name(idx *Index, at int64) ([]byte, error) {
|
||||
var sha [20]byte
|
||||
if _, err := idx.readAt(sha[:], v2ShaOffset(at)); err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmp := bytes.Compare(name, sha[:])
|
||||
if cmp != 0 {
|
||||
return nil, cmp, nil
|
||||
}
|
||||
return sha[:], nil
|
||||
}
|
||||
|
||||
// Entry implements IndexVersion.Entry for v2 packfiles by parsing and returning
|
||||
// the IndexEntry specified at the offset "at" in the given index file.
|
||||
func (v *V2) Entry(idx *Index, at int64) (*IndexEntry, error) {
|
||||
var offs [4]byte
|
||||
if _, err := idx.readAt(offs[:], v2SmallOffsetOffset(at, int64(idx.Count()))); err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loc := uint64(binary.BigEndian.Uint32(offs[:]))
|
||||
@ -37,12 +35,18 @@ func v2Search(idx *Index, name []byte, at int64) (*IndexEntry, int, error) {
|
||||
// offset.
|
||||
var offs [8]byte
|
||||
if _, err := idx.readAt(offs[:], int64(loc&0x7fffffff)); err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loc = binary.BigEndian.Uint64(offs[:])
|
||||
}
|
||||
return &IndexEntry{PackOffset: loc}, 0, nil
|
||||
return &IndexEntry{PackOffset: loc}, nil
|
||||
}
|
||||
|
||||
// Width implements IndexVersion.Width() by returning the number of bytes that
|
||||
// v2 packfile index header occupy.
|
||||
func (v *V2) Width() int64 {
|
||||
return indexV2Width
|
||||
}
|
||||
|
||||
// v2ShaOffset returns the offset of a SHA1 given at "at" in the V2 index file.
|
||||
|
@ -45,42 +45,28 @@ var (
|
||||
|
||||
V2Index = &Index{
|
||||
fanout: V2IndexFanout,
|
||||
version: V2,
|
||||
version: new(V2),
|
||||
}
|
||||
)
|
||||
|
||||
func TestIndexV2SearchExact(t *testing.T) {
|
||||
e, cmp, err := V2.Search(V2Index, V2IndexMediumSha, 1)
|
||||
func TestIndexV2EntryExact(t *testing.T) {
|
||||
e, err := new(V2).Entry(V2Index, 1)
|
||||
|
||||
assert.Equal(t, 0, cmp)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 2, e.PackOffset)
|
||||
}
|
||||
|
||||
func TestIndexV2SearchSmall(t *testing.T) {
|
||||
e, cmp, err := V2.Search(V2Index, V2IndexMediumSha, 0)
|
||||
func TestIndexV2EntryExtendedOffset(t *testing.T) {
|
||||
e, err := new(V2).Entry(V2Index, 2)
|
||||
|
||||
assert.Equal(t, 1, cmp)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, e)
|
||||
}
|
||||
|
||||
func TestIndexV2SearchBig(t *testing.T) {
|
||||
e, cmp, err := V2.Search(V2Index, V2IndexMediumSha, 2)
|
||||
|
||||
assert.Equal(t, -1, cmp)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, e)
|
||||
}
|
||||
|
||||
func TestIndexV2SearchExtendedOffset(t *testing.T) {
|
||||
e, cmp, err := V2.Search(V2Index, V2IndexLargeSha, 2)
|
||||
|
||||
assert.Equal(t, 0, cmp)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, e.PackOffset)
|
||||
}
|
||||
|
||||
func TestIndexVersionWidthV2(t *testing.T) {
|
||||
assert.EqualValues(t, 8, new(V2).Width())
|
||||
}
|
||||
|
||||
func init() {
|
||||
V2IndexFanout[1] = 1
|
||||
V2IndexFanout[2] = 2
|
||||
|
@ -1,54 +1,23 @@
|
||||
package pack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/git-lfs/git-lfs/errors"
|
||||
)
|
||||
|
||||
// IndexVersion is a constant type that represents the version of encoding used
|
||||
// by a particular index version.
|
||||
type IndexVersion uint32
|
||||
type IndexVersion interface {
|
||||
// Name returns the name of the object located at the given offset "at",
|
||||
// in the Index file "idx".
|
||||
//
|
||||
// It returns an error if the object at that location could not be
|
||||
// parsed.
|
||||
Name(idx *Index, at int64) ([]byte, error)
|
||||
|
||||
const (
|
||||
// VersionUnknown is the zero-value for IndexVersion, and represents an
|
||||
// unknown version.
|
||||
VersionUnknown IndexVersion = 0
|
||||
)
|
||||
// Entry parses and returns the full *IndexEntry located at the offset
|
||||
// "at" in the Index file "idx".
|
||||
//
|
||||
// If there was an error parsing the IndexEntry at that location, it
|
||||
// will be returned.
|
||||
Entry(idx *Index, at int64) (*IndexEntry, error)
|
||||
|
||||
// Width returns the width of the header given in the respective version.
|
||||
func (v IndexVersion) Width() int64 {
|
||||
switch v {
|
||||
case V2:
|
||||
return indexV2Width
|
||||
case V1:
|
||||
return indexV1Width
|
||||
}
|
||||
panic(fmt.Sprintf("git/odb/pack: width unknown for pack version %d", v))
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrIndexOutOfBounds is an error returned when the object lookup "at"
|
||||
// (see: Search() below) is out of bounds.
|
||||
ErrIndexOutOfBounds = errors.New("git/odb/pack: index is out of bounds")
|
||||
)
|
||||
|
||||
// Search searches index "idx" for an object given by "name" at location "at".
|
||||
//
|
||||
// If will return the object if it was found, or a comparison determining
|
||||
// whether to search above or below next.
|
||||
//
|
||||
// Otherwise, it will return an error.
|
||||
func (v IndexVersion) Search(idx *Index, name []byte, at int64) (*IndexEntry, int, error) {
|
||||
if at > int64(idx.Count()) {
|
||||
return nil, 0, ErrIndexOutOfBounds
|
||||
}
|
||||
|
||||
switch v {
|
||||
case V2:
|
||||
return v2Search(idx, name, at)
|
||||
case V1:
|
||||
return v1Search(idx, name, at)
|
||||
}
|
||||
return nil, 0, &UnsupportedVersionErr{Got: uint32(v)}
|
||||
// Width returns the number of bytes occupied by the header of a
|
||||
// particular index version.
|
||||
Width() int64
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
package pack
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIndexVersionWidthV1(t *testing.T) {
|
||||
assert.EqualValues(t, 0, V1.Width())
|
||||
}
|
||||
|
||||
func TestIndexVersionWidthV2(t *testing.T) {
|
||||
assert.EqualValues(t, 8, V2.Width())
|
||||
}
|
||||
|
||||
func TestIndexVersionWidthPanicsOnUnknownVersion(t *testing.T) {
|
||||
v := IndexVersion(5)
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Fatal("git/odb/pack: expected IndexVersion.Width() to panic()")
|
||||
}
|
||||
|
||||
assert.Equal(t, "git/odb/pack: width unknown for pack version 5", err)
|
||||
}()
|
||||
|
||||
v.Width()
|
||||
}
|
Loading…
Reference in New Issue
Block a user