forked from git-lfs/git-lfs
/
index.go
143 lines (123 loc) · 4.14 KB
/
index.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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])
}
// Close closes the packfile index if the underlying data stream is closeable.
// If so, it returns any error involved in closing.
func (i *Index) Close() error {
if close, ok := i.r.(io.Closer); ok {
return close.Close()
}
return nil
}
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)
}