-
Notifications
You must be signed in to change notification settings - Fork 25
/
pack.go
222 lines (197 loc) · 4.83 KB
/
pack.go
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package gitgo
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
type packfile struct {
basedir os.File
name SHA
objects map[SHA]*packObject
}
func (p *packfile) verify() error {
if p.objects == nil {
p.objects = map[SHA]*packObject{}
}
packf, err := os.Open(filepath.Join(p.basedir.Name(), "objects", "pack", string(p.name)+".pack"))
if err != nil {
return err
}
defer packf.Close()
idxf, err := os.Open(filepath.Join(p.basedir.Name(), "objects", "pack", string(p.name)+".idx"))
if err != nil {
return err
}
defer idxf.Close()
objs, err := VerifyPack(packf, idxf)
if err != nil {
return err
}
for _, obj := range objs {
p.objects[obj.Name] = obj
}
return nil
}
type packObject struct {
Name SHA
Offset int
Data []byte
_type packObjectType
PatchedData []byte
Size int // the uncompressed size
SizeInPackfile int // the compressed size
// only used for OBJ_OFS_DELTA
negativeOffset int
BaseObjectName SHA
BaseObjectType packObjectType
baseOffset int
Depth int
err error // was an error encountered while processing this object?
}
func (p *packObject) Type() string {
switch p.BaseObjectType {
case OBJ_COMMIT:
return "commit"
case OBJ_TREE:
return "tree"
case OBJ_BLOB:
return "blob"
default:
return p.BaseObjectType.String()
}
}
// normalize returns a GitObject equivalent to the packObject.
// packObject satisfies the GitObject interface, but if the pack
// object type is a commit, tree, or blob, it will return a Commit,
// Tree, or Blob struct instead of the packObject
func (p *packObject) normalize(basedir os.File) (GitObject, error) {
switch p.BaseObjectType {
case OBJ_COMMIT:
return p.Commit(basedir)
case OBJ_TREE:
return p.Tree(basedir)
case OBJ_BLOB:
return p.Blob(basedir)
default:
return p, nil
}
}
// Commit returns a Commit struct for the packObject.
func (p *packObject) Commit(basedir os.File) (Commit, error) {
if p.BaseObjectType != OBJ_COMMIT {
return Commit{}, fmt.Errorf("pack object is not a commit: %s", p.Type())
}
if p.PatchedData == nil {
p.PatchedData = p.Data
}
commit, err := parseCommit(bytes.NewReader(p.PatchedData), strconv.Itoa(p.Size), p.Name)
commit.rawData = p.PatchedData
return commit, err
}
// Tree returns a Tree struct for the packObject.
func (p *packObject) Tree(basedir os.File) (Tree, error) {
if p.BaseObjectType != OBJ_TREE {
return Tree{}, fmt.Errorf("pack object is not a tree: %s", p.Type())
}
if p.PatchedData == nil {
p.PatchedData = p.Data
}
tree, err := parseTree(bytes.NewReader(p.PatchedData), strconv.Itoa(p.Size), basedir)
return tree, err
}
// Blob returns a Blob struct for the packObject.
func (p *packObject) Blob(basedir os.File) (Blob, error) {
if p.BaseObjectType != OBJ_BLOB {
return Blob{}, fmt.Errorf("pack object is not a blob: %s", p.Type())
}
if p.PatchedData == nil {
p.PatchedData = p.Data
}
// TODO fix the size param
blob, err := parseBlob(bytes.NewReader(p.PatchedData), "")
blob.rawData = p.PatchedData
return blob, err
}
func (p *packObject) Patch(dict map[SHA]*packObject) error {
if len(p.PatchedData) != 0 {
return nil
}
if p._type < OBJ_OFS_DELTA {
if p.Data == nil {
return fmt.Errorf("base object data is nil")
}
p.PatchedData = p.Data
p.BaseObjectType = p._type
return nil
}
if p._type >= OBJ_OFS_DELTA {
base, ok := dict[p.BaseObjectName]
if !ok {
return fmt.Errorf("base object not in dictionary: %s", p.BaseObjectName)
}
err := base.Patch(dict)
if err != nil {
return err
}
// At the time patchDelta is called, we know that the base.PatchedData is non-nil
patched, err := patchDelta(bytes.NewReader(base.PatchedData), bytes.NewReader(p.Data))
if err != nil {
return err
}
p.PatchedData, err = ioutil.ReadAll(patched)
if err != nil {
return err
}
p.BaseObjectType = base.BaseObjectType
p.Depth += base.Depth
}
return nil
}
func (p *packObject) PatchedType() packObjectType {
if p._type < OBJ_OFS_DELTA {
return p._type
}
return p.BaseObjectType
}
//go:generate stringer -type=packObjectType
type packObjectType uint8
const (
_ packObjectType = iota
OBJ_COMMIT
OBJ_TREE
OBJ_BLOB
OBJ_TAG
_
OBJ_OFS_DELTA
OBJ_REF_DELTA
)
func (r *Repository) listPackfiles() ([]*packfile, error) {
basedir := r.Basedir
files, err := ioutil.ReadDir(filepath.Join(basedir.Name(), "objects", "pack"))
if err != nil {
return nil, err
}
packfileNames := []SHA{}
for _, file := range files {
base := strings.TrimSuffix(file.Name(), ".pack")
if base == file.Name() {
// this wasn't a packfile
continue
}
packfileNames = append(packfileNames, SHA(base))
}
packs := make([]*packfile, len(packfileNames))
for i, n := range packfileNames {
p := &packfile{basedir: basedir, name: n}
err = p.verify()
if err != nil {
return nil, err
}
packs[i] = p
}
return packs, nil
}