github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/odb/pack/index.go (about) 1 package pack 2 3 import ( 4 "bytes" 5 "io" 6 7 "github.com/git-lfs/git-lfs/errors" 8 ) 9 10 // Index stores information about the location of objects in a corresponding 11 // packfile. 12 type Index struct { 13 // version is the encoding version used by this index. 14 // 15 // Currently, versions 1 and 2 are supported. 16 version IndexVersion 17 // fanout is the L1 fanout table stored in this index. For a given index 18 // "i" into the array, the value stored at that index specifies the 19 // number of objects in the packfile/index that are lexicographically 20 // less than or equal to that index. 21 // 22 // See: https://github.com/git/git/blob/v2.13.0/Documentation/technical/pack-format.txt#L41-L45 23 fanout []uint32 24 25 // r is the underlying set of encoded data comprising this index file. 26 r io.ReaderAt 27 } 28 29 // Count returns the number of objects in the packfile. 30 func (i *Index) Count() int { 31 return int(i.fanout[255]) 32 } 33 34 // Close closes the packfile index if the underlying data stream is closeable. 35 // If so, it returns any error involved in closing. 36 func (i *Index) Close() error { 37 if close, ok := i.r.(io.Closer); ok { 38 return close.Close() 39 } 40 return nil 41 } 42 43 var ( 44 // errNotFound is an error returned by Index.Entry() (see: below) when 45 // an object cannot be found in the index. 46 errNotFound = errors.New("git/odb/pack: object not found in index") 47 ) 48 49 // IsNotFound returns whether a given error represents a missing object in the 50 // index. 51 func IsNotFound(err error) bool { 52 return err == errNotFound 53 } 54 55 // Entry returns an entry containing the offset of a given SHA1 "name". 56 // 57 // Entry operates in O(log(n))-time in the worst case, where "n" is the number 58 // of objects that begin with the first byte of "name". 59 // 60 // If the entry cannot be found, (nil, ErrNotFound) will be returned. If there 61 // was an error searching for or parsing an entry, it will be returned as (nil, 62 // err). 63 // 64 // Otherwise, (entry, nil) will be returned. 65 func (i *Index) Entry(name []byte) (*IndexEntry, error) { 66 var last *bounds 67 bounds := i.bounds(name) 68 69 for bounds.Left() < bounds.Right() { 70 if last.Equal(bounds) { 71 // If the bounds are unchanged, that means either that 72 // the object does not exist in the packfile, or the 73 // fanout table is corrupt. 74 // 75 // Either way, we won't be able to find the object. 76 // Return immediately to prevent infinite looping. 77 return nil, errNotFound 78 } 79 last = bounds 80 81 // Find the midpoint between the upper and lower bounds. 82 mid := bounds.Left() + ((bounds.Right() - bounds.Left()) / 2) 83 84 got, err := i.version.Name(i, mid) 85 if err != nil { 86 return nil, err 87 } 88 89 if cmp := bytes.Compare(name, got); cmp == 0 { 90 // If "cmp" is zero, that means the object at that index 91 // "at" had a SHA equal to the one given by name, and we 92 // are done. 93 return i.version.Entry(i, mid) 94 } else if cmp < 0 { 95 // If the comparison is less than 0, we searched past 96 // the desired object, so limit the upper bound of the 97 // search to the midpoint. 98 bounds = bounds.WithRight(mid) 99 } else if cmp > 0 { 100 // Likewise, if the comparison is greater than 0, we 101 // searched below the desired object. Modify the bounds 102 // accordingly. 103 bounds = bounds.WithLeft(mid) 104 } 105 106 } 107 108 return nil, errNotFound 109 } 110 111 // readAt is a convenience method that allow reading into the underlying data 112 // source from other callers within this package. 113 func (i *Index) readAt(p []byte, at int64) (n int, err error) { 114 return i.r.ReadAt(p, at) 115 } 116 117 // bounds returns the initial bounds for a given name using the fanout table to 118 // limit search results. 119 func (i *Index) bounds(name []byte) *bounds { 120 var left, right int64 121 122 if name[0] == 0 { 123 // If the lower bound is 0, there are no objects before it, 124 // start at the beginning of the index file. 125 left = 0 126 } else { 127 // Otherwise, make the lower bound the slot before the given 128 // object. 129 left = int64(i.fanout[name[0]-1]) 130 } 131 132 if name[0] == 255 { 133 // As above, if the upper bound is the max byte value, make the 134 // upper bound the last object in the list. 135 right = int64(i.Count()) 136 } else { 137 // Otherwise, make the upper bound the first object which is not 138 // within the given slot. 139 right = int64(i.fanout[name[0]+1]) 140 } 141 142 return newBounds(left, right) 143 }