aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/format/idxfile/writer.go
blob: 89b79cd1d5b2836e506a6d5b5f2a55948a77baee (plain) (blame)
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
186
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, uint32(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]
}