Merge lp:~jelmer/brz/transform-file-id into lp:brz/3.1

Proposed by Jelmer Vernooij on 2020-07-05
Status: Merged
Approved by: Jelmer Vernooij on 2020-07-05
Approved revision: 7580
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/transform-file-id
Merge into: lp:brz/3.1
Prerequisite: lp:~jelmer/brz/transform
Diff against target: 2570 lines (+1291/-910)
16 files modified
breezy/bzr/inventorytree.py (+3/-3)
breezy/bzr/tests/__init__.py (+1/-0)
breezy/bzr/tests/test_transform.py (+49/-0)
breezy/bzr/transform.py (+903/-0)
breezy/bzr/workingtree.py (+2/-2)
breezy/errors.py (+0/-16)
breezy/git/transform.py (+275/-0)
breezy/git/tree.py (+6/-7)
breezy/git/workingtree.py (+2/-6)
breezy/merge.py (+6/-11)
breezy/tests/per_tree/test_path_content_summary.py (+4/-2)
breezy/tests/per_tree/test_symlinks.py (+1/-1)
breezy/tests/per_workingtree/test_transform.py (+3/-2)
breezy/tests/test_errors.py (+0/-6)
breezy/tests/test_transform.py (+13/-34)
breezy/transform.py (+23/-820)
To merge this branch: bzr merge lp:~jelmer/brz/transform-file-id
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve on 2020-07-05
Review via email: mp+386859@code.launchpad.net

This proposal supersedes a proposal from 2020-06-29.

Commit message

Split out git and bzr-specific transforms.

Description of the change

Split out git and bzr-specific transforms.

To post a comment you must log in.
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Jelmer Vernooij (jelmer) : Posted in a previous version of this proposal
review: Approve
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Jelmer Vernooij (jelmer) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'breezy/bzr/inventorytree.py'
--- breezy/bzr/inventorytree.py 2020-07-04 01:15:59 +0000
+++ breezy/bzr/inventorytree.py 2020-07-05 14:44:34 +0000
@@ -325,7 +325,7 @@
325 return last_revision325 return last_revision
326326
327 def preview_transform(self, pb=None):327 def preview_transform(self, pb=None):
328 from ..transform import TransformPreview328 from .transform import TransformPreview
329 return TransformPreview(self, pb=pb)329 return TransformPreview(self, pb=pb)
330330
331331
@@ -510,8 +510,8 @@
510 self.set_parent_trees([(new_revid, rev_tree)])510 self.set_parent_trees([(new_revid, rev_tree)])
511511
512 def transform(self, pb=None):512 def transform(self, pb=None):
513 from ..transform import TreeTransform513 from .transform import InventoryTreeTransform
514 return TreeTransform(self, pb=pb)514 return InventoryTreeTransform(self, pb=pb)
515515
516516
517class _SmartAddHelper(object):517class _SmartAddHelper(object):
518518
=== modified file 'breezy/bzr/tests/__init__.py'
--- breezy/bzr/tests/__init__.py 2020-06-25 21:13:23 +0000
+++ breezy/bzr/tests/__init__.py 2020-07-05 14:44:34 +0000
@@ -81,6 +81,7 @@
81 'test_serializer',81 'test_serializer',
82 'test_tag',82 'test_tag',
83 'test_testament',83 'test_testament',
84 'test_transform',
84 'test_versionedfile',85 'test_versionedfile',
85 'test_vf_search',86 'test_vf_search',
86 'test_vfs_ratchet',87 'test_vfs_ratchet',
8788
=== added file 'breezy/bzr/tests/test_transform.py'
--- breezy/bzr/tests/test_transform.py 1970-01-01 00:00:00 +0000
+++ breezy/bzr/tests/test_transform.py 2020-07-05 14:44:34 +0000
@@ -0,0 +1,49 @@
1# Copyright (C) 2006-2012, 2016 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17from . import TestCaseWithTransport
18
19
20class TestInventoryAltered(TestCaseWithTransport):
21
22 def test_inventory_altered_unchanged(self):
23 tree = self.make_branch_and_tree('tree')
24 self.build_tree(['tree/foo'])
25 tree.add('foo', b'foo-id')
26 with tree.preview_transform() as tt:
27 self.assertEqual([], tt._inventory_altered())
28
29 def test_inventory_altered_changed_parent_id(self):
30 tree = self.make_branch_and_tree('tree')
31 self.build_tree(['tree/foo'])
32 tree.add('foo', b'foo-id')
33 with tree.preview_transform() as tt:
34 tt.unversion_file(tt.root)
35 tt.version_file(tt.root, file_id=b'new-id')
36 foo_trans_id = tt.trans_id_tree_path('foo')
37 foo_tuple = ('foo', foo_trans_id)
38 root_tuple = ('', tt.root)
39 self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
40
41 def test_inventory_altered_noop_changed_parent_id(self):
42 tree = self.make_branch_and_tree('tree')
43 self.build_tree(['tree/foo'])
44 tree.add('foo', b'foo-id')
45 with tree.preview_transform() as tt:
46 tt.unversion_file(tt.root)
47 tt.version_file(tt.root, file_id=tree.path2id(''))
48 tt.trans_id_tree_path('foo')
49 self.assertEqual([], tt._inventory_altered())
050
=== added file 'breezy/bzr/transform.py'
--- breezy/bzr/transform.py 1970-01-01 00:00:00 +0000
+++ breezy/bzr/transform.py 2020-07-05 14:44:34 +0000
@@ -0,0 +1,903 @@
1# Copyright (C) 2006-2011 Canonical Ltd
2# Copyright (C) 2020 Breezy Developers
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18from __future__ import absolute_import
19
20import errno
21import os
22from stat import S_IEXEC
23
24from .. import (
25 annotate,
26 errors,
27 lock,
28 osutils,
29 revision as _mod_revision,
30 tree,
31 ui,
32 )
33
34from ..i18n import gettext
35from ..mutabletree import MutableTree
36from ..sixish import text_type, viewvalues, viewitems
37from ..transform import (
38 ROOT_PARENT,
39 TreeTransform,
40 _FileMover,
41 _TransformResults,
42 DiskTreeTransform,
43 joinpath,
44 NoFinalPath,
45 FinalPaths,
46 unique_add,
47 TransformRenameFailed,
48 )
49from . import (
50 inventory,
51 inventorytree,
52 )
53
54
55class InventoryTreeTransform(TreeTransform):
56 """Tree transform for Bazaar trees."""
57
58 def version_file(self, trans_id, file_id=None):
59 """Schedule a file to become versioned."""
60 if file_id is None:
61 raise ValueError()
62 unique_add(self._new_id, trans_id, file_id)
63 unique_add(self._r_new_id, file_id, trans_id)
64
65 def cancel_versioning(self, trans_id):
66 """Undo a previous versioning of a file"""
67 file_id = self._new_id[trans_id]
68 del self._new_id[trans_id]
69 del self._r_new_id[file_id]
70
71 def _duplicate_ids(self):
72 """Each inventory id may only be used once"""
73 conflicts = []
74 try:
75 all_ids = self._tree.all_file_ids()
76 except errors.UnsupportedOperation:
77 # it's okay for non-file-id trees to raise UnsupportedOperation.
78 return []
79 removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
80 self._removed_id))
81 active_tree_ids = all_ids.difference(removed_tree_ids)
82 for trans_id, file_id in viewitems(self._new_id):
83 if file_id in active_tree_ids:
84 path = self._tree.id2path(file_id)
85 old_trans_id = self.trans_id_tree_path(path)
86 conflicts.append(('duplicate id', old_trans_id, trans_id))
87 return conflicts
88
89 def find_conflicts(self):
90 conflicts = super(InventoryTreeTransform, self).find_conflicts()
91 conflicts.extend(self._duplicate_ids())
92 return conflicts
93
94 def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
95 """Apply all changes to the inventory and filesystem.
96
97 If filesystem or inventory conflicts are present, MalformedTransform
98 will be thrown.
99
100 If apply succeeds, finalize is not necessary.
101
102 :param no_conflicts: if True, the caller guarantees there are no
103 conflicts, so no check is made.
104 :param precomputed_delta: An inventory delta to use instead of
105 calculating one.
106 :param _mover: Supply an alternate FileMover, for testing
107 """
108 for hook in MutableTree.hooks['pre_transform']:
109 hook(self._tree, self)
110 if not no_conflicts:
111 self._check_malformed()
112 with ui.ui_factory.nested_progress_bar() as child_pb:
113 if precomputed_delta is None:
114 child_pb.update(gettext('Apply phase'), 0, 2)
115 inventory_delta = self._generate_inventory_delta()
116 offset = 1
117 else:
118 inventory_delta = precomputed_delta
119 offset = 0
120 if _mover is None:
121 mover = _FileMover()
122 else:
123 mover = _mover
124 try:
125 child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
126 self._apply_removals(mover)
127 child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
128 modified_paths = self._apply_insertions(mover)
129 except BaseException:
130 mover.rollback()
131 raise
132 else:
133 mover.apply_deletions()
134 if self.final_file_id(self.root) is None:
135 inventory_delta = [e for e in inventory_delta if e[0] != '']
136 self._tree.apply_inventory_delta(inventory_delta)
137 self._apply_observed_sha1s()
138 self._done = True
139 self.finalize()
140 return _TransformResults(modified_paths, self.rename_count)
141
142 def _apply_removals(self, mover):
143 """Perform tree operations that remove directory/inventory names.
144
145 That is, delete files that are to be deleted, and put any files that
146 need renaming into limbo. This must be done in strict child-to-parent
147 order.
148
149 If inventory_delta is None, no inventory delta generation is performed.
150 """
151 tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
152 with ui.ui_factory.nested_progress_bar() as child_pb:
153 for num, (path, trans_id) in enumerate(tree_paths):
154 # do not attempt to move root into a subdirectory of itself.
155 if path == '':
156 continue
157 child_pb.update(gettext('removing file'), num, len(tree_paths))
158 full_path = self._tree.abspath(path)
159 if trans_id in self._removed_contents:
160 delete_path = os.path.join(self._deletiondir, trans_id)
161 mover.pre_delete(full_path, delete_path)
162 elif (trans_id in self._new_name or
163 trans_id in self._new_parent):
164 try:
165 mover.rename(full_path, self._limbo_name(trans_id))
166 except TransformRenameFailed as e:
167 if e.errno != errno.ENOENT:
168 raise
169 else:
170 self.rename_count += 1
171
172 def _apply_insertions(self, mover):
173 """Perform tree operations that insert directory/inventory names.
174
175 That is, create any files that need to be created, and restore from
176 limbo any files that needed renaming. This must be done in strict
177 parent-to-child order.
178
179 If inventory_delta is None, no inventory delta is calculated, and
180 no list of modified paths is returned.
181 """
182 new_paths = self.new_paths(filesystem_only=True)
183 modified_paths = []
184 with ui.ui_factory.nested_progress_bar() as child_pb:
185 for num, (path, trans_id) in enumerate(new_paths):
186 if (num % 10) == 0:
187 child_pb.update(gettext('adding file'),
188 num, len(new_paths))
189 full_path = self._tree.abspath(path)
190 if trans_id in self._needs_rename:
191 try:
192 mover.rename(self._limbo_name(trans_id), full_path)
193 except TransformRenameFailed as e:
194 # We may be renaming a dangling inventory id
195 if e.errno != errno.ENOENT:
196 raise
197 else:
198 self.rename_count += 1
199 # TODO: if trans_id in self._observed_sha1s, we should
200 # re-stat the final target, since ctime will be
201 # updated by the change.
202 if (trans_id in self._new_contents
203 or self.path_changed(trans_id)):
204 if trans_id in self._new_contents:
205 modified_paths.append(full_path)
206 if trans_id in self._new_executability:
207 self._set_executability(path, trans_id)
208 if trans_id in self._observed_sha1s:
209 o_sha1, o_st_val = self._observed_sha1s[trans_id]
210 st = osutils.lstat(full_path)
211 self._observed_sha1s[trans_id] = (o_sha1, st)
212 for path, trans_id in new_paths:
213 # new_paths includes stuff like workingtree conflicts. Only the
214 # stuff in new_contents actually comes from limbo.
215 if trans_id in self._limbo_files:
216 del self._limbo_files[trans_id]
217 self._new_contents.clear()
218 return modified_paths
219
220 def _apply_observed_sha1s(self):
221 """After we have finished renaming everything, update observed sha1s
222
223 This has to be done after self._tree.apply_inventory_delta, otherwise
224 it doesn't know anything about the files we are updating. Also, we want
225 to do this as late as possible, so that most entries end up cached.
226 """
227 # TODO: this doesn't update the stat information for directories. So
228 # the first 'bzr status' will still need to rewrite
229 # .bzr/checkout/dirstate. However, we at least don't need to
230 # re-read all of the files.
231 # TODO: If the operation took a while, we could do a time.sleep(3) here
232 # to allow the clock to tick over and ensure we won't have any
233 # problems. (we could observe start time, and finish time, and if
234 # it is less than eg 10% overhead, add a sleep call.)
235 paths = FinalPaths(self)
236 for trans_id, observed in viewitems(self._observed_sha1s):
237 path = paths.get_path(trans_id)
238 self._tree._observed_sha1(path, observed)
239
240 def get_preview_tree(self):
241 """Return a tree representing the result of the transform.
242
243 The tree is a snapshot, and altering the TreeTransform will invalidate
244 it.
245 """
246 return _PreviewTree(self)
247
248 def _inventory_altered(self):
249 """Determine which trans_ids need new Inventory entries.
250
251 An new entry is needed when anything that would be reflected by an
252 inventory entry changes, including file name, file_id, parent file_id,
253 file kind, and the execute bit.
254
255 Some care is taken to return entries with real changes, not cases
256 where the value is deleted and then restored to its original value,
257 but some actually unchanged values may be returned.
258
259 :returns: A list of (path, trans_id) for all items requiring an
260 inventory change. Ordered by path.
261 """
262 changed_ids = set()
263 # Find entries whose file_ids are new (or changed).
264 new_file_id = set(t for t in self._new_id
265 if self._new_id[t] != self.tree_file_id(t))
266 for id_set in [self._new_name, self._new_parent, new_file_id,
267 self._new_executability]:
268 changed_ids.update(id_set)
269 # removing implies a kind change
270 changed_kind = set(self._removed_contents)
271 # so does adding
272 changed_kind.intersection_update(self._new_contents)
273 # Ignore entries that are already known to have changed.
274 changed_kind.difference_update(changed_ids)
275 # to keep only the truly changed ones
276 changed_kind = (t for t in changed_kind
277 if self.tree_kind(t) != self.final_kind(t))
278 # all kind changes will alter the inventory
279 changed_ids.update(changed_kind)
280 # To find entries with changed parent_ids, find parents which existed,
281 # but changed file_id.
282 # Now add all their children to the set.
283 for parent_trans_id in new_file_id:
284 changed_ids.update(self.iter_tree_children(parent_trans_id))
285 return sorted(FinalPaths(self).get_paths(changed_ids))
286
287 def _generate_inventory_delta(self):
288 """Generate an inventory delta for the current transform."""
289 inventory_delta = []
290 new_paths = self._inventory_altered()
291 total_entries = len(new_paths) + len(self._removed_id)
292 with ui.ui_factory.nested_progress_bar() as child_pb:
293 for num, trans_id in enumerate(self._removed_id):
294 if (num % 10) == 0:
295 child_pb.update(gettext('removing file'),
296 num, total_entries)
297 if trans_id == self._new_root:
298 file_id = self._tree.path2id('')
299 else:
300 file_id = self.tree_file_id(trans_id)
301 # File-id isn't really being deleted, just moved
302 if file_id in self._r_new_id:
303 continue
304 path = self._tree_id_paths[trans_id]
305 inventory_delta.append((path, None, file_id, None))
306 new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
307 new_paths)
308 for num, (path, trans_id) in enumerate(new_paths):
309 if (num % 10) == 0:
310 child_pb.update(gettext('adding file'),
311 num + len(self._removed_id), total_entries)
312 file_id = new_path_file_ids[trans_id]
313 if file_id is None:
314 continue
315 kind = self.final_kind(trans_id)
316 if kind is None:
317 kind = self._tree.stored_kind(self._tree.id2path(file_id))
318 parent_trans_id = self.final_parent(trans_id)
319 parent_file_id = new_path_file_ids.get(parent_trans_id)
320 if parent_file_id is None:
321 parent_file_id = self.final_file_id(parent_trans_id)
322 if trans_id in self._new_reference_revision:
323 new_entry = inventory.TreeReference(
324 file_id,
325 self._new_name[trans_id],
326 self.final_file_id(self._new_parent[trans_id]),
327 None, self._new_reference_revision[trans_id])
328 else:
329 new_entry = inventory.make_entry(kind,
330 self.final_name(trans_id),
331 parent_file_id, file_id)
332 try:
333 old_path = self._tree.id2path(new_entry.file_id)
334 except errors.NoSuchId:
335 old_path = None
336 new_executability = self._new_executability.get(trans_id)
337 if new_executability is not None:
338 new_entry.executable = new_executability
339 inventory_delta.append(
340 (old_path, path, new_entry.file_id, new_entry))
341 return inventory_delta
342
343
344class TransformPreview(InventoryTreeTransform):
345 """A TreeTransform for generating preview trees.
346
347 Unlike TreeTransform, this version works when the input tree is a
348 RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
349 unversioned files in the input tree.
350 """
351
352 def __init__(self, tree, pb=None, case_sensitive=True):
353 tree.lock_read()
354 limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
355 DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
356
357 def canonical_path(self, path):
358 return path
359
360 def tree_kind(self, trans_id):
361 path = self._tree_id_paths.get(trans_id)
362 if path is None:
363 return None
364 kind = self._tree.path_content_summary(path)[0]
365 if kind == 'missing':
366 kind = None
367 return kind
368
369 def _set_mode(self, trans_id, mode_id, typefunc):
370 """Set the mode of new file contents.
371 The mode_id is the existing file to get the mode from (often the same
372 as trans_id). The operation is only performed if there's a mode match
373 according to typefunc.
374 """
375 # is it ok to ignore this? probably
376 pass
377
378 def iter_tree_children(self, parent_id):
379 """Iterate through the entry's tree children, if any"""
380 try:
381 path = self._tree_id_paths[parent_id]
382 except KeyError:
383 return
384 try:
385 entry = next(self._tree.iter_entries_by_dir(
386 specific_files=[path]))[1]
387 except StopIteration:
388 return
389 children = getattr(entry, 'children', {})
390 for child in children:
391 childpath = joinpath(path, child)
392 yield self.trans_id_tree_path(childpath)
393
394 def new_orphan(self, trans_id, parent_id):
395 raise NotImplementedError(self.new_orphan)
396
397
398class _PreviewTree(inventorytree.InventoryTree):
399 """Partial implementation of Tree to support show_diff_trees"""
400
401 def __init__(self, transform):
402 self._transform = transform
403 self._final_paths = FinalPaths(transform)
404 self.__by_parent = None
405 self._parent_ids = []
406 self._all_children_cache = {}
407 self._path2trans_id_cache = {}
408 self._final_name_cache = {}
409 self._iter_changes_cache = {
410 c.file_id: c for c in self._transform.iter_changes()}
411
412 def supports_tree_reference(self):
413 # TODO(jelmer): Support tree references in _PreviewTree.
414 # return self._transform._tree.supports_tree_reference()
415 return False
416
417 def _content_change(self, file_id):
418 """Return True if the content of this file changed"""
419 changes = self._iter_changes_cache.get(file_id)
420 return (changes is not None and changes.changed_content)
421
422 def _get_repository(self):
423 repo = getattr(self._transform._tree, '_repository', None)
424 if repo is None:
425 repo = self._transform._tree.branch.repository
426 return repo
427
428 def _iter_parent_trees(self):
429 for revision_id in self.get_parent_ids():
430 try:
431 yield self.revision_tree(revision_id)
432 except errors.NoSuchRevisionInTree:
433 yield self._get_repository().revision_tree(revision_id)
434
435 def _get_file_revision(self, path, file_id, vf, tree_revision):
436 parent_keys = [
437 (file_id, t.get_file_revision(t.id2path(file_id)))
438 for t in self._iter_parent_trees()]
439 vf.add_lines((file_id, tree_revision), parent_keys,
440 self.get_file_lines(path))
441 repo = self._get_repository()
442 base_vf = repo.texts
443 if base_vf not in vf.fallback_versionedfiles:
444 vf.fallback_versionedfiles.append(base_vf)
445 return tree_revision
446
447 def _stat_limbo_file(self, trans_id):
448 name = self._transform._limbo_name(trans_id)
449 return os.lstat(name)
450
451 @property
452 def _by_parent(self):
453 if self.__by_parent is None:
454 self.__by_parent = self._transform.by_parent()
455 return self.__by_parent
456
457 def _comparison_data(self, entry, path):
458 kind, size, executable, link_or_sha1 = self.path_content_summary(path)
459 if kind == 'missing':
460 kind = None
461 executable = False
462 else:
463 file_id = self._transform.final_file_id(self._path2trans_id(path))
464 executable = self.is_executable(path)
465 return kind, executable, None
466
467 def is_locked(self):
468 return False
469
470 def lock_read(self):
471 # Perhaps in theory, this should lock the TreeTransform?
472 return lock.LogicalLockResult(self.unlock)
473
474 def unlock(self):
475 pass
476
477 @property
478 def root_inventory(self):
479 """This Tree does not use inventory as its backing data."""
480 raise NotImplementedError(_PreviewTree.root_inventory)
481
482 def all_file_ids(self):
483 tree_ids = set(self._transform._tree.all_file_ids())
484 tree_ids.difference_update(self._transform.tree_file_id(t)
485 for t in self._transform._removed_id)
486 tree_ids.update(viewvalues(self._transform._new_id))
487 return tree_ids
488
489 def all_versioned_paths(self):
490 tree_paths = set(self._transform._tree.all_versioned_paths())
491
492 tree_paths.difference_update(
493 self._transform.trans_id_tree_path(t)
494 for t in self._transform._removed_id)
495
496 tree_paths.update(
497 self._final_paths._determine_path(t)
498 for t in self._transform._new_id)
499
500 return tree_paths
501
502 def _path2trans_id(self, path):
503 # We must not use None here, because that is a valid value to store.
504 trans_id = self._path2trans_id_cache.get(path, object)
505 if trans_id is not object:
506 return trans_id
507 segments = osutils.splitpath(path)
508 cur_parent = self._transform.root
509 for cur_segment in segments:
510 for child in self._all_children(cur_parent):
511 final_name = self._final_name_cache.get(child)
512 if final_name is None:
513 final_name = self._transform.final_name(child)
514 self._final_name_cache[child] = final_name
515 if final_name == cur_segment:
516 cur_parent = child
517 break
518 else:
519 self._path2trans_id_cache[path] = None
520 return None
521 self._path2trans_id_cache[path] = cur_parent
522 return cur_parent
523
524 def path2id(self, path):
525 if isinstance(path, list):
526 if path == []:
527 path = [""]
528 path = osutils.pathjoin(*path)
529 return self._transform.final_file_id(self._path2trans_id(path))
530
531 def id2path(self, file_id, recurse='down'):
532 trans_id = self._transform.trans_id_file_id(file_id)
533 try:
534 return self._final_paths._determine_path(trans_id)
535 except NoFinalPath:
536 raise errors.NoSuchId(self, file_id)
537
538 def _all_children(self, trans_id):
539 children = self._all_children_cache.get(trans_id)
540 if children is not None:
541 return children
542 children = set(self._transform.iter_tree_children(trans_id))
543 # children in the _new_parent set are provided by _by_parent.
544 children.difference_update(self._transform._new_parent)
545 children.update(self._by_parent.get(trans_id, []))
546 self._all_children_cache[trans_id] = children
547 return children
548
549 def extras(self):
550 possible_extras = set(self._transform.trans_id_tree_path(p) for p
551 in self._transform._tree.extras())
552 possible_extras.update(self._transform._new_contents)
553 possible_extras.update(self._transform._removed_id)
554 for trans_id in possible_extras:
555 if self._transform.final_file_id(trans_id) is None:
556 yield self._final_paths._determine_path(trans_id)
557
558 def _make_inv_entries(self, ordered_entries, specific_files=None):
559 for trans_id, parent_file_id in ordered_entries:
560 file_id = self._transform.final_file_id(trans_id)
561 if file_id is None:
562 continue
563 if (specific_files is not None
564 and self._final_paths.get_path(trans_id) not in specific_files):
565 continue
566 kind = self._transform.final_kind(trans_id)
567 if kind is None:
568 kind = self._transform._tree.stored_kind(
569 self._transform._tree.id2path(file_id))
570 new_entry = inventory.make_entry(
571 kind,
572 self._transform.final_name(trans_id),
573 parent_file_id, file_id)
574 yield new_entry, trans_id
575
576 def _list_files_by_dir(self):
577 todo = [ROOT_PARENT]
578 ordered_ids = []
579 while len(todo) > 0:
580 parent = todo.pop()
581 parent_file_id = self._transform.final_file_id(parent)
582 children = list(self._all_children(parent))
583 paths = dict(zip(children, self._final_paths.get_paths(children)))
584 children.sort(key=paths.get)
585 todo.extend(reversed(children))
586 for trans_id in children:
587 ordered_ids.append((trans_id, parent_file_id))
588 return ordered_ids
589
590 def iter_child_entries(self, path):
591 trans_id = self._path2trans_id(path)
592 if trans_id is None:
593 raise errors.NoSuchFile(path)
594 todo = [(child_trans_id, trans_id) for child_trans_id in
595 self._all_children(trans_id)]
596 for entry, trans_id in self._make_inv_entries(todo):
597 yield entry
598
599 def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
600 if recurse_nested:
601 raise NotImplementedError(
602 'follow tree references not yet supported')
603
604 # This may not be a maximally efficient implementation, but it is
605 # reasonably straightforward. An implementation that grafts the
606 # TreeTransform changes onto the tree's iter_entries_by_dir results
607 # might be more efficient, but requires tricky inferences about stack
608 # position.
609 ordered_ids = self._list_files_by_dir()
610 for entry, trans_id in self._make_inv_entries(ordered_ids,
611 specific_files):
612 yield self._final_paths.get_path(trans_id), entry
613
614 def _iter_entries_for_dir(self, dir_path):
615 """Return path, entry for items in a directory without recursing down."""
616 ordered_ids = []
617 dir_trans_id = self._path2trans_id(dir_path)
618 dir_id = self._transform.final_file_id(dir_trans_id)
619 for child_trans_id in self._all_children(dir_trans_id):
620 ordered_ids.append((child_trans_id, dir_id))
621 path_entries = []
622 for entry, trans_id in self._make_inv_entries(ordered_ids):
623 path_entries.append((self._final_paths.get_path(trans_id), entry))
624 path_entries.sort()
625 return path_entries
626
627 def list_files(self, include_root=False, from_dir=None, recursive=True,
628 recurse_nested=False):
629 """See WorkingTree.list_files."""
630 if recurse_nested:
631 raise NotImplementedError(
632 'follow tree references not yet supported')
633
634 # XXX This should behave like WorkingTree.list_files, but is really
635 # more like RevisionTree.list_files.
636 if from_dir == '.':
637 from_dir = None
638 if recursive:
639 prefix = None
640 if from_dir:
641 prefix = from_dir + '/'
642 entries = self.iter_entries_by_dir()
643 for path, entry in entries:
644 if entry.name == '' and not include_root:
645 continue
646 if prefix:
647 if not path.startswith(prefix):
648 continue
649 path = path[len(prefix):]
650 yield path, 'V', entry.kind, entry
651 else:
652 if from_dir is None and include_root is True:
653 root_entry = inventory.make_entry(
654 'directory', '', ROOT_PARENT, self.path2id(''))
655 yield '', 'V', 'directory', root_entry
656 entries = self._iter_entries_for_dir(from_dir or '')
657 for path, entry in entries:
658 yield path, 'V', entry.kind, entry
659
660 def kind(self, path):
661 trans_id = self._path2trans_id(path)
662 if trans_id is None:
663 raise errors.NoSuchFile(path)
664 return self._transform.final_kind(trans_id)
665
666 def stored_kind(self, path):
667 trans_id = self._path2trans_id(path)
668 if trans_id is None:
669 raise errors.NoSuchFile(path)
670 try:
671 return self._transform._new_contents[trans_id]
672 except KeyError:
673 return self._transform._tree.stored_kind(path)
674
675 def get_file_mtime(self, path):
676 """See Tree.get_file_mtime"""
677 file_id = self.path2id(path)
678 if file_id is None:
679 raise errors.NoSuchFile(path)
680 if not self._content_change(file_id):
681 return self._transform._tree.get_file_mtime(
682 self._transform._tree.id2path(file_id))
683 trans_id = self._path2trans_id(path)
684 return self._stat_limbo_file(trans_id).st_mtime
685
686 def get_file_size(self, path):
687 """See Tree.get_file_size"""
688 trans_id = self._path2trans_id(path)
689 if trans_id is None:
690 raise errors.NoSuchFile(path)
691 kind = self._transform.final_kind(trans_id)
692 if kind != 'file':
693 return None
694 if trans_id in self._transform._new_contents:
695 return self._stat_limbo_file(trans_id).st_size
696 if self.kind(path) == 'file':
697 return self._transform._tree.get_file_size(path)
698 else:
699 return None
700
701 def get_file_verifier(self, path, stat_value=None):
702 trans_id = self._path2trans_id(path)
703 if trans_id is None:
704 raise errors.NoSuchFile(path)
705 kind = self._transform._new_contents.get(trans_id)
706 if kind is None:
707 return self._transform._tree.get_file_verifier(path)
708 if kind == 'file':
709 with self.get_file(path) as fileobj:
710 return ("SHA1", osutils.sha_file(fileobj))
711
712 def get_file_sha1(self, path, stat_value=None):
713 trans_id = self._path2trans_id(path)
714 if trans_id is None:
715 raise errors.NoSuchFile(path)
716 kind = self._transform._new_contents.get(trans_id)
717 if kind is None:
718 return self._transform._tree.get_file_sha1(path)
719 if kind == 'file':
720 with self.get_file(path) as fileobj:
721 return osutils.sha_file(fileobj)
722
723 def get_reference_revision(self, path):
724 trans_id = self._path2trans_id(path)
725 if trans_id is None:
726 raise errors.NoSuchFile(path)
727 reference_revision = self._transform._new_reference_revision.get(trans_id)
728 if reference_revision is None:
729 return self._transform._tree.get_reference_revision(path)
730 return reference_revision
731
732 def is_executable(self, path):
733 trans_id = self._path2trans_id(path)
734 if trans_id is None:
735 return False
736 try:
737 return self._transform._new_executability[trans_id]
738 except KeyError:
739 try:
740 return self._transform._tree.is_executable(path)
741 except OSError as e:
742 if e.errno == errno.ENOENT:
743 return False
744 raise
745 except errors.NoSuchFile:
746 return False
747
748 def has_filename(self, path):
749 trans_id = self._path2trans_id(path)
750 if trans_id in self._transform._new_contents:
751 return True
752 elif trans_id in self._transform._removed_contents:
753 return False
754 else:
755 return self._transform._tree.has_filename(path)
756
757 def path_content_summary(self, path):
758 trans_id = self._path2trans_id(path)
759 tt = self._transform
760 tree_path = tt._tree_id_paths.get(trans_id)
761 kind = tt._new_contents.get(trans_id)
762 if kind is None:
763 if tree_path is None or trans_id in tt._removed_contents:
764 return 'missing', None, None, None
765 summary = tt._tree.path_content_summary(tree_path)
766 kind, size, executable, link_or_sha1 = summary
767 else:
768 link_or_sha1 = None
769 limbo_name = tt._limbo_name(trans_id)
770 if trans_id in tt._new_reference_revision:
771 kind = 'tree-reference'
772 if kind == 'file':
773 statval = os.lstat(limbo_name)
774 size = statval.st_size
775 if not tt._limbo_supports_executable():
776 executable = False
777 else:
778 executable = statval.st_mode & S_IEXEC
779 else:
780 size = None
781 executable = None
782 if kind == 'symlink':
783 link_or_sha1 = os.readlink(limbo_name)
784 if not isinstance(link_or_sha1, text_type):
785 link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
786 executable = tt._new_executability.get(trans_id, executable)
787 return kind, size, executable, link_or_sha1
788
789 def iter_changes(self, from_tree, include_unchanged=False,
790 specific_files=None, pb=None, extra_trees=None,
791 require_versioned=True, want_unversioned=False):
792 """See InterTree.iter_changes.
793
794 This has a fast path that is only used when the from_tree matches
795 the transform tree, and no fancy options are supplied.
796 """
797 if (from_tree is not self._transform._tree or include_unchanged
798 or specific_files or want_unversioned):
799 return tree.InterTree.get(from_tree, self).iter_changes(
800 include_unchanged=include_unchanged,
801 specific_files=specific_files,
802 pb=pb,
803 extra_trees=extra_trees,
804 require_versioned=require_versioned,
805 want_unversioned=want_unversioned)
806 if want_unversioned:
807 raise ValueError('want_unversioned is not supported')
808 return self._transform.iter_changes()
809
810 def get_file(self, path):
811 """See Tree.get_file"""
812 file_id = self.path2id(path)
813 if not self._content_change(file_id):
814 return self._transform._tree.get_file(path)
815 trans_id = self._path2trans_id(path)
816 name = self._transform._limbo_name(trans_id)
817 return open(name, 'rb')
818
819 def get_file_with_stat(self, path):
820 return self.get_file(path), None
821
822 def annotate_iter(self, path,
823 default_revision=_mod_revision.CURRENT_REVISION):
824 file_id = self.path2id(path)
825 changes = self._iter_changes_cache.get(file_id)
826 if changes is None:
827 get_old = True
828 else:
829 changed_content, versioned, kind = (
830 changes.changed_content, changes.versioned, changes.kind)
831 if kind[1] is None:
832 return None
833 get_old = (kind[0] == 'file' and versioned[0])
834 if get_old:
835 old_annotation = self._transform._tree.annotate_iter(
836 path, default_revision=default_revision)
837 else:
838 old_annotation = []
839 if changes is None:
840 return old_annotation
841 if not changed_content:
842 return old_annotation
843 # TODO: This is doing something similar to what WT.annotate_iter is
844 # doing, however it fails slightly because it doesn't know what
845 # the *other* revision_id is, so it doesn't know how to give the
846 # other as the origin for some lines, they all get
847 # 'default_revision'
848 # It would be nice to be able to use the new Annotator based
849 # approach, as well.
850 return annotate.reannotate([old_annotation],
851 self.get_file(path).readlines(),
852 default_revision)
853
854 def get_symlink_target(self, path):
855 """See Tree.get_symlink_target"""
856 file_id = self.path2id(path)
857 if not self._content_change(file_id):
858 return self._transform._tree.get_symlink_target(path)
859 trans_id = self._path2trans_id(path)
860 name = self._transform._limbo_name(trans_id)
861 return osutils.readlink(name)
862
863 def walkdirs(self, prefix=''):
864 pending = [self._transform.root]
865 while len(pending) > 0:
866 parent_id = pending.pop()
867 children = []
868 subdirs = []
869 prefix = prefix.rstrip('/')
870 parent_path = self._final_paths.get_path(parent_id)
871 parent_file_id = self._transform.final_file_id(parent_id)
872 for child_id in self._all_children(parent_id):
873 path_from_root = self._final_paths.get_path(child_id)
874 basename = self._transform.final_name(child_id)
875 file_id = self._transform.final_file_id(child_id)
876 kind = self._transform.final_kind(child_id)
877 if kind is not None:
878 versioned_kind = kind
879 else:
880 kind = 'unknown'
881 versioned_kind = self._transform._tree.stored_kind(
882 self._transform._tree.id2path(file_id))
883 if versioned_kind == 'directory':
884 subdirs.append(child_id)
885 children.append((path_from_root, basename, kind, None,
886 file_id, versioned_kind))
887 children.sort()
888 if parent_path.startswith(prefix):
889 yield (parent_path, parent_file_id), children
890 pending.extend(sorted(subdirs, key=self._final_paths.get_path,
891 reverse=True))
892
893 def get_parent_ids(self):
894 return self._parent_ids
895
896 def set_parent_ids(self, parent_ids):
897 self._parent_ids = parent_ids
898
899 def get_revision_tree(self, revision_id):
900 return self._transform._tree.get_revision_tree(revision_id)
901
902
903
0904
=== modified file 'breezy/bzr/workingtree.py'
--- breezy/bzr/workingtree.py 2020-07-04 00:38:37 +0000
+++ breezy/bzr/workingtree.py 2020-07-05 14:44:34 +0000
@@ -175,8 +175,8 @@
175 self.case_sensitive = False175 self.case_sensitive = False
176176
177 def transform(self, pb=None):177 def transform(self, pb=None):
178 from ..transform import TreeTransform178 from .transform import InventoryTreeTransform
179 return TreeTransform(self, pb=pb)179 return InventoryTreeTransform(self, pb=pb)
180180
181 def _setup_directory_is_tree_reference(self):181 def _setup_directory_is_tree_reference(self):
182 if self._branch.repository._format.supports_tree_reference:182 if self._branch.repository._format.supports_tree_reference:
183183
=== modified file 'breezy/errors.py'
--- breezy/errors.py 2020-06-25 21:17:44 +0000
+++ breezy/errors.py 2020-07-05 14:44:34 +0000
@@ -1508,22 +1508,6 @@
1508 _fmt = "Parameter %(param)s is neither unicode nor utf8."1508 _fmt = "Parameter %(param)s is neither unicode nor utf8."
15091509
15101510
1511class CantMoveRoot(BzrError):
1512
1513 _fmt = "Moving the root directory is not supported at this time"
1514
1515
1516class TransformRenameFailed(BzrError):
1517
1518 _fmt = "Failed to rename %(from_path)s to %(to_path)s: %(why)s"
1519
1520 def __init__(self, from_path, to_path, why, errno):
1521 self.from_path = from_path
1522 self.to_path = to_path
1523 self.why = why
1524 self.errno = errno
1525
1526
1527class BzrMoveFailedError(BzrError):1511class BzrMoveFailedError(BzrError):
15281512
1529 _fmt = ("Could not move %(from_path)s%(operator)s %(to_path)s"1513 _fmt = ("Could not move %(from_path)s%(operator)s %(to_path)s"
15301514
=== added file 'breezy/git/transform.py'
--- breezy/git/transform.py 1970-01-01 00:00:00 +0000
+++ breezy/git/transform.py 2020-07-05 14:44:34 +0000
@@ -0,0 +1,275 @@
1# Copyright (C) 2006-2011 Canonical Ltd
2# Copyright (C) 2020 Breezy Developers
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18from __future__ import absolute_import
19
20import errno
21import os
22
23from .. import errors, ui
24from ..i18n import gettext
25from ..mutabletree import MutableTree
26from ..sixish import viewitems
27from ..transform import (
28 TreeTransform,
29 _TransformResults,
30 _FileMover,
31 FinalPaths,
32 unique_add,
33 TransformRenameFailed,
34 )
35
36from ..bzr import inventory
37from ..bzr.transform import TransformPreview as GitTransformPreview
38
39
40class GitTreeTransform(TreeTransform):
41 """Tree transform for Bazaar trees."""
42
43 def version_file(self, trans_id, file_id=None):
44 """Schedule a file to become versioned."""
45 if file_id is None:
46 raise ValueError()
47 unique_add(self._new_id, trans_id, file_id)
48 unique_add(self._r_new_id, file_id, trans_id)
49
50 def cancel_versioning(self, trans_id):
51 """Undo a previous versioning of a file"""
52 file_id = self._new_id[trans_id]
53 del self._new_id[trans_id]
54 del self._r_new_id[file_id]
55
56 def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
57 """Apply all changes to the inventory and filesystem.
58
59 If filesystem or inventory conflicts are present, MalformedTransform
60 will be thrown.
61
62 If apply succeeds, finalize is not necessary.
63
64 :param no_conflicts: if True, the caller guarantees there are no
65 conflicts, so no check is made.
66 :param precomputed_delta: An inventory delta to use instead of
67 calculating one.
68 :param _mover: Supply an alternate FileMover, for testing
69 """
70 for hook in MutableTree.hooks['pre_transform']:
71 hook(self._tree, self)
72 if not no_conflicts:
73 self._check_malformed()
74 with ui.ui_factory.nested_progress_bar() as child_pb:
75 if precomputed_delta is None:
76 child_pb.update(gettext('Apply phase'), 0, 2)
77 changes = self._generate_transform_changes()
78 offset = 1
79 else:
80 changes = [
81 (op, np, ie) for (op, np, fid, ie) in precomputed_delta]
82 offset = 0
83 if _mover is None:
84 mover = _FileMover()
85 else:
86 mover = _mover
87 try:
88 child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
89 self._apply_removals(mover)
90 child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
91 modified_paths = self._apply_insertions(mover)
92 except BaseException:
93 mover.rollback()
94 raise
95 else:
96 mover.apply_deletions()
97 if self.final_file_id(self.root) is None:
98 changes = [e for e in changes if e[0] != '']
99 self._tree._apply_transform_delta(changes)
100 self._done = True
101 self.finalize()
102 return _TransformResults(modified_paths, self.rename_count)
103
104 def _apply_removals(self, mover):
105 """Perform tree operations that remove directory/inventory names.
106
107 That is, delete files that are to be deleted, and put any files that
108 need renaming into limbo. This must be done in strict child-to-parent
109 order.
110
111 If inventory_delta is None, no inventory delta generation is performed.
112 """
113 tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
114 with ui.ui_factory.nested_progress_bar() as child_pb:
115 for num, (path, trans_id) in enumerate(tree_paths):
116 # do not attempt to move root into a subdirectory of itself.
117 if path == '':
118 continue
119 child_pb.update(gettext('removing file'), num, len(tree_paths))
120 full_path = self._tree.abspath(path)
121 if trans_id in self._removed_contents:
122 delete_path = os.path.join(self._deletiondir, trans_id)
123 mover.pre_delete(full_path, delete_path)
124 elif (trans_id in self._new_name or
125 trans_id in self._new_parent):
126 try:
127 mover.rename(full_path, self._limbo_name(trans_id))
128 except TransformRenameFailed as e:
129 if e.errno != errno.ENOENT:
130 raise
131 else:
132 self.rename_count += 1
133
134 def _apply_insertions(self, mover):
135 """Perform tree operations that insert directory/inventory names.
136
137 That is, create any files that need to be created, and restore from
138 limbo any files that needed renaming. This must be done in strict
139 parent-to-child order.
140
141 If inventory_delta is None, no inventory delta is calculated, and
142 no list of modified paths is returned.
143 """
144 new_paths = self.new_paths(filesystem_only=True)
145 modified_paths = []
146 with ui.ui_factory.nested_progress_bar() as child_pb:
147 for num, (path, trans_id) in enumerate(new_paths):
148 if (num % 10) == 0:
149 child_pb.update(gettext('adding file'),
150 num, len(new_paths))
151 full_path = self._tree.abspath(path)
152 if trans_id in self._needs_rename:
153 try:
154 mover.rename(self._limbo_name(trans_id), full_path)
155 except TransformRenameFailed as e:
156 # We may be renaming a dangling inventory id
157 if e.errno != errno.ENOENT:
158 raise
159 else:
160 self.rename_count += 1
161 # TODO: if trans_id in self._observed_sha1s, we should
162 # re-stat the final target, since ctime will be
163 # updated by the change.
164 if (trans_id in self._new_contents
165 or self.path_changed(trans_id)):
166 if trans_id in self._new_contents:
167 modified_paths.append(full_path)
168 if trans_id in self._new_executability:
169 self._set_executability(path, trans_id)
170 if trans_id in self._observed_sha1s:
171 o_sha1, o_st_val = self._observed_sha1s[trans_id]
172 st = osutils.lstat(full_path)
173 self._observed_sha1s[trans_id] = (o_sha1, st)
174 for path, trans_id in new_paths:
175 # new_paths includes stuff like workingtree conflicts. Only the
176 # stuff in new_contents actually comes from limbo.
177 if trans_id in self._limbo_files:
178 del self._limbo_files[trans_id]
179 self._new_contents.clear()
180 return modified_paths
181
182 def _inventory_altered(self):
183 """Determine which trans_ids need new Inventory entries.
184
185 An new entry is needed when anything that would be reflected by an
186 inventory entry changes, including file name, file_id, parent file_id,
187 file kind, and the execute bit.
188
189 Some care is taken to return entries with real changes, not cases
190 where the value is deleted and then restored to its original value,
191 but some actually unchanged values may be returned.
192
193 :returns: A list of (path, trans_id) for all items requiring an
194 inventory change. Ordered by path.
195 """
196 changed_ids = set()
197 # Find entries whose file_ids are new (or changed).
198 new_file_id = set(t for t in self._new_id
199 if self._new_id[t] != self.tree_file_id(t))
200 for id_set in [self._new_name, self._new_parent, new_file_id,
201 self._new_executability]:
202 changed_ids.update(id_set)
203 # removing implies a kind change
204 changed_kind = set(self._removed_contents)
205 # so does adding
206 changed_kind.intersection_update(self._new_contents)
207 # Ignore entries that are already known to have changed.
208 changed_kind.difference_update(changed_ids)
209 # to keep only the truly changed ones
210 changed_kind = (t for t in changed_kind
211 if self.tree_kind(t) != self.final_kind(t))
212 # all kind changes will alter the inventory
213 changed_ids.update(changed_kind)
214 # To find entries with changed parent_ids, find parents which existed,
215 # but changed file_id.
216 # Now add all their children to the set.
217 for parent_trans_id in new_file_id:
218 changed_ids.update(self.iter_tree_children(parent_trans_id))
219 return sorted(FinalPaths(self).get_paths(changed_ids))
220
221 def _generate_transform_changes(self):
222 """Generate an inventory delta for the current transform."""
223 changes = []
224 new_paths = self._inventory_altered()
225 total_entries = len(new_paths) + len(self._removed_id)
226 with ui.ui_factory.nested_progress_bar() as child_pb:
227 for num, trans_id in enumerate(self._removed_id):
228 if (num % 10) == 0:
229 child_pb.update(gettext('removing file'),
230 num, total_entries)
231 if trans_id == self._new_root:
232 file_id = self._tree.path2id('')
233 else:
234 file_id = self.tree_file_id(trans_id)
235 # File-id isn't really being deleted, just moved
236 if file_id in self._r_new_id:
237 continue
238 path = self._tree_id_paths[trans_id]
239 changes.append((path, None, None))
240 new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
241 new_paths)
242 for num, (path, trans_id) in enumerate(new_paths):
243 if (num % 10) == 0:
244 child_pb.update(gettext('adding file'),
245 num + len(self._removed_id), total_entries)
246 file_id = new_path_file_ids[trans_id]
247 if file_id is None:
248 continue
249 kind = self.final_kind(trans_id)
250 if kind is None:
251 kind = self._tree.stored_kind(self._tree.id2path(file_id))
252 parent_trans_id = self.final_parent(trans_id)
253 parent_file_id = new_path_file_ids.get(parent_trans_id)
254 if parent_file_id is None:
255 parent_file_id = self.final_file_id(parent_trans_id)
256 if trans_id in self._new_reference_revision:
257 new_entry = inventory.TreeReference(
258 file_id,
259 self._new_name[trans_id],
260 self.final_file_id(self._new_parent[trans_id]),
261 None, self._new_reference_revision[trans_id])
262 else:
263 new_entry = inventory.make_entry(kind,
264 self.final_name(trans_id),
265 parent_file_id, file_id)
266 try:
267 old_path = self._tree.id2path(new_entry.file_id)
268 except errors.NoSuchId:
269 old_path = None
270 new_executability = self._new_executability.get(trans_id)
271 if new_executability is not None:
272 new_entry.executable = new_executability
273 changes.append(
274 (old_path, path, new_entry))
275 return changes
0276
=== modified file 'breezy/git/tree.py'
--- breezy/git/tree.py 2020-07-04 23:16:47 +0000
+++ breezy/git/tree.py 2020-07-05 14:44:34 +0000
@@ -725,8 +725,8 @@
725 yield (path_decoded, parent_id), children725 yield (path_decoded, parent_id), children
726726
727 def preview_transform(self, pb=None):727 def preview_transform(self, pb=None):
728 from ..transform import TransformPreview728 from .transform import GitTransformPreview
729 return TransformPreview(self, pb=pb)729 return GitTransformPreview(self, pb=pb)
730730
731731
732def tree_delta_from_git_changes(changes, mappings,732def tree_delta_from_git_changes(changes, mappings,
@@ -1635,13 +1635,12 @@
1635 raise NotImplementedError(self._live_entry)1635 raise NotImplementedError(self._live_entry)
16361636
1637 def transform(self, pb=None):1637 def transform(self, pb=None):
1638 from ..transform import TreeTransform1638 from .transform import GitTreeTransform
1639 return TreeTransform(self, pb=pb)1639 return GitTreeTransform(self, pb=pb)
16401640
1641 def preview_transform(self, pb=None):1641 def preview_transform(self, pb=None):
1642 from ..transform import TransformPreview1642 from ..transform import GitTransformPreview
1643 return TransformPreview(self, pb=pb)1643 return GitTransformPreview(self, pb=pb)
1644
16451644
16461645
1647class InterToIndexGitTree(InterGitTrees):1646class InterToIndexGitTree(InterGitTrees):
16481647
=== modified file 'breezy/git/workingtree.py'
--- breezy/git/workingtree.py 2020-07-04 14:58:52 +0000
+++ breezy/git/workingtree.py 2020-07-05 14:44:34 +0000
@@ -224,10 +224,6 @@
224 else:224 else:
225 self.case_sensitive = False225 self.case_sensitive = False
226226
227 def transform(self, pb=None):
228 from ..transform import TreeTransform
229 return TreeTransform(self, pb=pb)
230
231 def merge_modified(self):227 def merge_modified(self):
232 return {}228 return {}
233229
@@ -1075,8 +1071,8 @@
1075 def store_uncommitted(self):1071 def store_uncommitted(self):
1076 raise errors.StoringUncommittedNotSupported(self)1072 raise errors.StoringUncommittedNotSupported(self)
10771073
1078 def apply_inventory_delta(self, changes):1074 def _apply_transform_delta(self, changes):
1079 for (old_path, new_path, file_id, ie) in changes:1075 for (old_path, new_path, ie) in changes:
1080 if old_path is not None:1076 if old_path is not None:
1081 (index, old_subpath) = self._lookup_index(1077 (index, old_subpath) = self._lookup_index(
1082 encode_git_path(old_path))1078 encode_git_path(old_path))
10831079
=== modified file 'breezy/merge.py'
--- breezy/merge.py 2020-07-04 18:40:42 +0000
+++ breezy/merge.py 2020-07-05 14:44:34 +0000
@@ -31,7 +31,6 @@
31 revision as _mod_revision,31 revision as _mod_revision,
32 textfile,32 textfile,
33 trace,33 trace,
34 transform,
35 tree as _mod_tree,34 tree as _mod_tree,
36 tsort,35 tsort,
37 ui,36 ui,
@@ -48,6 +47,7 @@
48 errors,47 errors,
49 hooks,48 hooks,
50 registry,49 registry,
50 transform,
51 )51 )
52from .sixish import (52from .sixish import (
53 viewitems,53 viewitems,
@@ -780,10 +780,10 @@
780780
781 def _compute_transform(self):781 def _compute_transform(self):
782 if self._lca_trees is None:782 if self._lca_trees is None:
783 entries = self._entries3()783 entries = list(self._entries3())
784 resolver = self._three_way784 resolver = self._three_way
785 else:785 else:
786 entries = self._entries_lca()786 entries = list(self._entries_lca())
787 resolver = self._lca_multi_way787 resolver = self._lca_multi_way
788 # Prepare merge hooks788 # Prepare merge hooks
789 factories = Merger.hooks['merge_file_content']789 factories = Merger.hooks['merge_file_content']
@@ -794,7 +794,6 @@
794 for num, (file_id, changed, paths3, parents3, names3,794 for num, (file_id, changed, paths3, parents3, names3,
795 executable3) in enumerate(entries):795 executable3) in enumerate(entries):
796 trans_id = self.tt.trans_id_file_id(file_id)796 trans_id = self.tt.trans_id_file_id(file_id)
797
798 # Try merging each entry797 # Try merging each entry
799 child_pb.update(gettext('Preparing file merge'),798 child_pb.update(gettext('Preparing file merge'),
800 num, len(entries))799 num, len(entries))
@@ -835,7 +834,6 @@
835 other and this. names3 is a tuple of names for base, other and this.834 other and this. names3 is a tuple of names for base, other and this.
836 executable3 is a tuple of execute-bit values for base, other and this.835 executable3 is a tuple of execute-bit values for base, other and this.
837 """836 """
838 result = []
839 iterator = self.other_tree.iter_changes(self.base_tree,837 iterator = self.other_tree.iter_changes(self.base_tree,
840 specific_files=self.interesting_files,838 specific_files=self.interesting_files,
841 extra_trees=[self.this_tree])839 extra_trees=[self.this_tree])
@@ -863,10 +861,9 @@
863 names3 = change.name + (this_name,)861 names3 = change.name + (this_name,)
864 paths3 = change.path + (this_path, )862 paths3 = change.path + (this_path, )
865 executable3 = change.executable + (this_executable,)863 executable3 = change.executable + (this_executable,)
866 result.append(864 yield (
867 (change.file_id, change.changed_content, paths3,865 (change.file_id, change.changed_content, paths3,
868 parents3, names3, executable3))866 parents3, names3, executable3))
869 return result
870867
871 def _entries_lca(self):868 def _entries_lca(self):
872 """Gather data about files modified between multiple trees.869 """Gather data about files modified between multiple trees.
@@ -895,7 +892,6 @@
895 self.interesting_files, lookup_trees)892 self.interesting_files, lookup_trees)
896 else:893 else:
897 interesting_files = None894 interesting_files = None
898 result = []
899 from .multiwalker import MultiWalker895 from .multiwalker import MultiWalker
900 walker = MultiWalker(self.other_tree, self._lca_trees)896 walker = MultiWalker(self.other_tree, self._lca_trees)
901897
@@ -1039,7 +1035,7 @@
1039 raise AssertionError('unhandled kind: %s' % other_ie.kind)1035 raise AssertionError('unhandled kind: %s' % other_ie.kind)
10401036
1041 # If we have gotten this far, that means something has changed1037 # If we have gotten this far, that means something has changed
1042 result.append((file_id, content_changed,1038 yield (file_id, content_changed,
1043 ((base_path, lca_paths),1039 ((base_path, lca_paths),
1044 other_path, this_path),1040 other_path, this_path),
1045 ((base_ie.parent_id, lca_parent_ids),1041 ((base_ie.parent_id, lca_parent_ids),
@@ -1048,8 +1044,7 @@
1048 other_ie.name, this_ie.name),1044 other_ie.name, this_ie.name),
1049 ((base_ie.executable, lca_executable),1045 ((base_ie.executable, lca_executable),
1050 other_ie.executable, this_ie.executable)1046 other_ie.executable, this_ie.executable)
1051 ))1047 )
1052 return result
10531048
1054 def write_modified(self, results):1049 def write_modified(self, results):
1055 if not self.working_tree.supports_merge_modified():1050 if not self.working_tree.supports_merge_modified():
10561051
=== modified file 'breezy/tests/per_tree/test_path_content_summary.py'
--- breezy/tests/per_tree/test_path_content_summary.py 2020-07-04 00:38:37 +0000
+++ breezy/tests/per_tree/test_path_content_summary.py 2020-07-05 14:44:34 +0000
@@ -21,9 +21,11 @@
21from breezy import (21from breezy import (
22 osutils,22 osutils,
23 tests,23 tests,
24 transform,
25 )24 )
2625
26from breezy.bzr.transform import (
27 _PreviewTree,
28 )
27from breezy.tests import (29from breezy.tests import (
28 features,30 features,
29 per_tree,31 per_tree,
@@ -112,7 +114,7 @@
112 self.assertEqual('missing', summary[0])114 self.assertEqual('missing', summary[0])
113 self.assertIs(None, summary[2])115 self.assertIs(None, summary[2])
114 self.assertIs(None, summary[3])116 self.assertIs(None, summary[3])
115 elif isinstance(tree, transform._PreviewTree):117 elif isinstance(tree, _PreviewTree):
116 self.expectFailure('PreviewTree returns "missing" for unversioned'118 self.expectFailure('PreviewTree returns "missing" for unversioned'
117 'files', self.assertEqual, 'file', summary[0])119 'files', self.assertEqual, 'file', summary[0])
118 self.assertEqual('file', summary[0])120 self.assertEqual('file', summary[0])
119121
=== modified file 'breezy/tests/per_tree/test_symlinks.py'
--- breezy/tests/per_tree/test_symlinks.py 2019-06-29 21:31:14 +0000
+++ breezy/tests/per_tree/test_symlinks.py 2020-07-05 14:44:34 +0000
@@ -27,7 +27,7 @@
27 )27 )
28from breezy.mutabletree import MutableTree28from breezy.mutabletree import MutableTree
29from breezy.tests import TestSkipped29from breezy.tests import TestSkipped
30from breezy.transform import _PreviewTree30from breezy.bzr.transform import _PreviewTree
31from breezy.tests import (31from breezy.tests import (
32 features,32 features,
33 )33 )
3434
=== modified file 'breezy/tests/per_workingtree/test_transform.py'
--- breezy/tests/per_workingtree/test_transform.py 2020-07-05 00:03:01 +0000
+++ breezy/tests/per_workingtree/test_transform.py 2020-07-05 14:44:34 +0000
@@ -70,6 +70,7 @@
70 MalformedTransform,70 MalformedTransform,
71 NoFinalPath,71 NoFinalPath,
72 ReusingTransform,72 ReusingTransform,
73 TransformRenameFailed,
73)74)
7475
75from breezy.tests.per_workingtree import TestCaseWithWorkingTree76from breezy.tests.per_workingtree import TestCaseWithWorkingTree
@@ -335,7 +336,7 @@
335 with transform:336 with transform:
336 transform.delete_contents(root)337 transform.delete_contents(root)
337 e = self.assertRaises(AssertionError, self.assertRaises,338 e = self.assertRaises(AssertionError, self.assertRaises,
338 errors.TransformRenameFailed,339 TransformRenameFailed,
339 transform.apply)340 transform.apply)
340 self.assertContainsRe('TransformRenameFailed not raised', str(e))341 self.assertContainsRe('TransformRenameFailed not raised', str(e))
341342
@@ -1032,7 +1033,7 @@
1032 file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')1033 file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1033 dir_id = rename_transform.trans_id_file_id(b'first-id')1034 dir_id = rename_transform.trans_id_file_id(b'first-id')
1034 rename_transform.adjust_path('newname', dir_id, file_trans_id)1035 rename_transform.adjust_path('newname', dir_id, file_trans_id)
1035 e = self.assertRaises(errors.TransformRenameFailed,1036 e = self.assertRaises(TransformRenameFailed,
1036 rename_transform.apply)1037 rename_transform.apply)
1037 # On nix looks like:1038 # On nix looks like:
1038 # "Failed to rename .../work/.bzr/checkout/limbo/new-11039 # "Failed to rename .../work/.bzr/checkout/limbo/new-1
10391040
=== modified file 'breezy/tests/test_errors.py'
--- breezy/tests/test_errors.py 2020-06-25 21:17:44 +0000
+++ breezy/tests/test_errors.py 2020-07-05 14:44:34 +0000
@@ -515,12 +515,6 @@
515 str(e),515 str(e),
516 r'Cannot bind address "example\.com:22":.*Permission denied')516 r'Cannot bind address "example\.com:22":.*Permission denied')
517517
518 def test_transform_rename_failed(self):
519 e = errors.TransformRenameFailed(u"from", u"to", "readonly file", 2)
520 self.assertEqual(
521 u"Failed to rename from to to: readonly file",
522 str(e))
523
524518
525class TestErrorsUsingTransport(tests.TestCaseWithMemoryTransport):519class TestErrorsUsingTransport(tests.TestCaseWithMemoryTransport):
526 """Tests for errors that need to use a branch or repo."""520 """Tests for errors that need to use a branch or repo."""
527521
=== modified file 'breezy/tests/test_transform.py'
--- breezy/tests/test_transform.py 2020-07-05 00:03:01 +0000
+++ breezy/tests/test_transform.py 2020-07-05 14:44:34 +0000
@@ -87,9 +87,11 @@
87 MalformedTransform,87 MalformedTransform,
88 NoFinalPath,88 NoFinalPath,
89 ReusingTransform,89 ReusingTransform,
90 TransformPreview,90 TransformRenameFailed,
91 TreeTransform,
92)91)
92from ..bzr.transform import (
93 InventoryTreeTransform as TreeTransform,
94 )
9395
9496
95class TransformGroup(object):97class TransformGroup(object):
@@ -109,38 +111,6 @@
109 return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)111 return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
110112
111113
112class TestInventoryAltered(tests.TestCaseWithTransport):
113
114 def test_inventory_altered_unchanged(self):
115 tree = self.make_branch_and_tree('tree')
116 self.build_tree(['tree/foo'])
117 tree.add('foo', b'foo-id')
118 with tree.preview_transform() as tt:
119 self.assertEqual([], tt._inventory_altered())
120
121 def test_inventory_altered_changed_parent_id(self):
122 tree = self.make_branch_and_tree('tree')
123 self.build_tree(['tree/foo'])
124 tree.add('foo', b'foo-id')
125 with tree.preview_transform() as tt:
126 tt.unversion_file(tt.root)
127 tt.version_file(tt.root, file_id=b'new-id')
128 foo_trans_id = tt.trans_id_tree_path('foo')
129 foo_tuple = ('foo', foo_trans_id)
130 root_tuple = ('', tt.root)
131 self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
132
133 def test_inventory_altered_noop_changed_parent_id(self):
134 tree = self.make_branch_and_tree('tree')
135 self.build_tree(['tree/foo'])
136 tree.add('foo', b'foo-id')
137 with tree.preview_transform() as tt:
138 tt.unversion_file(tt.root)
139 tt.version_file(tt.root, file_id=tree.path2id(''))
140 tt.trans_id_tree_path('foo')
141 self.assertEqual([], tt._inventory_altered())
142
143
144class TestTransformMerge(TestCaseInTempDir):114class TestTransformMerge(TestCaseInTempDir):
145115
146 def test_text_merge(self):116 def test_text_merge(self):
@@ -1603,3 +1573,12 @@
1603 """If the file to be linked is unmodified, link"""1573 """If the file to be linked is unmodified, link"""
1604 transform.link_tree(self.child_tree, self.parent_tree)1574 transform.link_tree(self.child_tree, self.parent_tree)
1605 self.assertTrue(self.hardlinked())1575 self.assertTrue(self.hardlinked())
1576
1577
1578class ErrorTests(tests.TestCase):
1579
1580 def test_transform_rename_failed(self):
1581 e = TransformRenameFailed(u"from", u"to", "readonly file", 2)
1582 self.assertEqual(
1583 u"Failed to rename from to to: readonly file",
1584 str(e))
16061585
=== modified file 'breezy/transform.py'
--- breezy/transform.py 2020-07-04 19:23:30 +0000
+++ breezy/transform.py 2020-07-05 14:44:34 +0000
@@ -29,29 +29,20 @@
29 osutils,29 osutils,
30 registry,30 registry,
31 trace,31 trace,
32 tree,
33 )32 )
34lazy_import.lazy_import(globals(), """33lazy_import.lazy_import(globals(), """
35from breezy import (34from breezy import (
36 annotate,
37 cleanup,35 cleanup,
38 commit,
39 conflicts,36 conflicts,
40 lock,
41 multiparent,37 multiparent,
42 revision as _mod_revision,38 revision as _mod_revision,
43 ui,39 ui,
44 urlutils,40 urlutils,
45 )41 )
46from breezy.i18n import gettext42from breezy.i18n import gettext
47from breezy.bzr import (
48 inventory,
49 inventorytree,
50 )
51""")43""")
5244
53from .errors import (DuplicateKey,45from .errors import (DuplicateKey,
54 CantMoveRoot,
55 BzrError, InternalBzrError)46 BzrError, InternalBzrError)
56from .filters import filtered_output_bytes, ContentFilterContext47from .filters import filtered_output_bytes, ContentFilterContext
57from .mutabletree import MutableTree48from .mutabletree import MutableTree
@@ -101,6 +92,11 @@
101 _fmt = "Tree transform is malformed %(conflicts)r"92 _fmt = "Tree transform is malformed %(conflicts)r"
10293
10394
95class CantMoveRoot(BzrError):
96
97 _fmt = "Moving the root directory is not supported at this time"
98
99
104class ImmortalLimbo(BzrError):100class ImmortalLimbo(BzrError):
105101
106 _fmt = """Unable to delete transform temporary directory %(limbo_dir)s.102 _fmt = """Unable to delete transform temporary directory %(limbo_dir)s.
@@ -112,6 +108,17 @@
112 self.limbo_dir = limbo_dir108 self.limbo_dir = limbo_dir
113109
114110
111class TransformRenameFailed(BzrError):
112
113 _fmt = "Failed to rename %(from_path)s to %(to_path)s: %(why)s"
114
115 def __init__(self, from_path, to_path, why, errno):
116 self.from_path = from_path
117 self.to_path = to_path
118 self.why = why
119 self.errno = errno
120
121
115def unique_add(map, key, value):122def unique_add(map, key, value):
116 if key in map:123 if key in map:
117 raise DuplicateKey(key=key)124 raise DuplicateKey(key=key)
@@ -410,16 +417,11 @@
410417
411 def version_file(self, trans_id, file_id=None):418 def version_file(self, trans_id, file_id=None):
412 """Schedule a file to become versioned."""419 """Schedule a file to become versioned."""
413 if file_id is None:420 raise NotImplementedError(self.version_file)
414 raise ValueError()
415 unique_add(self._new_id, trans_id, file_id)
416 unique_add(self._r_new_id, file_id, trans_id)
417421
418 def cancel_versioning(self, trans_id):422 def cancel_versioning(self, trans_id):
419 """Undo a previous versioning of a file"""423 """Undo a previous versioning of a file"""
420 file_id = self._new_id[trans_id]424 raise NotImplementedError(self.cancel_versioning)
421 del self._new_id[trans_id]
422 del self._r_new_id[file_id]
423425
424 def new_paths(self, filesystem_only=False):426 def new_paths(self, filesystem_only=False):
425 """Determine the paths of all new and changed files.427 """Determine the paths of all new and changed files.
@@ -442,45 +444,6 @@
442 new_ids.update(id_set)444 new_ids.update(id_set)
443 return sorted(FinalPaths(self).get_paths(new_ids))445 return sorted(FinalPaths(self).get_paths(new_ids))
444446
445 def _inventory_altered(self):
446 """Determine which trans_ids need new Inventory entries.
447
448 An new entry is needed when anything that would be reflected by an
449 inventory entry changes, including file name, file_id, parent file_id,
450 file kind, and the execute bit.
451
452 Some care is taken to return entries with real changes, not cases
453 where the value is deleted and then restored to its original value,
454 but some actually unchanged values may be returned.
455
456 :returns: A list of (path, trans_id) for all items requiring an
457 inventory change. Ordered by path.
458 """
459 changed_ids = set()
460 # Find entries whose file_ids are new (or changed).
461 new_file_id = set(t for t in self._new_id
462 if self._new_id[t] != self.tree_file_id(t))
463 for id_set in [self._new_name, self._new_parent, new_file_id,
464 self._new_executability]:
465 changed_ids.update(id_set)
466 # removing implies a kind change
467 changed_kind = set(self._removed_contents)
468 # so does adding
469 changed_kind.intersection_update(self._new_contents)
470 # Ignore entries that are already known to have changed.
471 changed_kind.difference_update(changed_ids)
472 # to keep only the truly changed ones
473 changed_kind = (t for t in changed_kind
474 if self.tree_kind(t) != self.final_kind(t))
475 # all kind changes will alter the inventory
476 changed_ids.update(changed_kind)
477 # To find entries with changed parent_ids, find parents which existed,
478 # but changed file_id.
479 # Now add all their children to the set.
480 for parent_trans_id in new_file_id:
481 changed_ids.update(self.iter_tree_children(parent_trans_id))
482 return sorted(FinalPaths(self).get_paths(changed_ids))
483
484 def final_kind(self, trans_id):447 def final_kind(self, trans_id):
485 """Determine the final file kind, after any changes applied.448 """Determine the final file kind, after any changes applied.
486449
@@ -591,7 +554,6 @@
591 conflicts.extend(self._unversioned_parents(by_parent))554 conflicts.extend(self._unversioned_parents(by_parent))
592 conflicts.extend(self._parent_loops())555 conflicts.extend(self._parent_loops())
593 conflicts.extend(self._duplicate_entries(by_parent))556 conflicts.extend(self._duplicate_entries(by_parent))
594 conflicts.extend(self._duplicate_ids())
595 conflicts.extend(self._parent_type_conflicts(by_parent))557 conflicts.extend(self._parent_type_conflicts(by_parent))
596 conflicts.extend(self._improper_versioning())558 conflicts.extend(self._improper_versioning())
597 conflicts.extend(self._executability_conflicts())559 conflicts.extend(self._executability_conflicts())
@@ -773,24 +735,6 @@
773 last_trans_id = trans_id735 last_trans_id = trans_id
774 return conflicts736 return conflicts
775737
776 def _duplicate_ids(self):
777 """Each inventory id may only be used once"""
778 conflicts = []
779 try:
780 all_ids = self._tree.all_file_ids()
781 except errors.UnsupportedOperation:
782 # it's okay for non-file-id trees to raise UnsupportedOperation.
783 return []
784 removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
785 self._removed_id))
786 active_tree_ids = all_ids.difference(removed_tree_ids)
787 for trans_id, file_id in viewitems(self._new_id):
788 if file_id in active_tree_ids:
789 path = self._tree.id2path(file_id)
790 old_trans_id = self.trans_id_tree_path(path)
791 conflicts.append(('duplicate id', old_trans_id, trans_id))
792 return conflicts
793
794 def _parent_type_conflicts(self, by_parent):738 def _parent_type_conflicts(self, by_parent):
795 """Children must have a directory parent"""739 """Children must have a directory parent"""
796 conflicts = []740 conflicts = []
@@ -1075,7 +1019,7 @@
1075 The tree is a snapshot, and altering the TreeTransform will invalidate1019 The tree is a snapshot, and altering the TreeTransform will invalidate
1076 it.1020 it.
1077 """1021 """
1078 return _PreviewTree(self)1022 raise NotImplementedError(self.get_preview)
10791023
1080 def commit(self, branch, message, merge_parents=None, strict=False,1024 def commit(self, branch, message, merge_parents=None, strict=False,
1081 timestamp=None, timezone=None, committer=None, authors=None,1025 timestamp=None, timezone=None, committer=None, authors=None,
@@ -1120,6 +1064,7 @@
1120 if self._tree.get_revision_id() != last_rev_id:1064 if self._tree.get_revision_id() != last_rev_id:
1121 raise ValueError('TreeTransform not based on branch basis: %s' %1065 raise ValueError('TreeTransform not based on branch basis: %s' %
1122 self._tree.get_revision_id().decode('utf-8'))1066 self._tree.get_revision_id().decode('utf-8'))
1067 from . import commit
1123 revprops = commit.Commit.update_revprops(revprops, branch, authors)1068 revprops = commit.Commit.update_revprops(revprops, branch, authors)
1124 builder = branch.get_commit_builder(parent_ids,1069 builder = branch.get_commit_builder(parent_ids,
1125 timestamp=timestamp,1070 timestamp=timestamp,
@@ -1836,749 +1781,7 @@
1836 calculating one.1781 calculating one.
1837 :param _mover: Supply an alternate FileMover, for testing1782 :param _mover: Supply an alternate FileMover, for testing
1838 """1783 """
1839 for hook in MutableTree.hooks['pre_transform']:1784 raise NotImplementedError(self.apply)
1840 hook(self._tree, self)
1841 if not no_conflicts:
1842 self._check_malformed()
1843 with ui.ui_factory.nested_progress_bar() as child_pb:
1844 if precomputed_delta is None:
1845 child_pb.update(gettext('Apply phase'), 0, 2)
1846 inventory_delta = self._generate_inventory_delta()
1847 offset = 1
1848 else:
1849 inventory_delta = precomputed_delta
1850 offset = 0
1851 if _mover is None:
1852 mover = _FileMover()
1853 else:
1854 mover = _mover
1855 try:
1856 child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1857 self._apply_removals(mover)
1858 child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1859 modified_paths = self._apply_insertions(mover)
1860 except BaseException:
1861 mover.rollback()
1862 raise
1863 else:
1864 mover.apply_deletions()
1865 if not self.final_is_versioned(self.root):
1866 inventory_delta = [e for e in inventory_delta if e[0] != '']
1867 self._tree.apply_inventory_delta(inventory_delta)
1868 self._apply_observed_sha1s()
1869 self._done = True
1870 self.finalize()
1871 return _TransformResults(modified_paths, self.rename_count)
1872
1873 def _generate_inventory_delta(self):
1874 """Generate an inventory delta for the current transform."""
1875 inventory_delta = []
1876 new_paths = self._inventory_altered()
1877 total_entries = len(new_paths) + len(self._removed_id)
1878 with ui.ui_factory.nested_progress_bar() as child_pb:
1879 for num, trans_id in enumerate(self._removed_id):
1880 if (num % 10) == 0:
1881 child_pb.update(gettext('removing file'),
1882 num, total_entries)
1883 if trans_id == self._new_root:
1884 file_id = self._tree.path2id('')
1885 else:
1886 file_id = self.tree_file_id(trans_id)
1887 # File-id isn't really being deleted, just moved
1888 if file_id in self._r_new_id:
1889 continue
1890 path = self._tree_id_paths[trans_id]
1891 inventory_delta.append((path, None, file_id, None))
1892 new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1893 new_paths)
1894 for num, (path, trans_id) in enumerate(new_paths):
1895 if (num % 10) == 0:
1896 child_pb.update(gettext('adding file'),
1897 num + len(self._removed_id), total_entries)
1898 file_id = new_path_file_ids[trans_id]
1899 if file_id is None:
1900 continue
1901 kind = self.final_kind(trans_id)
1902 if kind is None:
1903 kind = self._tree.stored_kind(self._tree.id2path(file_id))
1904 parent_trans_id = self.final_parent(trans_id)
1905 parent_file_id = new_path_file_ids.get(parent_trans_id)
1906 if parent_file_id is None:
1907 parent_file_id = self.final_file_id(parent_trans_id)
1908 if trans_id in self._new_reference_revision:
1909 new_entry = inventory.TreeReference(
1910 file_id,
1911 self._new_name[trans_id],
1912 self.final_file_id(self._new_parent[trans_id]),
1913 None, self._new_reference_revision[trans_id])
1914 else:
1915 new_entry = inventory.make_entry(kind,
1916 self.final_name(trans_id),
1917 parent_file_id, file_id)
1918 try:
1919 old_path = self._tree.id2path(new_entry.file_id)
1920 except errors.NoSuchId:
1921 old_path = None
1922 new_executability = self._new_executability.get(trans_id)
1923 if new_executability is not None:
1924 new_entry.executable = new_executability
1925 inventory_delta.append(
1926 (old_path, path, new_entry.file_id, new_entry))
1927 return inventory_delta
1928
1929 def _apply_removals(self, mover):
1930 """Perform tree operations that remove directory/inventory names.
1931
1932 That is, delete files that are to be deleted, and put any files that
1933 need renaming into limbo. This must be done in strict child-to-parent
1934 order.
1935
1936 If inventory_delta is None, no inventory delta generation is performed.
1937 """
1938 tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
1939 with ui.ui_factory.nested_progress_bar() as child_pb:
1940 for num, (path, trans_id) in enumerate(tree_paths):
1941 # do not attempt to move root into a subdirectory of itself.
1942 if path == '':
1943 continue
1944 child_pb.update(gettext('removing file'), num, len(tree_paths))
1945 full_path = self._tree.abspath(path)
1946 if trans_id in self._removed_contents:
1947 delete_path = os.path.join(self._deletiondir, trans_id)
1948 mover.pre_delete(full_path, delete_path)
1949 elif (trans_id in self._new_name or
1950 trans_id in self._new_parent):
1951 try:
1952 mover.rename(full_path, self._limbo_name(trans_id))
1953 except errors.TransformRenameFailed as e:
1954 if e.errno != errno.ENOENT:
1955 raise
1956 else:
1957 self.rename_count += 1
1958
1959 def _apply_insertions(self, mover):
1960 """Perform tree operations that insert directory/inventory names.
1961
1962 That is, create any files that need to be created, and restore from
1963 limbo any files that needed renaming. This must be done in strict
1964 parent-to-child order.
1965
1966 If inventory_delta is None, no inventory delta is calculated, and
1967 no list of modified paths is returned.
1968 """
1969 new_paths = self.new_paths(filesystem_only=True)
1970 modified_paths = []
1971 with ui.ui_factory.nested_progress_bar() as child_pb:
1972 for num, (path, trans_id) in enumerate(new_paths):
1973 if (num % 10) == 0:
1974 child_pb.update(gettext('adding file'),
1975 num, len(new_paths))
1976 full_path = self._tree.abspath(path)
1977 if trans_id in self._needs_rename:
1978 try:
1979 mover.rename(self._limbo_name(trans_id), full_path)
1980 except errors.TransformRenameFailed as e:
1981 # We may be renaming a dangling inventory id
1982 if e.errno != errno.ENOENT:
1983 raise
1984 else:
1985 self.rename_count += 1
1986 # TODO: if trans_id in self._observed_sha1s, we should
1987 # re-stat the final target, since ctime will be
1988 # updated by the change.
1989 if (trans_id in self._new_contents
1990 or self.path_changed(trans_id)):
1991 if trans_id in self._new_contents:
1992 modified_paths.append(full_path)
1993 if trans_id in self._new_executability:
1994 self._set_executability(path, trans_id)
1995 if trans_id in self._observed_sha1s:
1996 o_sha1, o_st_val = self._observed_sha1s[trans_id]
1997 st = osutils.lstat(full_path)
1998 self._observed_sha1s[trans_id] = (o_sha1, st)
1999 for path, trans_id in new_paths:
2000 # new_paths includes stuff like workingtree conflicts. Only the
2001 # stuff in new_contents actually comes from limbo.
2002 if trans_id in self._limbo_files:
2003 del self._limbo_files[trans_id]
2004 self._new_contents.clear()
2005 return modified_paths
2006
2007 def _apply_observed_sha1s(self):
2008 """After we have finished renaming everything, update observed sha1s
2009
2010 This has to be done after self._tree.apply_inventory_delta, otherwise
2011 it doesn't know anything about the files we are updating. Also, we want
2012 to do this as late as possible, so that most entries end up cached.
2013 """
2014 # TODO: this doesn't update the stat information for directories. So
2015 # the first 'bzr status' will still need to rewrite
2016 # .bzr/checkout/dirstate. However, we at least don't need to
2017 # re-read all of the files.
2018 # TODO: If the operation took a while, we could do a time.sleep(3) here
2019 # to allow the clock to tick over and ensure we won't have any
2020 # problems. (we could observe start time, and finish time, and if
2021 # it is less than eg 10% overhead, add a sleep call.)
2022 paths = FinalPaths(self)
2023 for trans_id, observed in viewitems(self._observed_sha1s):
2024 path = paths.get_path(trans_id)
2025 self._tree._observed_sha1(path, observed)
2026
2027
2028class TransformPreview(DiskTreeTransform):
2029 """A TreeTransform for generating preview trees.
2030
2031 Unlike TreeTransform, this version works when the input tree is a
2032 RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
2033 unversioned files in the input tree.
2034 """
2035
2036 def __init__(self, tree, pb=None, case_sensitive=True):
2037 tree.lock_read()
2038 limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
2039 DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
2040
2041 def tree_kind(self, trans_id):
2042 path = self._tree_id_paths.get(trans_id)
2043 if path is None:
2044 return None
2045 kind = self._tree.path_content_summary(path)[0]
2046 if kind == 'missing':
2047 kind = None
2048 return kind
2049
2050 def _set_mode(self, trans_id, mode_id, typefunc):
2051 """Set the mode of new file contents.
2052 The mode_id is the existing file to get the mode from (often the same
2053 as trans_id). The operation is only performed if there's a mode match
2054 according to typefunc.
2055 """
2056 # is it ok to ignore this? probably
2057 pass
2058
2059 def iter_tree_children(self, parent_id):
2060 """Iterate through the entry's tree children, if any"""
2061 try:
2062 path = self._tree_id_paths[parent_id]
2063 except KeyError:
2064 return
2065 try:
2066 entry = next(self._tree.iter_entries_by_dir(
2067 specific_files=[path]))[1]
2068 except StopIteration:
2069 return
2070 children = getattr(entry, 'children', {})
2071 for child in children:
2072 childpath = joinpath(path, child)
2073 yield self.trans_id_tree_path(childpath)
2074
2075 def new_orphan(self, trans_id, parent_id):
2076 raise NotImplementedError(self.new_orphan)
2077
2078
2079class _PreviewTree(inventorytree.InventoryTree):
2080 """Partial implementation of Tree to support show_diff_trees"""
2081
2082 def __init__(self, transform):
2083 self._transform = transform
2084 self._final_paths = FinalPaths(transform)
2085 self.__by_parent = None
2086 self._parent_ids = []
2087 self._all_children_cache = {}
2088 self._path2trans_id_cache = {}
2089 self._final_name_cache = {}
2090 self._iter_changes_cache = dict((c.file_id, c) for c in
2091 self._transform.iter_changes())
2092
2093 def supports_tree_reference(self):
2094 # TODO(jelmer): Support tree references in _PreviewTree.
2095 # return self._transform._tree.supports_tree_reference()
2096 return False
2097
2098 def _content_change(self, file_id):
2099 """Return True if the content of this file changed"""
2100 changes = self._iter_changes_cache.get(file_id)
2101 return (changes is not None and changes.changed_content)
2102
2103 def _get_repository(self):
2104 repo = getattr(self._transform._tree, '_repository', None)
2105 if repo is None:
2106 repo = self._transform._tree.branch.repository
2107 return repo
2108
2109 def _iter_parent_trees(self):
2110 for revision_id in self.get_parent_ids():
2111 try:
2112 yield self.revision_tree(revision_id)
2113 except errors.NoSuchRevisionInTree:
2114 yield self._get_repository().revision_tree(revision_id)
2115
2116 def _get_file_revision(self, path, file_id, vf, tree_revision):
2117 parent_keys = [
2118 (file_id, t.get_file_revision(t.id2path(file_id)))
2119 for t in self._iter_parent_trees()]
2120 vf.add_lines((file_id, tree_revision), parent_keys,
2121 self.get_file_lines(path))
2122 repo = self._get_repository()
2123 base_vf = repo.texts
2124 if base_vf not in vf.fallback_versionedfiles:
2125 vf.fallback_versionedfiles.append(base_vf)
2126 return tree_revision
2127
2128 def _stat_limbo_file(self, trans_id):
2129 name = self._transform._limbo_name(trans_id)
2130 return os.lstat(name)
2131
2132 @property
2133 def _by_parent(self):
2134 if self.__by_parent is None:
2135 self.__by_parent = self._transform.by_parent()
2136 return self.__by_parent
2137
2138 def _comparison_data(self, entry, path):
2139 kind, size, executable, link_or_sha1 = self.path_content_summary(path)
2140 if kind == 'missing':
2141 kind = None
2142 executable = False
2143 else:
2144 executable = self.is_executable(path)
2145 return kind, executable, None
2146
2147 def is_locked(self):
2148 return False
2149
2150 def lock_read(self):
2151 # Perhaps in theory, this should lock the TreeTransform?
2152 return lock.LogicalLockResult(self.unlock)
2153
2154 def unlock(self):
2155 pass
2156
2157 @property
2158 def root_inventory(self):
2159 """This Tree does not use inventory as its backing data."""
2160 raise NotImplementedError(_PreviewTree.root_inventory)
2161
2162 def all_file_ids(self):
2163 tree_ids = set(self._transform._tree.all_file_ids())
2164 tree_ids.difference_update(self._transform.tree_file_id(t)
2165 for t in self._transform._removed_id)
2166 tree_ids.update(viewvalues(self._transform._new_id))
2167 return tree_ids
2168
2169 def all_versioned_paths(self):
2170 tree_paths = set(self._transform._tree.all_versioned_paths())
2171
2172 tree_paths.difference_update(
2173 self._transform.trans_id_tree_path(t)
2174 for t in self._transform._removed_id)
2175
2176 tree_paths.update(
2177 self._final_paths._determine_path(t)
2178 for t in self._transform._new_id)
2179
2180 return tree_paths
2181
2182 def _path2trans_id(self, path):
2183 # We must not use None here, because that is a valid value to store.
2184 trans_id = self._path2trans_id_cache.get(path, object)
2185 if trans_id is not object:
2186 return trans_id
2187 segments = splitpath(path)
2188 cur_parent = self._transform.root
2189 for cur_segment in segments:
2190 for child in self._all_children(cur_parent):
2191 final_name = self._final_name_cache.get(child)
2192 if final_name is None:
2193 final_name = self._transform.final_name(child)
2194 self._final_name_cache[child] = final_name
2195 if final_name == cur_segment:
2196 cur_parent = child
2197 break
2198 else:
2199 self._path2trans_id_cache[path] = None
2200 return None
2201 self._path2trans_id_cache[path] = cur_parent
2202 return cur_parent
2203
2204 def path2id(self, path):
2205 if isinstance(path, list):
2206 if path == []:
2207 path = [""]
2208 path = osutils.pathjoin(*path)
2209 return self._transform.final_file_id(self._path2trans_id(path))
2210
2211 def id2path(self, file_id, recurse='down'):
2212 trans_id = self._transform.trans_id_file_id(file_id)
2213 try:
2214 return self._final_paths._determine_path(trans_id)
2215 except NoFinalPath:
2216 raise errors.NoSuchId(self, file_id)
2217
2218 def _all_children(self, trans_id):
2219 children = self._all_children_cache.get(trans_id)
2220 if children is not None:
2221 return children
2222 children = set(self._transform.iter_tree_children(trans_id))
2223 # children in the _new_parent set are provided by _by_parent.
2224 children.difference_update(self._transform._new_parent)
2225 children.update(self._by_parent.get(trans_id, []))
2226 self._all_children_cache[trans_id] = children
2227 return children
2228
2229 def extras(self):
2230 possible_extras = set(self._transform.trans_id_tree_path(p) for p
2231 in self._transform._tree.extras())
2232 possible_extras.update(self._transform._new_contents)
2233 possible_extras.update(self._transform._removed_id)
2234 for trans_id in possible_extras:
2235 if not self._transform.final_is_versioned(trans_id):
2236 yield self._final_paths._determine_path(trans_id)
2237
2238 def _make_inv_entries(self, ordered_entries, specific_files=None):
2239 for trans_id, parent_file_id in ordered_entries:
2240 file_id = self._transform.final_file_id(trans_id)
2241 if file_id is None:
2242 continue
2243 if (specific_files is not None
2244 and self._final_paths.get_path(trans_id) not in specific_files):
2245 continue
2246 kind = self._transform.final_kind(trans_id)
2247 if kind is None:
2248 kind = self._transform._tree.stored_kind(
2249 self._transform._tree.id2path(file_id))
2250 new_entry = inventory.make_entry(
2251 kind,
2252 self._transform.final_name(trans_id),
2253 parent_file_id, file_id)
2254 yield new_entry, trans_id
2255
2256 def _list_files_by_dir(self):
2257 todo = [ROOT_PARENT]
2258 ordered_ids = []
2259 while len(todo) > 0:
2260 parent = todo.pop()
2261 parent_file_id = self._transform.final_file_id(parent)
2262 children = list(self._all_children(parent))
2263 paths = dict(zip(children, self._final_paths.get_paths(children)))
2264 children.sort(key=paths.get)
2265 todo.extend(reversed(children))
2266 for trans_id in children:
2267 ordered_ids.append((trans_id, parent_file_id))
2268 return ordered_ids
2269
2270 def iter_child_entries(self, path):
2271 trans_id = self._path2trans_id(path)
2272 if trans_id is None:
2273 raise errors.NoSuchFile(path)
2274 todo = [(child_trans_id, trans_id) for child_trans_id in
2275 self._all_children(trans_id)]
2276 for entry, trans_id in self._make_inv_entries(todo):
2277 yield entry
2278
2279 def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
2280 if recurse_nested:
2281 raise NotImplementedError(
2282 'follow tree references not yet supported')
2283
2284 # This may not be a maximally efficient implementation, but it is
2285 # reasonably straightforward. An implementation that grafts the
2286 # TreeTransform changes onto the tree's iter_entries_by_dir results
2287 # might be more efficient, but requires tricky inferences about stack
2288 # position.
2289 ordered_ids = self._list_files_by_dir()
2290 for entry, trans_id in self._make_inv_entries(ordered_ids,
2291 specific_files):
2292 yield self._final_paths.get_path(trans_id), entry
2293
2294 def _iter_entries_for_dir(self, dir_path):
2295 """Return path, entry for items in a directory without recursing down."""
2296 ordered_ids = []
2297 dir_trans_id = self._path2trans_id(dir_path)
2298 dir_id = self._transform.final_file_id(dir_trans_id)
2299 for child_trans_id in self._all_children(dir_trans_id):
2300 ordered_ids.append((child_trans_id, dir_id))
2301 path_entries = []
2302 for entry, trans_id in self._make_inv_entries(ordered_ids):
2303 path_entries.append((self._final_paths.get_path(trans_id), entry))
2304 path_entries.sort()
2305 return path_entries
2306
2307 def list_files(self, include_root=False, from_dir=None, recursive=True,
2308 recurse_nested=False):
2309 """See WorkingTree.list_files."""
2310 if recurse_nested:
2311 raise NotImplementedError(
2312 'follow tree references not yet supported')
2313
2314 # XXX This should behave like WorkingTree.list_files, but is really
2315 # more like RevisionTree.list_files.
2316 if from_dir == '.':
2317 from_dir = None
2318 if recursive:
2319 prefix = None
2320 if from_dir:
2321 prefix = from_dir + '/'
2322 entries = self.iter_entries_by_dir()
2323 for path, entry in entries:
2324 if entry.name == '' and not include_root:
2325 continue
2326 if prefix:
2327 if not path.startswith(prefix):
2328 continue
2329 path = path[len(prefix):]
2330 yield path, 'V', entry.kind, entry
2331 else:
2332 if from_dir is None and include_root is True:
2333 root_entry = inventory.make_entry(
2334 'directory', '', ROOT_PARENT, self.path2id(''))
2335 yield '', 'V', 'directory', root_entry
2336 entries = self._iter_entries_for_dir(from_dir or '')
2337 for path, entry in entries:
2338 yield path, 'V', entry.kind, entry
2339
2340 def kind(self, path):
2341 trans_id = self._path2trans_id(path)
2342 if trans_id is None:
2343 raise errors.NoSuchFile(path)
2344 return self._transform.final_kind(trans_id)
2345
2346 def stored_kind(self, path):
2347 trans_id = self._path2trans_id(path)
2348 if trans_id is None:
2349 raise errors.NoSuchFile(path)
2350 try:
2351 return self._transform._new_contents[trans_id]
2352 except KeyError:
2353 return self._transform._tree.stored_kind(path)
2354
2355 def get_file_mtime(self, path):
2356 """See Tree.get_file_mtime"""
2357 file_id = self.path2id(path)
2358 if file_id is None:
2359 raise errors.NoSuchFile(path)
2360 if not self._content_change(file_id):
2361 return self._transform._tree.get_file_mtime(
2362 self._transform._tree.id2path(file_id))
2363 trans_id = self._path2trans_id(path)
2364 return self._stat_limbo_file(trans_id).st_mtime
2365
2366 def get_file_size(self, path):
2367 """See Tree.get_file_size"""
2368 trans_id = self._path2trans_id(path)
2369 if trans_id is None:
2370 raise errors.NoSuchFile(path)
2371 kind = self._transform.final_kind(trans_id)
2372 if kind != 'file':
2373 return None
2374 if trans_id in self._transform._new_contents:
2375 return self._stat_limbo_file(trans_id).st_size
2376 if self.kind(path) == 'file':
2377 return self._transform._tree.get_file_size(path)
2378 else:
2379 return None
2380
2381 def get_file_verifier(self, path, stat_value=None):
2382 trans_id = self._path2trans_id(path)
2383 if trans_id is None:
2384 raise errors.NoSuchFile(path)
2385 kind = self._transform._new_contents.get(trans_id)
2386 if kind is None:
2387 return self._transform._tree.get_file_verifier(path)
2388 if kind == 'file':
2389 with self.get_file(path) as fileobj:
2390 return ("SHA1", sha_file(fileobj))
2391
2392 def get_file_sha1(self, path, stat_value=None):
2393 trans_id = self._path2trans_id(path)
2394 if trans_id is None:
2395 raise errors.NoSuchFile(path)
2396 kind = self._transform._new_contents.get(trans_id)
2397 if kind is None:
2398 return self._transform._tree.get_file_sha1(path)
2399 if kind == 'file':
2400 with self.get_file(path) as fileobj:
2401 return sha_file(fileobj)
2402
2403 def get_reference_revision(self, path):
2404 trans_id = self._path2trans_id(path)
2405 if trans_id is None:
2406 raise errors.NoSuchFile(path)
2407 reference_revision = self._transform._new_reference_revision.get(trans_id)
2408 if reference_revision is None:
2409 return self._transform._tree.get_reference_revision(path)
2410 return reference_revision
2411
2412 def is_executable(self, path):
2413 trans_id = self._path2trans_id(path)
2414 if trans_id is None:
2415 return False
2416 try:
2417 return self._transform._new_executability[trans_id]
2418 except KeyError:
2419 try:
2420 return self._transform._tree.is_executable(path)
2421 except OSError as e:
2422 if e.errno == errno.ENOENT:
2423 return False
2424 raise
2425 except errors.NoSuchFile:
2426 return False
2427
2428 def has_filename(self, path):
2429 trans_id = self._path2trans_id(path)
2430 if trans_id in self._transform._new_contents:
2431 return True
2432 elif trans_id in self._transform._removed_contents:
2433 return False
2434 else:
2435 return self._transform._tree.has_filename(path)
2436
2437 def path_content_summary(self, path):
2438 trans_id = self._path2trans_id(path)
2439 tt = self._transform
2440 tree_path = tt._tree_id_paths.get(trans_id)
2441 kind = tt._new_contents.get(trans_id)
2442 if kind is None:
2443 if tree_path is None or trans_id in tt._removed_contents:
2444 return 'missing', None, None, None
2445 summary = tt._tree.path_content_summary(tree_path)
2446 kind, size, executable, link_or_sha1 = summary
2447 else:
2448 link_or_sha1 = None
2449 limbo_name = tt._limbo_name(trans_id)
2450 if trans_id in tt._new_reference_revision:
2451 kind = 'tree-reference'
2452 if kind == 'file':
2453 statval = os.lstat(limbo_name)
2454 size = statval.st_size
2455 if not tt._limbo_supports_executable():
2456 executable = False
2457 else:
2458 executable = statval.st_mode & S_IEXEC
2459 else:
2460 size = None
2461 executable = None
2462 if kind == 'symlink':
2463 link_or_sha1 = os.readlink(limbo_name)
2464 if not isinstance(link_or_sha1, text_type):
2465 link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
2466 executable = tt._new_executability.get(trans_id, executable)
2467 return kind, size, executable, link_or_sha1
2468
2469 def iter_changes(self, from_tree, include_unchanged=False,
2470 specific_files=None, pb=None, extra_trees=None,
2471 require_versioned=True, want_unversioned=False):
2472 """See InterTree.iter_changes.
2473
2474 This has a fast path that is only used when the from_tree matches
2475 the transform tree, and no fancy options are supplied.
2476 """
2477 if (from_tree is not self._transform._tree or include_unchanged
2478 or specific_files or want_unversioned):
2479 from .bzr.inventorytree import InterInventoryTree
2480 return InterInventoryTree(from_tree, self).iter_changes(
2481 include_unchanged=include_unchanged,
2482 specific_files=specific_files,
2483 pb=pb,
2484 extra_trees=extra_trees,
2485 require_versioned=require_versioned,
2486 want_unversioned=want_unversioned)
2487 if want_unversioned:
2488 raise ValueError('want_unversioned is not supported')
2489 return self._transform.iter_changes()
2490
2491 def get_file(self, path):
2492 """See Tree.get_file"""
2493 file_id = self.path2id(path)
2494 if not self._content_change(file_id):
2495 return self._transform._tree.get_file(path)
2496 trans_id = self._path2trans_id(path)
2497 name = self._transform._limbo_name(trans_id)
2498 return open(name, 'rb')
2499
2500 def get_file_with_stat(self, path):
2501 return self.get_file(path), None
2502
2503 def annotate_iter(self, path,
2504 default_revision=_mod_revision.CURRENT_REVISION):
2505 file_id = self.path2id(path)
2506 changes = self._iter_changes_cache.get(file_id)
2507 if changes is None:
2508 get_old = True
2509 else:
2510 changed_content, versioned, kind = (
2511 changes.changed_content, changes.versioned, changes.kind)
2512 if kind[1] is None:
2513 return None
2514 get_old = (kind[0] == 'file' and versioned[0])
2515 if get_old:
2516 old_annotation = self._transform._tree.annotate_iter(
2517 path, default_revision=default_revision)
2518 else:
2519 old_annotation = []
2520 if changes is None:
2521 return old_annotation
2522 if not changed_content:
2523 return old_annotation
2524 # TODO: This is doing something similar to what WT.annotate_iter is
2525 # doing, however it fails slightly because it doesn't know what
2526 # the *other* revision_id is, so it doesn't know how to give the
2527 # other as the origin for some lines, they all get
2528 # 'default_revision'
2529 # It would be nice to be able to use the new Annotator based
2530 # approach, as well.
2531 return annotate.reannotate([old_annotation],
2532 self.get_file(path).readlines(),
2533 default_revision)
2534
2535 def get_symlink_target(self, path):
2536 """See Tree.get_symlink_target"""
2537 file_id = self.path2id(path)
2538 if not self._content_change(file_id):
2539 return self._transform._tree.get_symlink_target(path)
2540 trans_id = self._path2trans_id(path)
2541 name = self._transform._limbo_name(trans_id)
2542 return osutils.readlink(name)
2543
2544 def walkdirs(self, prefix=''):
2545 pending = [self._transform.root]
2546 while len(pending) > 0:
2547 parent_id = pending.pop()
2548 children = []
2549 subdirs = []
2550 prefix = prefix.rstrip('/')
2551 parent_path = self._final_paths.get_path(parent_id)
2552 parent_file_id = self._transform.final_file_id(parent_id)
2553 for child_id in self._all_children(parent_id):
2554 path_from_root = self._final_paths.get_path(child_id)
2555 basename = self._transform.final_name(child_id)
2556 file_id = self._transform.final_file_id(child_id)
2557 kind = self._transform.final_kind(child_id)
2558 if kind is not None:
2559 versioned_kind = kind
2560 else:
2561 kind = 'unknown'
2562 versioned_kind = self._transform._tree.stored_kind(
2563 self._transform._tree.id2path(file_id))
2564 if versioned_kind == 'directory':
2565 subdirs.append(child_id)
2566 children.append((path_from_root, basename, kind, None,
2567 file_id, versioned_kind))
2568 children.sort()
2569 if parent_path.startswith(prefix):
2570 yield (parent_path, parent_file_id), children
2571 pending.extend(sorted(subdirs, key=self._final_paths.get_path,
2572 reverse=True))
2573
2574 def get_parent_ids(self):
2575 return self._parent_ids
2576
2577 def set_parent_ids(self, parent_ids):
2578 self._parent_ids = parent_ids
2579
2580 def get_revision_tree(self, revision_id):
2581 return self._transform._tree.get_revision_tree(revision_id)
25821785
25831786
2584def joinpath(parent, child):1787def joinpath(parent, child):
@@ -3278,7 +2481,7 @@
3278 raise errors.FileExists(to, str(e))2481 raise errors.FileExists(to, str(e))
3279 # normal OSError doesn't include filenames so it's hard to see where2482 # normal OSError doesn't include filenames so it's hard to see where
3280 # the problem is, see https://bugs.launchpad.net/bzr/+bug/4917632483 # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
3281 raise errors.TransformRenameFailed(from_, to, str(e), e.errno)2484 raise TransformRenameFailed(from_, to, str(e), e.errno)
3282 self.past_renames.append((from_, to))2485 self.past_renames.append((from_, to))
32832486
3284 def pre_delete(self, from_, to):2487 def pre_delete(self, from_, to):
@@ -3297,7 +2500,7 @@
3297 try:2500 try:
3298 os.rename(to, from_)2501 os.rename(to, from_)
3299 except OSError as e:2502 except OSError as e:
3300 raise errors.TransformRenameFailed(to, from_, str(e), e.errno)2503 raise TransformRenameFailed(to, from_, str(e), e.errno)
3301 # after rollback, don't reuse _FileMover2504 # after rollback, don't reuse _FileMover
3302 self.past_renames = None2505 self.past_renames = None
3303 self.pending_deletions = None2506 self.pending_deletions = None

Subscribers

People subscribed via source and target branches