| OLD | NEW |
| (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] } |
| OLD | NEW |