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
|
package packidx
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"lindenii.org/go/furgit/object/id"
)
// ErrInvalidEntries reports that
// entries supplied for an index write
// are unsorted, duplicated, or not representable
// in the pack index format.
var ErrInvalidEntries = errors.New("internal/format/packidx: invalid entries")
// Entry is one object record for an index write.
type Entry struct {
// OID holds the object ID bytes;
// only the first hash-size bytes are meaningful.
OID [id.MaxObjectIDSize]byte
// Offset is the entry's pack file offset.
Offset uint64
// CRC32 is the CRC32 of the entry's packed data.
CRC32 uint32
}
// Write writes one pack index over entries to w.
//
// entries must be sorted by object ID without duplicates.
// packHash must be the pack's trailer hash;
// Write panics when its length does not match the object format.
func Write(w io.Writer, objectFormat id.ObjectFormat, entries []Entry, packHash []byte) error {
hashSize := objectFormat.Size()
if hashSize == 0 {
return id.ErrInvalidObjectFormat
}
if len(packHash) != hashSize {
panic("internal/format/packidx: invalid pack hash length")
}
if len(entries) > math.MaxUint32 {
return fmt.Errorf("%w: too many entries", ErrInvalidEntries)
}
for i := 1; i < len(entries); i++ {
if bytes.Compare(entries[i-1].OID[:hashSize], entries[i].OID[:hashSize]) >= 0 {
return fmt.Errorf("%w: not sorted by object ID", ErrInvalidEntries)
}
}
hashImpl, err := objectFormat.New()
if err != nil {
return fmt.Errorf("internal/format/packidx: %w", err)
}
bw := bufio.NewWriter(io.MultiWriter(w, hashImpl))
sw := &stickyWriter{w: bw}
sw.writeUint32(signature)
sw.writeUint32(version)
var counts [256]uint32
for i := range entries {
counts[entries[i].OID[0]]++
}
cumulative := uint32(0)
for _, count := range counts {
cumulative += count
sw.writeUint32(cumulative)
}
for i := range entries {
sw.write(entries[i].OID[:hashSize])
}
for i := range entries {
sw.writeUint32(entries[i].CRC32)
}
var largeOffsets []uint64
for i := range entries {
offset := entries[i].Offset
if offset < largeOffsetFlag {
sw.writeUint32(uint32(offset))
continue
}
slot := len(largeOffsets)
if slot >= largeOffsetFlag {
return fmt.Errorf("%w: too many large offsets", ErrInvalidEntries)
}
sw.writeUint32(largeOffsetFlag | uint32(slot))
largeOffsets = append(largeOffsets, offset)
}
for _, offset := range largeOffsets {
sw.writeUint64(offset)
}
sw.write(packHash)
if sw.err != nil {
return fmt.Errorf("internal/format/packidx: %w", sw.err)
}
err = bw.Flush()
if err != nil {
return fmt.Errorf("internal/format/packidx: %w", err)
}
_, err = w.Write(hashImpl.Sum(nil))
if err != nil {
return fmt.Errorf("internal/format/packidx: %w", err)
}
return nil
}
// stickyWriter forwards writes to w
// and retains the first error,
// turning subsequent writes into no-ops.
type stickyWriter struct {
w io.Writer
err error
}
func (sw *stickyWriter) write(p []byte) {
if sw.err != nil {
return
}
_, sw.err = sw.w.Write(p)
}
func (sw *stickyWriter) writeUint32(v uint32) {
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], v)
sw.write(buf[:])
}
func (sw *stickyWriter) writeUint64(v uint64) {
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], v)
sw.write(buf[:])
}
|