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

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: 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 package git
2
3 import "bytes"
4 import "fmt"
5 import "sort"
6 import "strconv"
7 import "strings"
8 import "sync"
9
10 import "infra/libs/infra_util"
11
12 // Tree is a true tree which represents a git 'tree' of objects, and can
13 // represent those objects recursively (unlike a true git 'tree').
14 //
15 // Trees are also mutable, so you can manipulate them with impunity. Note that
16 // ID() is not continuously updated, and you must call Rehash in order to
17 // recalculate the correct ID() after manipulating the Tree.
18 type Tree struct {
19 id ObjectID
20 cachedRawString *string
M-A Ruel 2014/10/18 00:47:06 You could use a string (non pointer) and just do t
iannucci 2014/10/20 21:11:57 It's possible to have an empty tree. I need to dis
21
22 children map[string]*Child
23 }
24
25 func (t *Tree) String() string {
26 return fmt.Sprintf("Tree(%s, %v)", t.id, t.children)
27 }
28
29 // ID returns the last-calculated ObjectId of this tree. Note that this ID()
30 // may be out-of-date if you have modified this Tree with {Set,Del}Child().
31 func (t *Tree) ID() ObjectID { return t.id }
32
33 // Type returns "tree" to satisfy the Object interface
34 func (t *Tree) Type() string { return "tree" }
35
36 // Complete indicates that this tree's ID() matches its contents. It can
37 // become false if you call DelChild or SetChild on this Tree.
38 func (t *Tree) Complete() bool { return t.cachedRawString != nil }
39
40 // NumChildren returns the number of immediate children this tree has.
41 func (t *Tree) NumChildren() int { return len(t.children) }
42
43 // Intern recursively interns this tree into the given repo.
44 //
45 // If the tree contains a missing object which is internable (i.g. not
46 // EmptyObject), it will also be interned.
47 //
48 // If the tree contains an object with an ID of NoID, this method will panic.
49 //
50 // If the tree contains a missing object which is not internable, this method
51 // will panic (see InternAllowMissing to negate this condition).
52 //
53 // This method has the side effect of causing all internable but !Complete()
54 // objects to become Complete(). In particular, this means that the value of
55 // t.ID() will be correct after calling this.
56 func (t *Tree) Intern(r *Repo) {
57 t.intern(r, false)
58 }
59
60 // InternAllowMissing is identical to Intern except that it will not panic
61 // on missing non-internable objects, but just treats them
62 func (t *Tree) InternAllowMissing(r *Repo) {
63 t.intern(r, true)
64 }
65
66 // Rehash causes an !Complete() tree to become Complete, and resets ID() to
67 // match the Tree's current state.
68 func (t *Tree) Rehash() {
69 if !t.Complete() {
70 t.RawString()
71 }
72 }
73
74 // NewEmptyTree creates a new Tree with the given |id|, but no children (e.g.
75 // it is !Complete()). You only want to use this if you know that you're going
76 // to be adding children to the Tree, otherwise use EmptyObject("tree", id).
77 func NewEmptyTree(id ObjectID, childrenHint int) *Tree {
78 return &Tree{
79 id: id,
80 children: make(map[string]*Child, childrenHint),
81 }
82 }
83
84 // TreeFromText returns a Complete() Tree for a `git ls-tree`-formatted
85 // tree string.
86 func TreeFromText(data []byte) (ret *Tree, err error) {
87 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
88 retPtr := NewEmptyTree(NoID, len(lines))
89 for _, line := range lines {
90 infoPath := strings.SplitN(line, "\t", 2)
91 var path string
92 if infoPath[1][0] == '"' {
93 path, err = strconv.Unquote(infoPath[1])
94 if err != nil {
95 return
96 }
97 } else {
98 path = infoPath[1]
99 }
100
101 modeTypeID := strings.Fields(infoPath[0])
102 var mode uint64
103 mode, err = strconv.ParseUint(modeTypeID[0], 8, 32)
104 if err != nil {
105 return
106 }
107
108 retPtr.SetChild(path, NewEmptyChild(Mode(mode), MakeObjectID(mod eTypeID[2])))
109 }
110 retPtr.Rehash()
111 ret = retPtr
112 return
113 }
114
115 // TreeFromRawWithID trusts id, and will return a Complete() *Tree. Data is
116 // formatted the same as RawString (i.e. in the raw git tree format).
117 func TreeFromRawWithID(id ObjectID, data []byte) (ret *Tree, err error) {
118 retPtr := NewEmptyTree(id, 16)
119 // <mode> SP <name> NUL [20]byte
120 buf := bytes.NewBuffer(data)
121 nom := infra_util.Nom(buf)
122 yoink := infra_util.Yoink(buf)
123 for buf.Len() != 0 {
124 var mode uint64
125 mode, err = strconv.ParseUint(nom(' '), 8, 64)
126 if err != nil {
127 return
128 }
129 name := nom('\x00')
130 id := MakeObjectIDRaw(yoink(20))
131 retPtr.SetChild(name, NewEmptyChild(Mode(int(mode)), id))
132 }
133 s := string(data)
134 retPtr.cachedRawString = &s
135 ret = retPtr
136 return
137 }
138
139 // GetChild returns the Child at the slash-delemited |path| relative to this
140 // Tree, or nil (if the child was not found).
141 func (t *Tree) GetChild(path string) *Child {
142 pathParts := strings.SplitN(path, "/", 2)
143 if len(pathParts) == 1 {
144 return t.children[path]
145 }
146
147 dir, rest := pathParts[0], pathParts[1]
148 intermediate, exists := t.children[dir]
149 if !exists {
150 panic(fmt.Errorf("intermediate child %s doesn't exist", dir))
151 }
152
153 if childTree, ok := intermediate.Object.(*Tree); !ok {
154 panic(fmt.Errorf("entry path is not a tree: %s", dir))
155 } else {
156 return childTree.GetChild(rest)
157 }
158 }
159
160 // SetChild adds |child| to this Tree at the given slash-delimited |path|. It
161 // will create intermediate empty Trees if necessary, but will panic() if you
162 // attempt to set the child under something which is not a tree.
163 func (t *Tree) SetChild(path string, child *Child) {
164 t.cachedRawString = nil
165 pathParts := strings.SplitN(path, "/", 2)
166 if len(pathParts) == 1 {
167 t.children[path] = child
168 } else {
169 dir, rest := pathParts[0], pathParts[1]
170 intermediate, exists := t.children[dir]
171 if !exists {
172 intermediate = NewEmptyChild(Mode(040000), NoID)
173 t.children[dir] = intermediate
174 }
175
176 if childTree, ok := intermediate.Object.(*Tree); !ok {
177 panic("entry path is not a tree: " + dir)
178 } else {
179 childTree.SetChild(rest, child)
180 }
181 }
182 }
183
184 // DelChild removes the child at the slash-delimited |path|. Will panic if
185 // |path| does not exist, or if one of the intermediate paths is not a Tree.
186 func (t *Tree) DelChild(path string) {
187 t.cachedRawString = nil
188 pathParts := strings.SplitN(path, "/", 2)
189 if len(pathParts) == 1 {
190 if _, has := t.children[path]; !has {
191 panic("entry path does not exist: " + path)
192 }
193 delete(t.children, path)
194 } else {
195 dir, rest := pathParts[0], pathParts[1]
196 child, exists := t.children[dir]
197 if !exists {
198 panic("entry path does not exist: " + dir)
199 }
200
201 if childTree, ok := child.Object.(*Tree); !ok {
202 panic("etnry path is not a tree: " + dir)
203 } else {
204 childTree.DelChild(rest)
205 if childTree.NumChildren() == 0 {
206 delete(t.children, dir)
207 }
208 }
209 }
210 }
211
212 // RawString returns a git hash-object compatible representation of this Tree,
213 // according to the currently-set children (e.g. it may return something which
214 // does not match ID).
215 func (t *Tree) RawString() string {
216 if t.cachedRawString == nil {
217 buf := &bytes.Buffer{}
218
219 entries := make(treeEntries, 0, len(t.children))
220 for path, child := range t.children {
221 entries = append(entries, treeEntry{
222 id: child.Object.ID(),
223 mode: child.Mode,
224 name: path,
225 })
226 }
227
228 sort.Sort(entries)
229
230 for _, ent := range entries {
231 fmt.Fprintf(buf, "%o %s\x00%s", ent.mode, ent.name, ent. id.RawString())
232 }
233 s := buf.String()
234 t.id = MakeObjectIDForData("tree", buf.Bytes())
235 t.cachedRawString = &s
236 }
237 return *t.cachedRawString
238 }
239
240 /// Private
241 type treeEntry struct {
242 id ObjectID
243 mode Mode
244 name string
245 }
246 type treeEntries []treeEntry
247
248 func (s treeEntries) Len() int { return len(s) }
249
250 // Extra trailing slash is how git sorts the tree objects too.
251 func (s treeEntries) Less(i, j int) bool {
252 a, b := s[i].name, s[j].name
253
254 if s[i].mode.Type() == "tree" {
255 a += "/"
256 }
257 if s[j].mode.Type() == "tree" {
258 b += "/"
259 }
260
261 return a < b
262 }
M-A Ruel 2014/10/18 00:47:06 Spacing
iannucci 2014/10/20 21:11:57 It's what gofmt does. What did you have in mind?
M-A Ruel 2014/10/21 00:55:53 gofmt allows both no empty line and one empty line
263 func (s treeEntries) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
264
265 func (t *Tree) intern(r *Repo, allowMissing bool) {
M-A Ruel 2014/10/18 00:47:06 Can you add a short comment about what it means?
iannucci 2014/10/20 21:11:57 Done.
266 if t.Complete() && r.HasObjectID(t.ID()) {
267 return
268 }
269 grp := sync.WaitGroup{}
270 for _, c := range t.children {
271 c := c
M-A Ruel 2014/10/18 00:47:06 ?
iannucci 2014/10/20 21:11:58 Again, avoids loop variable aliasing. See bottom o
272 grp.Add(1)
273 go func() {
274 defer grp.Done()
275 switch x := c.Object.(type) {
276 case *Tree:
277 x.intern(r, allowMissing)
278 case InternableObject:
279 r.Intern(x)
280 case Object:
281 if !(r.HasObjectID(x.ID()) || allowMissing) {
282 panic(fmt.Errorf("missing non-internable object: %v", x))
283 }
284 if x.ID() == NoID {
285 panic(fmt.Errorf("object has an ID of <N oID>"))
286 }
287 }
288 }()
289 }
290 grp.Wait()
291 if !(t.ID() != NoID && t.NumChildren() == 0) {
292 // Having an ID but no children is a special case. Because of ho w DelChild
293 // cleans up empty trees, you can't have an empty subtree, unles s you
294 // intentionally put an empty tree there with an ID.
295 r.Intern(t)
296 }
297 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698