aboutsummaryrefslogblamecommitdiffstats
path: root/plumbing/format/idxfile/writer.go
blob: aac68b5039174272a8f5d8b95ddb80e033e5b5c9 (plain) (tree)



































































































































                                                                                                
package idxfile

import (
	"bytes"
	"math"
	"sort"

	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/utils/binary"
)

type object struct {
	hash   plumbing.Hash
	offset int64
	crc    uint32
}

type objects []object

// Writer implements a packfile Observer interface and is used to generate
// indexes.
type Writer struct {
	count    uint32
	checksum plumbing.Hash
	objects  objects
}

// Create index returns a filled MemoryIndex with the information filled by
// the observer callbacks.
func (w *Writer) CreateIndex() (*MemoryIndex, error) {
	idx := new(MemoryIndex)
	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[:]...)

		// TODO: implement 64 bit offsets
		if o.offset > math.MaxInt32 {
			panic("64 bit offsets not implemented")
		}

		buf.Truncate(0)
		binary.WriteUint32(buf, uint32(o.offset))
		idx.Offset32[bucket] = append(idx.Offset32[bucket], buf.Bytes()...)

		buf.Truncate(0)
		binary.WriteUint32(buf, uint32(o.crc))
		idx.Crc32[bucket] = append(idx.Crc32[bucket], buf.Bytes()...)
	}

	for j := last + 1; j < 256; j++ {
		idx.Fanout[j] = uint32(len(w.objects))
	}

	idx.PackfileChecksum = w.checksum
	// TODO: fill IdxChecksum

	return idx, nil
}

// Add appends new object data.
func (w *Writer) Add(h plumbing.Hash, pos int64, crc uint32) {
	w.objects = append(w.objects, object{h, pos, crc})
}

// 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) error {
	w.Add(h, pos, crc)
	return nil
}

// OnFooter implements packfile.Observer interface.
func (w *Writer) OnFooter(h plumbing.Hash) error {
	w.checksum = h
	return nil
}

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]
}