11 // Set allows access of objects stored across a set of packfiles.
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
18 // closeFn is a function that is run by Close(), designated to free
19 // resources held by the *Set, like open packfiles.
24 // nameRe is a regular expression that matches the basename of a
25 // filepath that is a packfile.
27 // It includes one matchgroup, which is the SHA-1 name of the pack.
28 nameRe = regexp.MustCompile(`^pack-([a-f0-9]{40}).pack$`)
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").
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")
41 paths, err := filepath.Glob(filepath.Join(pd, "pack-*.pack"))
46 packs := make([]*Packfile, 0, len(paths))
48 for _, path := range paths {
49 submatch := nameRe.FindStringSubmatch(filepath.Base(path))
50 if len(submatch) != 2 {
56 packf, err := os.Open(filepath.Join(pd, fmt.Sprintf("pack-%s.pack", name)))
61 idxf, err := os.Open(filepath.Join(pd, fmt.Sprintf("pack-%s.idx", name)))
66 pack, err := DecodePackfile(packf)
71 idx, err := DecodeIndex(idxf)
78 packs = append(packs, pack)
80 return NewSetPacks(packs...), nil
83 // NewSetPacks creates a new *Set from the given packfiles.
84 func NewSetPacks(packs ...*Packfile) *Set {
85 m := make(map[byte][]*Packfile)
87 for i := 0; i < 256; i++ {
90 for j := 0; j < len(packs); j++ {
95 count = pack.idx.fanout[n]
97 count = pack.idx.fanout[n] - pack.idx.fanout[n-1]
101 m[n] = append(m[n], pack)
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]
115 closeFn: func() error {
116 for _, pack := range packs {
117 if err := pack.Close(); err != nil {
126 // Close closes all open packfiles, returning an error if one was encountered.
127 func (s *Set) Close() error {
128 if s.closeFn == nil {
134 // Object opens (but does not unpack, or, apply the delta-base chain) a given
135 // object in the first packfile that matches it.
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
141 // If the object was unable to be found in any of the packfiles, (nil,
142 // ErrNotFound) will be returned.
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.
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)
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)
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.
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.
165 // If no packfiles match the given file, return ErrIsNotFound, along with no
167 func (s *Set) each(name []byte, fn iterFn) (*Object, error) {
173 for _, pack := range s.m[key] {
184 return nil, errNotFound