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

Commit 0b8b8da

Browse files
authored
difftree for git.Trees (#273)
Last PR to fix #82: This PR modifies the difftree package itself. The old version extracted the files in both trees and compare them by hand. The new version turn the trees into merkletrie.Noders and call the merkletrie.Difftree function on them. How to review this PR: treenoder.go: defines the treeNoder type that wraps a git.Tree and implements merkletrie.Noder. change.go: defines the type of the output of a difftree operation. The type is the same as before, but I have moved it into its own file to keep the package organized. The old package defines the Action type too (insert, delete, modify), now, we reuse merkletrie.Action and it is no longer a field, but a method. change_adaptor.go: defines functions to turn merkletrie.Changes into difftree.Changes. difftree.go: before this patch this file holds all the logic to do a difftree, now it just turns the git.Trees into treeNoders, call merkletrie.difftree on them, and turns the resulting merkletrie.Changes into difftree.Changes. The only interesting piece of code here is that noders don't have the concept of mode (file permissions). The treenoder type codifies git.Tree modes into the merkletrie.Noder hash, so changes in the mode of a file are detected as modifications, just as the original git diff-tree command does.
1 parent efe9ecf commit 0b8b8da

File tree

7 files changed

+1269
-536
lines changed

7 files changed

+1269
-536
lines changed

plumbing/difftree/change.go

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package difftree
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"strings"
7+
8+
"srcd.works/go-git.v4/plumbing/object"
9+
"srcd.works/go-git.v4/utils/merkletrie"
10+
)
11+
12+
// Change values represent a detected change between two git trees. For
13+
// modifications, From is the original status of the node and To is its
14+
// final status. For insertions, From is the zero value and for
15+
// deletions To is the zero value.
16+
type Change struct {
17+
From ChangeEntry
18+
To ChangeEntry
19+
}
20+
21+
var empty = ChangeEntry{}
22+
23+
// Action returns the kind of action represented by the change, an
24+
// insertion, a deletion or a modification.
25+
func (c *Change) Action() (merkletrie.Action, error) {
26+
if c.From == empty && c.To == empty {
27+
return merkletrie.Action(0),
28+
fmt.Errorf("malformed change: empty from and to")
29+
}
30+
if c.From == empty {
31+
return merkletrie.Insert, nil
32+
}
33+
if c.To == empty {
34+
return merkletrie.Delete, nil
35+
}
36+
37+
return merkletrie.Modify, nil
38+
}
39+
40+
// Files return the files before and after a change.
41+
// For insertions from will be nil. For deletions to will be nil.
42+
func (c *Change) Files() (from, to *object.File, err error) {
43+
action, err := c.Action()
44+
if err != nil {
45+
return
46+
}
47+
48+
if action == merkletrie.Insert || action == merkletrie.Modify {
49+
to, err = c.To.Tree.TreeEntryFile(&c.To.TreeEntry)
50+
if err != nil {
51+
return
52+
}
53+
}
54+
55+
if action == merkletrie.Delete || action == merkletrie.Modify {
56+
from, err = c.From.Tree.TreeEntryFile(&c.From.TreeEntry)
57+
if err != nil {
58+
return
59+
}
60+
}
61+
62+
return
63+
}
64+
65+
func (c *Change) String() string {
66+
action, err := c.Action()
67+
if err != nil {
68+
return fmt.Sprintf("malformed change")
69+
}
70+
71+
return fmt.Sprintf("<Action: %s, Path: %s>", action, c.name())
72+
}
73+
74+
func (c *Change) name() string {
75+
if c.From != empty {
76+
return c.From.Name
77+
}
78+
79+
return c.To.Name
80+
}
81+
82+
// ChangeEntry values represent a node that has suffered a change.
83+
type ChangeEntry struct {
84+
// Full path of the node using "/" as separator.
85+
Name string
86+
// Parent tree of the node that has changed.
87+
Tree *object.Tree
88+
// The entry of the node.
89+
TreeEntry object.TreeEntry
90+
}
91+
92+
// Changes represents a collection of changes between two git trees.
93+
// Implements sort.Interface lexicographically over the path of the
94+
// changed files.
95+
type Changes []*Change
96+
97+
func (c Changes) Len() int {
98+
return len(c)
99+
}
100+
101+
func (c Changes) Swap(i, j int) {
102+
c[i], c[j] = c[j], c[i]
103+
}
104+
105+
func (c Changes) Less(i, j int) bool {
106+
return strings.Compare(c[i].name(), c[j].name()) < 0
107+
}
108+
109+
func (c Changes) String() string {
110+
var buffer bytes.Buffer
111+
buffer.WriteString("[")
112+
comma := ""
113+
for _, v := range c {
114+
buffer.WriteString(comma)
115+
buffer.WriteString(v.String())
116+
comma = ", "
117+
}
118+
buffer.WriteString("]")
119+
120+
return buffer.String()
121+
}

plumbing/difftree/change_adaptor.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package difftree
2+
3+
// The folowing functions transform changes types form the merkletrie
4+
// package to changes types from this package.
5+
6+
import (
7+
"fmt"
8+
9+
"srcd.works/go-git.v4/plumbing/object"
10+
"srcd.works/go-git.v4/utils/merkletrie"
11+
"srcd.works/go-git.v4/utils/merkletrie/noder"
12+
)
13+
14+
func newChange(c merkletrie.Change) (*Change, error) {
15+
ret := &Change{}
16+
17+
var err error
18+
if ret.From, err = newChangeEntry(c.From); err != nil {
19+
return nil, fmt.Errorf("From field: ", err)
20+
}
21+
22+
if ret.To, err = newChangeEntry(c.To); err != nil {
23+
return nil, fmt.Errorf("To field: ", err)
24+
}
25+
26+
return ret, nil
27+
}
28+
29+
func newChangeEntry(p noder.Path) (ChangeEntry, error) {
30+
if p == nil {
31+
return empty, nil
32+
}
33+
34+
asTreeNoder, ok := p.Last().(*treeNoder)
35+
if !ok {
36+
return ChangeEntry{}, fmt.Errorf("cannot transform non-TreeNoders")
37+
}
38+
39+
return ChangeEntry{
40+
Name: p.String(),
41+
Tree: asTreeNoder.parent,
42+
TreeEntry: object.TreeEntry{
43+
Name: asTreeNoder.name,
44+
Mode: asTreeNoder.mode,
45+
Hash: asTreeNoder.hash,
46+
},
47+
}, nil
48+
}
49+
50+
func newChanges(src merkletrie.Changes) (Changes, error) {
51+
ret := make(Changes, len(src))
52+
var err error
53+
for i, e := range src {
54+
ret[i], err = newChange(e)
55+
if err != nil {
56+
return nil, fmt.Errorf("change #%d: %s", err)
57+
}
58+
}
59+
60+
return ret, nil
61+
}

0 commit comments

Comments
 (0)