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

Commit 3bd5e82

Browse files
authored
Merge pull request #874 from smola/patchcontext
plumbing: add context to allow cancel on diff/patch computing
2 parents 8ad72db + 9251ea7 commit 3bd5e82

File tree

9 files changed

+297
-9
lines changed

9 files changed

+297
-9
lines changed

plumbing/object/change.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package object
22

33
import (
44
"bytes"
5+
"context"
56
"fmt"
67
"strings"
78

@@ -81,7 +82,15 @@ func (c *Change) String() string {
8182
// Patch returns a Patch with all the file changes in chunks. This
8283
// representation can be used to create several diff outputs.
8384
func (c *Change) Patch() (*Patch, error) {
84-
return getPatch("", c)
85+
return c.PatchContext(context.Background())
86+
}
87+
88+
// Patch returns a Patch with all the file changes in chunks. This
89+
// representation can be used to create several diff outputs.
90+
// If context expires, an non-nil error will be returned
91+
// Provided context must be non-nil
92+
func (c *Change) PatchContext(ctx context.Context) (*Patch, error) {
93+
return getPatchContext(ctx, "", c)
8594
}
8695

8796
func (c *Change) name() string {
@@ -136,5 +145,13 @@ func (c Changes) String() string {
136145
// Patch returns a Patch with all the changes in chunks. This
137146
// representation can be used to create several diff outputs.
138147
func (c Changes) Patch() (*Patch, error) {
139-
return getPatch("", c...)
148+
return c.PatchContext(context.Background())
149+
}
150+
151+
// Patch returns a Patch with all the changes in chunks. This
152+
// representation can be used to create several diff outputs.
153+
// If context expires, an non-nil error will be returned
154+
// Provided context must be non-nil
155+
func (c Changes) PatchContext(ctx context.Context) (*Patch, error) {
156+
return getPatchContext(ctx, "", c...)
140157
}

plumbing/object/change_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package object
22

33
import (
4+
"context"
45
"sort"
56

67
"gopkg.in/src-d/go-git.v4/plumbing"
@@ -82,6 +83,12 @@ func (s *ChangeSuite) TestInsert(c *C) {
8283
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 1)
8384
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Add)
8485

86+
p, err = change.PatchContext(context.Background())
87+
c.Assert(err, IsNil)
88+
c.Assert(len(p.FilePatches()), Equals, 1)
89+
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 1)
90+
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Add)
91+
8592
str := change.String()
8693
c.Assert(str, Equals, "<Action: Insert, Path: examples/clone/main.go>")
8794
}
@@ -134,6 +141,12 @@ func (s *ChangeSuite) TestDelete(c *C) {
134141
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 1)
135142
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Delete)
136143

144+
p, err = change.PatchContext(context.Background())
145+
c.Assert(err, IsNil)
146+
c.Assert(len(p.FilePatches()), Equals, 1)
147+
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 1)
148+
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Delete)
149+
137150
str := change.String()
138151
c.Assert(str, Equals, "<Action: Delete, Path: utils/difftree/difftree.go>")
139152
}
@@ -206,6 +219,18 @@ func (s *ChangeSuite) TestModify(c *C) {
206219
c.Assert(p.FilePatches()[0].Chunks()[5].Type(), Equals, diff.Add)
207220
c.Assert(p.FilePatches()[0].Chunks()[6].Type(), Equals, diff.Equal)
208221

222+
p, err = change.PatchContext(context.Background())
223+
c.Assert(err, IsNil)
224+
c.Assert(len(p.FilePatches()), Equals, 1)
225+
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 7)
226+
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Equal)
227+
c.Assert(p.FilePatches()[0].Chunks()[1].Type(), Equals, diff.Delete)
228+
c.Assert(p.FilePatches()[0].Chunks()[2].Type(), Equals, diff.Add)
229+
c.Assert(p.FilePatches()[0].Chunks()[3].Type(), Equals, diff.Equal)
230+
c.Assert(p.FilePatches()[0].Chunks()[4].Type(), Equals, diff.Delete)
231+
c.Assert(p.FilePatches()[0].Chunks()[5].Type(), Equals, diff.Add)
232+
c.Assert(p.FilePatches()[0].Chunks()[6].Type(), Equals, diff.Equal)
233+
209234
str := change.String()
210235
c.Assert(str, Equals, "<Action: Modify, Path: utils/difftree/difftree.go>")
211236
}
@@ -367,3 +392,39 @@ func (s *ChangeSuite) TestChangesSort(c *C) {
367392
sort.Sort(changes)
368393
c.Assert(changes.String(), Equals, expected)
369394
}
395+
396+
func (s *ChangeSuite) TestCancel(c *C) {
397+
// Commit a5078b19f08f63e7948abd0a5e2fb7d319d3a565 of the go-git
398+
// fixture inserted "examples/clone/main.go".
399+
//
400+
// On that commit, the "examples/clone" tree is
401+
// 6efca3ff41cab651332f9ebc0c96bb26be809615
402+
//
403+
// and the "examples/colone/main.go" is
404+
// f95dc8f7923add1a8b9f72ecb1e8db1402de601a
405+
406+
path := "examples/clone/main.go"
407+
name := "main.go"
408+
mode := filemode.Regular
409+
blob := plumbing.NewHash("f95dc8f7923add1a8b9f72ecb1e8db1402de601a")
410+
tree := plumbing.NewHash("6efca3ff41cab651332f9ebc0c96bb26be809615")
411+
412+
change := &Change{
413+
From: empty,
414+
To: ChangeEntry{
415+
Name: path,
416+
Tree: s.tree(c, tree),
417+
TreeEntry: TreeEntry{
418+
Name: name,
419+
Mode: mode,
420+
Hash: blob,
421+
},
422+
},
423+
}
424+
425+
ctx, cancel := context.WithCancel(context.Background())
426+
cancel()
427+
p, err := change.PatchContext(ctx)
428+
c.Assert(p, IsNil)
429+
c.Assert(err, ErrorMatches, "operation canceled")
430+
}

plumbing/object/commit.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package object
33
import (
44
"bufio"
55
"bytes"
6+
"context"
67
"errors"
78
"fmt"
89
"io"
@@ -75,7 +76,8 @@ func (c *Commit) Tree() (*Tree, error) {
7576
}
7677

7778
// Patch returns the Patch between the actual commit and the provided one.
78-
func (c *Commit) Patch(to *Commit) (*Patch, error) {
79+
// Error will be return if context expires. Provided context must be non-nil
80+
func (c *Commit) PatchContext(ctx context.Context, to *Commit) (*Patch, error) {
7981
fromTree, err := c.Tree()
8082
if err != nil {
8183
return nil, err
@@ -86,7 +88,12 @@ func (c *Commit) Patch(to *Commit) (*Patch, error) {
8688
return nil, err
8789
}
8890

89-
return fromTree.Patch(toTree)
91+
return fromTree.PatchContext(ctx, toTree)
92+
}
93+
94+
// Patch returns the Patch between the actual commit and the provided one.
95+
func (c *Commit) Patch(to *Commit) (*Patch, error) {
96+
return c.PatchContext(context.Background(), to)
9097
}
9198

9299
// Parents return a CommitIter to the parent Commits.

plumbing/object/commit_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package object
22

33
import (
44
"bytes"
5+
"context"
56
"io"
67
"strings"
78
"time"
@@ -132,6 +133,59 @@ Binary files /dev/null and b/binary.jpg differ
132133
c.Assert(buf.String(), Equals, patch.String())
133134
}
134135

136+
func (s *SuiteCommit) TestPatchContext(c *C) {
137+
from := s.commit(c, plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"))
138+
to := s.commit(c, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
139+
140+
patch, err := from.PatchContext(context.Background(), to)
141+
c.Assert(err, IsNil)
142+
143+
buf := bytes.NewBuffer(nil)
144+
err = patch.Encode(buf)
145+
c.Assert(err, IsNil)
146+
147+
c.Assert(buf.String(), Equals, `diff --git a/vendor/foo.go b/vendor/foo.go
148+
new file mode 100644
149+
index 0000000000000000000000000000000000000000..9dea2395f5403188298c1dabe8bdafe562c491e3
150+
--- /dev/null
151+
+++ b/vendor/foo.go
152+
@@ -0,0 +1,7 @@
153+
+package main
154+
+
155+
+import "fmt"
156+
+
157+
+func main() {
158+
+ fmt.Println("Hello, playground")
159+
+}
160+
`)
161+
c.Assert(buf.String(), Equals, patch.String())
162+
163+
from = s.commit(c, plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47"))
164+
to = s.commit(c, plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"))
165+
166+
patch, err = from.PatchContext(context.Background(), to)
167+
c.Assert(err, IsNil)
168+
169+
buf.Reset()
170+
err = patch.Encode(buf)
171+
c.Assert(err, IsNil)
172+
173+
c.Assert(buf.String(), Equals, `diff --git a/CHANGELOG b/CHANGELOG
174+
deleted file mode 100644
175+
index d3ff53e0564a9f87d8e84b6e28e5060e517008aa..0000000000000000000000000000000000000000
176+
--- a/CHANGELOG
177+
+++ /dev/null
178+
@@ -1 +0,0 @@
179+
-Initial changelog
180+
diff --git a/binary.jpg b/binary.jpg
181+
new file mode 100644
182+
index 0000000000000000000000000000000000000000..d5c0f4ab811897cadf03aec358ae60d21f91c50d
183+
Binary files /dev/null and b/binary.jpg differ
184+
`)
185+
186+
c.Assert(buf.String(), Equals, patch.String())
187+
}
188+
135189
func (s *SuiteCommit) TestCommitEncodeDecodeIdempotent(c *C) {
136190
ts, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05-07:00")
137191
c.Assert(err, IsNil)
@@ -363,3 +417,15 @@ sYyf9RfOnw/KUFAQbdtvLx3ikODQC+D3KBtuKI9ISHQfgw==
363417
_, ok := e.Identities["Sunny <[email protected]>"]
364418
c.Assert(ok, Equals, true)
365419
}
420+
421+
func (s *SuiteCommit) TestPatchCancel(c *C) {
422+
from := s.commit(c, plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"))
423+
to := s.commit(c, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
424+
425+
ctx, cancel := context.WithCancel(context.Background())
426+
cancel()
427+
patch, err := from.PatchContext(ctx, to)
428+
c.Assert(patch, IsNil)
429+
c.Assert(err, ErrorMatches, "operation canceled")
430+
431+
}

plumbing/object/difftree.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package object
22

33
import (
44
"bytes"
5+
"context"
56

67
"gopkg.in/src-d/go-git.v4/utils/merkletrie"
78
"gopkg.in/src-d/go-git.v4/utils/merkletrie/noder"
@@ -10,15 +11,25 @@ import (
1011
// DiffTree compares the content and mode of the blobs found via two
1112
// tree objects.
1213
func DiffTree(a, b *Tree) (Changes, error) {
14+
return DiffTreeContext(context.Background(), a, b)
15+
}
16+
17+
// DiffTree compares the content and mode of the blobs found via two
18+
// tree objects. Provided context must be non-nil.
19+
// An error will be return if context expires
20+
func DiffTreeContext(ctx context.Context, a, b *Tree) (Changes, error) {
1321
from := NewTreeRootNode(a)
1422
to := NewTreeRootNode(b)
1523

1624
hashEqual := func(a, b noder.Hasher) bool {
1725
return bytes.Equal(a.Hash(), b.Hash())
1826
}
1927

20-
merkletrieChanges, err := merkletrie.DiffTree(from, to, hashEqual)
28+
merkletrieChanges, err := merkletrie.DiffTreeContext(ctx, from, to, hashEqual)
2129
if err != nil {
30+
if err == merkletrie.ErrCanceled {
31+
return nil, ErrCanceled
32+
}
2233
return nil, err
2334
}
2435

plumbing/object/patch.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package object
22

33
import (
44
"bytes"
5+
"context"
6+
"errors"
57
"fmt"
68
"io"
79
"math"
@@ -15,10 +17,25 @@ import (
1517
dmp "github.com/sergi/go-diff/diffmatchpatch"
1618
)
1719

20+
var (
21+
ErrCanceled = errors.New("operation canceled")
22+
)
23+
1824
func getPatch(message string, changes ...*Change) (*Patch, error) {
25+
ctx := context.Background()
26+
return getPatchContext(ctx, message, changes...)
27+
}
28+
29+
func getPatchContext(ctx context.Context, message string, changes ...*Change) (*Patch, error) {
1930
var filePatches []fdiff.FilePatch
2031
for _, c := range changes {
21-
fp, err := filePatch(c)
32+
select {
33+
case <-ctx.Done():
34+
return nil, ErrCanceled
35+
default:
36+
}
37+
38+
fp, err := filePatchWithContext(ctx, c)
2239
if err != nil {
2340
return nil, err
2441
}
@@ -29,7 +46,7 @@ func getPatch(message string, changes ...*Change) (*Patch, error) {
2946
return &Patch{message, filePatches}, nil
3047
}
3148

32-
func filePatch(c *Change) (fdiff.FilePatch, error) {
49+
func filePatchWithContext(ctx context.Context, c *Change) (fdiff.FilePatch, error) {
3350
from, to, err := c.Files()
3451
if err != nil {
3552
return nil, err
@@ -52,6 +69,12 @@ func filePatch(c *Change) (fdiff.FilePatch, error) {
5269

5370
var chunks []fdiff.Chunk
5471
for _, d := range diffs {
72+
select {
73+
case <-ctx.Done():
74+
return nil, ErrCanceled
75+
default:
76+
}
77+
5578
var op fdiff.Operation
5679
switch d.Type {
5780
case dmp.DiffEqual:
@@ -70,6 +93,11 @@ func filePatch(c *Change) (fdiff.FilePatch, error) {
7093
from: c.From,
7194
to: c.To,
7295
}, nil
96+
97+
}
98+
99+
func filePatch(c *Change) (fdiff.FilePatch, error) {
100+
return filePatchWithContext(context.Background(), c)
73101
}
74102

75103
func fileContent(f *File) (content string, isBinary bool, err error) {

plumbing/object/tree.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package object
22

33
import (
44
"bufio"
5+
"context"
56
"errors"
67
"fmt"
78
"io"
@@ -295,15 +296,30 @@ func (from *Tree) Diff(to *Tree) (Changes, error) {
295296
return DiffTree(from, to)
296297
}
297298

299+
// Diff returns a list of changes between this tree and the provided one
300+
// Error will be returned if context expires
301+
// Provided context must be non nil
302+
func (from *Tree) DiffContext(ctx context.Context, to *Tree) (Changes, error) {
303+
return DiffTreeContext(ctx, from, to)
304+
}
305+
298306
// Patch returns a slice of Patch objects with all the changes between trees
299307
// in chunks. This representation can be used to create several diff outputs.
300308
func (from *Tree) Patch(to *Tree) (*Patch, error) {
301-
changes, err := DiffTree(from, to)
309+
return from.PatchContext(context.Background(), to)
310+
}
311+
312+
// Patch returns a slice of Patch objects with all the changes between trees
313+
// in chunks. This representation can be used to create several diff outputs.
314+
// If context expires, an error will be returned
315+
// Provided context must be non-nil
316+
func (from *Tree) PatchContext(ctx context.Context, to *Tree) (*Patch, error) {
317+
changes, err := DiffTreeContext(ctx, from, to)
302318
if err != nil {
303319
return nil, err
304320
}
305321

306-
return changes.Patch()
322+
return changes.PatchContext(ctx)
307323
}
308324

309325
// treeEntryIter facilitates iterating through the TreeEntry objects in a Tree.

0 commit comments

Comments
 (0)