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

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: more tests and refactors Created 6 years, 1 month 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
« no previous file with comments | « go/src/infra/libs/git/repo/repo_test.go ('k') | go/src/infra/libs/git/treeDiff.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
5 package git
6
7 import (
8 "bytes"
9 "fmt"
10 "sort"
11 "strconv"
12 "strings"
13
14 "infra/libs/infra_util"
15 )
16
17 // Contstants //////////////////////////////////////////////////////////////////
18
19 const EmptyTreeHash = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
20
21 // Types ///////////////////////////////////////////////////////////////////////
22
23 // Tree is a true tree which represents a git 'tree' of objects, and can
24 // represent those objects recursively (unlike a true git 'tree').
25 //
26 // Trees are also mutable, so you can manipulate them with impunity. Note that
27 // ID() is not continuously updated, and you must call Rehash in order to
28 // recalculate the correct ID() after manipulating the Tree.
29 type Tree struct {
30 id *ObjectID
31 cachedRawString *string
32
33 children map[string]*Child
34 }
35
36 // Constructors ///////////////////////////////////////////////////////////////
37
38 // NewEmptyTree creates a new Tree with the given |id|, but no children (e.g.
39 // it is !Complete()). You only want to use this if you know that you're going
40 // to be adding children to the Tree, otherwise use EmptyObject(TreeType, id).
41 func NewEmptyTree(id Identifiable, childrenHint int) *Tree {
42 return &Tree{
43 id: id.ID(),
44 children: make(map[string]*Child, childrenHint),
45 }
46 }
47
48 func BlankTreeChild(childrenHint int) *Child {
49 return &Child{NewEmptyTree(&NoID, childrenHint), 040000}
50 }
51
52 // TreeFromText returns a Complete() Tree for a `git ls-tree`-formatted
53 // tree string.
54 func NewTreeFromText(data []byte) (ret *Tree, err error) {
55 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
56 retPtr := NewEmptyTree(&NoID, len(lines))
57 for _, line := range lines {
58 infoPath := strings.SplitN(line, "\t", 2)
59 if len(infoPath) != 2 {
60 return nil, fmt.Errorf("NewTreeFromText: unparsable line %#v", line)
61 }
62 var path string
63 if infoPath[1][0] == '"' {
64 path, err = strconv.Unquote(infoPath[1])
65 if err != nil {
66 return
67 }
68 } else {
69 path = infoPath[1]
70 }
71
72 modeTypeID := strings.Fields(infoPath[0])
73 if len(modeTypeID) != 3 {
74 return nil, fmt.Errorf("NewTreeFromText: unparsable line %#v", line)
75 }
76 var mode uint64
77 mode, err = strconv.ParseUint(modeTypeID[0], 8, 32)
78 if err != nil {
79 return
80 }
81
82 id, err := MakeObjectIDErr(modeTypeID[2])
83 if err != nil {
84 return nil, err
85 }
86 c, err := NewEmptyChild(Mode(mode), id)
87 if err != nil {
88 return nil, err
89 }
90 retPtr.SetChild(path, c)
91 }
92 retPtr.RawString()
93 ret = retPtr
94 return
95 }
96
97 func NewTreeFromRaw(data []byte) (ret *Tree, err error) {
98 return NewTreeFromRawWithID(MakeObjectIDForData(TreeType, data), data)
99 }
100
101 // TreeFromRawWithID trusts id, and will return a Complete() *Tree. Data is
102 // formatted the same as RawString (i.e. in the raw git tree format).
103 func NewTreeFromRawWithID(id Identifiable, data []byte) (ret *Tree, err error) {
104 defer func() {
105 if r := recover(); r != nil {
106 err = r.(error)
107 }
108 }()
109
110 retPtr := NewEmptyTree(id, 16)
111 // <mode> SP <name> NUL [20]byte
112 buf := bytes.NewBuffer(data)
113 nom := infra_util.Nom(buf)
114 yoink := infra_util.Yoink(buf)
115 for buf.Len() != 0 {
116 var mode uint64
117 mode, err = strconv.ParseUint(nom(' '), 8, 64)
118 if err != nil {
119 return
120 }
121 name := nom('\x00')
122
123 id := ObjectID{}
124 copy(id.raw[:], yoink(20))
125 c, err := NewEmptyChild(Mode(int(mode)), &id)
126 if err != nil {
127 return nil, err
128 }
129
130 retPtr.SetChild(name, c)
131 }
132 s := string(data)
133 retPtr.cachedRawString = &s
134 ret = retPtr
135 return
136 }
137
138 // Factories ///////////////////////////////////////////////////////////////////
139
140 // LoadFullTree gets a recursively-enumerated Tree (or an EmptyObject)
141 //
142 // withBlobs:
143 // true - Load blobs from Repo
144 // false - Blobs will be EmptyObject
145 //
146 // missingErr:
147 // true - All entries in tree must load
148 // false - Missing entries will remain EmptyObject (or an !Complete() Tree)
149 func LoadFullTree(s GitService, id Identifiable, withBlobs, missingErr bool) (Ob ject, error) {
150 obj, err := s.GetObjectID(id)
151 if err != nil {
152 return nil, err
153 }
154 if obj == nil {
155 if !missingErr {
156 return NewEmptyObjectErr(TreeType, id)
157 }
158 return nil, fmt.Errorf("object %s missing", id)
159 }
160
161 switch x := obj.(type) {
162 case *Tree:
163 case *Commit:
164 info := s.ObjectInfo(x.ID().String() + ":")
165 if info == nil {
166 return nil, fmt.Errorf("object %s: missing", x.ID())
167 }
168 return LoadFullTree(s, info.ID, withBlobs, missingErr)
169 default:
170 return nil, fmt.Errorf("object %v is not a Tree!", x)
171 }
172 base := obj.(*Tree)
173
174 type change struct {
175 path string
176 child *Child
177 }
178 changes := make(chan change, len(base.children))
179 allErrs := infra_util.FanOutIn(len(base.children), func(ch chan<- func() error) {
180 for p, c := range base.children {
181 p, c := p, c
182 ch <- func() (err error) {
183 var rslt Object
184
185 switch c.Mode.Type() {
186 case TreeType:
187 rslt, err = LoadFullTree(s, c.Object.ID( ), withBlobs, missingErr)
188
189 case BlobType:
190 rslt = c.Object
191 if withBlobs {
192 rslt, err = s.GetObjectID(c.Obje ct)
193 }
194 }
195
196 if err == nil {
197 if rslt != nil {
198 changes <- change{p, &Child{rslt , c.Mode}}
199 } else if missingErr {
200 err = fmt.Errorf("object %s miss ing", c.Object.ID())
201 }
202 }
203 return
204 }
205 }
206 })
207 close(changes)
208 for c := range changes {
209 base.children[c.path] = c.child
210 }
211
212 return base, allErrs
213 }
214
215 // Member functions ////////////////////////////////////////////////////////////
216
217 func (t *Tree) String() string {
218 return fmt.Sprintf("Tree(%s, %v)", t.id, t.children)
219 }
220
221 // ID returns the last-calculated ObjectId of this tree. Note that this ID()
222 // may be out-of-date if you have modified this Tree with {Set,Del}Child().
223 func (t *Tree) ID() *ObjectID { return t.id }
224
225 // Type returns TreeType to satisfy the Object interface
226 func (t *Tree) Type() ObjectType { return TreeType }
227
228 // Complete indicates that this tree's ID() matches its contents. It can
229 // become false if you call DelChild or SetChild on this Tree.
230 func (t *Tree) Complete() bool { return t.cachedRawString != nil }
231
232 // NumChildren returns the number of immediate children this tree has.
233 func (t *Tree) NumChildren() int { return len(t.children) }
234
235 // Intern recursively interns this tree into the given repo.
236 //
237 // If the tree contains a missing object which is internable (i.g. not
238 // EmptyObject), it will also be interned.
239 //
240 // If the tree contains an object with an ID of NoID, this method will panic.
241 //
242 // If the tree contains a missing object which is not internable, this method
243 // will panic (see InternAllowMissing to negate this condition).
244 //
245 // This method has the side effect of causing all internable but !Complete()
246 // objects to become Complete(). In particular, this means that the value of
247 // t.ID() will be correct after calling this.
248 func (t *Tree) Intern(s GitService, allowMissing bool) (*ObjectID, error) {
249 err := infra_util.FanOutIn(len(t.children), func(ch chan<- func() error) {
250 for _, c := range t.children {
251 c := c
252 ch <- func() (err error) {
253 switch x := c.Object.(type) {
254 case *Tree:
255 _, err = x.Intern(s, allowMissing)
256 case InternableObject:
257 s.Intern(x)
258 case Object:
259 if !s.HasObjectID(x) && !allowMissing {
260 return fmt.Errorf("missing non-i nternable object: %v", x)
261 }
262 if *x.ID() == NoID {
263 return fmt.Errorf("object has an ID of <NoID>")
264 }
265 }
266 return err
267 }
268 }
269 })
270
271 if err != nil {
272 return nil, err
273 }
274
275 return s.Intern(t)
276 }
277
278 // GetChild returns the Child at the slash-delemited |path| relative to this
279 // Tree, or nil (if the child was not found).
280 func (t *Tree) GetChild(path string) *Child {
281 pathParts := strings.SplitN(path, "/", 2)
282 if len(pathParts) == 1 {
283 return t.children[path]
284 }
285
286 dir, rest := pathParts[0], pathParts[1]
287 intermediate, exists := t.children[dir]
288 if !exists {
289 return nil
290 }
291
292 if childTree, ok := intermediate.Object.(*Tree); !ok {
293 panic(fmt.Errorf("entry path is not a tree: %s", dir))
294 } else {
295 return childTree.GetChild(rest)
296 }
297 }
298
299 // SetChild adds |child| to this Tree at the given slash-delimited |path|. It
300 // will create intermediate empty Trees if necessary, but will panic() if you
301 // attempt to set the child under something which is not a tree.
302 func (t *Tree) SetChild(path string, child *Child) {
303 t.cachedRawString = nil
304 pathParts := strings.SplitN(path, "/", 2)
305 if len(pathParts) == 1 {
306 if len(path) == 0 {
307 panic(fmt.Errorf("attempting to SetChild with an empty p ath"))
308 }
309 t.children[path] = child
310 } else {
311 dir, rest := pathParts[0], pathParts[1]
312 intermediate, exists := t.children[dir]
313 if !exists {
314 intermediate = BlankTreeChild(1)
315 t.children[dir] = intermediate
316 }
317
318 switch x := intermediate.Object.(type) {
319 case *Tree:
320 x.SetChild(rest, child)
321 return
322 case *EmptyObject:
323 if x.Type() == TreeType {
324 if *x.ID() == NoID || x.ID().String() == EmptyTr eeHash {
325 intermediate := BlankTreeChild(1)
326 t.children[dir] = intermediate
327 intermediate.Object.(*Tree).SetChild(res t, child)
328 } else {
329 panic(fmt.Errorf("entry path was a place holder tree with an id: %v", x))
330 }
331 return
332 }
333 }
334 panic(fmt.Errorf("entry path is not a tree: %s", dir))
335 }
336 }
337
338 // DelChild removes the child at the slash-delimited |path|. Will panic if
339 // |path| does not exist, or if one of the intermediate paths is not a Tree.
340 func (t *Tree) DelChild(path string) bool {
341 t.cachedRawString = nil
342 pathParts := strings.SplitN(path, "/", 2)
343 if len(pathParts) == 1 {
344 var ret bool
345 _, ret = t.children[path]
346 delete(t.children, path)
347 return ret
348 } else {
349 dir, rest := pathParts[0], pathParts[1]
350 child, exists := t.children[dir]
351 if !exists {
352 return false
353 }
354
355 if childTree, ok := child.Object.(*Tree); !ok {
356 panic(fmt.Errorf("entry path is not a tree: %s", dir))
357 } else {
358 ret := childTree.DelChild(rest)
359 if childTree.NumChildren() == 0 {
360 delete(t.children, dir)
361 }
362 return ret
363 }
364 }
365 }
366
367 // RawString returns a git hash-object compatible representation of this Tree,
368 // according to the currently-set children (e.g. it may return something which
369 // does not match ID).
370 func (t *Tree) RawString() string {
371 if t.cachedRawString == nil {
372 buf := &bytes.Buffer{}
373
374 entries := make(treeEntries, 0, len(t.children))
375 for path, child := range t.children {
376 switch x := child.Object.(type) {
377 case *Tree:
378 x.RawString()
379 }
380
381 entries = append(entries, treeEntry{
382 id: child.Object.ID(),
383 mode: child.Mode,
384 name: path,
385 })
386 }
387
388 sort.Sort(entries)
389
390 for _, ent := range entries {
391 fmt.Fprintf(buf, "%o %s\x00%s", ent.mode, ent.name, ent. id.RawString())
392 }
393 s := buf.String()
394 t.id = MakeObjectIDForData(TreeType, buf.Bytes())
395 t.cachedRawString = &s
396 }
397 return *t.cachedRawString
398 }
399
400 /// Private ///////////////////////////////////////////////////////////////////
401
402 type treeEntry struct {
403 id *ObjectID
404 mode Mode
405 name string
406 }
407 type treeEntries []treeEntry
408
409 func (s treeEntries) Len() int { return len(s) }
410
411 // Extra trailing slash is how git sorts the tree objects too.
412 func (s treeEntries) Less(i, j int) bool {
413 a, b := s[i].name, s[j].name
414
415 if s[i].mode.Type() == TreeType {
416 a += "/"
417 }
418 if s[j].mode.Type() == TreeType {
419 b += "/"
420 }
421
422 return a < b
423 }
424 func (s treeEntries) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
OLDNEW
« no previous file with comments | « go/src/infra/libs/git/repo/repo_test.go ('k') | go/src/infra/libs/git/treeDiff.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698