github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/odb/pack/packfile.go (about)

     1  package pack
     2  
     3  import (
     4  	"compress/zlib"
     5  	"io"
     6  	"io/ioutil"
     7  
     8  	"github.com/git-lfs/git-lfs/errors"
     9  )
    10  
    11  // Packfile encapsulates the behavior of accessing an unpacked representation of
    12  // all of the objects encoded in a single packfile.
    13  type Packfile struct {
    14  	// Version is the version of the packfile.
    15  	Version uint32
    16  	// Objects is the total number of objects in the packfile.
    17  	Objects uint32
    18  	// idx is the corresponding "pack-*.idx" file giving the positions of
    19  	// objects in this packfile.
    20  	idx *Index
    21  
    22  	// r is an io.ReaderAt that allows read access to the packfile itself.
    23  	r io.ReaderAt
    24  }
    25  
    26  // Close closes the packfile if the underlying data stream is closeable. If so,
    27  // it returns any error involved in closing.
    28  func (p *Packfile) Close() error {
    29  	var iErr error
    30  	if p.idx != nil {
    31  		iErr = p.idx.Close()
    32  	}
    33  
    34  	if close, ok := p.r.(io.Closer); ok {
    35  		return close.Close()
    36  	}
    37  	return iErr
    38  }
    39  
    40  // Object returns a reference to an object packed in the receiving *Packfile. It
    41  // does not attempt to unpack the packfile, rather, that is accomplished by
    42  // calling Unpack() on the returned *Object.
    43  //
    44  // If there was an error loading or buffering the base, it will be returned
    45  // without an object.
    46  //
    47  // If the object given by the SHA-1 name, "name", could not be found,
    48  // (nil, errNotFound) will be returned.
    49  //
    50  // If the object was able to be loaded successfully, it will be returned without
    51  // any error.
    52  func (p *Packfile) Object(name []byte) (*Object, error) {
    53  	// First, try and determine the offset of the last entry in the
    54  	// delta-base chain by loading it from the corresponding pack index.
    55  	entry, err := p.idx.Entry(name)
    56  	if err != nil {
    57  		if !IsNotFound(err) {
    58  			// If the error was not an errNotFound, re-wrap it with
    59  			// additional context.
    60  			err = errors.Wrap(err,
    61  				"git/odb/pack: could not load index")
    62  		}
    63  		return nil, err
    64  	}
    65  
    66  	// If all goes well, then unpack the object at that given offset.
    67  	r, err := p.find(int64(entry.PackOffset))
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	return &Object{
    73  		data: r,
    74  		typ:  r.Type(),
    75  	}, nil
    76  }
    77  
    78  // find finds and returns a Chain element corresponding to the offset of its
    79  // last element as given by the "offset" argument.
    80  //
    81  // If find returns a ChainBase, it loads that data into memory, but does not
    82  // zlib-flate it. Otherwise, if find returns a ChainDelta, it loads all of the
    83  // leading elements in the chain recursively, but does not apply one delta to
    84  // another.
    85  func (p *Packfile) find(offset int64) (Chain, error) {
    86  	// Read the first byte in the chain element.
    87  	buf := make([]byte, 1)
    88  	if _, err := p.r.ReadAt(buf, offset); err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	// Store the original offset; this will be compared to when loading
    93  	// chain elements of type OBJ_OFS_DELTA.
    94  	objectOffset := offset
    95  
    96  	// Of the first byte, (0123 4567):
    97  	//   - Bit 0 is the M.S.B., and indicates whether there is more data
    98  	//     encoded in the length.
    99  	//   - Bits 1-3 ((buf[0] >> 4) & 0x7) are the object type.
   100  	//   - Bits 4-7 (buf[0] & 0xf) are the first 4 bits of the variable
   101  	//     length size of the encoded delta or base.
   102  	typ := PackedObjectType((buf[0] >> 4) & 0x7)
   103  	size := uint64(buf[0] & 0xf)
   104  	shift := uint(4)
   105  	offset += 1
   106  
   107  	for buf[0]&0x80 != 0 {
   108  		// If there is more data to be read, read it.
   109  		if _, err := p.r.ReadAt(buf, offset); err != nil {
   110  			return nil, err
   111  		}
   112  
   113  		// And update the size, bitshift, and offset accordingly.
   114  		size |= (uint64(buf[0]&0x7f) << shift)
   115  		shift += 7
   116  		offset += 1
   117  	}
   118  
   119  	switch typ {
   120  	case TypeObjectOffsetDelta, TypeObjectReferenceDelta:
   121  		// If the type of delta-base element is a delta, (either
   122  		// OBJ_OFS_DELTA, or OBJ_REFS_DELTA), we must load the base,
   123  		// which itself could be either of the two above, or a
   124  		// OBJ_COMMIT, OBJ_BLOB, etc.
   125  		//
   126  		// Recursively load the base, and keep track of the updated
   127  		// offset.
   128  		base, offset, err := p.findBase(typ, offset, objectOffset)
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  
   133  		// Now load the delta to apply to the base, given at the offset
   134  		// "offset" and for length "size".
   135  		//
   136  		// NB: The delta instructions are zlib compressed, so ensure
   137  		// that we uncompress the instructions first.
   138  		zr, err := zlib.NewReader(&OffsetReaderAt{
   139  			o: offset,
   140  			r: p.r,
   141  		})
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  
   146  		delta, err := ioutil.ReadAll(zr)
   147  		if err != nil {
   148  			return nil, err
   149  		}
   150  
   151  		// Then compose the two and return it as a *ChainDelta.
   152  		return &ChainDelta{
   153  			base:  base,
   154  			delta: delta,
   155  		}, nil
   156  	case TypeCommit, TypeTree, TypeBlob, TypeTag:
   157  		// Otherwise, the object's contents are given to be the
   158  		// following zlib-compressed data.
   159  		//
   160  		// The length of the compressed data itself is not known,
   161  		// rather, "size" determines the length of the data after
   162  		// inflation.
   163  		return &ChainBase{
   164  			offset: offset,
   165  			size:   int64(size),
   166  			typ:    typ,
   167  
   168  			r: p.r,
   169  		}, nil
   170  	}
   171  	// Otherwise, we received an invalid object type.
   172  	return nil, errUnrecognizedObjectType
   173  }
   174  
   175  // findBase finds the base (an object, or another delta) for a given
   176  // OBJ_OFS_DELTA or OBJ_REFS_DELTA at the given offset.
   177  //
   178  // It returns the preceding Chain, as well as an updated read offset into the
   179  // underlying packfile data.
   180  //
   181  // If any of the above could not be completed successfully, findBase returns an
   182  // error.
   183  func (p *Packfile) findBase(typ PackedObjectType, offset, objOffset int64) (Chain, int64, error) {
   184  	var baseOffset int64
   185  
   186  	// We assume that we have to read at least 20 bytes (the SHA-1 length in
   187  	// the case of a OBJ_REF_DELTA, or greater than the length of the base
   188  	// offset encoded in an OBJ_OFS_DELTA).
   189  	var sha [20]byte
   190  	if _, err := p.r.ReadAt(sha[:], offset); err != nil {
   191  		return nil, baseOffset, err
   192  	}
   193  
   194  	switch typ {
   195  	case TypeObjectOffsetDelta:
   196  		// If the object is of type OBJ_OFS_DELTA, read a
   197  		// variable-length integer, and find the object at that
   198  		// location.
   199  		i := 0
   200  		c := int64(sha[i])
   201  		baseOffset = c & 0x7f
   202  
   203  		for c&0x80 != 0 {
   204  			i += 1
   205  			c = int64(sha[i])
   206  
   207  			baseOffset += 1
   208  			baseOffset <<= 7
   209  			baseOffset |= c & 0x7f
   210  		}
   211  
   212  		baseOffset = objOffset - baseOffset
   213  		offset += int64(i) + 1
   214  	case TypeObjectReferenceDelta:
   215  		// If the delta is an OBJ_REFS_DELTA, find the location of its
   216  		// base by reading the SHA-1 name and looking it up in the
   217  		// corresponding pack index file.
   218  		e, err := p.idx.Entry(sha[:])
   219  		if err != nil {
   220  			return nil, baseOffset, err
   221  		}
   222  
   223  		baseOffset = int64(e.PackOffset)
   224  		offset += 20
   225  	default:
   226  		// If we did not receive an OBJ_OFS_DELTA, or OBJ_REF_DELTA, the
   227  		// type given is not a delta-fied type. Return an error.
   228  		return nil, baseOffset, errors.Errorf(
   229  			"git/odb/pack: type %s is not deltafied", typ)
   230  	}
   231  
   232  	// Once we have determined the base offset of the object's chain base,
   233  	// read the delta-base chain beginning at that offset.
   234  	r, err := p.find(baseOffset)
   235  	return r, offset, err
   236  }