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 }