Chromium Code Reviews| 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 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 } | |
| OLD | NEW |