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
190
191
192
193
194
195
196
|
package filesystem
import (
"io"
"os"
"path"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/filemode"
"gopkg.in/src-d/go-git.v4/utils/merkletrie/noder"
"gopkg.in/src-d/go-billy.v4"
)
var ignore = map[string]bool{
".git": true,
}
// The node represents a file or a directory in a billy.Filesystem. It
// implements the interface noder.Noder of merkletrie package.
//
// This implementation implements a "standard" hash method being able to be
// compared with any other noder.Noder implementation inside of go-git.
type node struct {
fs billy.Filesystem
submodules map[string]plumbing.Hash
path string
hash []byte
children []noder.Noder
isDir bool
}
// NewRootNode returns the root node based on a given billy.Filesystem.
//
// In order to provide the submodule hash status, a map[string]plumbing.Hash
// should be provided where the key is the path of the submodule and the commit
// of the submodule HEAD
func NewRootNode(
fs billy.Filesystem,
submodules map[string]plumbing.Hash,
) noder.Noder {
return &node{fs: fs, submodules: submodules, isDir: true}
}
// Hash the hash of a filesystem is the result of concatenating the computed
// plumbing.Hash of the file as a Blob and its plumbing.FileMode; that way the
// difftree algorithm will detect changes in the contents of files and also in
// their mode.
//
// The hash of a directory is always a 24-bytes slice of zero values
func (n *node) Hash() []byte {
return n.hash
}
func (n *node) Name() string {
return path.Base(n.path)
}
func (n *node) IsDir() bool {
return n.isDir
}
func (n *node) Children() ([]noder.Noder, error) {
if err := n.calculateChildren(); err != nil {
return nil, err
}
return n.children, nil
}
func (n *node) NumChildren() (int, error) {
if err := n.calculateChildren(); err != nil {
return -1, err
}
return len(n.children), nil
}
func (n *node) calculateChildren() error {
if !n.IsDir() {
return nil
}
if len(n.children) != 0 {
return nil
}
files, err := n.fs.ReadDir(n.path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return nil
}
for _, file := range files {
if _, ok := ignore[file.Name()]; ok {
continue
}
c, err := n.newChildNode(file)
if err != nil {
return err
}
n.children = append(n.children, c)
}
return nil
}
func (n *node) newChildNode(file os.FileInfo) (*node, error) {
path := path.Join(n.path, file.Name())
hash, err := n.calculateHash(path, file)
if err != nil {
return nil, err
}
node := &node{
fs: n.fs,
submodules: n.submodules,
path: path,
hash: hash,
isDir: file.IsDir(),
}
if hash, isSubmodule := n.submodules[path]; isSubmodule {
node.hash = append(hash[:], filemode.Submodule.Bytes()...)
node.isDir = false
}
return node, nil
}
func (n *node) calculateHash(path string, file os.FileInfo) ([]byte, error) {
if file.IsDir() {
return make([]byte, 24), nil
}
var hash plumbing.Hash
var err error
if file.Mode()&os.ModeSymlink != 0 {
hash, err = n.doCalculateHashForSymlink(path, file)
} else {
hash, err = n.doCalculateHashForRegular(path, file)
}
if err != nil {
return nil, err
}
mode, err := filemode.NewFromOSFileMode(file.Mode())
if err != nil {
return nil, err
}
return append(hash[:], mode.Bytes()...), nil
}
func (n *node) doCalculateHashForRegular(path string, file os.FileInfo) (plumbing.Hash, error) {
f, err := n.fs.Open(path)
if err != nil {
return plumbing.ZeroHash, err
}
defer f.Close()
h := plumbing.NewHasher(plumbing.BlobObject, file.Size())
if _, err := io.Copy(h, f); err != nil {
return plumbing.ZeroHash, err
}
return h.Sum(), nil
}
func (n *node) doCalculateHashForSymlink(path string, file os.FileInfo) (plumbing.Hash, error) {
target, err := n.fs.Readlink(path)
if err != nil {
return plumbing.ZeroHash, err
}
h := plumbing.NewHasher(plumbing.BlobObject, file.Size())
if _, err := h.Write([]byte(target)); err != nil {
return plumbing.ZeroHash, err
}
return h.Sum(), nil
}
func (n *node) String() string {
return n.path
}
|