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