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  }