-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathwriter.go
189 lines (163 loc) · 3.52 KB
/
writer.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
package git
import (
"bufio"
"bytes"
"compress/zlib"
"crypto/sha1"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
"io/ioutil"
"os"
)
// Writer writes git object format for blobs, trees, and commits.
//
// TODO short writes on tree objects are likely to fail.
type Writer interface {
// Write writes p to the underlying Writer. Write returns an error
// if caller has not first called WriteHeader.
//
// Close flushes written data. This does not close the original writer.
io.WriteCloser
// WriteHeader must be called before writing any data. If you don't know
// the size of data to be written, pass a negative integer; size is always
// ignored for tree types. In such cases, an intermediary file is used
// to determine size.
WriteHeader(t Type, size int) (int, error)
// Hash returns sha1 sum of data written.
Hash() string
}
type writer struct {
io.Writer
zw *zlib.Writer
hh hash.Hash
// used in case size is unknown
tmp *os.File
tw *treeWriter
t Type
wroteHeader bool
err error
finalize func() error
}
// NewWriter returns a new Writer that writes to staging.
func NewWriter(staging io.Writer) Writer {
// TODO need to insert treeWriter here, before zlib.NewWriter, also not sure about hh ???
// actually, maybe after WriteHeader is done.
g := &writer{
zw: zlib.NewWriter(staging),
hh: sha1.New(),
}
g.Writer = io.MultiWriter(g.zw, g.hh)
return g
}
func (g *writer) WriteHeader(t Type, s int) (n int, err error) {
if g.wroteHeader {
return 0, errors.New("Header already written.")
}
g.t = t
g.wroteHeader = true
if t == Tree || s < 0 {
g.tmp, err = ioutil.TempFile("", "gitwriter")
g.tw = &treeWriter{
Writer: g.tmp,
rbuf: new(bytes.Buffer),
wbuf: new(bytes.Buffer),
sum: make([]byte, 20),
}
} else {
n, err = g.Write(t.Header(s))
}
return
}
func (g *writer) Write(p []byte) (int, error) {
if !g.wroteHeader {
return 0, errors.New("Must call WriteHeader before calling Write.")
}
if g.tw != nil {
return g.tw.Write(p)
}
return g.Writer.Write(p)
}
func (g *writer) Close() error {
if g.tw != nil {
defer g.tmp.Close()
defer os.Remove(g.tmp.Name())
fi, err := g.tmp.Stat()
if err != nil {
return err
}
size := int(fi.Size())
_, err = g.tmp.Seek(0, 0)
if err != nil {
return err
}
_, err = g.Writer.Write(g.t.Header(size))
if err != nil {
return err
}
_, err = io.Copy(g.Writer, g.tmp)
if err != nil {
return err
}
}
if err := g.zw.Close(); err != nil {
return err
}
if g.finalize != nil {
return g.finalize()
}
return nil
}
func (g *writer) Hash() string {
return fmt.Sprintf("%x", g.hh.Sum(nil))
}
// treeWriter handles PrettyReader formatted tree stream.
// TODO this could use some work.
type treeWriter struct {
io.Writer
rbuf *bytes.Buffer
wbuf *bytes.Buffer
// len 20
sum []byte
}
// TODO this is going to bomb on a short read
func (g *treeWriter) Write(p []byte) (n int, err error) {
var mode, h, name []byte
g.rbuf.Write(p)
r := bufio.NewReader(g.rbuf)
for {
mode, err = r.ReadBytes(' ')
if err != nil {
break
}
if mode[0] == '0' {
mode = mode[1:]
}
// discard type
_, err = r.ReadBytes(' ')
if err != nil {
break
}
h, err = r.ReadBytes('\t')
if err != nil {
break
}
h = h[:len(h)-1]
_, err = hex.Decode(g.sum, h)
if err != nil {
break
}
name, err = r.ReadBytes('\n')
if err != nil {
break
}
name[len(name)-1] = '\x00'
g.wbuf.Write(mode)
g.wbuf.Write(name)
g.wbuf.Write(g.sum)
}
n, _ = g.Writer.Write(g.wbuf.Next(len(p)))
return n, err
}