135 lines
3.9 KiB
Go
135 lines
3.9 KiB
Go
package pack
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
|
)
|
|
|
|
// Index stores information about the location of objects in a corresponding
|
|
// packfile.
|
|
type Index struct {
|
|
// version is the encoding version used by this index.
|
|
//
|
|
// Currently, versions 1 and 2 are supported.
|
|
version IndexVersion
|
|
// fanout is the L1 fanout table stored in this index. For a given index
|
|
// "i" into the array, the value stored at that index specifies the
|
|
// number of objects in the packfile/index that are lexicographically
|
|
// less than or equal to that index.
|
|
//
|
|
// See: https://github.com/git/git/blob/v2.13.0/Documentation/technical/pack-format.txt#L41-L45
|
|
fanout []uint32
|
|
|
|
// r is the underlying set of encoded data comprising this index file.
|
|
r io.ReaderAt
|
|
}
|
|
|
|
// Count returns the number of objects in the packfile.
|
|
func (i *Index) Count() int {
|
|
return int(i.fanout[255])
|
|
}
|
|
|
|
var (
|
|
// errNotFound is an error returned by Index.Entry() (see: below) when
|
|
// an object cannot be found in the index.
|
|
errNotFound = errors.New("git/odb/pack: object not found in index")
|
|
)
|
|
|
|
// IsNotFound returns whether a given error represents a missing object in the
|
|
// index.
|
|
func IsNotFound(err error) bool {
|
|
return err == errNotFound
|
|
}
|
|
|
|
// Entry returns an entry containing the offset of a given SHA1 "name".
|
|
//
|
|
// Entry operates in O(log(n))-time in the worst case, where "n" is the number
|
|
// of objects that begin with the first byte of "name".
|
|
//
|
|
// If the entry cannot be found, (nil, ErrNotFound) will be returned. If there
|
|
// was an error searching for or parsing an entry, it will be returned as (nil,
|
|
// err).
|
|
//
|
|
// Otherwise, (entry, nil) will be returned.
|
|
func (i *Index) Entry(name []byte) (*IndexEntry, error) {
|
|
var last *bounds
|
|
bounds := i.bounds(name)
|
|
|
|
for bounds.Left() < bounds.Right() {
|
|
if last.Equal(bounds) {
|
|
// If the bounds are unchanged, that means either that
|
|
// the object does not exist in the packfile, or the
|
|
// fanout table is corrupt.
|
|
//
|
|
// Either way, we won't be able to find the object.
|
|
// Return immediately to prevent infinite looping.
|
|
return nil, errNotFound
|
|
}
|
|
last = bounds
|
|
|
|
// Find the midpoint between the upper and lower bounds.
|
|
mid := bounds.Left() + ((bounds.Right() - bounds.Left()) / 2)
|
|
|
|
got, err := i.version.Name(i, mid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 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
|
|
// search to the midpoint.
|
|
bounds = bounds.WithRight(mid)
|
|
} else if cmp > 0 {
|
|
// Likewise, if the comparison is greater than 0, we
|
|
// searched below the desired object. Modify the bounds
|
|
// accordingly.
|
|
bounds = bounds.WithLeft(mid)
|
|
}
|
|
|
|
}
|
|
|
|
return nil, errNotFound
|
|
}
|
|
|
|
// readAt is a convenience method that allow reading into the underlying data
|
|
// source from other callers within this package.
|
|
func (i *Index) readAt(p []byte, at int64) (n int, err error) {
|
|
return i.r.ReadAt(p, at)
|
|
}
|
|
|
|
// bounds returns the initial bounds for a given name using the fanout table to
|
|
// limit search results.
|
|
func (i *Index) bounds(name []byte) *bounds {
|
|
var left, right int64
|
|
|
|
if name[0] == 0 {
|
|
// If the lower bound is 0, there are no objects before it,
|
|
// start at the beginning of the index file.
|
|
left = 0
|
|
} else {
|
|
// Otherwise, make the lower bound the slot before the given
|
|
// object.
|
|
left = int64(i.fanout[name[0]-1])
|
|
}
|
|
|
|
if name[0] == 255 {
|
|
// As above, if the upper bound is the max byte value, make the
|
|
// upper bound the last object in the list.
|
|
right = int64(i.Count())
|
|
} else {
|
|
// Otherwise, make the upper bound the first object which is not
|
|
// within the given slot.
|
|
right = int64(i.fanout[name[0]+1])
|
|
}
|
|
|
|
return newBounds(left, right)
|
|
}
|