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  }