| package idxfile |
| |
| import ( |
| "bytes" |
| "fmt" |
| "math" |
| "sort" |
| "sync" |
| |
| "gopkg.in/src-d/go-git.v4/plumbing" |
| "gopkg.in/src-d/go-git.v4/utils/binary" |
| ) |
| |
| // objects implements sort.Interface and uses hash as sorting key. |
| type objects []Entry |
| |
| // Writer implements a packfile Observer interface and is used to generate |
| // indexes. |
| type Writer struct { |
| m sync.Mutex |
| |
| count uint32 |
| checksum plumbing.Hash |
| objects objects |
| offset64 uint32 |
| finished bool |
| index *MemoryIndex |
| added map[plumbing.Hash]struct{} |
| } |
| |
| // Index returns a previously created MemoryIndex or creates a new one if |
| // needed. |
| func (w *Writer) Index() (*MemoryIndex, error) { |
| w.m.Lock() |
| defer w.m.Unlock() |
| |
| if w.index == nil { |
| return w.createIndex() |
| } |
| |
| return w.index, nil |
| } |
| |
| // Add appends new object data. |
| func (w *Writer) Add(h plumbing.Hash, pos uint64, crc uint32) { |
| w.m.Lock() |
| defer w.m.Unlock() |
| |
| if w.added == nil { |
| w.added = make(map[plumbing.Hash]struct{}) |
| } |
| |
| if _, ok := w.added[h]; !ok { |
| w.added[h] = struct{}{} |
| w.objects = append(w.objects, Entry{h, crc, pos}) |
| } |
| |
| } |
| |
| func (w *Writer) Finished() bool { |
| return w.finished |
| } |
| |
| // OnHeader implements packfile.Observer interface. |
| func (w *Writer) OnHeader(count uint32) error { |
| w.count = count |
| w.objects = make(objects, 0, count) |
| return nil |
| } |
| |
| // OnInflatedObjectHeader implements packfile.Observer interface. |
| func (w *Writer) OnInflatedObjectHeader(t plumbing.ObjectType, objSize int64, pos int64) error { |
| return nil |
| } |
| |
| // OnInflatedObjectContent implements packfile.Observer interface. |
| func (w *Writer) OnInflatedObjectContent(h plumbing.Hash, pos int64, crc uint32, _ []byte) error { |
| w.Add(h, uint64(pos), crc) |
| return nil |
| } |
| |
| // OnFooter implements packfile.Observer interface. |
| func (w *Writer) OnFooter(h plumbing.Hash) error { |
| w.checksum = h |
| w.finished = true |
| _, err := w.createIndex() |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| // creatIndex returns a filled MemoryIndex with the information filled by |
| // the observer callbacks. |
| func (w *Writer) createIndex() (*MemoryIndex, error) { |
| if !w.finished { |
| return nil, fmt.Errorf("the index still hasn't finished building") |
| } |
| |
| idx := new(MemoryIndex) |
| w.index = idx |
| |
| sort.Sort(w.objects) |
| |
| // unmap all fans by default |
| for i := range idx.FanoutMapping { |
| idx.FanoutMapping[i] = noMapping |
| } |
| |
| buf := new(bytes.Buffer) |
| |
| last := -1 |
| bucket := -1 |
| for i, o := range w.objects { |
| fan := o.Hash[0] |
| |
| // fill the gaps between fans |
| for j := last + 1; j < int(fan); j++ { |
| idx.Fanout[j] = uint32(i) |
| } |
| |
| // update the number of objects for this position |
| idx.Fanout[fan] = uint32(i + 1) |
| |
| // we move from one bucket to another, update counters and allocate |
| // memory |
| if last != int(fan) { |
| bucket++ |
| idx.FanoutMapping[fan] = bucket |
| last = int(fan) |
| |
| idx.Names = append(idx.Names, make([]byte, 0)) |
| idx.Offset32 = append(idx.Offset32, make([]byte, 0)) |
| idx.CRC32 = append(idx.CRC32, make([]byte, 0)) |
| } |
| |
| idx.Names[bucket] = append(idx.Names[bucket], o.Hash[:]...) |
| |
| offset := o.Offset |
| if offset > math.MaxInt32 { |
| offset = w.addOffset64(offset) |
| } |
| |
| buf.Truncate(0) |
| binary.WriteUint32(buf, uint32(offset)) |
| idx.Offset32[bucket] = append(idx.Offset32[bucket], buf.Bytes()...) |
| |
| buf.Truncate(0) |
| binary.WriteUint32(buf, o.CRC32) |
| idx.CRC32[bucket] = append(idx.CRC32[bucket], buf.Bytes()...) |
| } |
| |
| for j := last + 1; j < 256; j++ { |
| idx.Fanout[j] = uint32(len(w.objects)) |
| } |
| |
| idx.Version = VersionSupported |
| idx.PackfileChecksum = w.checksum |
| |
| return idx, nil |
| } |
| |
| func (w *Writer) addOffset64(pos uint64) uint64 { |
| buf := new(bytes.Buffer) |
| binary.WriteUint64(buf, pos) |
| w.index.Offset64 = append(w.index.Offset64, buf.Bytes()...) |
| |
| index := uint64(w.offset64 | (1 << 31)) |
| w.offset64++ |
| |
| return index |
| } |
| |
| func (o objects) Len() int { |
| return len(o) |
| } |
| |
| func (o objects) Less(i int, j int) bool { |
| cmp := bytes.Compare(o[i].Hash[:], o[j].Hash[:]) |
| return cmp < 0 |
| } |
| |
| func (o objects) Swap(i int, j int) { |
| o[i], o[j] = o[j], o[i] |
| } |