Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 032196f

Browse files
committed
remote: option to skip delta compression while encoding packfiles
One use of go-git is to transfer git data from a non-standard git repo (not stored in a file system, for example) to a "remote" backed by a standard, local .git repo In this scenario, delta compression is not needed to reduce transfer time over the "network", because there is no network. The underlying storage layer has already taken care of the data tranfer, and sending the objects to local .git storage doesn't require compression. So this PR gives the user the option to turn off compression when it isn't needed. Of course, this results in a larger, uncompressed local .git repo, but the user can then run `git gc` or `git repack` on that repo if they care about the storage costs. Turning this on reduces total push time of a 36K repo by 50 seconds (out of a pre-PR total of 3m26s).
1 parent bb3217c commit 032196f

File tree

10 files changed

+97
-28
lines changed

10 files changed

+97
-28
lines changed

options.go

+7
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ type PushOptions struct {
175175
// Progress is where the human readable information sent by the server is
176176
// stored, if nil nothing is stored.
177177
Progress sideband.Progress
178+
// SkipCompression can be true if the caller doesn't need
179+
// delta-compressed objects pushed to the remote. This can be useful to
180+
// avoid CPU overhead when the objects don't need to be
181+
// transferred across a network. If the remote wants a compressed
182+
// repo after the transfer, it can run `git gc --aggressive` or
183+
// `git repack -a -d -f --depth=250 --window=250` as needed.
184+
SkipCompression bool
178185
}
179186

180187
// Validate validates the fields and sets the default values.

plumbing/format/packfile/delta_selector.go

+25-4
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,20 @@ func newDeltaSelector(s storer.EncodedObjectStorer) *deltaSelector {
3333

3434
// ObjectsToPack creates a list of ObjectToPack from the hashes provided,
3535
// creating deltas if it's suitable, using an specific internal logic
36-
func (dw *deltaSelector) ObjectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack, error) {
37-
otp, err := dw.objectsToPack(hashes)
36+
func (dw *deltaSelector) ObjectsToPack(
37+
hashes []plumbing.Hash,
38+
skipCompression bool,
39+
) ([]*ObjectToPack, error) {
40+
41+
otp, err := dw.objectsToPack(hashes, skipCompression)
3842
if err != nil {
3943
return nil, err
4044
}
4145

46+
if skipCompression {
47+
return otp, nil
48+
}
49+
4250
dw.sort(otp)
4351

4452
var objectGroups [][]*ObjectToPack
@@ -77,10 +85,19 @@ func (dw *deltaSelector) ObjectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack,
7785
return otp, nil
7886
}
7987

80-
func (dw *deltaSelector) objectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack, error) {
88+
func (dw *deltaSelector) objectsToPack(
89+
hashes []plumbing.Hash,
90+
skipCompression bool,
91+
) ([]*ObjectToPack, error) {
8192
var objectsToPack []*ObjectToPack
8293
for _, h := range hashes {
83-
o, err := dw.encodedDeltaObject(h)
94+
var o plumbing.EncodedObject
95+
var err error
96+
if skipCompression {
97+
o, err = dw.encodedObject(h)
98+
} else {
99+
o, err = dw.encodedDeltaObject(h)
100+
}
84101
if err != nil {
85102
return nil, err
86103
}
@@ -93,6 +110,10 @@ func (dw *deltaSelector) objectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack,
93110
objectsToPack = append(objectsToPack, otp)
94111
}
95112

113+
if skipCompression {
114+
return objectsToPack, nil
115+
}
116+
96117
if err := dw.fixAndBreakChains(objectsToPack); err != nil {
97118
return nil, err
98119
}

plumbing/format/packfile/delta_selector_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -146,31 +146,31 @@ func (s *DeltaSelectorSuite) createTestObjects() {
146146
func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) {
147147
// Different type
148148
hashes := []plumbing.Hash{s.hashes["base"], s.hashes["treeType"]}
149-
otp, err := s.ds.ObjectsToPack(hashes)
149+
otp, err := s.ds.ObjectsToPack(hashes, false)
150150
c.Assert(err, IsNil)
151151
c.Assert(len(otp), Equals, 2)
152152
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["base"]])
153153
c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["treeType"]])
154154

155155
// Size radically different
156156
hashes = []plumbing.Hash{s.hashes["bigBase"], s.hashes["target"]}
157-
otp, err = s.ds.ObjectsToPack(hashes)
157+
otp, err = s.ds.ObjectsToPack(hashes, false)
158158
c.Assert(err, IsNil)
159159
c.Assert(len(otp), Equals, 2)
160160
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["bigBase"]])
161161
c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["target"]])
162162

163163
// Delta Size Limit with no best delta yet
164164
hashes = []plumbing.Hash{s.hashes["smallBase"], s.hashes["smallTarget"]}
165-
otp, err = s.ds.ObjectsToPack(hashes)
165+
otp, err = s.ds.ObjectsToPack(hashes, false)
166166
c.Assert(err, IsNil)
167167
c.Assert(len(otp), Equals, 2)
168168
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["smallBase"]])
169169
c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["smallTarget"]])
170170

171171
// It will create the delta
172172
hashes = []plumbing.Hash{s.hashes["base"], s.hashes["target"]}
173-
otp, err = s.ds.ObjectsToPack(hashes)
173+
otp, err = s.ds.ObjectsToPack(hashes, false)
174174
c.Assert(err, IsNil)
175175
c.Assert(len(otp), Equals, 2)
176176
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["target"]])
@@ -185,7 +185,7 @@ func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) {
185185
s.hashes["o2"],
186186
s.hashes["o3"],
187187
}
188-
otp, err = s.ds.ObjectsToPack(hashes)
188+
otp, err = s.ds.ObjectsToPack(hashes, false)
189189
c.Assert(err, IsNil)
190190
c.Assert(len(otp), Equals, 3)
191191
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["o1"]])
@@ -208,7 +208,7 @@ func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) {
208208

209209
// Don't sort so we can easily check the sliding window without
210210
// creating a bunch of new objects.
211-
otp, err = s.ds.objectsToPack(hashes)
211+
otp, err = s.ds.objectsToPack(hashes, false)
212212
c.Assert(err, IsNil)
213213
err = s.ds.walk(otp)
214214
c.Assert(err, IsNil)

plumbing/format/packfile/encoder.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import (
1414
// Encoder gets the data from the storage and write it into the writer in PACK
1515
// format
1616
type Encoder struct {
17-
selector *deltaSelector
18-
w *offsetWriter
19-
zw *zlib.Writer
20-
hasher plumbing.Hasher
17+
selector *deltaSelector
18+
w *offsetWriter
19+
zw *zlib.Writer
20+
hasher plumbing.Hasher
2121
// offsets is a map of object hashes to corresponding offsets in the packfile.
2222
// It is used to determine offset of the base of a delta when a OFS_DELTA is
2323
// used.
@@ -47,8 +47,11 @@ func NewEncoder(w io.Writer, s storer.EncodedObjectStorer, useRefDeltas bool) *E
4747

4848
// Encode creates a packfile containing all the objects referenced in hashes
4949
// and writes it to the writer in the Encoder.
50-
func (e *Encoder) Encode(hashes []plumbing.Hash) (plumbing.Hash, error) {
51-
objects, err := e.selector.ObjectsToPack(hashes)
50+
func (e *Encoder) Encode(
51+
hashes []plumbing.Hash,
52+
skipCompression bool,
53+
) (plumbing.Hash, error) {
54+
objects, err := e.selector.ObjectsToPack(hashes, skipCompression)
5255
if err != nil {
5356
return plumbing.ZeroHash, err
5457
}
@@ -137,7 +140,7 @@ func (e *Encoder) writeOfsDeltaHeader(deltaOffset int64, base plumbing.Hash) err
137140

138141
// for OFS_DELTA, offset of the base is interpreted as negative offset
139142
// relative to the type-byte of the header of the ofs-delta entry.
140-
relativeOffset := deltaOffset-baseOffset
143+
relativeOffset := deltaOffset - baseOffset
141144
if relativeOffset <= 0 {
142145
return fmt.Errorf("bad offset for OFS_DELTA entry: %d", relativeOffset)
143146
}

plumbing/format/packfile/encoder_advanced_test.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,24 @@ func (s *EncoderAdvancedSuite) TestEncodeDecode(c *C) {
2727
fixs.Test(c, func(f *fixtures.Fixture) {
2828
storage, err := filesystem.NewStorage(f.DotGit())
2929
c.Assert(err, IsNil)
30-
s.testEncodeDecode(c, storage)
30+
s.testEncodeDecode(c, storage, false)
3131
})
3232

3333
}
3434

35-
func (s *EncoderAdvancedSuite) testEncodeDecode(c *C, storage storer.Storer) {
35+
func (s *EncoderAdvancedSuite) TestEncodeDecodeSkipCompression(c *C) {
36+
fixs := fixtures.Basic().ByTag("packfile").ByTag(".git")
37+
fixs = append(fixs, fixtures.ByURL("https://github.com/src-d/go-git.git").
38+
ByTag("packfile").ByTag(".git").One())
39+
fixs.Test(c, func(f *fixtures.Fixture) {
40+
storage, err := filesystem.NewStorage(f.DotGit())
41+
c.Assert(err, IsNil)
42+
s.testEncodeDecode(c, storage, true)
43+
})
44+
45+
}
46+
47+
func (s *EncoderAdvancedSuite) testEncodeDecode(c *C, storage storer.Storer, skipCompression bool) {
3648

3749
objIter, err := storage.IterEncodedObjects(plumbing.AnyObject)
3850
c.Assert(err, IsNil)
@@ -57,7 +69,7 @@ func (s *EncoderAdvancedSuite) testEncodeDecode(c *C, storage storer.Storer) {
5769

5870
buf := bytes.NewBuffer(nil)
5971
enc := NewEncoder(buf, storage, false)
60-
_, err = enc.Encode(hashes)
72+
_, err = enc.Encode(hashes, skipCompression)
6173
c.Assert(err, IsNil)
6274

6375
scanner := NewScanner(buf)

plumbing/format/packfile/encoder_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func (s *EncoderSuite) SetUpTest(c *C) {
2626
}
2727

2828
func (s *EncoderSuite) TestCorrectPackHeader(c *C) {
29-
hash, err := s.enc.Encode([]plumbing.Hash{})
29+
hash, err := s.enc.Encode([]plumbing.Hash{}, false)
3030
c.Assert(err, IsNil)
3131

3232
hb := [20]byte(hash)
@@ -47,7 +47,7 @@ func (s *EncoderSuite) TestCorrectPackWithOneEmptyObject(c *C) {
4747
_, err := s.store.SetEncodedObject(o)
4848
c.Assert(err, IsNil)
4949

50-
hash, err := s.enc.Encode([]plumbing.Hash{o.Hash()})
50+
hash, err := s.enc.Encode([]plumbing.Hash{o.Hash()}, false)
5151
c.Assert(err, IsNil)
5252

5353
// PACK + VERSION(2) + OBJECT NUMBER(1)
@@ -74,13 +74,13 @@ func (s *EncoderSuite) TestMaxObjectSize(c *C) {
7474
o.SetType(plumbing.CommitObject)
7575
_, err := s.store.SetEncodedObject(o)
7676
c.Assert(err, IsNil)
77-
hash, err := s.enc.Encode([]plumbing.Hash{o.Hash()})
77+
hash, err := s.enc.Encode([]plumbing.Hash{o.Hash()}, false)
7878
c.Assert(err, IsNil)
7979
c.Assert(hash.IsZero(), Not(Equals), true)
8080
}
8181

8282
func (s *EncoderSuite) TestHashNotFound(c *C) {
83-
h, err := s.enc.Encode([]plumbing.Hash{plumbing.NewHash("BAD")})
83+
h, err := s.enc.Encode([]plumbing.Hash{plumbing.NewHash("BAD")}, false)
8484
c.Assert(h, Equals, plumbing.ZeroHash)
8585
c.Assert(err, NotNil)
8686
c.Assert(err, Equals, plumbing.ErrObjectNotFound)

plumbing/transport/server/server.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func (s *upSession) UploadPack(ctx context.Context, req *packp.UploadPackRequest
165165
pr, pw := io.Pipe()
166166
e := packfile.NewEncoder(pw, s.storer, false)
167167
go func() {
168-
_, err := e.Encode(objs)
168+
_, err := e.Encode(objs, false)
169169
pw.CloseWithError(err)
170170
}()
171171

plumbing/transport/test/receive_pack.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ func (s *ReceivePackSuite) testSendPackDeleteReference(c *C) {
348348
func (s *ReceivePackSuite) emptyPackfile() io.ReadCloser {
349349
var buf bytes.Buffer
350350
e := packfile.NewEncoder(&buf, memory.NewStorage(), false)
351-
_, err := e.Encode(nil)
351+
_, err := e.Encode(nil, false)
352352
if err != nil {
353353
panic(err)
354354
}

remote.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) error {
149149
}
150150
}
151151

152-
rs, err := pushHashes(ctx, s, r.s, req, hashesToPush)
152+
rs, err := pushHashes(
153+
ctx, s, r.s, req, hashesToPush, o.SkipCompression)
153154
if err != nil {
154155
return err
155156
}
@@ -800,14 +801,15 @@ func pushHashes(
800801
sto storer.EncodedObjectStorer,
801802
req *packp.ReferenceUpdateRequest,
802803
hs []plumbing.Hash,
804+
skipCompression bool,
803805
) (*packp.ReportStatus, error) {
804806

805807
rd, wr := io.Pipe()
806808
req.Packfile = rd
807809
done := make(chan error)
808810
go func() {
809811
e := packfile.NewEncoder(wr, sto, false)
810-
if _, err := e.Encode(hs); err != nil {
812+
if _, err := e.Encode(hs, skipCompression); err != nil {
811813
done <- wr.CloseWithError(err)
812814
return
813815
}

remote_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,30 @@ func (s *RemoteSuite) TestPushContext(c *C) {
380380
c.Assert(err, NotNil)
381381
}
382382

383+
func (s *RemoteSuite) TestPushContextSkipCompression(c *C) {
384+
url := c.MkDir()
385+
_, err := PlainInit(url, true)
386+
c.Assert(err, IsNil)
387+
388+
fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit()
389+
sto, err := filesystem.NewStorage(fs)
390+
c.Assert(err, IsNil)
391+
392+
r := newRemote(sto, &config.RemoteConfig{
393+
Name: DefaultRemoteName,
394+
URLs: []string{url},
395+
})
396+
397+
ctx, cancel := context.WithCancel(context.Background())
398+
cancel()
399+
400+
err = r.PushContext(ctx, &PushOptions{
401+
RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"},
402+
SkipCompression: true,
403+
})
404+
c.Assert(err, NotNil)
405+
}
406+
383407
func (s *RemoteSuite) TestPushTags(c *C) {
384408
url := c.MkDir()
385409
server, err := PlainInit(url, true)

0 commit comments

Comments
 (0)