Merge lp://staging/~abentley/bzr/nested-trees-composite-tree into lp://staging/~bzr/bzr/trunk-old
- nested-trees-composite-tree
- Merge into trunk-old
Status: | Work in progress |
---|---|
Proposed branch: | lp://staging/~abentley/bzr/nested-trees-composite-tree |
Merge into: | lp://staging/~bzr/bzr/trunk-old |
Diff against target: |
1484 lines (has conflicts)
Text conflict in bzrlib/tests/__init__.py Text conflict in bzrlib/transform.py |
To merge this branch: | bzr merge lp://staging/~abentley/bzr/nested-trees-composite-tree |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pool | Pending | ||
Review via email: mp+6264@code.staging.launchpad.net |
Commit message
Description of the change
Martin Pool (mbp) wrote : | # |
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Hi Martin,
>
> You wanted some documentation of CompositeTree to be included with the
> patch that introduces it. Since NestedTrees is also introduced by this
> patch and is arguably more important in the long run, I've included
> documentation for it as well.
>
> I hope this is the kind of thing you were looking for.
So yes, it is.
I wonder if this would be better described in the overview of major classes (in HACKING, I think?) than separated out into something only about nested trees. If you were going to add a new documentation file you should link it into the index, but here I think it's better not to add one.
I think for ReST you need a blank line between the heading underline and the following text?
In CompositeTree, I'd like to see something about how you obtain one.
It's good to write it up like this because it makes the issues more clear. For example, what will happen if code gets a CompositeTree and tries to treat it like a regular tree? Will it just error? (Some of these can be discussed in the thread about the general design but probably they should be answered in the docs.) If "by con
1279 +It is constructed from a Tree (typically a RevisionTree or WorkingTree) and a
1280 +Branch.
If you mean you just do
CompositeTree
then a code sample to say so would be unambiguous.
+
+NestedTrees
+-----------
+NestedTrees is an API providing access to a set of nested trees. It is able to
+convert paths and file-ids into references to specific Trees. It provides
+caching, to avoid retrieving the same Tree multiple times. It provides
+locking, to ensure trees are locked and unlocked at appropriate times.
+
+It is used by CompositeTree, and is a recommended API for all other operations.
+It is expected to acquire more capabilities as the needs of sets of nested
+trees become clearer.
Names that sound like they're plurals of some other class name are confusing. (Is this about ``NestedTree``, which sounds like a kind of tree?) Maybe NestedTreeSet or TreeNest?
Again I'd ask how you get one of them.
Thanks
Martin Pool (mbp) wrote : | # |
How did you get on while I was away with the enhancement proposal for the whole thing? Reading that seems like the next step forward.
Aaron Bentley (abentley) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Martin Pool wrote:
> How did you get on while I was away with the enhancement proposal for the whole thing?
I'm working on launchpad stuff for a bit. My unfinished draft is here:
bzr+ssh:
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAko
g+oAn2Ux8BctPmo
=KaIe
-----END PGP SIGNATURE-----
Alexander Belchenko (bialix) wrote : | # |
PING. Why this one is blocked?
Unmerged revisions
- 1937. By Aaron Bentley
-
Add documentation of CompositeTree and NestedTrees
- 1936. By Aaron Bentley
-
Merge bzr.dev into composite-tree.
- 1935. By Aaron Bentley
-
Merge bzr.dev into composite-tree.
- 1934. By Aaron Bentley
-
Merge bzr.dev into composite-tree.
- 1933. By Aaron Bentley
-
Merge bzr.dev into composite-tree.
- 1932. By Aaron Bentley
-
Merge bzr.dev into composite-tree.
- 1931. By Aaron Bentley
-
Merge bzr.dev into composite-tree
- 1930. By Aaron Bentley
-
More updates from review
- 1929. By Aaron Bentley
-
More updates from review.
- 1928. By Aaron Bentley
-
Updates from review
Preview Diff
1 | === added file 'bzrlib/composite_tree.py' |
2 | --- bzrlib/composite_tree.py 1970-01-01 00:00:00 +0000 |
3 | +++ bzrlib/composite_tree.py 2009-08-31 04:38:31 +0000 |
4 | @@ -0,0 +1,761 @@ |
5 | +# Copyright (C) 2007, 2008, 2009 Canonical Ltd |
6 | +# |
7 | +# This program is free software; you can redistribute it and/or modify |
8 | +# it under the terms of the GNU General Public License as published by |
9 | +# the Free Software Foundation; either version 2 of the License, or |
10 | +# (at your option) any later version. |
11 | +# |
12 | +# This program is distributed in the hope that it will be useful, |
13 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | +# GNU General Public License for more details. |
16 | +# |
17 | +# You should have received a copy of the GNU General Public License |
18 | +# along with this program; if not, write to the Free Software |
19 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | + |
21 | +"""Tree-like class for manipulating nested trees to be like a single tree""" |
22 | + |
23 | +from bzrlib import ( |
24 | + conflicts as _mod_conflicts, |
25 | + errors, |
26 | + osutils, |
27 | + transform, |
28 | + tree, |
29 | + ) |
30 | + |
31 | +class NestedTrees(object): |
32 | + """Provide data about a collection of nested trees. |
33 | + |
34 | + This is a sparse interface that has high fidelity to its underlying |
35 | + implementation. It may be used directly, or as the internal state of |
36 | + CompositeTree/_CompositeInventory. |
37 | + """ |
38 | + |
39 | + def __init__(self, root_tree, root_branch): |
40 | + self.root_tree = root_tree |
41 | + self.root_branch = root_branch |
42 | + self.lock_count = 0 |
43 | + self.lock_type = None |
44 | + self._clear_cached_trees() |
45 | + |
46 | + def _clear_cached_trees(self): |
47 | + if self.lock_type is not None: |
48 | + raise AssertionError('Cannot clear cached trees when locked.') |
49 | + self._subtrees_by_relpath = {} |
50 | + self._all_trees_scanned = False |
51 | + self._locked_trees = [] |
52 | + |
53 | + def lock_read(self): |
54 | + """Read-lock all open trees, open new trees read-locked.""" |
55 | + if self.lock_count == 0: |
56 | + self.root_tree.lock_read() |
57 | + self.lock_type = 'read' |
58 | + self._locked_trees.append(self.root_tree) |
59 | + self.lock_count += 1 |
60 | + |
61 | + def lock_write(self): |
62 | + """Write-lock all open trees, open new trees write-locked.""" |
63 | + if self.lock_count == 0: |
64 | + self.root_tree.lock_write() |
65 | + self.lock_type = 'write' |
66 | + self._locked_trees.append(self.root_tree) |
67 | + self.lock_count += 1 |
68 | + |
69 | + def lock_tree_write(self): |
70 | + """Write-lock all open trees, open new trees write-locked. |
71 | + |
72 | + Branches are read-locked, instead of being write-locked as usual. |
73 | + """ |
74 | + if self.lock_count == 0: |
75 | + self.root_tree.lock_tree_write() |
76 | + self.lock_type = 'tree_write' |
77 | + self._locked_trees.append(self.root_tree) |
78 | + self.lock_count += 1 |
79 | + |
80 | + def unlock(self): |
81 | + """Unlock open trees.""" |
82 | + self.lock_count -= 1 |
83 | + if self.lock_count < 0: |
84 | + raise AssertionError('lock count is negative.') |
85 | + if self.lock_count == 0: |
86 | + for tree in reversed(self._locked_trees): |
87 | + tree.unlock() |
88 | + self.lock_type = None |
89 | + self._clear_cached_trees() |
90 | + |
91 | + def _must_be_locked(self): |
92 | + if self.lock_type is None: |
93 | + raise errors.ObjectNotLocked(self) |
94 | + |
95 | + def paths_info(self, paths): |
96 | + """Return path, relpath, inventory entry, and tree for input paths. |
97 | + |
98 | + The path is the path to the subtree relative to the root tree. The |
99 | + relpath is the path within the subtree. The inventory entry will be |
100 | + None if the path is not versioned. The tree is the subtree |
101 | + containing the relpath. |
102 | + |
103 | + :param paths: A list of paths relative to the root tree. |
104 | + :return: a list of (path, relpath, entry, tree) tuples. |
105 | + """ |
106 | + self._must_be_locked() |
107 | + return [self._path_info(p) for p in paths] |
108 | + |
109 | + def _path_info(self, path): |
110 | + """Find the relpath, tree and inventory entry for a path. |
111 | + |
112 | + The path is the path to the subtree relative to the root tree. The |
113 | + relpath is the path within the subtree. The inventory will be |
114 | + None if the path is not versioned. The tree is the subtree |
115 | + containing the relpath. |
116 | + |
117 | + :param path: A path relative to the root tree. |
118 | + :return: a (path, relpath, entry, tree) tuple. |
119 | + """ |
120 | + parts = osutils.splitpath(path) |
121 | + tree = self.root_tree |
122 | + branch = self.root_branch |
123 | + node = tree.inventory.root |
124 | + relpath = [] |
125 | + for part in parts: |
126 | + relpath.append(part) |
127 | + children = getattr(node, 'children', {}) |
128 | + node = children.get(part) |
129 | + if node is None: |
130 | + return path, osutils.pathjoin(*relpath), None, tree |
131 | + try: |
132 | + kind = tree.kind(node.file_id) |
133 | + except errors.NoSuchFile: |
134 | + pass |
135 | + else: |
136 | + if kind == 'tree-reference': |
137 | + tree, branch = self.get_tree_branch(branch, tree, |
138 | + node.file_id, osutils.pathjoin(*relpath)) |
139 | + node = tree.inventory.root |
140 | + relpath = [] |
141 | + continue |
142 | + if relpath == []: |
143 | + relpath_str = '' |
144 | + else: |
145 | + relpath_str = osutils.pathjoin(*relpath) |
146 | + return path, relpath_str, node, tree |
147 | + |
148 | + def get_tree(self, file_id): |
149 | + """Return the Tree that contains a given file-id.""" |
150 | + return self.get_tree_and_treepath(file_id)[0] |
151 | + |
152 | + def get_tree_and_treepath(self, file_id, skip_references=False): |
153 | + """Get a tree containing file_id and path relative to the root tree. |
154 | + |
155 | + The path is the path to the subtree, not the file in the subtree. |
156 | + |
157 | + :skip_references: if True, only results where the file_id is not |
158 | + a tree reference will be supplied. |
159 | + """ |
160 | + for path, tree in self.all_trees().iteritems(): |
161 | + if file_id in tree.inventory: |
162 | + if skip_references: |
163 | + try: |
164 | + if tree.kind(file_id) == 'tree-reference': |
165 | + continue |
166 | + except errors.NoSuchFile: |
167 | + pass |
168 | + return tree, path |
169 | + else: |
170 | + raise errors.NoSuchId(self, file_id) |
171 | + |
172 | + def get_path_info(self, path): |
173 | + """Determine the tree containing a path and the tree's pathname. |
174 | + |
175 | + :return: a tuple of (tree, tree_path, relpath), where tree_path |
176 | + is the path to the tree, relative to the root_tree, and relpath |
177 | + is the path relative to the returned tree. |
178 | + """ |
179 | + trees = self.all_trees() |
180 | + prefix = path |
181 | + while True: |
182 | + prefix = osutils.dirname(prefix) |
183 | + subpath = path[len(prefix):] |
184 | + subpath = subpath.lstrip('/') |
185 | + try: |
186 | + return trees[prefix], prefix, subpath |
187 | + except KeyError: |
188 | + pass |
189 | + if subpath == '': |
190 | + raise AssertionError('Subpath cannot be empty.') |
191 | + |
192 | + def iter_pathinfo_by_tree(self, paths): |
193 | + """Iterate through the path information for provided paths. |
194 | + |
195 | + Iterates through (tree, tree_path, relpaths) tuples, where |
196 | + tree is a tree containing some of the paths, tree_path is the relative |
197 | + path to the tree from the root_tree, and relpaths are the paths within |
198 | + the returned tree. |
199 | + """ |
200 | + tree_path = None |
201 | + tree = None |
202 | + # Reverse sort should guarantee that all subtree paths are processed |
203 | + # before tree paths. So if path.startswith(tree_path), we're in |
204 | + # the same tree. |
205 | + for path in sorted(paths, reverse=True): |
206 | + if tree_path is None or not path.startswith(tree_path): |
207 | + if tree is not None: |
208 | + yield tree, tree_path, subpaths |
209 | + tree, tree_path, subpath = self.get_path_info(path) |
210 | + subpaths = [subpath] |
211 | + else: |
212 | + subpaths.append(path[len(tree_path):].lstrip('/')) |
213 | + if tree is not None: |
214 | + yield tree, tree_path, subpaths |
215 | + |
216 | + def _scan_subtrees(self): |
217 | + self._must_be_locked() |
218 | + def do_scan(prefix, relpath, tree, containing_tree, branch): |
219 | + self._register_tree_relpath(tree, containing_tree, relpath) |
220 | + for path, file_id in tree.iter_references(): |
221 | + tree_path = tree.id2path(file_id) |
222 | + composite_path = osutils.pathjoin(prefix, tree_path) |
223 | + subtree, subbranch = self.get_tree_branch(branch, tree, |
224 | + file_id, tree_path) |
225 | + do_scan(composite_path, tree_path, subtree, tree, subbranch) |
226 | + do_scan('', '', self.root_tree, None, self.root_branch) |
227 | + |
228 | + def _register_tree_relpath(self, tree, containing_tree, relpath): |
229 | + """Register the relpath of a Tree in the relevant dicts.""" |
230 | + tree_relpaths = self._subtrees_by_relpath.get(containing_tree, {}) |
231 | + tree_relpaths[relpath] = tree |
232 | + self._subtrees_by_relpath[containing_tree] = tree_relpaths |
233 | + |
234 | + def get_tree_branch(self, branch, tree, file_id, path): |
235 | + """Retrieve the tree and branch for a subtree path |
236 | + |
237 | + :param branch: The branch associated with the containing tree |
238 | + :param tree: The containing tree |
239 | + :param file_id: The file id of the subtree |
240 | + :param path: The tree-relative path to the subtree |
241 | + """ |
242 | + subbranch = branch.reference_parent(file_id, path) |
243 | + try: |
244 | + subtree = self._subtrees_by_relpath[tree][path] |
245 | + except KeyError: |
246 | + # Unknown tree, so we must try and retrieve it. |
247 | + subtree = tree.get_nested_tree(file_id, branch) |
248 | + # Tree must be locked to match the other components of this |
249 | + # NestedTree so that they all behave the same. |
250 | + if self.lock_type is not None: |
251 | + if self.lock_type == 'read': |
252 | + subtree.lock_read() |
253 | + elif self.lock_type == 'write': |
254 | + subtree.lock_write() |
255 | + elif self.lock_type == 'tree_write': |
256 | + subtree.lock_tree_write() |
257 | + self._locked_trees.append(subtree) |
258 | + self._register_tree_relpath(subtree, tree, path) |
259 | + return subtree, subbranch |
260 | + |
261 | + def all_trees(self): |
262 | + """Return a dict of composite_path to tree for all subtrees.""" |
263 | + if not self._all_trees_scanned: |
264 | + self._scan_subtrees() |
265 | + self._all_trees_scanned = True |
266 | + root_tree_paths = {} |
267 | + def do_tree(tree, root_tree_path): |
268 | + root_tree_paths[root_tree_path] = tree |
269 | + subtrees = self._subtrees_by_relpath.get(tree, {}) |
270 | + for relpath, subtree in subtrees.iteritems(): |
271 | + do_tree(subtree, osutils.pathjoin(root_tree_path, relpath)) |
272 | + do_tree(self.root_tree, '') |
273 | + return root_tree_paths |
274 | + |
275 | + |
276 | +class _CompositeInventory(object): |
277 | + """An inventory that combines the inventories of several subtrees. |
278 | + |
279 | + Intended for use as CompositeTree.inventory, not for direct instantiation. |
280 | + """ |
281 | + |
282 | + def __init__(self, nested_trees): |
283 | + self._nested_trees = nested_trees |
284 | + |
285 | + def _get_ci_path_info(self, path): |
286 | + file_id = None |
287 | + tree = self._nested_trees.root_tree |
288 | + node = tree.inventory.root |
289 | + canonical_path = [] |
290 | + remaining_path = osutils.splitpath(path) |
291 | + while len(remaining_path) > 0: |
292 | + part = remaining_path[0] |
293 | + next_node = None |
294 | + children = getattr(node, 'children', {}) |
295 | + for child in children.itervalues(): |
296 | + if child.name.lower() == part.lower(): |
297 | + next_node = child |
298 | + if child.name == part: |
299 | + break |
300 | + if next_node is None: |
301 | + break |
302 | + remaining_path.pop(0) |
303 | + canonical_path.append(next_node.name) |
304 | + node = next_node |
305 | + if node.kind == 'tree-reference': |
306 | + tree, path_ = self._nested_trees.get_tree_and_treepath( |
307 | + node.file_id, skip_references=True) |
308 | + node = tree.inventory.root |
309 | + if node is not None: |
310 | + file_id = node.file_id |
311 | + canonical_path.extend(remaining_path) |
312 | + if len(canonical_path) == 0: |
313 | + result = '' |
314 | + else: |
315 | + result = osutils.pathjoin(*canonical_path) |
316 | + return result, file_id |
317 | + |
318 | + def path2id(self, path): |
319 | + """See Inventory.path2id.""" |
320 | + path, relpath, entry, tree = self._nested_trees.paths_info([path])[0] |
321 | + if entry is None: |
322 | + return None |
323 | + return entry.file_id |
324 | + |
325 | + def id2path(self, file_id): |
326 | + """See Inventory.id2path.""" |
327 | + tree, path = self._nested_trees.get_tree_and_treepath(file_id) |
328 | + return osutils.pathjoin(*[path, tree.id2path(file_id)]) |
329 | + |
330 | + def get_file_kind(self, file_id): |
331 | + """See Inventory.get_file_kind.""" |
332 | + tree = self._nested_trees.get_tree_and_treepath(file_id, |
333 | + skip_references=True)[0] |
334 | + return tree.inventory.get_file_kind(file_id) |
335 | + |
336 | + def iter_entries_by_dir(self, specific_file_ids=None, _tree=None, |
337 | + _branch=None, _root=''): |
338 | + """See Inventory.iter_entries_by_dir.""" |
339 | + tree = _tree |
340 | + root = _root |
341 | + branch = _branch |
342 | + if tree is None: |
343 | + tree = self._nested_trees.root_tree |
344 | + if branch is None: |
345 | + branch = self._nested_trees.root_branch |
346 | + for subpath, entry in tree.iter_entries_by_dir(): |
347 | + if subpath == '': |
348 | + path = root |
349 | + else: |
350 | + path = osutils.pathjoin(root, subpath) |
351 | + try: |
352 | + kind = tree.kind(entry.file_id) |
353 | + except errors.NoSuchFile: |
354 | + kind = None |
355 | + if kind == 'tree-reference': |
356 | + subtree, subbranch = self._nested_trees.get_tree_branch( |
357 | + branch, tree, entry.file_id, path) |
358 | + for path, entry in self.iter_entries_by_dir( |
359 | + specific_file_ids, _tree=subtree, |
360 | + _branch=subbranch, _root=path): |
361 | + yield path, entry |
362 | + else: |
363 | + if (specific_file_ids is None or entry.file_id in |
364 | + specific_file_ids): |
365 | + yield path, entry |
366 | + |
367 | + def iter_entries(self, from_dir=None): |
368 | + """See Tree.iter_entries. |
369 | + |
370 | + Note: violates the expected output order, but this is not generally |
371 | + a problem. |
372 | + """ |
373 | + if from_dir is not None: |
374 | + from_dir_path = self.id2path(from_dir) |
375 | + for path, entry in self.iter_entries_by_dir(): |
376 | + if from_dir is not None: |
377 | + if not path.startswith(from_dir_path): |
378 | + continue |
379 | + path = path[len(from_dir_path):].lstrip('/') |
380 | + yield path, entry |
381 | + |
382 | + def __getitem__(self, file_id): |
383 | + tree = self._nested_trees.get_tree(file_id) |
384 | + return tree.inventory[file_id] |
385 | + |
386 | + def __contains__(self, file_id): |
387 | + try: |
388 | + self._nested_trees.get_tree(file_id) |
389 | + except errors.NoSuchId: |
390 | + return False |
391 | + else: |
392 | + return True |
393 | + |
394 | + |
395 | +class CompositeTree(object): |
396 | + """Implements parts of Tree interface in terms of subtrees. |
397 | + |
398 | + Essentially, this is a by-value representation of a set of nested |
399 | + subtrees. |
400 | + Care should be taken in its use-- not suitable for every problem. |
401 | + """ |
402 | + |
403 | + def __init__(self, root_tree, root_branch): |
404 | + self._nested_trees = NestedTrees(root_tree, root_branch) |
405 | + self.inventory = _CompositeInventory(self._nested_trees) |
406 | + |
407 | + @property |
408 | + def basedir(self): |
409 | + """See WorkingTree.basedir.""" |
410 | + return self._nested_trees.root_tree.basedir |
411 | + |
412 | + def move(self, from_paths, to_dir, after=False, _to_names=None): |
413 | + """Rename files, possibly across trees""" |
414 | + self.lock_tree_write() |
415 | + try: |
416 | + paths_info = self._nested_trees.paths_info(from_paths+[to_dir]) |
417 | + to_path, to_relpath, new_parent, to_tree = paths_info[-1] |
418 | + if _to_names is not None and paths_info[0][2] is None: |
419 | + raise errors.BzrRenameFailedError(from_paths[0], |
420 | + osutils.pathjoin(to_dir, _to_names[0]), |
421 | + errors.NotVersionedError(path=from_paths[0])) |
422 | + if new_parent is None: |
423 | + if _to_names is None: |
424 | + from_ = '' |
425 | + to = to_dir |
426 | + else: |
427 | + from_ = from_paths[0] |
428 | + to = osutils.pathjoin(to_dir, _to_names[0]) |
429 | + raise errors.BzrMoveFailedError(from_, to, |
430 | + errors.NotVersionedError(path=to_dir)) |
431 | + pending_trees = {} |
432 | + result = [] |
433 | + for path, relpath, entry, tree, in paths_info: |
434 | + if entry is None: |
435 | + raise errors.BzrMoveFailedError(path, '', |
436 | + errors.NotVersionedError(path=str(path))) |
437 | + if tree.basedir not in pending_trees: |
438 | + pending_trees[tree.basedir] = tree |
439 | + pairs = [] |
440 | + changes = [] |
441 | + |
442 | + for num, (from_path, from_relpath, entry, from_tree) in\ |
443 | + enumerate(paths_info[:-1]): |
444 | + full_to_path = osutils.pathjoin(to_path, entry.name) |
445 | + pairs.append((from_path, full_to_path)) |
446 | + new_entry = entry.copy() |
447 | + new_entry.parent_id = new_parent.file_id |
448 | + if _to_names is not None: |
449 | + new_entry.name = _to_names[num] |
450 | + changes.append((from_path, full_to_path, new_entry.file_id, |
451 | + new_entry)) |
452 | + if not after: |
453 | + self._move_files(to_path, to_tree, _to_names, paths_info[:-1]) |
454 | + self.apply_inventory_delta(changes) |
455 | + return pairs |
456 | + finally: |
457 | + self.unlock() |
458 | + |
459 | + def _move_files(self, to_path, to_tree, to_names, paths_info): |
460 | + trans_id_tree_path = {} |
461 | + tt = transform.TreeTransform(self._nested_trees.root_tree) |
462 | + parent_trans_id = tt.trans_id_tree_path(to_path) |
463 | + for num, (from_path, from_relpath, entry, from_tree) in enumerate( |
464 | + paths_info): |
465 | + # If the source doesn't exist and the target does, |
466 | + # don't touch the files but just update the inventory. |
467 | + to_exists = to_tree.has_filename(osutils.pathjoin(to_path, |
468 | + entry.name)) |
469 | + from_exists = from_tree.has_filename(from_relpath) |
470 | + if not from_exists and to_exists: |
471 | + continue |
472 | + |
473 | + file_trans_id = tt.trans_id_tree_path(from_path) |
474 | + trans_id_tree_path[file_trans_id] = from_path |
475 | + if to_names is not None: |
476 | + to_name = to_names[num] |
477 | + else: |
478 | + to_name = entry.name |
479 | + tt.adjust_path(to_name, parent_trans_id, file_trans_id) |
480 | + try: |
481 | + try: |
482 | + tt.apply(skip_inventory=True) |
483 | + except errors.MalformedTransform, e: |
484 | + if e.conflicts[0][0] == 'duplicate': |
485 | + fp = transform.FinalPaths(tt) |
486 | + source = trans_id_tree_path[e.conflicts[0][1]] |
487 | + dest = fp.get_path(e.conflicts[0][1]) |
488 | + raise errors.RenameFailedFilesExist(source, dest) |
489 | + else: |
490 | + raise |
491 | + finally: |
492 | + tt.finalize() |
493 | + |
494 | + def rename_one(self, from_rel, to_rel, after=False): |
495 | + """See WorkingTree.rename_one.""" |
496 | + # We could be moving a file over subtree boundaries, figure out which |
497 | + # trees are used. |
498 | + self.lock_tree_write() |
499 | + try: |
500 | + from_tree, from_prefix, from_relpath = ( |
501 | + self._nested_trees.get_path_info(from_rel)) |
502 | + to_tree, to_prefix, to_relpath = ( |
503 | + self._nested_trees.get_path_info(to_rel)) |
504 | + if from_tree == to_tree: |
505 | + self._nested_trees.root_tree.rename_one(from_rel, to_rel, |
506 | + after=after) |
507 | + else: |
508 | + file_id = self.path2id(from_rel) |
509 | + osutils.rename(osutils.pathjoin(from_prefix, from_relpath) |
510 | + , osutils.pathjoin(to_prefix, to_relpath)) |
511 | + to_tree.add([to_relpath], ids=[file_id]) |
512 | + from_tree.remove([from_relpath]) |
513 | + finally: |
514 | + self.unlock() |
515 | + |
516 | + def get_file(self, file_id): |
517 | + """See Tree.get_file.""" |
518 | + return self._nested_trees.get_tree(file_id).get_file(file_id) |
519 | + |
520 | + def get_file_text(self, file_id, path=None): |
521 | + """See Tree.get_file_text.""" |
522 | + tree, relpath = self._find_tree_path(file_id, path) |
523 | + return tree.get_file_text(file_id, relpath) |
524 | + |
525 | + def get_file_size(self, file_id): |
526 | + """See Tree.get_file_size.""" |
527 | + return self._nested_trees.get_tree(file_id).get_file_size(file_id) |
528 | + |
529 | + def _find_tree_path(self, file_id, path): |
530 | + if path is not None: |
531 | + path, relpath, entry, tree =\ |
532 | + self._nested_trees.paths_info([path])[0] |
533 | + else: |
534 | + tree = self._nested_trees.get_tree(file_id) |
535 | + relpath = None |
536 | + return tree, relpath |
537 | + |
538 | + def is_executable(self, file_id, path=None): |
539 | + """See RevisionTree.is_executable.""" |
540 | + tree, path = self._find_tree_path(file_id, path) |
541 | + return tree.is_executable(file_id, path) |
542 | + |
543 | + def has_id(self, file_id): |
544 | + """See Tree.has_id.""" |
545 | + try: |
546 | + tree = self._nested_trees.get_tree(file_id) |
547 | + except errors.NoSuchId: |
548 | + return False |
549 | + if tree is None: |
550 | + return False |
551 | + return tree.has_id(file_id) |
552 | + |
553 | + def changes_from(self, other, want_unchanged=False, specific_files=None, |
554 | + extra_trees=None, require_versioned=False, include_root=False, |
555 | + want_unversioned=False): |
556 | + """Return a TreeDelta of the changes from other to this tree. |
557 | + |
558 | + :param other: A tree to compare with. |
559 | + :param specific_files: An optional list of file paths to restrict the |
560 | + comparison to. When mapping filenames to ids, all matches in all |
561 | + trees (including optional extra_trees) are used, and all children |
562 | + of matched directories are included. |
563 | + :param want_unchanged: An optional boolean requesting the inclusion |
564 | + of unchanged entries in the result. |
565 | + :param extra_trees: An optional list of additional trees to use when |
566 | + mapping the contents of specific_files (paths) to file_ids. |
567 | + :param require_versioned: An optional boolean (defaults to False). |
568 | + When supplied and True all the 'specific_files' must be |
569 | + versioned, or a PathsNotVersionedError will be thrown. |
570 | + :param want_unversioned: Scan for unversioned paths. |
571 | + |
572 | + The comparison will be performed by an InterTree object looked up on |
573 | + self and other. |
574 | + """ |
575 | + # Martin observes that Tree.changes_from returns a TreeDelta and this |
576 | + # may confuse people, because the class name of the returned object |
577 | + # is a synonym of the object referenced in the method name. |
578 | + return tree.InterTree.get(other, self).compare( |
579 | + want_unchanged=want_unchanged, |
580 | + specific_files=specific_files, |
581 | + extra_trees=extra_trees, |
582 | + require_versioned=require_versioned, |
583 | + include_root=include_root, |
584 | + want_unversioned=want_unversioned, |
585 | + ) |
586 | + |
587 | + def paths2ids(self, paths, trees=[], require_versioned=True): |
588 | + """See Tree.paths2ids.""" |
589 | + return tree.find_ids_across_trees(paths, [self] + list(trees), |
590 | + require_versioned) |
591 | + |
592 | + def path2id(self, path): |
593 | + """See Tree.path2id.""" |
594 | + # FIXME -- shouldn't return missing files |
595 | + return self.inventory.path2id(path) |
596 | + |
597 | + def iter_changes(self, from_tree, include_unchanged=False, |
598 | + specific_files=None, pb=None, extra_trees=None, |
599 | + require_versioned=True, want_unversioned=False): |
600 | + """See InterTree.iter_changes.""" |
601 | + intertree = tree.InterTree.get(from_tree, self) |
602 | + return intertree.iter_changes(include_unchanged, specific_files, pb, |
603 | + extra_trees, require_versioned, |
604 | + want_unversioned=want_unversioned) |
605 | + |
606 | + def lock_read(self): |
607 | + """See Tree.lock_read.""" |
608 | + self._nested_trees.lock_read() |
609 | + |
610 | + def lock_write(self): |
611 | + """See MutableTree.lock_write.""" |
612 | + self._nested_trees.lock_write() |
613 | + |
614 | + def lock_tree_write(self): |
615 | + """See MutableTree.lock_tree_write.""" |
616 | + self._nested_trees.lock_tree_write() |
617 | + |
618 | + def unlock(self): |
619 | + """See Tree.unlock.""" |
620 | + self._nested_trees.unlock() |
621 | + |
622 | + def extras(self): |
623 | + """See Tree.extras.""" |
624 | + result = [] |
625 | + for prefix, tree in self._nested_trees.all_trees().iteritems(): |
626 | + result.extend(osutils.pathjoin(prefix, p) |
627 | + for p in tree.extras()) |
628 | + return iter(result) |
629 | + |
630 | + def iter_entries_by_dir(self, specific_file_ids=None, _tree=None, |
631 | + _branch=None, _root=''): |
632 | + """See Tree.iter_entries_by_dir.""" |
633 | + return self.inventory.iter_entries_by_dir(specific_file_ids) |
634 | + |
635 | + def conflicts(self): |
636 | + """See WorkingTree.conflicts.""" |
637 | + # FIXME adjust paths in conflicts |
638 | + conflicts = [] |
639 | + for prefix, tree in self._nested_trees.all_trees().iteritems(): |
640 | + conflicts.extend(tree.conflicts()) |
641 | + return _mod_conflicts.ConflictList(conflicts) |
642 | + |
643 | + def all_file_ids(self): |
644 | + """See Tree.all_file_ids.""" |
645 | + file_ids = set() |
646 | + for tree in self._nested_trees.all_trees().itervalues(): |
647 | + file_ids.update(tree.all_file_ids()) |
648 | + return file_ids |
649 | + |
650 | + def is_ignored(self, filename): |
651 | + """See Tree.is_ignored.""" |
652 | + tree, prefix, filename = self._nested_trees.get_path_info(filename) |
653 | + return tree.is_ignored(filename) |
654 | + |
655 | + def kind(self, file_id): |
656 | + """See Tree.kind""" |
657 | + return self._nested_trees.get_tree(file_id).kind(file_id) |
658 | + |
659 | + def has_filename(self, filename): |
660 | + """See Tree.has_filename.""" |
661 | + tree, prefix, filename = self._nested_trees.get_path_info(filename) |
662 | + return tree.has_filename(filename) |
663 | + |
664 | + def filter_unversioned_files(self, paths): |
665 | + """See Tree.filter_unversioned_files.""" |
666 | + unversioned = set() |
667 | + for tree, tree_path, subpaths in \ |
668 | + self._nested_trees.iter_pathinfo_by_tree(paths): |
669 | + unversioned.update(osutils.pathjoin(tree_path, s) for s |
670 | + in tree.filter_unversioned_files(subpaths)) |
671 | + return unversioned |
672 | + |
673 | + def _comparison_data(self, entry, path): |
674 | + tree, prefix, path = self._nested_trees.get_path_info(path) |
675 | + kind, executable, stat = tree._comparison_data(entry, path) |
676 | + if kind == 'tree-reference': |
677 | + kind = 'directory' |
678 | + return kind, executable, stat |
679 | + |
680 | + def _file_size(self, entry, stat_value): |
681 | + return self._nested_trees.get_tree(entry.file_id)._file_size( |
682 | + entry, stat_value) |
683 | + |
684 | + def get_canonical_inventory_path(self, path): |
685 | + """See Tree.get_canonical_inventory_path.""" |
686 | + return self.inventory._get_ci_path_info(path)[0] |
687 | + |
688 | + def get_canonical_inventory_paths(self, paths): |
689 | + """See Tree.get_canonical_inventory_paths.""" |
690 | + return [self.get_canonical_inventory_path(p) for p in paths] |
691 | + |
692 | + def get_parent_ids(self): |
693 | + """See Tree.get_parent_ids.""" |
694 | + return self._nested_trees.root_tree.get_parent_ids() |
695 | + |
696 | + def get_file_sha1(self, file_id, path=None, stat_value=None): |
697 | + """See Tree.get_file_sha1.""" |
698 | + tree, relpath = self._find_tree_path(file_id, path) |
699 | + return tree.get_file_sha1(file_id, relpath, stat_value) |
700 | + |
701 | + def get_file_mtime(self, file_id, path=None): |
702 | + """See Tree.get_file_mtime.""" |
703 | + tree, relpath = self._find_tree_path(file_id, path) |
704 | + return tree.get_file_mtime( file_id, relpath) |
705 | + |
706 | + def get_symlink_target(self, file_id): |
707 | + """See Tree.get_symlink_target.""" |
708 | + return self._nested_trees.get_tree(file_id).get_symlink_target(file_id) |
709 | + |
710 | + def iter_children(self, file_id): |
711 | + """See Tree.iter_children.""" |
712 | + tree = self._nested_trees.get_tree_and_treepath(file_id, |
713 | + skip_references=True)[0] |
714 | + return tree.iter_children(file_id) |
715 | + |
716 | + def id2path(self, file_id): |
717 | + """See Tree.id2path.""" |
718 | + tree, path = self._nested_trees.get_tree_and_treepath(file_id) |
719 | + return osutils.pathjoin(path, tree.id2path(file_id)) |
720 | + |
721 | + def apply_inventory_delta(self, changes): |
722 | + """Apply an inventory delta to a set of subtrees. |
723 | + |
724 | + The delta is rewritten into a collection of deltas, one for each |
725 | + affected subtree, then applied to each. |
726 | + """ |
727 | + deltas = {} |
728 | + for old_path, new_path, file_id, new_entry in changes: |
729 | + if new_path is not None: |
730 | + if new_entry.kind == 'tree-reference': |
731 | + raise ValueError('Cannot introduce or change a' |
732 | + ' tree-reference in' |
733 | + ' CompositeTree.apply_inventory_delta.') |
734 | + new_tree, new_tree_path = \ |
735 | + self._nested_trees.get_tree_and_treepath( |
736 | + new_entry.parent_id, skip_references=True) |
737 | + if new_tree_path != '': |
738 | + new_path = new_path[len(new_tree_path) + 1:] |
739 | + else: |
740 | + new_tree_path = None |
741 | + if old_path is not None: |
742 | + old_tree, old_tree_path = \ |
743 | + self._nested_trees.get_tree_and_treepath(file_id) |
744 | + if old_tree_path != '': |
745 | + old_path = old_path[len(old_tree_path) + 1:] |
746 | + old_tree_delta, old_tree = deltas.setdefault(old_tree_path, |
747 | + ([], old_tree)) |
748 | + if old_tree_path == new_tree_path: |
749 | + old_tree_delta.append((old_path, new_path, file_id, |
750 | + new_entry)) |
751 | + else: |
752 | + old_tree_delta.append((old_path, None, file_id, None)) |
753 | + else: |
754 | + old_tree_path = None |
755 | + if new_path is not None and new_tree_path != old_tree_path: |
756 | + new_tree_delta, new_tree = deltas.setdefault(new_tree_path, |
757 | + ([], new_tree)) |
758 | + new_tree_delta.append((None, new_path, file_id, new_entry)) |
759 | + for delta, tree in deltas.itervalues(): |
760 | + tree.apply_inventory_delta(delta) |
761 | + |
762 | + @property |
763 | + def case_sensitive(self): |
764 | + """If True, the underlying filesystem is case sensitive.""" |
765 | + return self._nested_trees.root_tree.case_sensitive |
766 | |
767 | === modified file 'bzrlib/export/__init__.py' |
768 | --- bzrlib/export/__init__.py 2009-03-23 14:59:43 +0000 |
769 | +++ bzrlib/export/__init__.py 2009-08-31 04:38:31 +0000 |
770 | @@ -146,20 +146,20 @@ |
771 | else: |
772 | subdir_id = inv.path2id(subdir) |
773 | entries = inv.iter_entries(subdir_id) |
774 | - if subdir is None: |
775 | - entries.next() # skip root |
776 | - for entry in entries: |
777 | + for path, entry in entries: |
778 | + if path == '': |
779 | + continue |
780 | # The .bzr* namespace is reserved for "magic" files like |
781 | # .bzrignore and .bzrrules - do not export these |
782 | - if entry[0].startswith(".bzr"): |
783 | + if path.startswith(".bzr"): |
784 | continue |
785 | if subdir is None: |
786 | - if not tree.has_filename(entry[0]): |
787 | + if not tree.has_filename(path): |
788 | continue |
789 | else: |
790 | - if not tree.has_filename(os.path.join(subdir, entry[0])): |
791 | + if not tree.has_filename(os.path.join(subdir, path)): |
792 | continue |
793 | - yield entry |
794 | + yield path, entry |
795 | |
796 | |
797 | register_lazy_exporter(None, [], 'bzrlib.export.dir_exporter', 'dir_exporter') |
798 | |
799 | === modified file 'bzrlib/revisiontree.py' |
800 | --- bzrlib/revisiontree.py 2009-08-28 05:00:33 +0000 |
801 | +++ bzrlib/revisiontree.py 2009-08-31 04:38:31 +0000 |
802 | @@ -140,6 +140,9 @@ |
803 | def get_reference_revision(self, file_id, path=None): |
804 | return self.inventory[file_id].reference_revision |
805 | |
806 | + def get_nested_tree(self, file_id, branch, path=None): |
807 | + return self._get_nested_revision_tree(file_id, branch, path) |
808 | + |
809 | def get_root_id(self): |
810 | if self.inventory.root: |
811 | return self.inventory.root.file_id |
812 | |
813 | === modified file 'bzrlib/tests/__init__.py' |
814 | --- bzrlib/tests/__init__.py 2009-08-28 21:05:31 +0000 |
815 | +++ bzrlib/tests/__init__.py 2009-08-31 04:38:32 +0000 |
816 | @@ -3697,6 +3697,183 @@ |
817 | This function can be replaced if you need to change the default test |
818 | suite on a global basis, but it is not encouraged. |
819 | """ |
820 | +<<<<<<< TREE |
821 | +======= |
822 | + testmod_names = [ |
823 | + 'bzrlib.doc', |
824 | + 'bzrlib.tests.blackbox', |
825 | + 'bzrlib.tests.branch_implementations', |
826 | + 'bzrlib.tests.bzrdir_implementations', |
827 | + 'bzrlib.tests.commands', |
828 | + 'bzrlib.tests.interrepository_implementations', |
829 | + 'bzrlib.tests.intertree_implementations', |
830 | + 'bzrlib.tests.inventory_implementations', |
831 | + 'bzrlib.tests.per_interbranch', |
832 | + 'bzrlib.tests.per_lock', |
833 | + 'bzrlib.tests.per_repository', |
834 | + 'bzrlib.tests.per_repository_chk', |
835 | + 'bzrlib.tests.per_repository_reference', |
836 | + 'bzrlib.tests.test__chk_map', |
837 | + 'bzrlib.tests.test__dirstate_helpers', |
838 | + 'bzrlib.tests.test__groupcompress', |
839 | + 'bzrlib.tests.test__walkdirs_win32', |
840 | + 'bzrlib.tests.test_ancestry', |
841 | + 'bzrlib.tests.test_annotate', |
842 | + 'bzrlib.tests.test_api', |
843 | + 'bzrlib.tests.test_atomicfile', |
844 | + 'bzrlib.tests.test_bad_files', |
845 | + 'bzrlib.tests.test_bisect_multi', |
846 | + 'bzrlib.tests.test_branch', |
847 | + 'bzrlib.tests.test_branchbuilder', |
848 | + 'bzrlib.tests.test_btree_index', |
849 | + 'bzrlib.tests.test_bugtracker', |
850 | + 'bzrlib.tests.test_bundle', |
851 | + 'bzrlib.tests.test_bzrdir', |
852 | + 'bzrlib.tests.test__chunks_to_lines', |
853 | + 'bzrlib.tests.test_cache_utf8', |
854 | + 'bzrlib.tests.test_chk_map', |
855 | + 'bzrlib.tests.test_chunk_writer', |
856 | + 'bzrlib.tests.test_clean_tree', |
857 | + 'bzrlib.tests.test_commands', |
858 | + 'bzrlib.tests.test_commit', |
859 | + 'bzrlib.tests.test_commit_merge', |
860 | + 'bzrlib.tests.test_composite_tree', |
861 | + 'bzrlib.tests.test_config', |
862 | + 'bzrlib.tests.test_conflicts', |
863 | + 'bzrlib.tests.test_counted_lock', |
864 | + 'bzrlib.tests.test_decorators', |
865 | + 'bzrlib.tests.test_delta', |
866 | + 'bzrlib.tests.test_debug', |
867 | + 'bzrlib.tests.test_deprecated_graph', |
868 | + 'bzrlib.tests.test_diff', |
869 | + 'bzrlib.tests.test_directory_service', |
870 | + 'bzrlib.tests.test_dirstate', |
871 | + 'bzrlib.tests.test_email_message', |
872 | + 'bzrlib.tests.test_eol_filters', |
873 | + 'bzrlib.tests.test_errors', |
874 | + 'bzrlib.tests.test_export', |
875 | + 'bzrlib.tests.test_extract', |
876 | + 'bzrlib.tests.test_fetch', |
877 | + 'bzrlib.tests.test_fifo_cache', |
878 | + 'bzrlib.tests.test_filters', |
879 | + 'bzrlib.tests.test_ftp_transport', |
880 | + 'bzrlib.tests.test_foreign', |
881 | + 'bzrlib.tests.test_generate_docs', |
882 | + 'bzrlib.tests.test_generate_ids', |
883 | + 'bzrlib.tests.test_globbing', |
884 | + 'bzrlib.tests.test_gpg', |
885 | + 'bzrlib.tests.test_graph', |
886 | + 'bzrlib.tests.test_groupcompress', |
887 | + 'bzrlib.tests.test_hashcache', |
888 | + 'bzrlib.tests.test_help', |
889 | + 'bzrlib.tests.test_hooks', |
890 | + 'bzrlib.tests.test_http', |
891 | + 'bzrlib.tests.test_http_implementations', |
892 | + 'bzrlib.tests.test_http_response', |
893 | + 'bzrlib.tests.test_https_ca_bundle', |
894 | + 'bzrlib.tests.test_identitymap', |
895 | + 'bzrlib.tests.test_ignores', |
896 | + 'bzrlib.tests.test_index', |
897 | + 'bzrlib.tests.test_info', |
898 | + 'bzrlib.tests.test_inv', |
899 | + 'bzrlib.tests.test_inventory_delta', |
900 | + 'bzrlib.tests.test_knit', |
901 | + 'bzrlib.tests.test_lazy_import', |
902 | + 'bzrlib.tests.test_lazy_regex', |
903 | + 'bzrlib.tests.test_lockable_files', |
904 | + 'bzrlib.tests.test_lockdir', |
905 | + 'bzrlib.tests.test_log', |
906 | + 'bzrlib.tests.test_lru_cache', |
907 | + 'bzrlib.tests.test_lsprof', |
908 | + 'bzrlib.tests.test_mail_client', |
909 | + 'bzrlib.tests.test_memorytree', |
910 | + 'bzrlib.tests.test_merge', |
911 | + 'bzrlib.tests.test_merge3', |
912 | + 'bzrlib.tests.test_merge_core', |
913 | + 'bzrlib.tests.test_merge_directive', |
914 | + 'bzrlib.tests.test_missing', |
915 | + 'bzrlib.tests.test_msgeditor', |
916 | + 'bzrlib.tests.test_multiparent', |
917 | + 'bzrlib.tests.test_mutabletree', |
918 | + 'bzrlib.tests.test_nonascii', |
919 | + 'bzrlib.tests.test_options', |
920 | + 'bzrlib.tests.test_osutils', |
921 | + 'bzrlib.tests.test_osutils_encodings', |
922 | + 'bzrlib.tests.test_pack', |
923 | + 'bzrlib.tests.test_pack_repository', |
924 | + 'bzrlib.tests.test_patch', |
925 | + 'bzrlib.tests.test_patches', |
926 | + 'bzrlib.tests.test_permissions', |
927 | + 'bzrlib.tests.test_plugins', |
928 | + 'bzrlib.tests.test_progress', |
929 | + 'bzrlib.tests.test_read_bundle', |
930 | + 'bzrlib.tests.test_reconcile', |
931 | + 'bzrlib.tests.test_reconfigure', |
932 | + 'bzrlib.tests.test_registry', |
933 | + 'bzrlib.tests.test_remote', |
934 | + 'bzrlib.tests.test_rename_map', |
935 | + 'bzrlib.tests.test_repository', |
936 | + 'bzrlib.tests.test_revert', |
937 | + 'bzrlib.tests.test_revision', |
938 | + 'bzrlib.tests.test_revisionspec', |
939 | + 'bzrlib.tests.test_revisiontree', |
940 | + 'bzrlib.tests.test_rio', |
941 | + 'bzrlib.tests.test_rules', |
942 | + 'bzrlib.tests.test_sampler', |
943 | + 'bzrlib.tests.test_selftest', |
944 | + 'bzrlib.tests.test_serializer', |
945 | + 'bzrlib.tests.test_setup', |
946 | + 'bzrlib.tests.test_sftp_transport', |
947 | + 'bzrlib.tests.test_shelf', |
948 | + 'bzrlib.tests.test_shelf_ui', |
949 | + 'bzrlib.tests.test_smart', |
950 | + 'bzrlib.tests.test_smart_add', |
951 | + 'bzrlib.tests.test_smart_request', |
952 | + 'bzrlib.tests.test_smart_transport', |
953 | + 'bzrlib.tests.test_smtp_connection', |
954 | + 'bzrlib.tests.test_source', |
955 | + 'bzrlib.tests.test_ssh_transport', |
956 | + 'bzrlib.tests.test_status', |
957 | + 'bzrlib.tests.test_store', |
958 | + 'bzrlib.tests.test_strace', |
959 | + 'bzrlib.tests.test_subsume', |
960 | + 'bzrlib.tests.test_switch', |
961 | + 'bzrlib.tests.test_symbol_versioning', |
962 | + 'bzrlib.tests.test_tag', |
963 | + 'bzrlib.tests.test_testament', |
964 | + 'bzrlib.tests.test_textfile', |
965 | + 'bzrlib.tests.test_textmerge', |
966 | + 'bzrlib.tests.test_timestamp', |
967 | + 'bzrlib.tests.test_trace', |
968 | + 'bzrlib.tests.test_transactions', |
969 | + 'bzrlib.tests.test_transform', |
970 | + 'bzrlib.tests.test_transport', |
971 | + 'bzrlib.tests.test_transport_implementations', |
972 | + 'bzrlib.tests.test_transport_log', |
973 | + 'bzrlib.tests.test_tree', |
974 | + 'bzrlib.tests.test_treebuilder', |
975 | + 'bzrlib.tests.test_tsort', |
976 | + 'bzrlib.tests.test_tuned_gzip', |
977 | + 'bzrlib.tests.test_ui', |
978 | + 'bzrlib.tests.test_uncommit', |
979 | + 'bzrlib.tests.test_upgrade', |
980 | + 'bzrlib.tests.test_upgrade_stacked', |
981 | + 'bzrlib.tests.test_urlutils', |
982 | + 'bzrlib.tests.test_version', |
983 | + 'bzrlib.tests.test_version_info', |
984 | + 'bzrlib.tests.test_versionedfile', |
985 | + 'bzrlib.tests.test_weave', |
986 | + 'bzrlib.tests.test_whitebox', |
987 | + 'bzrlib.tests.test_win32utils', |
988 | + 'bzrlib.tests.test_workingtree', |
989 | + 'bzrlib.tests.test_workingtree_4', |
990 | + 'bzrlib.tests.test_wsgi', |
991 | + 'bzrlib.tests.test_xml', |
992 | + 'bzrlib.tests.tree_implementations', |
993 | + 'bzrlib.tests.workingtree_implementations', |
994 | + 'bzrlib.util.tests.test_bencode', |
995 | + ] |
996 | +>>>>>>> MERGE-SOURCE |
997 | |
998 | loader = TestUtil.TestLoader() |
999 | |
1000 | |
1001 | === modified file 'bzrlib/tests/per_workingtree/test_add_reference.py' |
1002 | --- bzrlib/tests/per_workingtree/test_add_reference.py 2009-07-10 07:14:02 +0000 |
1003 | +++ bzrlib/tests/per_workingtree/test_add_reference.py 2009-08-31 04:38:32 +0000 |
1004 | @@ -63,7 +63,8 @@ |
1005 | basis = tree.basis_tree() |
1006 | basis.lock_read() |
1007 | try: |
1008 | - sub_tree = tree.get_nested_tree('sub-tree-root-id') |
1009 | + sub_tree = tree.get_nested_tree('sub-tree-root-id', |
1010 | + tree.branch) |
1011 | self.assertEqual(sub_tree.last_revision(), |
1012 | tree.get_reference_revision('sub-tree-root-id')) |
1013 | finally: |
1014 | @@ -108,8 +109,9 @@ |
1015 | tree, sub_tree = self.make_nested_trees() |
1016 | tree.lock_read() |
1017 | try: |
1018 | - sub_tree2 = tree.get_nested_tree('sub-tree-root-id') |
1019 | + sub_tree2 = tree.get_nested_tree('sub-tree-root-id', tree.branch) |
1020 | self.assertEqual(sub_tree.basedir, sub_tree2.basedir) |
1021 | - sub_tree2 = tree.get_nested_tree('sub-tree-root-id', 'sub-tree') |
1022 | + sub_tree2 = tree.get_nested_tree('sub-tree-root-id', tree.branch, |
1023 | + 'sub-tree') |
1024 | finally: |
1025 | tree.unlock() |
1026 | |
1027 | === added file 'bzrlib/tests/test_composite_tree.py' |
1028 | --- bzrlib/tests/test_composite_tree.py 1970-01-01 00:00:00 +0000 |
1029 | +++ bzrlib/tests/test_composite_tree.py 2009-08-31 04:38:32 +0000 |
1030 | @@ -0,0 +1,362 @@ |
1031 | +# Copyright (C) 2007, 2009 Canonical Ltd |
1032 | +# |
1033 | +# This program is free software; you can redistribute it and/or modify |
1034 | +# it under the terms of the GNU General Public License as published by |
1035 | +# the Free Software Foundation; either version 2 of the License, or |
1036 | +# (at your option) any later version. |
1037 | +# |
1038 | +# This program is distributed in the hope that it will be useful, |
1039 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1040 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1041 | +# GNU General Public License for more details. |
1042 | +# |
1043 | +# You should have received a copy of the GNU General Public License |
1044 | +# along with this program; if not, write to the Free Software |
1045 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
1046 | + |
1047 | + |
1048 | +import os |
1049 | + |
1050 | +from bzrlib import ( |
1051 | + composite_tree, |
1052 | + conflicts, |
1053 | + errors, |
1054 | + inventory, |
1055 | + tests, |
1056 | + ) |
1057 | +from bzrlib.export import export |
1058 | + |
1059 | + |
1060 | +class TestNestedTrees(tests.TestCaseWithTransport): |
1061 | + |
1062 | + def make_branch_and_tree(self, path, format='pack-0.92-subtree'): |
1063 | + return tests.TestCaseWithTransport.make_branch_and_tree( |
1064 | + self, path, format) |
1065 | + |
1066 | + def test_paths_info(self): |
1067 | + tree = self.make_branch_and_tree('.') |
1068 | + subtree = self.make_branch_and_tree('sub') |
1069 | + subsubtree = self.make_branch_and_tree('sub/sub') |
1070 | + subsubtree.set_root_id('sub-sub-root-id') |
1071 | + subtree.add_reference(subsubtree) |
1072 | + tree.add_reference(subtree) |
1073 | + nested = composite_tree.NestedTrees(tree, tree.branch) |
1074 | + nested.lock_read() |
1075 | + self.addCleanup(nested.unlock) |
1076 | + fullpath, relpath, found_entry, found_tree = \ |
1077 | + nested.paths_info(['sub/sub'])[0] |
1078 | + self.assertEqual('sub-sub-root-id', found_entry.file_id) |
1079 | + self.assertEqual(subsubtree.basedir, found_tree.basedir) |
1080 | + self.assertEqual('', relpath) |
1081 | + self.assertEqual('sub/sub', fullpath) |
1082 | + |
1083 | + # Now testing an unversioned, non-existent path |
1084 | + fullpath, relpath, found_entry, found_tree = \ |
1085 | + nested.paths_info(['sub/subb'])[0] |
1086 | + self.assertIs(None, found_entry) |
1087 | + self.assertIsNot(None, found_tree) |
1088 | + |
1089 | + def lock_read(self, item): |
1090 | + item.lock_read() |
1091 | + self.addCleanup(item.unlock) |
1092 | + |
1093 | + def test_all_trees(self): |
1094 | + tree = self.make_branch_and_tree('tree') |
1095 | + subtree = self.make_branch_and_tree('tree/subtree') |
1096 | + tree.add_reference(subtree) |
1097 | + subsubtree = self.make_branch_and_tree('tree/subtree/subsubtree') |
1098 | + subtree.add_reference(subsubtree) |
1099 | + nested = composite_tree.NestedTrees(tree, tree.branch) |
1100 | + self.lock_read(nested) |
1101 | + result = nested.all_trees() |
1102 | + expected = {'': tree, 'subtree': subtree, |
1103 | + 'subtree/subsubtree': subsubtree} |
1104 | + def bases(input): |
1105 | + return dict((y, x.basedir) for y, x in input.iteritems()) |
1106 | + self.assertEqual(bases(expected), bases(result)) |
1107 | + |
1108 | + |
1109 | +class TestCompositeTree(tests.TestCaseWithTransport): |
1110 | + |
1111 | + def make_branch_and_tree(self, path, format='pack-0.92-subtree'): |
1112 | + return tests.TestCaseWithTransport.make_branch_and_tree( |
1113 | + self, path, format) |
1114 | + |
1115 | + def make_tree_files(self, subfile=False): |
1116 | + tree = self.make_branch_and_tree('.') |
1117 | + self.build_tree(['file']) |
1118 | + tree.set_root_id('tree-root') |
1119 | + tree.add(['file'], ['file-id']) |
1120 | + subtree = self.make_branch_and_tree('subtree') |
1121 | + subtree.set_root_id('subtree-id') |
1122 | + if subfile: |
1123 | + self.build_tree(['subtree/subfile']) |
1124 | + subtree.add('subfile', 'subfile-id') |
1125 | + tree.add_reference(subtree) |
1126 | + tree.lock_write() |
1127 | + self.addCleanup(tree.unlock) |
1128 | + return tree, subtree |
1129 | + |
1130 | + def test_move_across_trees(self): |
1131 | + tree, subtree = self.make_tree_files() |
1132 | + composite = composite_tree.CompositeTree(tree, tree.branch) |
1133 | + pairs = composite.move(['file'], 'subtree') |
1134 | + subtree.lock_write() |
1135 | + self.addCleanup(subtree.unlock) |
1136 | + self.assertIs(None, tree.path2id('file')) |
1137 | + self.assertEqual('file-id', subtree.path2id('file')) |
1138 | + self.failUnlessExists('subtree/file') |
1139 | + self.failIfExists('file') |
1140 | + self.assertEqual([('file', 'subtree/file')], pairs) |
1141 | + |
1142 | + def test_move_after(self): |
1143 | + tree, subtree = self.make_tree_files() |
1144 | + os.rename('file', 'subtree/file') |
1145 | + composite = composite_tree.CompositeTree(tree, tree.branch) |
1146 | + composite.move(['file'], 'subtree', after=True) |
1147 | + self.assertIs(None, tree.path2id('file')) |
1148 | + self.assertEqual('file-id', subtree.path2id('file')) |
1149 | + |
1150 | + def test_move_needs_after(self): |
1151 | + tree, subtree = self.make_tree_files() |
1152 | + self.build_tree(['subtree/file']) |
1153 | + composite = composite_tree.CompositeTree(tree, tree.branch) |
1154 | + self.assertRaises(errors.RenameFailedFilesExist, composite.move, |
1155 | + ['file'], 'subtree') |
1156 | + |
1157 | + def test_move_force_after(self): |
1158 | + tree, subtree = self.make_tree_files() |
1159 | + self.build_tree(['subtree/file']) |
1160 | + composite = composite_tree.CompositeTree(tree, tree.branch) |
1161 | + composite.move(['file'], 'subtree', |
1162 | + after=True) |
1163 | + self.assertIs(None, tree.path2id('file')) |
1164 | + self.assertEqual('file-id', subtree.path2id('file')) |
1165 | + |
1166 | + def test_move_to_unversioned(self): |
1167 | + tree, subtree = self.make_tree_files() |
1168 | + tree.remove('subtree') |
1169 | + composite = composite_tree.CompositeTree(tree, tree.branch) |
1170 | + self.assertRaises(errors.BzrMoveFailedError, composite.move, ['file'], |
1171 | + 'subtree', after=True) |
1172 | + |
1173 | + def test_move_from_unversioned(self): |
1174 | + tree, subtree = self.make_tree_files() |
1175 | + tree.remove('file') |
1176 | + composite = composite_tree.CompositeTree(tree, tree.branch) |
1177 | + self.assertRaises(errors.BzrMoveFailedError, composite.move, ['file'], |
1178 | + 'subtree', after=True) |
1179 | + |
1180 | + def test_rename_into_subtree(self): |
1181 | + tree, subtree = self.make_tree_files() |
1182 | + composite = composite_tree.CompositeTree(tree, tree.branch) |
1183 | + composite.rename_one('file', 'subtree/file1') |
1184 | + self.failUnlessExists('subtree/file1') |
1185 | + self.assertEqual('file-id', subtree.path2id('file1')) |
1186 | + |
1187 | + def test_iter_entries_by_dir(self): |
1188 | + tree, subtree = self.make_tree_files() |
1189 | + composite = self.get_locked_composite(tree) |
1190 | + for path, entry in composite.iter_entries_by_dir(): |
1191 | + self.assertNotContainsRe(path, '/$') |
1192 | + |
1193 | + def test_iter_entries_by_dir_follow_subtree(self): |
1194 | + """Ensure that iter_entries_by_dir follows subtrees. |
1195 | + |
1196 | + This makes sure that even when the cached kind is directory, subtree |
1197 | + is followed. |
1198 | + """ |
1199 | + tree = self.make_branch_and_tree('.') |
1200 | + self.build_tree(['subtree/', 'subtree/file']) |
1201 | + subtree = self.make_branch_and_tree('subtree') |
1202 | + self.build_tree(['subtree/file']) |
1203 | + subtree.add('file') |
1204 | + tree.add_reference(subtree) |
1205 | + composite = self.get_locked_composite(tree) |
1206 | + paths = [p for p, e in composite.iter_entries_by_dir()] |
1207 | + self.assertTrue('subtree' in paths) |
1208 | + self.assertTrue('subtree/file' in paths) |
1209 | + |
1210 | + def test_iter_changes(self): |
1211 | + tree, subtree = self.make_tree_files() |
1212 | + new = self.get_locked_composite(tree) |
1213 | + old = self.get_locked_composite(tree.basis_tree(), tree.branch) |
1214 | + for file_id, paths, content, versioned, parent, name, kind, executable\ |
1215 | + in new.iter_changes(old): |
1216 | + self.assertNotEqual('tree-reference', kind[1]) |
1217 | + |
1218 | + def test_iter_changes_accepts_specific_files_for_deletion(self): |
1219 | + """Test corner case requiring all_file_ids()""" |
1220 | + tree, subtree = self.make_tree_files() |
1221 | + old = self.get_locked_composite(tree) |
1222 | + new = self.get_locked_composite(tree.basis_tree(), tree.branch) |
1223 | + for file_id, paths, content, versioned, parent, name, kind, executable\ |
1224 | + in new.iter_changes(old, specific_files=['subtree']): |
1225 | + self.assertNotEqual('tree-reference', kind[1]) |
1226 | + |
1227 | + def test_inventory(self): |
1228 | + tree, subtree = self.make_tree_files() |
1229 | + composite = self.get_locked_composite(tree) |
1230 | + self.assertRaises(errors.NoSuchId, lambda x: composite.inventory[x], |
1231 | + 'not-present') |
1232 | + subtree_entry = composite.inventory['subtree-id'] |
1233 | + self.assertEqual('tree-reference', subtree_entry.kind) |
1234 | + |
1235 | + def test_comparison_data(self): |
1236 | + tree, subtree = self.make_tree_files() |
1237 | + composite = self.get_locked_composite(tree) |
1238 | + entry = composite.inventory['subtree-id'] |
1239 | + kind, exe, stat = composite._comparison_data(entry, 'subtree') |
1240 | + self.assertEqual('directory', kind) |
1241 | + |
1242 | + def test_apply_inventory_delta(self): |
1243 | + tree, subtree = self.make_tree_files() |
1244 | + composite = composite_tree.CompositeTree(tree, tree.branch) |
1245 | + # Move an existing file into a new subtree. |
1246 | + new_entry = tree.inventory['file-id'].copy() |
1247 | + new_entry.parent_id = 'subtree-id' |
1248 | + composite.lock_write() |
1249 | + try: |
1250 | + composite.apply_inventory_delta([('file', 'subtree/file', |
1251 | + 'file-id', new_entry)]) |
1252 | + finally: |
1253 | + composite.unlock() |
1254 | + self.assertEqual('file', subtree.id2path('file-id')) |
1255 | + self.assertEqual('file-id', subtree.path2id('file')) |
1256 | + self.assertEqual(None, tree.path2id('file')) |
1257 | + |
1258 | + # Rename subtree/file to subtree/smile |
1259 | + new_entry = new_entry.copy() |
1260 | + new_entry.name = 'smile' |
1261 | + composite.lock_write() |
1262 | + try: |
1263 | + composite.apply_inventory_delta([('subtree/file', 'subtree/smile', |
1264 | + 'file-id', new_entry)]) |
1265 | + finally: |
1266 | + composite.unlock() |
1267 | + self.assertEqual('file-id', subtree.path2id('smile')) |
1268 | + |
1269 | + # Delete subtree/smile |
1270 | + composite.lock_write() |
1271 | + try: |
1272 | + composite.apply_inventory_delta([('subtree/smile', None, 'file-id', |
1273 | + None)]) |
1274 | + finally: |
1275 | + composite.unlock() |
1276 | + self.assertEqual(None, subtree.path2id('smile')) |
1277 | + |
1278 | + # Create subtree/smile |
1279 | + composite.lock_write() |
1280 | + try: |
1281 | + composite.apply_inventory_delta([(None, 'subtree/smile', 'file-id', |
1282 | + new_entry)]) |
1283 | + finally: |
1284 | + composite.unlock() |
1285 | + self.assertEqual('file-id', subtree.path2id('smile')) |
1286 | + |
1287 | + # Move subtree/smile back into root tree |
1288 | + new_entry = new_entry.copy() |
1289 | + new_entry.parent_id = tree.get_root_id() |
1290 | + composite.lock_write() |
1291 | + try: |
1292 | + composite.apply_inventory_delta([('subtree/smile', 'smile', |
1293 | + 'file-id', new_entry)]) |
1294 | + finally: |
1295 | + composite.unlock() |
1296 | + self.assertEqual(None, subtree.path2id('smile')) |
1297 | + self.assertEqual('file-id', tree.path2id('smile')) |
1298 | + |
1299 | + def test_apply_inventory_delta_tree_reference(self): |
1300 | + tree = self.make_branch_and_tree('.') |
1301 | + tree.set_root_id('root-id') |
1302 | + composite = composite_tree.CompositeTree(tree, tree.branch) |
1303 | + composite.lock_write() |
1304 | + self.addCleanup(composite.unlock) |
1305 | + foo = inventory.TreeReference( |
1306 | + 'foo-id', 'foo', 'root-id', reference_revision='foo-rev-id') |
1307 | + e = self.assertRaises(ValueError, composite.apply_inventory_delta, |
1308 | + [(None, 'foo', 'foo-id', foo)]) |
1309 | + self.assertEqual('Cannot introduce or change a tree-reference in' |
1310 | + ' CompositeTree.apply_inventory_delta.', str(e)) |
1311 | + |
1312 | + def get_locked_composite(self, tree, branch=None): |
1313 | + if branch is None: |
1314 | + branch = tree.branch |
1315 | + composite = composite_tree.CompositeTree(tree, branch) |
1316 | + composite.lock_read() |
1317 | + self.addCleanup(composite.unlock) |
1318 | + return composite |
1319 | + |
1320 | + def test_all_file_ids(self): |
1321 | + tree, subtree = self.make_tree_files(subfile=True) |
1322 | + composite = self.get_locked_composite(tree) |
1323 | + self.assertEqual(set(['tree-root', 'file-id', 'subtree-id', |
1324 | + 'subfile-id']), composite.all_file_ids()) |
1325 | + |
1326 | + def test_paths2ids(self): |
1327 | + tree, subtree = self.make_tree_files() |
1328 | + composite = self.get_locked_composite(tree) |
1329 | + self.assertEqual(set(['file-id', 'subtree-id']), |
1330 | + composite.paths2ids(['file', 'subtree'])) |
1331 | + |
1332 | + def test_iter_children(self): |
1333 | + tree, subtree = self.make_tree_files(subfile=True) |
1334 | + composite = self.get_locked_composite(tree) |
1335 | + self.assertEqual(set(['subfile-id']), |
1336 | + set(composite.iter_children('subtree-id'))) |
1337 | + self.assertEqual(set(['file-id', 'subtree-id']), |
1338 | + set(composite.iter_children('tree-root'))) |
1339 | + |
1340 | + def test_case_sensitive(self): |
1341 | + tree, subtree = self.make_tree_files() |
1342 | + composite = self.get_locked_composite(tree) |
1343 | + self.assertEqual(tree.case_sensitive, composite.case_sensitive) |
1344 | + tree.case_sensitive = not tree.case_sensitive |
1345 | + self.assertEqual(tree.case_sensitive, composite.case_sensitive) |
1346 | + |
1347 | + def make_ci_tree(self): |
1348 | + tree, subtree = self.make_tree_files() |
1349 | + self.build_tree(['subtree/subFile']) |
1350 | + subtree.add('subFile') |
1351 | + return self.get_locked_composite(tree) |
1352 | + |
1353 | + def test_get_canonical_inventory_path(self): |
1354 | + """Finds subtree paths.""" |
1355 | + composite = self.make_ci_tree() |
1356 | + self.assertEqual('subtree/subFile', |
1357 | + composite.get_canonical_inventory_path( |
1358 | + 'SUBTREE/SUBFILE')) |
1359 | + |
1360 | + def test_get_canonical_inventory_path_preserves_missing(self): |
1361 | + """Returns unversion path segments verbatim.""" |
1362 | + composite = self.make_ci_tree() |
1363 | + self.assertEqual('subtree/subFile/X', |
1364 | + composite.get_canonical_inventory_path( |
1365 | + 'SUBTREE/SUBFILE/X')) |
1366 | + |
1367 | + def test_get_canonical_inventory_paths(self): |
1368 | + """Finds subtree paths.""" |
1369 | + composite = self.make_ci_tree() |
1370 | + self.assertEqual(['subtree/subFile'], |
1371 | + composite.get_canonical_inventory_paths( |
1372 | + ['SUBTREE/SUBFILE'])) |
1373 | + |
1374 | + def test_get_file_kind(self): |
1375 | + composite = self.get_locked_composite(self.make_tree_files()[0]) |
1376 | + inv = composite.inventory |
1377 | + self.assertEqual('file', inv.get_file_kind('file-id')) |
1378 | + self.assertEqual('directory', inv.get_file_kind('subtree-id')) |
1379 | + |
1380 | + def test_export(self): |
1381 | + tree, subtree = self.make_tree_files(subfile=True) |
1382 | + composite = self.get_locked_composite(tree) |
1383 | + export(composite, 'output', None, None, 'subtree') |
1384 | + self.assertEqual(['subfile'], os.listdir('output')) |
1385 | + |
1386 | + def test_conflicts_path(self): |
1387 | + tree, subtree = self.make_tree_files() |
1388 | + subtree.add_conflicts([conflicts.ContentsConflict('foo')]) |
1389 | + composite = self.get_locked_composite(tree) |
1390 | + [conflict] = composite.conflicts() |
1391 | + self.expectFailure('Conflict reporting for composite trees is wonky.', |
1392 | + self.assertEqual, 'subtree/foo', conflict.path) |
1393 | |
1394 | === modified file 'bzrlib/transform.py' |
1395 | --- bzrlib/transform.py 2009-08-28 05:00:33 +0000 |
1396 | +++ bzrlib/transform.py 2009-08-31 04:38:31 +0000 |
1397 | @@ -1315,6 +1315,7 @@ |
1398 | tree.case_sensitive) |
1399 | self._deletiondir = deletiondir |
1400 | |
1401 | +<<<<<<< TREE |
1402 | def canonical_path(self, path): |
1403 | """Get the canonical tree-relative path""" |
1404 | # don't follow final symlinks |
1405 | @@ -1397,6 +1398,10 @@ |
1406 | yield self.trans_id_tree_path(childpath) |
1407 | |
1408 | def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None): |
1409 | +======= |
1410 | + def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None, |
1411 | + skip_inventory=False): |
1412 | +>>>>>>> MERGE-SOURCE |
1413 | """Apply all changes to the inventory and filesystem. |
1414 | |
1415 | If filesystem or inventory conflicts are present, MalformedTransform |
1416 | @@ -1437,7 +1442,8 @@ |
1417 | mover.apply_deletions() |
1418 | finally: |
1419 | child_pb.finished() |
1420 | - self._tree.apply_inventory_delta(inventory_delta) |
1421 | + if not skip_inventory: |
1422 | + self._tree.apply_inventory_delta(inventory_delta) |
1423 | self._done = True |
1424 | self.finalize() |
1425 | return _TransformResults(modified_paths, self.rename_count) |
1426 | |
1427 | === modified file 'bzrlib/workingtree_4.py' |
1428 | --- bzrlib/workingtree_4.py 2009-08-25 04:43:21 +0000 |
1429 | +++ bzrlib/workingtree_4.py 2009-08-31 04:38:31 +0000 |
1430 | @@ -414,9 +414,9 @@ |
1431 | |
1432 | def get_reference_revision(self, file_id, path=None): |
1433 | # referenced tree's revision is whatever's currently there |
1434 | - return self.get_nested_tree(file_id, path).last_revision() |
1435 | + return self.get_nested_tree(file_id, self.branch, path).last_revision() |
1436 | |
1437 | - def get_nested_tree(self, file_id, path=None): |
1438 | + def get_nested_tree(self, file_id, branch, path=None): |
1439 | if path is None: |
1440 | path = self.id2path(file_id) |
1441 | # else: check file_id is at path? |
1442 | @@ -1806,6 +1806,9 @@ |
1443 | target = target.decode('utf8') |
1444 | return target |
1445 | |
1446 | + def get_nested_tree(self, file_id, branch, path=None): |
1447 | + return self._get_nested_revision_tree(file_id, branch, path) |
1448 | + |
1449 | def get_revision_id(self): |
1450 | """Return the revision id for this tree.""" |
1451 | return self._revision_id |
1452 | |
1453 | === added file 'doc/developers/nested-trees.txt' |
1454 | --- doc/developers/nested-trees.txt 1970-01-01 00:00:00 +0000 |
1455 | +++ doc/developers/nested-trees.txt 2009-08-31 04:38:32 +0000 |
1456 | @@ -0,0 +1,28 @@ |
1457 | +Nested Trees Implementation |
1458 | +########################### |
1459 | + |
1460 | +CompositeTree |
1461 | +------------- |
1462 | +CompositeTree is a shim to enable subtree support for certain commands. |
1463 | + |
1464 | +It is constructed from a Tree (typically a RevisionTree or WorkingTree) and a |
1465 | +Branch. It provides an alternate view of the Tree and its subtrees, in which |
1466 | +the subtrees and their contents appear to be part of the root tree. The roots |
1467 | +of subtrees are presented as directories, not as tree references. |
1468 | + |
1469 | +It can be used with export, diff and status. It should be tested with every |
1470 | +command that uses it, because it is not guaranteed to be a complete |
1471 | +implementation of the Tree API. It is not suitable as input to operations that |
1472 | +must know about the locations of subtrees, such as revert. |
1473 | + |
1474 | + |
1475 | +NestedTrees |
1476 | +----------- |
1477 | +NestedTrees is an API providing access to a set of nested trees. It is able to |
1478 | +convert paths and file-ids into references to specific Trees. It provides |
1479 | +caching, to avoid retrieving the same Tree multiple times. It provides |
1480 | +locking, to ensure trees are locked and unlocked at appropriate times. |
1481 | + |
1482 | +It is used by CompositeTree, and is a recommended API for all other operations. |
1483 | +It is expected to acquire more capabilities as the needs of sets of nested |
1484 | +trees become clearer. |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Hi Martin,
You wanted some documentation of CompositeTree to be included with the
patch that introduces it. Since NestedTrees is also introduced by this
patch and is arguably more important in the long run, I've included
documentation for it as well.
I hope this is the kind of thing you were looking for.
reviewer mbp
Aaron enigmail. mozdev. org
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAko B0ogACgkQ0F+ nu1YWqI3BFgCfTM E8bivflaYQe2afj cdgVPeO 3zncVrShuQltoj+ vB
HdgAnRvPljssQet
=BAWZ
-----END PGP SIGNATURE-----