Imported Upstream version 2.4.2
[scm/test.git] / git / odb / pack / set.go
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 }