github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/odb/pack/set.go (about) 1 package pack 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "regexp" 8 "sort" 9 ) 10 11 // Set allows access of objects stored across a set of packfiles. 12 type Set struct { 13 // m maps the leading byte of a SHA-1 object name to a set of packfiles 14 // that might contain that object, in order of which packfile is most 15 // likely to contain that object. 16 m map[byte][]*Packfile 17 18 // closeFn is a function that is run by Close(), designated to free 19 // resources held by the *Set, like open packfiles. 20 closeFn func() error 21 } 22 23 var ( 24 // nameRe is a regular expression that matches the basename of a 25 // filepath that is a packfile. 26 // 27 // It includes one matchgroup, which is the SHA-1 name of the pack. 28 nameRe = regexp.MustCompile(`^pack-([a-f0-9]{40}).pack$`) 29 ) 30 31 // NewSet creates a new *Set of all packfiles found in a given object database's 32 // root (i.e., "/path/to/repo/.git/objects"). 33 // 34 // It finds all packfiles in the "pack" subdirectory, and instantiates a *Set 35 // containing them. If there was an error parsing the packfiles in that 36 // directory, or the directory was otherwise unable to be observed, NewSet 37 // returns that error. 38 func NewSet(db string) (*Set, error) { 39 pd := filepath.Join(db, "pack") 40 41 paths, err := filepath.Glob(filepath.Join(pd, "pack-*.pack")) 42 if err != nil { 43 return nil, err 44 } 45 46 packs := make([]*Packfile, 0, len(paths)) 47 48 for _, path := range paths { 49 submatch := nameRe.FindStringSubmatch(filepath.Base(path)) 50 if len(submatch) != 2 { 51 continue 52 } 53 54 name := submatch[1] 55 56 packf, err := os.Open(filepath.Join(pd, fmt.Sprintf("pack-%s.pack", name))) 57 if err != nil { 58 return nil, err 59 } 60 61 idxf, err := os.Open(filepath.Join(pd, fmt.Sprintf("pack-%s.idx", name))) 62 if err != nil { 63 return nil, err 64 } 65 66 pack, err := DecodePackfile(packf) 67 if err != nil { 68 return nil, err 69 } 70 71 idx, err := DecodeIndex(idxf) 72 if err != nil { 73 return nil, err 74 } 75 76 pack.idx = idx 77 78 packs = append(packs, pack) 79 } 80 return NewSetPacks(packs...), nil 81 } 82 83 // NewSetPacks creates a new *Set from the given packfiles. 84 func NewSetPacks(packs ...*Packfile) *Set { 85 m := make(map[byte][]*Packfile) 86 87 for i := 0; i < 256; i++ { 88 n := byte(i) 89 90 for j := 0; j < len(packs); j++ { 91 pack := packs[j] 92 93 var count uint32 94 if n == 0 { 95 count = pack.idx.fanout[n] 96 } else { 97 count = pack.idx.fanout[n] - pack.idx.fanout[n-1] 98 } 99 100 if count > 0 { 101 m[n] = append(m[n], pack) 102 } 103 } 104 105 sort.Slice(m[n], func(i, j int) bool { 106 ni := m[n][i].idx.fanout[n] 107 nj := m[n][j].idx.fanout[n] 108 109 return ni > nj 110 }) 111 } 112 113 return &Set{ 114 m: m, 115 closeFn: func() error { 116 for _, pack := range packs { 117 if err := pack.Close(); err != nil { 118 return err 119 } 120 } 121 return nil 122 }, 123 } 124 } 125 126 // Close closes all open packfiles, returning an error if one was encountered. 127 func (s *Set) Close() error { 128 if s.closeFn == nil { 129 return nil 130 } 131 return s.closeFn() 132 } 133 134 // Object opens (but does not unpack, or, apply the delta-base chain) a given 135 // object in the first packfile that matches it. 136 // 137 // Object searches packfiles contained in the set in order of how many objects 138 // they have that begin with the first by of the given SHA-1 "name", in 139 // descending order. 140 // 141 // If the object was unable to be found in any of the packfiles, (nil, 142 // ErrNotFound) will be returned. 143 // 144 // If there was otherwise an error opening the object for reading from any of 145 // the packfiles, it will be returned, and no other packfiles will be searched. 146 // 147 // Otherwise, the object will be returned without error. 148 func (s *Set) Object(name []byte) (*Object, error) { 149 return s.each(name, func(p *Packfile) (*Object, error) { 150 return p.Object(name) 151 }) 152 } 153 154 // iterFn is a function that takes a given packfile and opens an object from it. 155 type iterFn func(p *Packfile) (o *Object, err error) 156 157 // each executes the given iterFn "fn" on each Packfile that has any objects 158 // beginning with a prefix of the SHA-1 "name", in order of which packfiles have 159 // the most objects beginning with that prefix. 160 // 161 // If any invocation of "fn" returns a non-nil error, it will either be a) 162 // returned immediately, if the error is not ErrIsNotFound, or b) continued 163 // immediately, if the error is ErrNotFound. 164 // 165 // If no packfiles match the given file, return ErrIsNotFound, along with no 166 // object. 167 func (s *Set) each(name []byte, fn iterFn) (*Object, error) { 168 var key byte 169 if len(name) > 0 { 170 key = name[0] 171 } 172 173 for _, pack := range s.m[key] { 174 o, err := fn(pack) 175 if err != nil { 176 if IsNotFound(err) { 177 continue 178 } 179 return nil, err 180 } 181 return o, nil 182 } 183 184 return nil, errNotFound 185 }