Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(58)

Side by Side Diff: go/src/infra/libs/git/tree.go

Issue 662113003: Drover's back, baby! (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git/+/master
Patch Set: Lots of fixes Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 package git
5
6 import (
7 "bytes"
8 "fmt"
9 "sort"
10 "strconv"
11 "strings"
12 "sync"
13
14 "infra/libs/infra_util"
15 )
16
17 // Mode is a typed representation of git's tree mode concept
18 type Mode int
M-A Ruel 2014/10/21 00:55:55 Mode is maybe a tad too generic. But I see that it
19
20 // Type returns a Object.Type() compatible string for this mode.
21 func (m Mode) Type() ObjectType {
22 switch {
23 case m == 0040000:
24 return TreeType
25 case m == 0160000:
26 return CommitType
27 case (m & 0100000) != 0:
28 return BlobType
29 default:
30 panic(fmt.Sprintf("unknown type for mode %d", m))
M-A Ruel 2014/10/21 00:55:55 fmt.Errorf() but I don't like that input data can
31 }
32 }
33
34 func (m Mode) String() string { return fmt.Sprintf("%06o", m) }
35
36 // Child is an entry in a Tree. It ties a Mode to an Object.
37 type Child struct {
38 Object Object
39 Mode Mode
40 }
41
42 func (c Child) String() string {
43 return fmt.Sprintf("Child(%s, %s)", c.Object, c.Mode)
44 }
45
46 // Tree is a true tree which represents a git 'tree' of objects, and can
47 // represent those objects recursively (unlike a true git 'tree').
48 //
49 // Trees are also mutable, so you can manipulate them with impunity. Note that
50 // ID() is not continuously updated, and you must call Rehash in order to
51 // recalculate the correct ID() after manipulating the Tree.
52 type Tree struct {
53 id ObjectID
54 cachedRawString *string
55
56 children map[string]*Child
57 }
58
59 func (t *Tree) String() string {
60 return fmt.Sprintf("Tree(%s, %v)", t.id, t.children)
61 }
62
63 // ID returns the last-calculated ObjectId of this tree. Note that this ID()
64 // may be out-of-date if you have modified this Tree with {Set,Del}Child().
65 func (t *Tree) ID() ObjectID { return t.id }
M-A Ruel 2014/10/21 00:55:55 Why not call Rehash automatically?
iannucci 2016/05/23 21:53:43 Because if it's an empty tree, rehashing it would
66
67 // Type returns TreeType to satisfy the Object interface
68 func (t *Tree) Type() ObjectType { return TreeType }
69
70 // Complete indicates that this tree's ID() matches its contents. It can
71 // become false if you call DelChild or SetChild on this Tree.
72 func (t *Tree) Complete() bool { return t.cachedRawString != nil }
73
74 // NumChildren returns the number of immediate children this tree has.
75 func (t *Tree) NumChildren() int { return len(t.children) }
76
77 // Intern recursively interns this tree into the given repo.
78 //
79 // If the tree contains a missing object which is internable (i.g. not
80 // EmptyObject), it will also be interned.
81 //
82 // If the tree contains an object with an ID of NoID, this method will panic.
83 //
84 // If the tree contains a missing object which is not internable, this method
85 // will panic (see InternAllowMissing to negate this condition).
86 //
87 // This method has the side effect of causing all internable but !Complete()
88 // objects to become Complete(). In particular, this means that the value of
89 // t.ID() will be correct after calling this.
90 func (t *Tree) Intern(r *Repo) {
91 t.intern(r, false)
92 }
93
94 // InternAllowMissing is identical to Intern except that it will not panic
95 // on missing non-internable objects, but just treats them
96 func (t *Tree) InternAllowMissing(r *Repo) {
97 t.intern(r, true)
98 }
99
100 // Rehash causes an !Complete() tree to become Complete, and resets ID() to
101 // match the Tree's current state.
102 func (t *Tree) Rehash() {
103 if !t.Complete() {
104 t.RawString()
105 }
106 }
107
108 // NewEmptyTree creates a new Tree with the given |id|, but no children (e.g.
109 // it is !Complete()). You only want to use this if you know that you're going
110 // to be adding children to the Tree, otherwise use EmptyObject(TreeType, id).
111 func NewEmptyTree(id ObjectID, childrenHint int) *Tree {
112 return &Tree{
113 id: id,
114 children: make(map[string]*Child, childrenHint),
115 }
116 }
117
118 // NewEmptyChild returns a Child containing an EmptyObject of id.
119 func NewEmptyChild(mode Mode, id ObjectID) *Child {
120 return &Child{
121 NewEmptyObject(mode.Type(), id),
122 mode,
123 }
124 }
125
126 // TreeFromText returns a Complete() Tree for a `git ls-tree`-formatted
M-A Ruel 2014/10/21 00:55:55 Note that there could be cases where it doesn't fi
iannucci 2016/05/23 21:53:43 The thought crossed my mind, but for now I think i
127 // tree string.
128 func TreeFromText(data []byte) (ret *Tree, err error) {
129 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
130 retPtr := NewEmptyTree(NoID, len(lines))
131 for _, line := range lines {
132 infoPath := strings.SplitN(line, "\t", 2)
133 var path string
134 if infoPath[1][0] == '"' {
135 path, err = strconv.Unquote(infoPath[1])
136 if err != nil {
137 return
138 }
139 } else {
140 path = infoPath[1]
141 }
142
143 modeTypeID := strings.Fields(infoPath[0])
144 var mode uint64
145 mode, err = strconv.ParseUint(modeTypeID[0], 8, 32)
146 if err != nil {
147 return
148 }
149
150 retPtr.SetChild(path, NewEmptyChild(Mode(mode), MakeObjectID(mod eTypeID[2])))
151 }
152 retPtr.Rehash()
153 ret = retPtr
154 return
155 }
156
157 // TreeFromRawWithID trusts id, and will return a Complete() *Tree. Data is
158 // formatted the same as RawString (i.e. in the raw git tree format).
159 func TreeFromRawWithID(id ObjectID, data []byte) (ret *Tree, err error) {
160 retPtr := NewEmptyTree(id, 16)
161 // <mode> SP <name> NUL [20]byte
162 buf := bytes.NewBuffer(data)
163 nom := infra_util.Nom(buf)
164 yoink := infra_util.Yoink(buf)
165 for buf.Len() != 0 {
166 var mode uint64
167 mode, err = strconv.ParseUint(nom(' '), 8, 64)
168 if err != nil {
169 return
170 }
171 name := nom('\x00')
172 id := MakeObjectIDRaw(yoink(20))
173 retPtr.SetChild(name, NewEmptyChild(Mode(int(mode)), id))
174 }
175 s := string(data)
176 retPtr.cachedRawString = &s
177 ret = retPtr
178 return
179 }
180
181 // GetChild returns the Child at the slash-delemited |path| relative to this
182 // Tree, or nil (if the child was not found).
183 func (t *Tree) GetChild(path string) *Child {
184 pathParts := strings.SplitN(path, "/", 2)
185 if len(pathParts) == 1 {
186 return t.children[path]
187 }
188
189 dir, rest := pathParts[0], pathParts[1]
190 intermediate, exists := t.children[dir]
191 if !exists {
192 panic(fmt.Errorf("intermediate child %s doesn't exist", dir))
M-A Ruel 2014/10/21 00:55:55 return an error, it's not panic worthy.
193 }
194
195 if childTree, ok := intermediate.Object.(*Tree); !ok {
196 panic(fmt.Errorf("entry path is not a tree: %s", dir))
197 } else {
198 return childTree.GetChild(rest)
199 }
200 }
201
202 // SetChild adds |child| to this Tree at the given slash-delimited |path|. It
203 // will create intermediate empty Trees if necessary, but will panic() if you
204 // attempt to set the child under something which is not a tree.
205 func (t *Tree) SetChild(path string, child *Child) {
206 t.cachedRawString = nil
207 pathParts := strings.SplitN(path, "/", 2)
208 if len(pathParts) == 1 {
209 t.children[path] = child
210 } else {
211 dir, rest := pathParts[0], pathParts[1]
212 intermediate, exists := t.children[dir]
213 if !exists {
214 intermediate = NewEmptyChild(Mode(040000), NoID)
215 t.children[dir] = intermediate
216 }
217
218 if childTree, ok := intermediate.Object.(*Tree); !ok {
219 panic("entry path is not a tree: " + dir)
220 } else {
221 childTree.SetChild(rest, child)
222 }
223 }
224 }
225
226 // DelChild removes the child at the slash-delimited |path|. Will panic if
227 // |path| does not exist, or if one of the intermediate paths is not a Tree.
228 func (t *Tree) DelChild(path string) {
229 t.cachedRawString = nil
230 pathParts := strings.SplitN(path, "/", 2)
231 if len(pathParts) == 1 {
232 if _, has := t.children[path]; !has {
233 panic("entry path does not exist: " + path)
234 }
235 delete(t.children, path)
236 } else {
237 dir, rest := pathParts[0], pathParts[1]
238 child, exists := t.children[dir]
239 if !exists {
240 panic("entry path does not exist: " + dir)
241 }
242
243 if childTree, ok := child.Object.(*Tree); !ok {
244 panic("etnry path is not a tree: " + dir)
245 } else {
246 childTree.DelChild(rest)
247 if childTree.NumChildren() == 0 {
248 delete(t.children, dir)
249 }
250 }
251 }
252 }
253
254 // RawString returns a git hash-object compatible representation of this Tree,
255 // according to the currently-set children (e.g. it may return something which
256 // does not match ID).
257 func (t *Tree) RawString() string {
258 if t.cachedRawString == nil {
M-A Ruel 2014/10/21 00:55:55 It's not necessary that it is a pointer, because i
iannucci 2016/05/23 21:53:43 Except that it /can/ be "" :). $ git hash-object
259 buf := &bytes.Buffer{}
260
261 entries := make(treeEntries, 0, len(t.children))
262 for path, child := range t.children {
263 entries = append(entries, treeEntry{
264 id: child.Object.ID(),
265 mode: child.Mode,
266 name: path,
267 })
268 }
269
270 sort.Sort(entries)
271
272 for _, ent := range entries {
273 fmt.Fprintf(buf, "%o %s\x00%s", ent.mode, ent.name, ent. id.RawString())
274 }
275 s := buf.String()
276 t.id = MakeObjectIDForData(TreeType, buf.Bytes())
277 t.cachedRawString = &s
278 }
279 return *t.cachedRawString
280 }
281
282 /// Private
M-A Ruel 2014/10/21 00:55:54 assuming is a file block section, add an empty lin
iannucci 2016/05/23 21:53:43 Done.
283 type treeEntry struct {
284 id ObjectID
285 mode Mode
286 name string
287 }
288 type treeEntries []treeEntry
289
290 func (s treeEntries) Len() int { return len(s) }
291
292 // Extra trailing slash is how git sorts the tree objects too.
293 func (s treeEntries) Less(i, j int) bool {
294 a, b := s[i].name, s[j].name
295
296 if s[i].mode.Type() == TreeType {
297 a += "/"
298 }
299 if s[j].mode.Type() == TreeType {
300 b += "/"
301 }
302
303 return a < b
304 }
305 func (s treeEntries) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
306
307 // Interns the Tree recursively. If allowMissing is true, this will not panic
M-A Ruel 2014/10/21 00:55:55 // intern interns the ..
308 // when the repo is missing an object which is non-internable in this tree (i.e.
309 // if you have an EmptyObject in the Tree, and the repo doesn't contain the
310 // real object).
311 func (t *Tree) intern(r *Repo, allowMissing bool) {
312 if t.Complete() && r.HasObjectID(t.ID()) {
313 return
314 }
315 grp := sync.WaitGroup{}
316 for _, c := range t.children {
317 c := c
318 grp.Add(1)
319 go func() {
320 defer grp.Done()
321 switch x := c.Object.(type) {
322 case *Tree:
323 x.intern(r, allowMissing)
324 case InternableObject:
325 r.Intern(x)
326 case Object:
327 if !(r.HasObjectID(x.ID()) || allowMissing) {
328 panic(fmt.Errorf("missing non-internable object: %v", x))
329 }
330 if x.ID() == NoID {
331 panic(fmt.Errorf("object has an ID of <N oID>"))
332 }
333 }
334 }()
335 }
336 grp.Wait()
337 if !(t.ID() != NoID && t.NumChildren() == 0) {
338 // Having an ID but no children is a special case. Because of ho w DelChild
339 // cleans up empty trees, you can't have an empty subtree, unles s you
340 // intentionally put an empty tree there with an ID.
341 r.Intern(t)
M-A Ruel 2014/10/21 00:55:55 What about a directory containing a directory that
iannucci 2016/05/23 21:53:43 It's not a standard thing in git. You can contrive
342 }
343 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698