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