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

Commit 61c5306

Browse files
committed
difftree changes to provide both files
1 parent 3249776 commit 61c5306

File tree

2 files changed

+603
-0
lines changed

2 files changed

+603
-0
lines changed

tree_diff.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package git
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"sort"
8+
"strings"
9+
10+
"gopkg.in/src-d/go-git.v3/core"
11+
)
12+
13+
type Action int
14+
15+
func (a Action) String() string {
16+
switch a {
17+
case Insert:
18+
return "Insert"
19+
case Delete:
20+
return "Delete"
21+
case Modify:
22+
return "Modify"
23+
default:
24+
panic(fmt.Sprintf("unsupported action: %d", a))
25+
}
26+
}
27+
28+
const (
29+
Insert Action = iota
30+
Delete
31+
Modify
32+
)
33+
34+
type Change struct {
35+
Action
36+
Name string
37+
Files [2]*File
38+
}
39+
40+
func (c *Change) String() string {
41+
return fmt.Sprintf("<Action: %s, Path: %s>", c.Action, c.Name)
42+
}
43+
44+
type Changes []*Change
45+
46+
func newEmpty() Changes {
47+
return make([]*Change, 0, 0)
48+
}
49+
50+
func DiffTree(a, b *Tree) ([]*Change, error) {
51+
if a == b {
52+
return newEmpty(), nil
53+
}
54+
55+
if a == nil || b == nil {
56+
return newWithEmpty(a, b)
57+
}
58+
59+
return newDiffTree(a, b)
60+
}
61+
62+
func (c Changes) Len() int {
63+
return len(c)
64+
}
65+
66+
func (c Changes) Swap(i, j int) {
67+
c[i], c[j] = c[j], c[i]
68+
}
69+
70+
func (c Changes) Less(i, j int) bool {
71+
return strings.Compare(c[i].Name, c[j].Name) < 0
72+
}
73+
74+
func (c Changes) String() string {
75+
var buffer bytes.Buffer
76+
buffer.WriteString("[")
77+
comma := ""
78+
for _, v := range c {
79+
buffer.WriteString(comma)
80+
buffer.WriteString(v.String())
81+
comma = ", "
82+
}
83+
buffer.WriteString("]")
84+
85+
return buffer.String()
86+
}
87+
88+
func newWithEmpty(a, b *Tree) (Changes, error) {
89+
changes := newEmpty()
90+
91+
var action Action
92+
var tree *Tree
93+
if a == nil {
94+
action = Insert
95+
tree = b
96+
} else {
97+
action = Delete
98+
tree = a
99+
}
100+
101+
iter := tree.Files()
102+
defer iter.Close()
103+
104+
for {
105+
file, err := iter.Next()
106+
if err == io.EOF {
107+
break
108+
} else if err != nil {
109+
return nil, fmt.Errorf("cannot get next file: %s", err)
110+
}
111+
112+
var files [2]*File
113+
if action == Insert {
114+
files[1] = file
115+
} else {
116+
files[0] = file
117+
}
118+
119+
changes = append(changes, &Change{
120+
Action: action,
121+
Name: file.Name,
122+
Files: files,
123+
})
124+
}
125+
126+
return changes, nil
127+
}
128+
129+
// FIXME: this is very inefficient, but correct.
130+
// The proper way to do this is to implement a diff-tree algorithm,
131+
// while taking advantage of the tree hashes to avoid traversing
132+
// subtrees when the hash is equal in both inputs.
133+
func newDiffTree(a, b *Tree) ([]*Change, error) {
134+
result := make([]*Change, 0)
135+
136+
aChanges, err := newWithEmpty(a, nil)
137+
if err != nil {
138+
return nil, fmt.Errorf("cannot create nil-diff of source tree: %s", err)
139+
}
140+
sort.Sort(aChanges)
141+
142+
bChanges, err := newWithEmpty(nil, b)
143+
if err != nil {
144+
return nil, fmt.Errorf("cannot create nil-diff of destination tree: %s", err)
145+
}
146+
sort.Sort(bChanges)
147+
148+
for len(aChanges) > 0 && len(bChanges) > 0 {
149+
switch comp := strings.Compare(aChanges[0].Name, bChanges[0].Name); {
150+
case comp == 0: // append as "Modify" or ignore if not changed
151+
modified, err := hasChange(a, b, aChanges[0].Name)
152+
if err != nil {
153+
return nil, err
154+
}
155+
156+
if modified {
157+
result = append(result, &Change{
158+
Action: Modify,
159+
Name: aChanges[0].Name,
160+
Files: [2]*File{aChanges[0].Files[0], bChanges[0].Files[1]},
161+
})
162+
}
163+
164+
aChanges = aChanges[1:]
165+
bChanges = bChanges[1:]
166+
case comp < 0: // delete first a change
167+
result = append(result, aChanges[0])
168+
aChanges = aChanges[1:]
169+
case comp > 0: // insert first b change
170+
result = append(result, bChanges[0])
171+
bChanges = bChanges[1:]
172+
}
173+
}
174+
175+
// append all remaining changes in aChanges, if any, as deletes
176+
// append all remaining changes in bChanges, if any, as inserts
177+
result = append(result, aChanges...)
178+
result = append(result, bChanges...)
179+
180+
return result, nil
181+
}
182+
183+
func hasChange(a, b *Tree, path string) (bool, error) {
184+
ha, err := hash(a, path)
185+
if err != nil {
186+
return false, err
187+
}
188+
189+
hb, err := hash(b, path)
190+
if err != nil {
191+
return false, err
192+
}
193+
194+
return ha != hb, nil
195+
}
196+
197+
func hash(tree *Tree, path string) (core.Hash, error) {
198+
file, err := tree.File(path)
199+
if err != nil {
200+
var empty core.Hash
201+
return empty, fmt.Errorf("cannot find file %s in tree: %s", path, err)
202+
}
203+
204+
return file.Hash, nil
205+
}

0 commit comments

Comments
 (0)