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

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
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
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.
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Revision history for this message
Jelmer Vernooij (jelmer) : Posted in a previous version of this proposal
review: Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :
Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/bzr/inventorytree.py'
2--- breezy/bzr/inventorytree.py 2020-07-04 01:15:59 +0000
3+++ breezy/bzr/inventorytree.py 2020-07-05 14:44:34 +0000
4@@ -325,7 +325,7 @@
5 return last_revision
6
7 def preview_transform(self, pb=None):
8- from ..transform import TransformPreview
9+ from .transform import TransformPreview
10 return TransformPreview(self, pb=pb)
11
12
13@@ -510,8 +510,8 @@
14 self.set_parent_trees([(new_revid, rev_tree)])
15
16 def transform(self, pb=None):
17- from ..transform import TreeTransform
18- return TreeTransform(self, pb=pb)
19+ from .transform import InventoryTreeTransform
20+ return InventoryTreeTransform(self, pb=pb)
21
22
23 class _SmartAddHelper(object):
24
25=== modified file 'breezy/bzr/tests/__init__.py'
26--- breezy/bzr/tests/__init__.py 2020-06-25 21:13:23 +0000
27+++ breezy/bzr/tests/__init__.py 2020-07-05 14:44:34 +0000
28@@ -81,6 +81,7 @@
29 'test_serializer',
30 'test_tag',
31 'test_testament',
32+ 'test_transform',
33 'test_versionedfile',
34 'test_vf_search',
35 'test_vfs_ratchet',
36
37=== added file 'breezy/bzr/tests/test_transform.py'
38--- breezy/bzr/tests/test_transform.py 1970-01-01 00:00:00 +0000
39+++ breezy/bzr/tests/test_transform.py 2020-07-05 14:44:34 +0000
40@@ -0,0 +1,49 @@
41+# Copyright (C) 2006-2012, 2016 Canonical Ltd
42+#
43+# This program is free software; you can redistribute it and/or modify
44+# it under the terms of the GNU General Public License as published by
45+# the Free Software Foundation; either version 2 of the License, or
46+# (at your option) any later version.
47+#
48+# This program is distributed in the hope that it will be useful,
49+# but WITHOUT ANY WARRANTY; without even the implied warranty of
50+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
51+# GNU General Public License for more details.
52+#
53+# You should have received a copy of the GNU General Public License
54+# along with this program; if not, write to the Free Software
55+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
56+
57+from . import TestCaseWithTransport
58+
59+
60+class TestInventoryAltered(TestCaseWithTransport):
61+
62+ def test_inventory_altered_unchanged(self):
63+ tree = self.make_branch_and_tree('tree')
64+ self.build_tree(['tree/foo'])
65+ tree.add('foo', b'foo-id')
66+ with tree.preview_transform() as tt:
67+ self.assertEqual([], tt._inventory_altered())
68+
69+ def test_inventory_altered_changed_parent_id(self):
70+ tree = self.make_branch_and_tree('tree')
71+ self.build_tree(['tree/foo'])
72+ tree.add('foo', b'foo-id')
73+ with tree.preview_transform() as tt:
74+ tt.unversion_file(tt.root)
75+ tt.version_file(tt.root, file_id=b'new-id')
76+ foo_trans_id = tt.trans_id_tree_path('foo')
77+ foo_tuple = ('foo', foo_trans_id)
78+ root_tuple = ('', tt.root)
79+ self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
80+
81+ def test_inventory_altered_noop_changed_parent_id(self):
82+ tree = self.make_branch_and_tree('tree')
83+ self.build_tree(['tree/foo'])
84+ tree.add('foo', b'foo-id')
85+ with tree.preview_transform() as tt:
86+ tt.unversion_file(tt.root)
87+ tt.version_file(tt.root, file_id=tree.path2id(''))
88+ tt.trans_id_tree_path('foo')
89+ self.assertEqual([], tt._inventory_altered())
90
91=== added file 'breezy/bzr/transform.py'
92--- breezy/bzr/transform.py 1970-01-01 00:00:00 +0000
93+++ breezy/bzr/transform.py 2020-07-05 14:44:34 +0000
94@@ -0,0 +1,903 @@
95+# Copyright (C) 2006-2011 Canonical Ltd
96+# Copyright (C) 2020 Breezy Developers
97+#
98+# This program is free software; you can redistribute it and/or modify
99+# it under the terms of the GNU General Public License as published by
100+# the Free Software Foundation; either version 2 of the License, or
101+# (at your option) any later version.
102+#
103+# This program is distributed in the hope that it will be useful,
104+# but WITHOUT ANY WARRANTY; without even the implied warranty of
105+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
106+# GNU General Public License for more details.
107+#
108+# You should have received a copy of the GNU General Public License
109+# along with this program; if not, write to the Free Software
110+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
111+
112+from __future__ import absolute_import
113+
114+import errno
115+import os
116+from stat import S_IEXEC
117+
118+from .. import (
119+ annotate,
120+ errors,
121+ lock,
122+ osutils,
123+ revision as _mod_revision,
124+ tree,
125+ ui,
126+ )
127+
128+from ..i18n import gettext
129+from ..mutabletree import MutableTree
130+from ..sixish import text_type, viewvalues, viewitems
131+from ..transform import (
132+ ROOT_PARENT,
133+ TreeTransform,
134+ _FileMover,
135+ _TransformResults,
136+ DiskTreeTransform,
137+ joinpath,
138+ NoFinalPath,
139+ FinalPaths,
140+ unique_add,
141+ TransformRenameFailed,
142+ )
143+from . import (
144+ inventory,
145+ inventorytree,
146+ )
147+
148+
149+class InventoryTreeTransform(TreeTransform):
150+ """Tree transform for Bazaar trees."""
151+
152+ def version_file(self, trans_id, file_id=None):
153+ """Schedule a file to become versioned."""
154+ if file_id is None:
155+ raise ValueError()
156+ unique_add(self._new_id, trans_id, file_id)
157+ unique_add(self._r_new_id, file_id, trans_id)
158+
159+ def cancel_versioning(self, trans_id):
160+ """Undo a previous versioning of a file"""
161+ file_id = self._new_id[trans_id]
162+ del self._new_id[trans_id]
163+ del self._r_new_id[file_id]
164+
165+ def _duplicate_ids(self):
166+ """Each inventory id may only be used once"""
167+ conflicts = []
168+ try:
169+ all_ids = self._tree.all_file_ids()
170+ except errors.UnsupportedOperation:
171+ # it's okay for non-file-id trees to raise UnsupportedOperation.
172+ return []
173+ removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
174+ self._removed_id))
175+ active_tree_ids = all_ids.difference(removed_tree_ids)
176+ for trans_id, file_id in viewitems(self._new_id):
177+ if file_id in active_tree_ids:
178+ path = self._tree.id2path(file_id)
179+ old_trans_id = self.trans_id_tree_path(path)
180+ conflicts.append(('duplicate id', old_trans_id, trans_id))
181+ return conflicts
182+
183+ def find_conflicts(self):
184+ conflicts = super(InventoryTreeTransform, self).find_conflicts()
185+ conflicts.extend(self._duplicate_ids())
186+ return conflicts
187+
188+ def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
189+ """Apply all changes to the inventory and filesystem.
190+
191+ If filesystem or inventory conflicts are present, MalformedTransform
192+ will be thrown.
193+
194+ If apply succeeds, finalize is not necessary.
195+
196+ :param no_conflicts: if True, the caller guarantees there are no
197+ conflicts, so no check is made.
198+ :param precomputed_delta: An inventory delta to use instead of
199+ calculating one.
200+ :param _mover: Supply an alternate FileMover, for testing
201+ """
202+ for hook in MutableTree.hooks['pre_transform']:
203+ hook(self._tree, self)
204+ if not no_conflicts:
205+ self._check_malformed()
206+ with ui.ui_factory.nested_progress_bar() as child_pb:
207+ if precomputed_delta is None:
208+ child_pb.update(gettext('Apply phase'), 0, 2)
209+ inventory_delta = self._generate_inventory_delta()
210+ offset = 1
211+ else:
212+ inventory_delta = precomputed_delta
213+ offset = 0
214+ if _mover is None:
215+ mover = _FileMover()
216+ else:
217+ mover = _mover
218+ try:
219+ child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
220+ self._apply_removals(mover)
221+ child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
222+ modified_paths = self._apply_insertions(mover)
223+ except BaseException:
224+ mover.rollback()
225+ raise
226+ else:
227+ mover.apply_deletions()
228+ if self.final_file_id(self.root) is None:
229+ inventory_delta = [e for e in inventory_delta if e[0] != '']
230+ self._tree.apply_inventory_delta(inventory_delta)
231+ self._apply_observed_sha1s()
232+ self._done = True
233+ self.finalize()
234+ return _TransformResults(modified_paths, self.rename_count)
235+
236+ def _apply_removals(self, mover):
237+ """Perform tree operations that remove directory/inventory names.
238+
239+ That is, delete files that are to be deleted, and put any files that
240+ need renaming into limbo. This must be done in strict child-to-parent
241+ order.
242+
243+ If inventory_delta is None, no inventory delta generation is performed.
244+ """
245+ tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
246+ with ui.ui_factory.nested_progress_bar() as child_pb:
247+ for num, (path, trans_id) in enumerate(tree_paths):
248+ # do not attempt to move root into a subdirectory of itself.
249+ if path == '':
250+ continue
251+ child_pb.update(gettext('removing file'), num, len(tree_paths))
252+ full_path = self._tree.abspath(path)
253+ if trans_id in self._removed_contents:
254+ delete_path = os.path.join(self._deletiondir, trans_id)
255+ mover.pre_delete(full_path, delete_path)
256+ elif (trans_id in self._new_name or
257+ trans_id in self._new_parent):
258+ try:
259+ mover.rename(full_path, self._limbo_name(trans_id))
260+ except TransformRenameFailed as e:
261+ if e.errno != errno.ENOENT:
262+ raise
263+ else:
264+ self.rename_count += 1
265+
266+ def _apply_insertions(self, mover):
267+ """Perform tree operations that insert directory/inventory names.
268+
269+ That is, create any files that need to be created, and restore from
270+ limbo any files that needed renaming. This must be done in strict
271+ parent-to-child order.
272+
273+ If inventory_delta is None, no inventory delta is calculated, and
274+ no list of modified paths is returned.
275+ """
276+ new_paths = self.new_paths(filesystem_only=True)
277+ modified_paths = []
278+ with ui.ui_factory.nested_progress_bar() as child_pb:
279+ for num, (path, trans_id) in enumerate(new_paths):
280+ if (num % 10) == 0:
281+ child_pb.update(gettext('adding file'),
282+ num, len(new_paths))
283+ full_path = self._tree.abspath(path)
284+ if trans_id in self._needs_rename:
285+ try:
286+ mover.rename(self._limbo_name(trans_id), full_path)
287+ except TransformRenameFailed as e:
288+ # We may be renaming a dangling inventory id
289+ if e.errno != errno.ENOENT:
290+ raise
291+ else:
292+ self.rename_count += 1
293+ # TODO: if trans_id in self._observed_sha1s, we should
294+ # re-stat the final target, since ctime will be
295+ # updated by the change.
296+ if (trans_id in self._new_contents
297+ or self.path_changed(trans_id)):
298+ if trans_id in self._new_contents:
299+ modified_paths.append(full_path)
300+ if trans_id in self._new_executability:
301+ self._set_executability(path, trans_id)
302+ if trans_id in self._observed_sha1s:
303+ o_sha1, o_st_val = self._observed_sha1s[trans_id]
304+ st = osutils.lstat(full_path)
305+ self._observed_sha1s[trans_id] = (o_sha1, st)
306+ for path, trans_id in new_paths:
307+ # new_paths includes stuff like workingtree conflicts. Only the
308+ # stuff in new_contents actually comes from limbo.
309+ if trans_id in self._limbo_files:
310+ del self._limbo_files[trans_id]
311+ self._new_contents.clear()
312+ return modified_paths
313+
314+ def _apply_observed_sha1s(self):
315+ """After we have finished renaming everything, update observed sha1s
316+
317+ This has to be done after self._tree.apply_inventory_delta, otherwise
318+ it doesn't know anything about the files we are updating. Also, we want
319+ to do this as late as possible, so that most entries end up cached.
320+ """
321+ # TODO: this doesn't update the stat information for directories. So
322+ # the first 'bzr status' will still need to rewrite
323+ # .bzr/checkout/dirstate. However, we at least don't need to
324+ # re-read all of the files.
325+ # TODO: If the operation took a while, we could do a time.sleep(3) here
326+ # to allow the clock to tick over and ensure we won't have any
327+ # problems. (we could observe start time, and finish time, and if
328+ # it is less than eg 10% overhead, add a sleep call.)
329+ paths = FinalPaths(self)
330+ for trans_id, observed in viewitems(self._observed_sha1s):
331+ path = paths.get_path(trans_id)
332+ self._tree._observed_sha1(path, observed)
333+
334+ def get_preview_tree(self):
335+ """Return a tree representing the result of the transform.
336+
337+ The tree is a snapshot, and altering the TreeTransform will invalidate
338+ it.
339+ """
340+ return _PreviewTree(self)
341+
342+ def _inventory_altered(self):
343+ """Determine which trans_ids need new Inventory entries.
344+
345+ An new entry is needed when anything that would be reflected by an
346+ inventory entry changes, including file name, file_id, parent file_id,
347+ file kind, and the execute bit.
348+
349+ Some care is taken to return entries with real changes, not cases
350+ where the value is deleted and then restored to its original value,
351+ but some actually unchanged values may be returned.
352+
353+ :returns: A list of (path, trans_id) for all items requiring an
354+ inventory change. Ordered by path.
355+ """
356+ changed_ids = set()
357+ # Find entries whose file_ids are new (or changed).
358+ new_file_id = set(t for t in self._new_id
359+ if self._new_id[t] != self.tree_file_id(t))
360+ for id_set in [self._new_name, self._new_parent, new_file_id,
361+ self._new_executability]:
362+ changed_ids.update(id_set)
363+ # removing implies a kind change
364+ changed_kind = set(self._removed_contents)
365+ # so does adding
366+ changed_kind.intersection_update(self._new_contents)
367+ # Ignore entries that are already known to have changed.
368+ changed_kind.difference_update(changed_ids)
369+ # to keep only the truly changed ones
370+ changed_kind = (t for t in changed_kind
371+ if self.tree_kind(t) != self.final_kind(t))
372+ # all kind changes will alter the inventory
373+ changed_ids.update(changed_kind)
374+ # To find entries with changed parent_ids, find parents which existed,
375+ # but changed file_id.
376+ # Now add all their children to the set.
377+ for parent_trans_id in new_file_id:
378+ changed_ids.update(self.iter_tree_children(parent_trans_id))
379+ return sorted(FinalPaths(self).get_paths(changed_ids))
380+
381+ def _generate_inventory_delta(self):
382+ """Generate an inventory delta for the current transform."""
383+ inventory_delta = []
384+ new_paths = self._inventory_altered()
385+ total_entries = len(new_paths) + len(self._removed_id)
386+ with ui.ui_factory.nested_progress_bar() as child_pb:
387+ for num, trans_id in enumerate(self._removed_id):
388+ if (num % 10) == 0:
389+ child_pb.update(gettext('removing file'),
390+ num, total_entries)
391+ if trans_id == self._new_root:
392+ file_id = self._tree.path2id('')
393+ else:
394+ file_id = self.tree_file_id(trans_id)
395+ # File-id isn't really being deleted, just moved
396+ if file_id in self._r_new_id:
397+ continue
398+ path = self._tree_id_paths[trans_id]
399+ inventory_delta.append((path, None, file_id, None))
400+ new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
401+ new_paths)
402+ for num, (path, trans_id) in enumerate(new_paths):
403+ if (num % 10) == 0:
404+ child_pb.update(gettext('adding file'),
405+ num + len(self._removed_id), total_entries)
406+ file_id = new_path_file_ids[trans_id]
407+ if file_id is None:
408+ continue
409+ kind = self.final_kind(trans_id)
410+ if kind is None:
411+ kind = self._tree.stored_kind(self._tree.id2path(file_id))
412+ parent_trans_id = self.final_parent(trans_id)
413+ parent_file_id = new_path_file_ids.get(parent_trans_id)
414+ if parent_file_id is None:
415+ parent_file_id = self.final_file_id(parent_trans_id)
416+ if trans_id in self._new_reference_revision:
417+ new_entry = inventory.TreeReference(
418+ file_id,
419+ self._new_name[trans_id],
420+ self.final_file_id(self._new_parent[trans_id]),
421+ None, self._new_reference_revision[trans_id])
422+ else:
423+ new_entry = inventory.make_entry(kind,
424+ self.final_name(trans_id),
425+ parent_file_id, file_id)
426+ try:
427+ old_path = self._tree.id2path(new_entry.file_id)
428+ except errors.NoSuchId:
429+ old_path = None
430+ new_executability = self._new_executability.get(trans_id)
431+ if new_executability is not None:
432+ new_entry.executable = new_executability
433+ inventory_delta.append(
434+ (old_path, path, new_entry.file_id, new_entry))
435+ return inventory_delta
436+
437+
438+class TransformPreview(InventoryTreeTransform):
439+ """A TreeTransform for generating preview trees.
440+
441+ Unlike TreeTransform, this version works when the input tree is a
442+ RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
443+ unversioned files in the input tree.
444+ """
445+
446+ def __init__(self, tree, pb=None, case_sensitive=True):
447+ tree.lock_read()
448+ limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
449+ DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
450+
451+ def canonical_path(self, path):
452+ return path
453+
454+ def tree_kind(self, trans_id):
455+ path = self._tree_id_paths.get(trans_id)
456+ if path is None:
457+ return None
458+ kind = self._tree.path_content_summary(path)[0]
459+ if kind == 'missing':
460+ kind = None
461+ return kind
462+
463+ def _set_mode(self, trans_id, mode_id, typefunc):
464+ """Set the mode of new file contents.
465+ The mode_id is the existing file to get the mode from (often the same
466+ as trans_id). The operation is only performed if there's a mode match
467+ according to typefunc.
468+ """
469+ # is it ok to ignore this? probably
470+ pass
471+
472+ def iter_tree_children(self, parent_id):
473+ """Iterate through the entry's tree children, if any"""
474+ try:
475+ path = self._tree_id_paths[parent_id]
476+ except KeyError:
477+ return
478+ try:
479+ entry = next(self._tree.iter_entries_by_dir(
480+ specific_files=[path]))[1]
481+ except StopIteration:
482+ return
483+ children = getattr(entry, 'children', {})
484+ for child in children:
485+ childpath = joinpath(path, child)
486+ yield self.trans_id_tree_path(childpath)
487+
488+ def new_orphan(self, trans_id, parent_id):
489+ raise NotImplementedError(self.new_orphan)
490+
491+
492+class _PreviewTree(inventorytree.InventoryTree):
493+ """Partial implementation of Tree to support show_diff_trees"""
494+
495+ def __init__(self, transform):
496+ self._transform = transform
497+ self._final_paths = FinalPaths(transform)
498+ self.__by_parent = None
499+ self._parent_ids = []
500+ self._all_children_cache = {}
501+ self._path2trans_id_cache = {}
502+ self._final_name_cache = {}
503+ self._iter_changes_cache = {
504+ c.file_id: c for c in self._transform.iter_changes()}
505+
506+ def supports_tree_reference(self):
507+ # TODO(jelmer): Support tree references in _PreviewTree.
508+ # return self._transform._tree.supports_tree_reference()
509+ return False
510+
511+ def _content_change(self, file_id):
512+ """Return True if the content of this file changed"""
513+ changes = self._iter_changes_cache.get(file_id)
514+ return (changes is not None and changes.changed_content)
515+
516+ def _get_repository(self):
517+ repo = getattr(self._transform._tree, '_repository', None)
518+ if repo is None:
519+ repo = self._transform._tree.branch.repository
520+ return repo
521+
522+ def _iter_parent_trees(self):
523+ for revision_id in self.get_parent_ids():
524+ try:
525+ yield self.revision_tree(revision_id)
526+ except errors.NoSuchRevisionInTree:
527+ yield self._get_repository().revision_tree(revision_id)
528+
529+ def _get_file_revision(self, path, file_id, vf, tree_revision):
530+ parent_keys = [
531+ (file_id, t.get_file_revision(t.id2path(file_id)))
532+ for t in self._iter_parent_trees()]
533+ vf.add_lines((file_id, tree_revision), parent_keys,
534+ self.get_file_lines(path))
535+ repo = self._get_repository()
536+ base_vf = repo.texts
537+ if base_vf not in vf.fallback_versionedfiles:
538+ vf.fallback_versionedfiles.append(base_vf)
539+ return tree_revision
540+
541+ def _stat_limbo_file(self, trans_id):
542+ name = self._transform._limbo_name(trans_id)
543+ return os.lstat(name)
544+
545+ @property
546+ def _by_parent(self):
547+ if self.__by_parent is None:
548+ self.__by_parent = self._transform.by_parent()
549+ return self.__by_parent
550+
551+ def _comparison_data(self, entry, path):
552+ kind, size, executable, link_or_sha1 = self.path_content_summary(path)
553+ if kind == 'missing':
554+ kind = None
555+ executable = False
556+ else:
557+ file_id = self._transform.final_file_id(self._path2trans_id(path))
558+ executable = self.is_executable(path)
559+ return kind, executable, None
560+
561+ def is_locked(self):
562+ return False
563+
564+ def lock_read(self):
565+ # Perhaps in theory, this should lock the TreeTransform?
566+ return lock.LogicalLockResult(self.unlock)
567+
568+ def unlock(self):
569+ pass
570+
571+ @property
572+ def root_inventory(self):
573+ """This Tree does not use inventory as its backing data."""
574+ raise NotImplementedError(_PreviewTree.root_inventory)
575+
576+ def all_file_ids(self):
577+ tree_ids = set(self._transform._tree.all_file_ids())
578+ tree_ids.difference_update(self._transform.tree_file_id(t)
579+ for t in self._transform._removed_id)
580+ tree_ids.update(viewvalues(self._transform._new_id))
581+ return tree_ids
582+
583+ def all_versioned_paths(self):
584+ tree_paths = set(self._transform._tree.all_versioned_paths())
585+
586+ tree_paths.difference_update(
587+ self._transform.trans_id_tree_path(t)
588+ for t in self._transform._removed_id)
589+
590+ tree_paths.update(
591+ self._final_paths._determine_path(t)
592+ for t in self._transform._new_id)
593+
594+ return tree_paths
595+
596+ def _path2trans_id(self, path):
597+ # We must not use None here, because that is a valid value to store.
598+ trans_id = self._path2trans_id_cache.get(path, object)
599+ if trans_id is not object:
600+ return trans_id
601+ segments = osutils.splitpath(path)
602+ cur_parent = self._transform.root
603+ for cur_segment in segments:
604+ for child in self._all_children(cur_parent):
605+ final_name = self._final_name_cache.get(child)
606+ if final_name is None:
607+ final_name = self._transform.final_name(child)
608+ self._final_name_cache[child] = final_name
609+ if final_name == cur_segment:
610+ cur_parent = child
611+ break
612+ else:
613+ self._path2trans_id_cache[path] = None
614+ return None
615+ self._path2trans_id_cache[path] = cur_parent
616+ return cur_parent
617+
618+ def path2id(self, path):
619+ if isinstance(path, list):
620+ if path == []:
621+ path = [""]
622+ path = osutils.pathjoin(*path)
623+ return self._transform.final_file_id(self._path2trans_id(path))
624+
625+ def id2path(self, file_id, recurse='down'):
626+ trans_id = self._transform.trans_id_file_id(file_id)
627+ try:
628+ return self._final_paths._determine_path(trans_id)
629+ except NoFinalPath:
630+ raise errors.NoSuchId(self, file_id)
631+
632+ def _all_children(self, trans_id):
633+ children = self._all_children_cache.get(trans_id)
634+ if children is not None:
635+ return children
636+ children = set(self._transform.iter_tree_children(trans_id))
637+ # children in the _new_parent set are provided by _by_parent.
638+ children.difference_update(self._transform._new_parent)
639+ children.update(self._by_parent.get(trans_id, []))
640+ self._all_children_cache[trans_id] = children
641+ return children
642+
643+ def extras(self):
644+ possible_extras = set(self._transform.trans_id_tree_path(p) for p
645+ in self._transform._tree.extras())
646+ possible_extras.update(self._transform._new_contents)
647+ possible_extras.update(self._transform._removed_id)
648+ for trans_id in possible_extras:
649+ if self._transform.final_file_id(trans_id) is None:
650+ yield self._final_paths._determine_path(trans_id)
651+
652+ def _make_inv_entries(self, ordered_entries, specific_files=None):
653+ for trans_id, parent_file_id in ordered_entries:
654+ file_id = self._transform.final_file_id(trans_id)
655+ if file_id is None:
656+ continue
657+ if (specific_files is not None
658+ and self._final_paths.get_path(trans_id) not in specific_files):
659+ continue
660+ kind = self._transform.final_kind(trans_id)
661+ if kind is None:
662+ kind = self._transform._tree.stored_kind(
663+ self._transform._tree.id2path(file_id))
664+ new_entry = inventory.make_entry(
665+ kind,
666+ self._transform.final_name(trans_id),
667+ parent_file_id, file_id)
668+ yield new_entry, trans_id
669+
670+ def _list_files_by_dir(self):
671+ todo = [ROOT_PARENT]
672+ ordered_ids = []
673+ while len(todo) > 0:
674+ parent = todo.pop()
675+ parent_file_id = self._transform.final_file_id(parent)
676+ children = list(self._all_children(parent))
677+ paths = dict(zip(children, self._final_paths.get_paths(children)))
678+ children.sort(key=paths.get)
679+ todo.extend(reversed(children))
680+ for trans_id in children:
681+ ordered_ids.append((trans_id, parent_file_id))
682+ return ordered_ids
683+
684+ def iter_child_entries(self, path):
685+ trans_id = self._path2trans_id(path)
686+ if trans_id is None:
687+ raise errors.NoSuchFile(path)
688+ todo = [(child_trans_id, trans_id) for child_trans_id in
689+ self._all_children(trans_id)]
690+ for entry, trans_id in self._make_inv_entries(todo):
691+ yield entry
692+
693+ def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
694+ if recurse_nested:
695+ raise NotImplementedError(
696+ 'follow tree references not yet supported')
697+
698+ # This may not be a maximally efficient implementation, but it is
699+ # reasonably straightforward. An implementation that grafts the
700+ # TreeTransform changes onto the tree's iter_entries_by_dir results
701+ # might be more efficient, but requires tricky inferences about stack
702+ # position.
703+ ordered_ids = self._list_files_by_dir()
704+ for entry, trans_id in self._make_inv_entries(ordered_ids,
705+ specific_files):
706+ yield self._final_paths.get_path(trans_id), entry
707+
708+ def _iter_entries_for_dir(self, dir_path):
709+ """Return path, entry for items in a directory without recursing down."""
710+ ordered_ids = []
711+ dir_trans_id = self._path2trans_id(dir_path)
712+ dir_id = self._transform.final_file_id(dir_trans_id)
713+ for child_trans_id in self._all_children(dir_trans_id):
714+ ordered_ids.append((child_trans_id, dir_id))
715+ path_entries = []
716+ for entry, trans_id in self._make_inv_entries(ordered_ids):
717+ path_entries.append((self._final_paths.get_path(trans_id), entry))
718+ path_entries.sort()
719+ return path_entries
720+
721+ def list_files(self, include_root=False, from_dir=None, recursive=True,
722+ recurse_nested=False):
723+ """See WorkingTree.list_files."""
724+ if recurse_nested:
725+ raise NotImplementedError(
726+ 'follow tree references not yet supported')
727+
728+ # XXX This should behave like WorkingTree.list_files, but is really
729+ # more like RevisionTree.list_files.
730+ if from_dir == '.':
731+ from_dir = None
732+ if recursive:
733+ prefix = None
734+ if from_dir:
735+ prefix = from_dir + '/'
736+ entries = self.iter_entries_by_dir()
737+ for path, entry in entries:
738+ if entry.name == '' and not include_root:
739+ continue
740+ if prefix:
741+ if not path.startswith(prefix):
742+ continue
743+ path = path[len(prefix):]
744+ yield path, 'V', entry.kind, entry
745+ else:
746+ if from_dir is None and include_root is True:
747+ root_entry = inventory.make_entry(
748+ 'directory', '', ROOT_PARENT, self.path2id(''))
749+ yield '', 'V', 'directory', root_entry
750+ entries = self._iter_entries_for_dir(from_dir or '')
751+ for path, entry in entries:
752+ yield path, 'V', entry.kind, entry
753+
754+ def kind(self, path):
755+ trans_id = self._path2trans_id(path)
756+ if trans_id is None:
757+ raise errors.NoSuchFile(path)
758+ return self._transform.final_kind(trans_id)
759+
760+ def stored_kind(self, path):
761+ trans_id = self._path2trans_id(path)
762+ if trans_id is None:
763+ raise errors.NoSuchFile(path)
764+ try:
765+ return self._transform._new_contents[trans_id]
766+ except KeyError:
767+ return self._transform._tree.stored_kind(path)
768+
769+ def get_file_mtime(self, path):
770+ """See Tree.get_file_mtime"""
771+ file_id = self.path2id(path)
772+ if file_id is None:
773+ raise errors.NoSuchFile(path)
774+ if not self._content_change(file_id):
775+ return self._transform._tree.get_file_mtime(
776+ self._transform._tree.id2path(file_id))
777+ trans_id = self._path2trans_id(path)
778+ return self._stat_limbo_file(trans_id).st_mtime
779+
780+ def get_file_size(self, path):
781+ """See Tree.get_file_size"""
782+ trans_id = self._path2trans_id(path)
783+ if trans_id is None:
784+ raise errors.NoSuchFile(path)
785+ kind = self._transform.final_kind(trans_id)
786+ if kind != 'file':
787+ return None
788+ if trans_id in self._transform._new_contents:
789+ return self._stat_limbo_file(trans_id).st_size
790+ if self.kind(path) == 'file':
791+ return self._transform._tree.get_file_size(path)
792+ else:
793+ return None
794+
795+ def get_file_verifier(self, path, stat_value=None):
796+ trans_id = self._path2trans_id(path)
797+ if trans_id is None:
798+ raise errors.NoSuchFile(path)
799+ kind = self._transform._new_contents.get(trans_id)
800+ if kind is None:
801+ return self._transform._tree.get_file_verifier(path)
802+ if kind == 'file':
803+ with self.get_file(path) as fileobj:
804+ return ("SHA1", osutils.sha_file(fileobj))
805+
806+ def get_file_sha1(self, path, stat_value=None):
807+ trans_id = self._path2trans_id(path)
808+ if trans_id is None:
809+ raise errors.NoSuchFile(path)
810+ kind = self._transform._new_contents.get(trans_id)
811+ if kind is None:
812+ return self._transform._tree.get_file_sha1(path)
813+ if kind == 'file':
814+ with self.get_file(path) as fileobj:
815+ return osutils.sha_file(fileobj)
816+
817+ def get_reference_revision(self, path):
818+ trans_id = self._path2trans_id(path)
819+ if trans_id is None:
820+ raise errors.NoSuchFile(path)
821+ reference_revision = self._transform._new_reference_revision.get(trans_id)
822+ if reference_revision is None:
823+ return self._transform._tree.get_reference_revision(path)
824+ return reference_revision
825+
826+ def is_executable(self, path):
827+ trans_id = self._path2trans_id(path)
828+ if trans_id is None:
829+ return False
830+ try:
831+ return self._transform._new_executability[trans_id]
832+ except KeyError:
833+ try:
834+ return self._transform._tree.is_executable(path)
835+ except OSError as e:
836+ if e.errno == errno.ENOENT:
837+ return False
838+ raise
839+ except errors.NoSuchFile:
840+ return False
841+
842+ def has_filename(self, path):
843+ trans_id = self._path2trans_id(path)
844+ if trans_id in self._transform._new_contents:
845+ return True
846+ elif trans_id in self._transform._removed_contents:
847+ return False
848+ else:
849+ return self._transform._tree.has_filename(path)
850+
851+ def path_content_summary(self, path):
852+ trans_id = self._path2trans_id(path)
853+ tt = self._transform
854+ tree_path = tt._tree_id_paths.get(trans_id)
855+ kind = tt._new_contents.get(trans_id)
856+ if kind is None:
857+ if tree_path is None or trans_id in tt._removed_contents:
858+ return 'missing', None, None, None
859+ summary = tt._tree.path_content_summary(tree_path)
860+ kind, size, executable, link_or_sha1 = summary
861+ else:
862+ link_or_sha1 = None
863+ limbo_name = tt._limbo_name(trans_id)
864+ if trans_id in tt._new_reference_revision:
865+ kind = 'tree-reference'
866+ if kind == 'file':
867+ statval = os.lstat(limbo_name)
868+ size = statval.st_size
869+ if not tt._limbo_supports_executable():
870+ executable = False
871+ else:
872+ executable = statval.st_mode & S_IEXEC
873+ else:
874+ size = None
875+ executable = None
876+ if kind == 'symlink':
877+ link_or_sha1 = os.readlink(limbo_name)
878+ if not isinstance(link_or_sha1, text_type):
879+ link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
880+ executable = tt._new_executability.get(trans_id, executable)
881+ return kind, size, executable, link_or_sha1
882+
883+ def iter_changes(self, from_tree, include_unchanged=False,
884+ specific_files=None, pb=None, extra_trees=None,
885+ require_versioned=True, want_unversioned=False):
886+ """See InterTree.iter_changes.
887+
888+ This has a fast path that is only used when the from_tree matches
889+ the transform tree, and no fancy options are supplied.
890+ """
891+ if (from_tree is not self._transform._tree or include_unchanged
892+ or specific_files or want_unversioned):
893+ return tree.InterTree.get(from_tree, self).iter_changes(
894+ include_unchanged=include_unchanged,
895+ specific_files=specific_files,
896+ pb=pb,
897+ extra_trees=extra_trees,
898+ require_versioned=require_versioned,
899+ want_unversioned=want_unversioned)
900+ if want_unversioned:
901+ raise ValueError('want_unversioned is not supported')
902+ return self._transform.iter_changes()
903+
904+ def get_file(self, path):
905+ """See Tree.get_file"""
906+ file_id = self.path2id(path)
907+ if not self._content_change(file_id):
908+ return self._transform._tree.get_file(path)
909+ trans_id = self._path2trans_id(path)
910+ name = self._transform._limbo_name(trans_id)
911+ return open(name, 'rb')
912+
913+ def get_file_with_stat(self, path):
914+ return self.get_file(path), None
915+
916+ def annotate_iter(self, path,
917+ default_revision=_mod_revision.CURRENT_REVISION):
918+ file_id = self.path2id(path)
919+ changes = self._iter_changes_cache.get(file_id)
920+ if changes is None:
921+ get_old = True
922+ else:
923+ changed_content, versioned, kind = (
924+ changes.changed_content, changes.versioned, changes.kind)
925+ if kind[1] is None:
926+ return None
927+ get_old = (kind[0] == 'file' and versioned[0])
928+ if get_old:
929+ old_annotation = self._transform._tree.annotate_iter(
930+ path, default_revision=default_revision)
931+ else:
932+ old_annotation = []
933+ if changes is None:
934+ return old_annotation
935+ if not changed_content:
936+ return old_annotation
937+ # TODO: This is doing something similar to what WT.annotate_iter is
938+ # doing, however it fails slightly because it doesn't know what
939+ # the *other* revision_id is, so it doesn't know how to give the
940+ # other as the origin for some lines, they all get
941+ # 'default_revision'
942+ # It would be nice to be able to use the new Annotator based
943+ # approach, as well.
944+ return annotate.reannotate([old_annotation],
945+ self.get_file(path).readlines(),
946+ default_revision)
947+
948+ def get_symlink_target(self, path):
949+ """See Tree.get_symlink_target"""
950+ file_id = self.path2id(path)
951+ if not self._content_change(file_id):
952+ return self._transform._tree.get_symlink_target(path)
953+ trans_id = self._path2trans_id(path)
954+ name = self._transform._limbo_name(trans_id)
955+ return osutils.readlink(name)
956+
957+ def walkdirs(self, prefix=''):
958+ pending = [self._transform.root]
959+ while len(pending) > 0:
960+ parent_id = pending.pop()
961+ children = []
962+ subdirs = []
963+ prefix = prefix.rstrip('/')
964+ parent_path = self._final_paths.get_path(parent_id)
965+ parent_file_id = self._transform.final_file_id(parent_id)
966+ for child_id in self._all_children(parent_id):
967+ path_from_root = self._final_paths.get_path(child_id)
968+ basename = self._transform.final_name(child_id)
969+ file_id = self._transform.final_file_id(child_id)
970+ kind = self._transform.final_kind(child_id)
971+ if kind is not None:
972+ versioned_kind = kind
973+ else:
974+ kind = 'unknown'
975+ versioned_kind = self._transform._tree.stored_kind(
976+ self._transform._tree.id2path(file_id))
977+ if versioned_kind == 'directory':
978+ subdirs.append(child_id)
979+ children.append((path_from_root, basename, kind, None,
980+ file_id, versioned_kind))
981+ children.sort()
982+ if parent_path.startswith(prefix):
983+ yield (parent_path, parent_file_id), children
984+ pending.extend(sorted(subdirs, key=self._final_paths.get_path,
985+ reverse=True))
986+
987+ def get_parent_ids(self):
988+ return self._parent_ids
989+
990+ def set_parent_ids(self, parent_ids):
991+ self._parent_ids = parent_ids
992+
993+ def get_revision_tree(self, revision_id):
994+ return self._transform._tree.get_revision_tree(revision_id)
995+
996+
997+
998
999=== modified file 'breezy/bzr/workingtree.py'
1000--- breezy/bzr/workingtree.py 2020-07-04 00:38:37 +0000
1001+++ breezy/bzr/workingtree.py 2020-07-05 14:44:34 +0000
1002@@ -175,8 +175,8 @@
1003 self.case_sensitive = False
1004
1005 def transform(self, pb=None):
1006- from ..transform import TreeTransform
1007- return TreeTransform(self, pb=pb)
1008+ from .transform import InventoryTreeTransform
1009+ return InventoryTreeTransform(self, pb=pb)
1010
1011 def _setup_directory_is_tree_reference(self):
1012 if self._branch.repository._format.supports_tree_reference:
1013
1014=== modified file 'breezy/errors.py'
1015--- breezy/errors.py 2020-06-25 21:17:44 +0000
1016+++ breezy/errors.py 2020-07-05 14:44:34 +0000
1017@@ -1508,22 +1508,6 @@
1018 _fmt = "Parameter %(param)s is neither unicode nor utf8."
1019
1020
1021-class CantMoveRoot(BzrError):
1022-
1023- _fmt = "Moving the root directory is not supported at this time"
1024-
1025-
1026-class TransformRenameFailed(BzrError):
1027-
1028- _fmt = "Failed to rename %(from_path)s to %(to_path)s: %(why)s"
1029-
1030- def __init__(self, from_path, to_path, why, errno):
1031- self.from_path = from_path
1032- self.to_path = to_path
1033- self.why = why
1034- self.errno = errno
1035-
1036-
1037 class BzrMoveFailedError(BzrError):
1038
1039 _fmt = ("Could not move %(from_path)s%(operator)s %(to_path)s"
1040
1041=== added file 'breezy/git/transform.py'
1042--- breezy/git/transform.py 1970-01-01 00:00:00 +0000
1043+++ breezy/git/transform.py 2020-07-05 14:44:34 +0000
1044@@ -0,0 +1,275 @@
1045+# Copyright (C) 2006-2011 Canonical Ltd
1046+# Copyright (C) 2020 Breezy Developers
1047+#
1048+# This program is free software; you can redistribute it and/or modify
1049+# it under the terms of the GNU General Public License as published by
1050+# the Free Software Foundation; either version 2 of the License, or
1051+# (at your option) any later version.
1052+#
1053+# This program is distributed in the hope that it will be useful,
1054+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1055+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1056+# GNU General Public License for more details.
1057+#
1058+# You should have received a copy of the GNU General Public License
1059+# along with this program; if not, write to the Free Software
1060+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1061+
1062+from __future__ import absolute_import
1063+
1064+import errno
1065+import os
1066+
1067+from .. import errors, ui
1068+from ..i18n import gettext
1069+from ..mutabletree import MutableTree
1070+from ..sixish import viewitems
1071+from ..transform import (
1072+ TreeTransform,
1073+ _TransformResults,
1074+ _FileMover,
1075+ FinalPaths,
1076+ unique_add,
1077+ TransformRenameFailed,
1078+ )
1079+
1080+from ..bzr import inventory
1081+from ..bzr.transform import TransformPreview as GitTransformPreview
1082+
1083+
1084+class GitTreeTransform(TreeTransform):
1085+ """Tree transform for Bazaar trees."""
1086+
1087+ def version_file(self, trans_id, file_id=None):
1088+ """Schedule a file to become versioned."""
1089+ if file_id is None:
1090+ raise ValueError()
1091+ unique_add(self._new_id, trans_id, file_id)
1092+ unique_add(self._r_new_id, file_id, trans_id)
1093+
1094+ def cancel_versioning(self, trans_id):
1095+ """Undo a previous versioning of a file"""
1096+ file_id = self._new_id[trans_id]
1097+ del self._new_id[trans_id]
1098+ del self._r_new_id[file_id]
1099+
1100+ def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1101+ """Apply all changes to the inventory and filesystem.
1102+
1103+ If filesystem or inventory conflicts are present, MalformedTransform
1104+ will be thrown.
1105+
1106+ If apply succeeds, finalize is not necessary.
1107+
1108+ :param no_conflicts: if True, the caller guarantees there are no
1109+ conflicts, so no check is made.
1110+ :param precomputed_delta: An inventory delta to use instead of
1111+ calculating one.
1112+ :param _mover: Supply an alternate FileMover, for testing
1113+ """
1114+ for hook in MutableTree.hooks['pre_transform']:
1115+ hook(self._tree, self)
1116+ if not no_conflicts:
1117+ self._check_malformed()
1118+ with ui.ui_factory.nested_progress_bar() as child_pb:
1119+ if precomputed_delta is None:
1120+ child_pb.update(gettext('Apply phase'), 0, 2)
1121+ changes = self._generate_transform_changes()
1122+ offset = 1
1123+ else:
1124+ changes = [
1125+ (op, np, ie) for (op, np, fid, ie) in precomputed_delta]
1126+ offset = 0
1127+ if _mover is None:
1128+ mover = _FileMover()
1129+ else:
1130+ mover = _mover
1131+ try:
1132+ child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1133+ self._apply_removals(mover)
1134+ child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1135+ modified_paths = self._apply_insertions(mover)
1136+ except BaseException:
1137+ mover.rollback()
1138+ raise
1139+ else:
1140+ mover.apply_deletions()
1141+ if self.final_file_id(self.root) is None:
1142+ changes = [e for e in changes if e[0] != '']
1143+ self._tree._apply_transform_delta(changes)
1144+ self._done = True
1145+ self.finalize()
1146+ return _TransformResults(modified_paths, self.rename_count)
1147+
1148+ def _apply_removals(self, mover):
1149+ """Perform tree operations that remove directory/inventory names.
1150+
1151+ That is, delete files that are to be deleted, and put any files that
1152+ need renaming into limbo. This must be done in strict child-to-parent
1153+ order.
1154+
1155+ If inventory_delta is None, no inventory delta generation is performed.
1156+ """
1157+ tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
1158+ with ui.ui_factory.nested_progress_bar() as child_pb:
1159+ for num, (path, trans_id) in enumerate(tree_paths):
1160+ # do not attempt to move root into a subdirectory of itself.
1161+ if path == '':
1162+ continue
1163+ child_pb.update(gettext('removing file'), num, len(tree_paths))
1164+ full_path = self._tree.abspath(path)
1165+ if trans_id in self._removed_contents:
1166+ delete_path = os.path.join(self._deletiondir, trans_id)
1167+ mover.pre_delete(full_path, delete_path)
1168+ elif (trans_id in self._new_name or
1169+ trans_id in self._new_parent):
1170+ try:
1171+ mover.rename(full_path, self._limbo_name(trans_id))
1172+ except TransformRenameFailed as e:
1173+ if e.errno != errno.ENOENT:
1174+ raise
1175+ else:
1176+ self.rename_count += 1
1177+
1178+ def _apply_insertions(self, mover):
1179+ """Perform tree operations that insert directory/inventory names.
1180+
1181+ That is, create any files that need to be created, and restore from
1182+ limbo any files that needed renaming. This must be done in strict
1183+ parent-to-child order.
1184+
1185+ If inventory_delta is None, no inventory delta is calculated, and
1186+ no list of modified paths is returned.
1187+ """
1188+ new_paths = self.new_paths(filesystem_only=True)
1189+ modified_paths = []
1190+ with ui.ui_factory.nested_progress_bar() as child_pb:
1191+ for num, (path, trans_id) in enumerate(new_paths):
1192+ if (num % 10) == 0:
1193+ child_pb.update(gettext('adding file'),
1194+ num, len(new_paths))
1195+ full_path = self._tree.abspath(path)
1196+ if trans_id in self._needs_rename:
1197+ try:
1198+ mover.rename(self._limbo_name(trans_id), full_path)
1199+ except TransformRenameFailed as e:
1200+ # We may be renaming a dangling inventory id
1201+ if e.errno != errno.ENOENT:
1202+ raise
1203+ else:
1204+ self.rename_count += 1
1205+ # TODO: if trans_id in self._observed_sha1s, we should
1206+ # re-stat the final target, since ctime will be
1207+ # updated by the change.
1208+ if (trans_id in self._new_contents
1209+ or self.path_changed(trans_id)):
1210+ if trans_id in self._new_contents:
1211+ modified_paths.append(full_path)
1212+ if trans_id in self._new_executability:
1213+ self._set_executability(path, trans_id)
1214+ if trans_id in self._observed_sha1s:
1215+ o_sha1, o_st_val = self._observed_sha1s[trans_id]
1216+ st = osutils.lstat(full_path)
1217+ self._observed_sha1s[trans_id] = (o_sha1, st)
1218+ for path, trans_id in new_paths:
1219+ # new_paths includes stuff like workingtree conflicts. Only the
1220+ # stuff in new_contents actually comes from limbo.
1221+ if trans_id in self._limbo_files:
1222+ del self._limbo_files[trans_id]
1223+ self._new_contents.clear()
1224+ return modified_paths
1225+
1226+ def _inventory_altered(self):
1227+ """Determine which trans_ids need new Inventory entries.
1228+
1229+ An new entry is needed when anything that would be reflected by an
1230+ inventory entry changes, including file name, file_id, parent file_id,
1231+ file kind, and the execute bit.
1232+
1233+ Some care is taken to return entries with real changes, not cases
1234+ where the value is deleted and then restored to its original value,
1235+ but some actually unchanged values may be returned.
1236+
1237+ :returns: A list of (path, trans_id) for all items requiring an
1238+ inventory change. Ordered by path.
1239+ """
1240+ changed_ids = set()
1241+ # Find entries whose file_ids are new (or changed).
1242+ new_file_id = set(t for t in self._new_id
1243+ if self._new_id[t] != self.tree_file_id(t))
1244+ for id_set in [self._new_name, self._new_parent, new_file_id,
1245+ self._new_executability]:
1246+ changed_ids.update(id_set)
1247+ # removing implies a kind change
1248+ changed_kind = set(self._removed_contents)
1249+ # so does adding
1250+ changed_kind.intersection_update(self._new_contents)
1251+ # Ignore entries that are already known to have changed.
1252+ changed_kind.difference_update(changed_ids)
1253+ # to keep only the truly changed ones
1254+ changed_kind = (t for t in changed_kind
1255+ if self.tree_kind(t) != self.final_kind(t))
1256+ # all kind changes will alter the inventory
1257+ changed_ids.update(changed_kind)
1258+ # To find entries with changed parent_ids, find parents which existed,
1259+ # but changed file_id.
1260+ # Now add all their children to the set.
1261+ for parent_trans_id in new_file_id:
1262+ changed_ids.update(self.iter_tree_children(parent_trans_id))
1263+ return sorted(FinalPaths(self).get_paths(changed_ids))
1264+
1265+ def _generate_transform_changes(self):
1266+ """Generate an inventory delta for the current transform."""
1267+ changes = []
1268+ new_paths = self._inventory_altered()
1269+ total_entries = len(new_paths) + len(self._removed_id)
1270+ with ui.ui_factory.nested_progress_bar() as child_pb:
1271+ for num, trans_id in enumerate(self._removed_id):
1272+ if (num % 10) == 0:
1273+ child_pb.update(gettext('removing file'),
1274+ num, total_entries)
1275+ if trans_id == self._new_root:
1276+ file_id = self._tree.path2id('')
1277+ else:
1278+ file_id = self.tree_file_id(trans_id)
1279+ # File-id isn't really being deleted, just moved
1280+ if file_id in self._r_new_id:
1281+ continue
1282+ path = self._tree_id_paths[trans_id]
1283+ changes.append((path, None, None))
1284+ new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1285+ new_paths)
1286+ for num, (path, trans_id) in enumerate(new_paths):
1287+ if (num % 10) == 0:
1288+ child_pb.update(gettext('adding file'),
1289+ num + len(self._removed_id), total_entries)
1290+ file_id = new_path_file_ids[trans_id]
1291+ if file_id is None:
1292+ continue
1293+ kind = self.final_kind(trans_id)
1294+ if kind is None:
1295+ kind = self._tree.stored_kind(self._tree.id2path(file_id))
1296+ parent_trans_id = self.final_parent(trans_id)
1297+ parent_file_id = new_path_file_ids.get(parent_trans_id)
1298+ if parent_file_id is None:
1299+ parent_file_id = self.final_file_id(parent_trans_id)
1300+ if trans_id in self._new_reference_revision:
1301+ new_entry = inventory.TreeReference(
1302+ file_id,
1303+ self._new_name[trans_id],
1304+ self.final_file_id(self._new_parent[trans_id]),
1305+ None, self._new_reference_revision[trans_id])
1306+ else:
1307+ new_entry = inventory.make_entry(kind,
1308+ self.final_name(trans_id),
1309+ parent_file_id, file_id)
1310+ try:
1311+ old_path = self._tree.id2path(new_entry.file_id)
1312+ except errors.NoSuchId:
1313+ old_path = None
1314+ new_executability = self._new_executability.get(trans_id)
1315+ if new_executability is not None:
1316+ new_entry.executable = new_executability
1317+ changes.append(
1318+ (old_path, path, new_entry))
1319+ return changes
1320
1321=== modified file 'breezy/git/tree.py'
1322--- breezy/git/tree.py 2020-07-04 23:16:47 +0000
1323+++ breezy/git/tree.py 2020-07-05 14:44:34 +0000
1324@@ -725,8 +725,8 @@
1325 yield (path_decoded, parent_id), children
1326
1327 def preview_transform(self, pb=None):
1328- from ..transform import TransformPreview
1329- return TransformPreview(self, pb=pb)
1330+ from .transform import GitTransformPreview
1331+ return GitTransformPreview(self, pb=pb)
1332
1333
1334 def tree_delta_from_git_changes(changes, mappings,
1335@@ -1635,13 +1635,12 @@
1336 raise NotImplementedError(self._live_entry)
1337
1338 def transform(self, pb=None):
1339- from ..transform import TreeTransform
1340- return TreeTransform(self, pb=pb)
1341+ from .transform import GitTreeTransform
1342+ return GitTreeTransform(self, pb=pb)
1343
1344 def preview_transform(self, pb=None):
1345- from ..transform import TransformPreview
1346- return TransformPreview(self, pb=pb)
1347-
1348+ from ..transform import GitTransformPreview
1349+ return GitTransformPreview(self, pb=pb)
1350
1351
1352 class InterToIndexGitTree(InterGitTrees):
1353
1354=== modified file 'breezy/git/workingtree.py'
1355--- breezy/git/workingtree.py 2020-07-04 14:58:52 +0000
1356+++ breezy/git/workingtree.py 2020-07-05 14:44:34 +0000
1357@@ -224,10 +224,6 @@
1358 else:
1359 self.case_sensitive = False
1360
1361- def transform(self, pb=None):
1362- from ..transform import TreeTransform
1363- return TreeTransform(self, pb=pb)
1364-
1365 def merge_modified(self):
1366 return {}
1367
1368@@ -1075,8 +1071,8 @@
1369 def store_uncommitted(self):
1370 raise errors.StoringUncommittedNotSupported(self)
1371
1372- def apply_inventory_delta(self, changes):
1373- for (old_path, new_path, file_id, ie) in changes:
1374+ def _apply_transform_delta(self, changes):
1375+ for (old_path, new_path, ie) in changes:
1376 if old_path is not None:
1377 (index, old_subpath) = self._lookup_index(
1378 encode_git_path(old_path))
1379
1380=== modified file 'breezy/merge.py'
1381--- breezy/merge.py 2020-07-04 18:40:42 +0000
1382+++ breezy/merge.py 2020-07-05 14:44:34 +0000
1383@@ -31,7 +31,6 @@
1384 revision as _mod_revision,
1385 textfile,
1386 trace,
1387- transform,
1388 tree as _mod_tree,
1389 tsort,
1390 ui,
1391@@ -48,6 +47,7 @@
1392 errors,
1393 hooks,
1394 registry,
1395+ transform,
1396 )
1397 from .sixish import (
1398 viewitems,
1399@@ -780,10 +780,10 @@
1400
1401 def _compute_transform(self):
1402 if self._lca_trees is None:
1403- entries = self._entries3()
1404+ entries = list(self._entries3())
1405 resolver = self._three_way
1406 else:
1407- entries = self._entries_lca()
1408+ entries = list(self._entries_lca())
1409 resolver = self._lca_multi_way
1410 # Prepare merge hooks
1411 factories = Merger.hooks['merge_file_content']
1412@@ -794,7 +794,6 @@
1413 for num, (file_id, changed, paths3, parents3, names3,
1414 executable3) in enumerate(entries):
1415 trans_id = self.tt.trans_id_file_id(file_id)
1416-
1417 # Try merging each entry
1418 child_pb.update(gettext('Preparing file merge'),
1419 num, len(entries))
1420@@ -835,7 +834,6 @@
1421 other and this. names3 is a tuple of names for base, other and this.
1422 executable3 is a tuple of execute-bit values for base, other and this.
1423 """
1424- result = []
1425 iterator = self.other_tree.iter_changes(self.base_tree,
1426 specific_files=self.interesting_files,
1427 extra_trees=[self.this_tree])
1428@@ -863,10 +861,9 @@
1429 names3 = change.name + (this_name,)
1430 paths3 = change.path + (this_path, )
1431 executable3 = change.executable + (this_executable,)
1432- result.append(
1433+ yield (
1434 (change.file_id, change.changed_content, paths3,
1435 parents3, names3, executable3))
1436- return result
1437
1438 def _entries_lca(self):
1439 """Gather data about files modified between multiple trees.
1440@@ -895,7 +892,6 @@
1441 self.interesting_files, lookup_trees)
1442 else:
1443 interesting_files = None
1444- result = []
1445 from .multiwalker import MultiWalker
1446 walker = MultiWalker(self.other_tree, self._lca_trees)
1447
1448@@ -1039,7 +1035,7 @@
1449 raise AssertionError('unhandled kind: %s' % other_ie.kind)
1450
1451 # If we have gotten this far, that means something has changed
1452- result.append((file_id, content_changed,
1453+ yield (file_id, content_changed,
1454 ((base_path, lca_paths),
1455 other_path, this_path),
1456 ((base_ie.parent_id, lca_parent_ids),
1457@@ -1048,8 +1044,7 @@
1458 other_ie.name, this_ie.name),
1459 ((base_ie.executable, lca_executable),
1460 other_ie.executable, this_ie.executable)
1461- ))
1462- return result
1463+ )
1464
1465 def write_modified(self, results):
1466 if not self.working_tree.supports_merge_modified():
1467
1468=== modified file 'breezy/tests/per_tree/test_path_content_summary.py'
1469--- breezy/tests/per_tree/test_path_content_summary.py 2020-07-04 00:38:37 +0000
1470+++ breezy/tests/per_tree/test_path_content_summary.py 2020-07-05 14:44:34 +0000
1471@@ -21,9 +21,11 @@
1472 from breezy import (
1473 osutils,
1474 tests,
1475- transform,
1476 )
1477
1478+from breezy.bzr.transform import (
1479+ _PreviewTree,
1480+ )
1481 from breezy.tests import (
1482 features,
1483 per_tree,
1484@@ -112,7 +114,7 @@
1485 self.assertEqual('missing', summary[0])
1486 self.assertIs(None, summary[2])
1487 self.assertIs(None, summary[3])
1488- elif isinstance(tree, transform._PreviewTree):
1489+ elif isinstance(tree, _PreviewTree):
1490 self.expectFailure('PreviewTree returns "missing" for unversioned'
1491 'files', self.assertEqual, 'file', summary[0])
1492 self.assertEqual('file', summary[0])
1493
1494=== modified file 'breezy/tests/per_tree/test_symlinks.py'
1495--- breezy/tests/per_tree/test_symlinks.py 2019-06-29 21:31:14 +0000
1496+++ breezy/tests/per_tree/test_symlinks.py 2020-07-05 14:44:34 +0000
1497@@ -27,7 +27,7 @@
1498 )
1499 from breezy.mutabletree import MutableTree
1500 from breezy.tests import TestSkipped
1501-from breezy.transform import _PreviewTree
1502+from breezy.bzr.transform import _PreviewTree
1503 from breezy.tests import (
1504 features,
1505 )
1506
1507=== modified file 'breezy/tests/per_workingtree/test_transform.py'
1508--- breezy/tests/per_workingtree/test_transform.py 2020-07-05 00:03:01 +0000
1509+++ breezy/tests/per_workingtree/test_transform.py 2020-07-05 14:44:34 +0000
1510@@ -70,6 +70,7 @@
1511 MalformedTransform,
1512 NoFinalPath,
1513 ReusingTransform,
1514+ TransformRenameFailed,
1515 )
1516
1517 from breezy.tests.per_workingtree import TestCaseWithWorkingTree
1518@@ -335,7 +336,7 @@
1519 with transform:
1520 transform.delete_contents(root)
1521 e = self.assertRaises(AssertionError, self.assertRaises,
1522- errors.TransformRenameFailed,
1523+ TransformRenameFailed,
1524 transform.apply)
1525 self.assertContainsRe('TransformRenameFailed not raised', str(e))
1526
1527@@ -1032,7 +1033,7 @@
1528 file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1529 dir_id = rename_transform.trans_id_file_id(b'first-id')
1530 rename_transform.adjust_path('newname', dir_id, file_trans_id)
1531- e = self.assertRaises(errors.TransformRenameFailed,
1532+ e = self.assertRaises(TransformRenameFailed,
1533 rename_transform.apply)
1534 # On nix looks like:
1535 # "Failed to rename .../work/.bzr/checkout/limbo/new-1
1536
1537=== modified file 'breezy/tests/test_errors.py'
1538--- breezy/tests/test_errors.py 2020-06-25 21:17:44 +0000
1539+++ breezy/tests/test_errors.py 2020-07-05 14:44:34 +0000
1540@@ -515,12 +515,6 @@
1541 str(e),
1542 r'Cannot bind address "example\.com:22":.*Permission denied')
1543
1544- def test_transform_rename_failed(self):
1545- e = errors.TransformRenameFailed(u"from", u"to", "readonly file", 2)
1546- self.assertEqual(
1547- u"Failed to rename from to to: readonly file",
1548- str(e))
1549-
1550
1551 class TestErrorsUsingTransport(tests.TestCaseWithMemoryTransport):
1552 """Tests for errors that need to use a branch or repo."""
1553
1554=== modified file 'breezy/tests/test_transform.py'
1555--- breezy/tests/test_transform.py 2020-07-05 00:03:01 +0000
1556+++ breezy/tests/test_transform.py 2020-07-05 14:44:34 +0000
1557@@ -87,9 +87,11 @@
1558 MalformedTransform,
1559 NoFinalPath,
1560 ReusingTransform,
1561- TransformPreview,
1562- TreeTransform,
1563+ TransformRenameFailed,
1564 )
1565+from ..bzr.transform import (
1566+ InventoryTreeTransform as TreeTransform,
1567+ )
1568
1569
1570 class TransformGroup(object):
1571@@ -109,38 +111,6 @@
1572 return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
1573
1574
1575-class TestInventoryAltered(tests.TestCaseWithTransport):
1576-
1577- def test_inventory_altered_unchanged(self):
1578- tree = self.make_branch_and_tree('tree')
1579- self.build_tree(['tree/foo'])
1580- tree.add('foo', b'foo-id')
1581- with tree.preview_transform() as tt:
1582- self.assertEqual([], tt._inventory_altered())
1583-
1584- def test_inventory_altered_changed_parent_id(self):
1585- tree = self.make_branch_and_tree('tree')
1586- self.build_tree(['tree/foo'])
1587- tree.add('foo', b'foo-id')
1588- with tree.preview_transform() as tt:
1589- tt.unversion_file(tt.root)
1590- tt.version_file(tt.root, file_id=b'new-id')
1591- foo_trans_id = tt.trans_id_tree_path('foo')
1592- foo_tuple = ('foo', foo_trans_id)
1593- root_tuple = ('', tt.root)
1594- self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1595-
1596- def test_inventory_altered_noop_changed_parent_id(self):
1597- tree = self.make_branch_and_tree('tree')
1598- self.build_tree(['tree/foo'])
1599- tree.add('foo', b'foo-id')
1600- with tree.preview_transform() as tt:
1601- tt.unversion_file(tt.root)
1602- tt.version_file(tt.root, file_id=tree.path2id(''))
1603- tt.trans_id_tree_path('foo')
1604- self.assertEqual([], tt._inventory_altered())
1605-
1606-
1607 class TestTransformMerge(TestCaseInTempDir):
1608
1609 def test_text_merge(self):
1610@@ -1603,3 +1573,12 @@
1611 """If the file to be linked is unmodified, link"""
1612 transform.link_tree(self.child_tree, self.parent_tree)
1613 self.assertTrue(self.hardlinked())
1614+
1615+
1616+class ErrorTests(tests.TestCase):
1617+
1618+ def test_transform_rename_failed(self):
1619+ e = TransformRenameFailed(u"from", u"to", "readonly file", 2)
1620+ self.assertEqual(
1621+ u"Failed to rename from to to: readonly file",
1622+ str(e))
1623
1624=== modified file 'breezy/transform.py'
1625--- breezy/transform.py 2020-07-04 19:23:30 +0000
1626+++ breezy/transform.py 2020-07-05 14:44:34 +0000
1627@@ -29,29 +29,20 @@
1628 osutils,
1629 registry,
1630 trace,
1631- tree,
1632 )
1633 lazy_import.lazy_import(globals(), """
1634 from breezy import (
1635- annotate,
1636 cleanup,
1637- commit,
1638 conflicts,
1639- lock,
1640 multiparent,
1641 revision as _mod_revision,
1642 ui,
1643 urlutils,
1644 )
1645 from breezy.i18n import gettext
1646-from breezy.bzr import (
1647- inventory,
1648- inventorytree,
1649- )
1650 """)
1651
1652 from .errors import (DuplicateKey,
1653- CantMoveRoot,
1654 BzrError, InternalBzrError)
1655 from .filters import filtered_output_bytes, ContentFilterContext
1656 from .mutabletree import MutableTree
1657@@ -101,6 +92,11 @@
1658 _fmt = "Tree transform is malformed %(conflicts)r"
1659
1660
1661+class CantMoveRoot(BzrError):
1662+
1663+ _fmt = "Moving the root directory is not supported at this time"
1664+
1665+
1666 class ImmortalLimbo(BzrError):
1667
1668 _fmt = """Unable to delete transform temporary directory %(limbo_dir)s.
1669@@ -112,6 +108,17 @@
1670 self.limbo_dir = limbo_dir
1671
1672
1673+class TransformRenameFailed(BzrError):
1674+
1675+ _fmt = "Failed to rename %(from_path)s to %(to_path)s: %(why)s"
1676+
1677+ def __init__(self, from_path, to_path, why, errno):
1678+ self.from_path = from_path
1679+ self.to_path = to_path
1680+ self.why = why
1681+ self.errno = errno
1682+
1683+
1684 def unique_add(map, key, value):
1685 if key in map:
1686 raise DuplicateKey(key=key)
1687@@ -410,16 +417,11 @@
1688
1689 def version_file(self, trans_id, file_id=None):
1690 """Schedule a file to become versioned."""
1691- if file_id is None:
1692- raise ValueError()
1693- unique_add(self._new_id, trans_id, file_id)
1694- unique_add(self._r_new_id, file_id, trans_id)
1695+ raise NotImplementedError(self.version_file)
1696
1697 def cancel_versioning(self, trans_id):
1698 """Undo a previous versioning of a file"""
1699- file_id = self._new_id[trans_id]
1700- del self._new_id[trans_id]
1701- del self._r_new_id[file_id]
1702+ raise NotImplementedError(self.cancel_versioning)
1703
1704 def new_paths(self, filesystem_only=False):
1705 """Determine the paths of all new and changed files.
1706@@ -442,45 +444,6 @@
1707 new_ids.update(id_set)
1708 return sorted(FinalPaths(self).get_paths(new_ids))
1709
1710- def _inventory_altered(self):
1711- """Determine which trans_ids need new Inventory entries.
1712-
1713- An new entry is needed when anything that would be reflected by an
1714- inventory entry changes, including file name, file_id, parent file_id,
1715- file kind, and the execute bit.
1716-
1717- Some care is taken to return entries with real changes, not cases
1718- where the value is deleted and then restored to its original value,
1719- but some actually unchanged values may be returned.
1720-
1721- :returns: A list of (path, trans_id) for all items requiring an
1722- inventory change. Ordered by path.
1723- """
1724- changed_ids = set()
1725- # Find entries whose file_ids are new (or changed).
1726- new_file_id = set(t for t in self._new_id
1727- if self._new_id[t] != self.tree_file_id(t))
1728- for id_set in [self._new_name, self._new_parent, new_file_id,
1729- self._new_executability]:
1730- changed_ids.update(id_set)
1731- # removing implies a kind change
1732- changed_kind = set(self._removed_contents)
1733- # so does adding
1734- changed_kind.intersection_update(self._new_contents)
1735- # Ignore entries that are already known to have changed.
1736- changed_kind.difference_update(changed_ids)
1737- # to keep only the truly changed ones
1738- changed_kind = (t for t in changed_kind
1739- if self.tree_kind(t) != self.final_kind(t))
1740- # all kind changes will alter the inventory
1741- changed_ids.update(changed_kind)
1742- # To find entries with changed parent_ids, find parents which existed,
1743- # but changed file_id.
1744- # Now add all their children to the set.
1745- for parent_trans_id in new_file_id:
1746- changed_ids.update(self.iter_tree_children(parent_trans_id))
1747- return sorted(FinalPaths(self).get_paths(changed_ids))
1748-
1749 def final_kind(self, trans_id):
1750 """Determine the final file kind, after any changes applied.
1751
1752@@ -591,7 +554,6 @@
1753 conflicts.extend(self._unversioned_parents(by_parent))
1754 conflicts.extend(self._parent_loops())
1755 conflicts.extend(self._duplicate_entries(by_parent))
1756- conflicts.extend(self._duplicate_ids())
1757 conflicts.extend(self._parent_type_conflicts(by_parent))
1758 conflicts.extend(self._improper_versioning())
1759 conflicts.extend(self._executability_conflicts())
1760@@ -773,24 +735,6 @@
1761 last_trans_id = trans_id
1762 return conflicts
1763
1764- def _duplicate_ids(self):
1765- """Each inventory id may only be used once"""
1766- conflicts = []
1767- try:
1768- all_ids = self._tree.all_file_ids()
1769- except errors.UnsupportedOperation:
1770- # it's okay for non-file-id trees to raise UnsupportedOperation.
1771- return []
1772- removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
1773- self._removed_id))
1774- active_tree_ids = all_ids.difference(removed_tree_ids)
1775- for trans_id, file_id in viewitems(self._new_id):
1776- if file_id in active_tree_ids:
1777- path = self._tree.id2path(file_id)
1778- old_trans_id = self.trans_id_tree_path(path)
1779- conflicts.append(('duplicate id', old_trans_id, trans_id))
1780- return conflicts
1781-
1782 def _parent_type_conflicts(self, by_parent):
1783 """Children must have a directory parent"""
1784 conflicts = []
1785@@ -1075,7 +1019,7 @@
1786 The tree is a snapshot, and altering the TreeTransform will invalidate
1787 it.
1788 """
1789- return _PreviewTree(self)
1790+ raise NotImplementedError(self.get_preview)
1791
1792 def commit(self, branch, message, merge_parents=None, strict=False,
1793 timestamp=None, timezone=None, committer=None, authors=None,
1794@@ -1120,6 +1064,7 @@
1795 if self._tree.get_revision_id() != last_rev_id:
1796 raise ValueError('TreeTransform not based on branch basis: %s' %
1797 self._tree.get_revision_id().decode('utf-8'))
1798+ from . import commit
1799 revprops = commit.Commit.update_revprops(revprops, branch, authors)
1800 builder = branch.get_commit_builder(parent_ids,
1801 timestamp=timestamp,
1802@@ -1836,749 +1781,7 @@
1803 calculating one.
1804 :param _mover: Supply an alternate FileMover, for testing
1805 """
1806- for hook in MutableTree.hooks['pre_transform']:
1807- hook(self._tree, self)
1808- if not no_conflicts:
1809- self._check_malformed()
1810- with ui.ui_factory.nested_progress_bar() as child_pb:
1811- if precomputed_delta is None:
1812- child_pb.update(gettext('Apply phase'), 0, 2)
1813- inventory_delta = self._generate_inventory_delta()
1814- offset = 1
1815- else:
1816- inventory_delta = precomputed_delta
1817- offset = 0
1818- if _mover is None:
1819- mover = _FileMover()
1820- else:
1821- mover = _mover
1822- try:
1823- child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1824- self._apply_removals(mover)
1825- child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1826- modified_paths = self._apply_insertions(mover)
1827- except BaseException:
1828- mover.rollback()
1829- raise
1830- else:
1831- mover.apply_deletions()
1832- if not self.final_is_versioned(self.root):
1833- inventory_delta = [e for e in inventory_delta if e[0] != '']
1834- self._tree.apply_inventory_delta(inventory_delta)
1835- self._apply_observed_sha1s()
1836- self._done = True
1837- self.finalize()
1838- return _TransformResults(modified_paths, self.rename_count)
1839-
1840- def _generate_inventory_delta(self):
1841- """Generate an inventory delta for the current transform."""
1842- inventory_delta = []
1843- new_paths = self._inventory_altered()
1844- total_entries = len(new_paths) + len(self._removed_id)
1845- with ui.ui_factory.nested_progress_bar() as child_pb:
1846- for num, trans_id in enumerate(self._removed_id):
1847- if (num % 10) == 0:
1848- child_pb.update(gettext('removing file'),
1849- num, total_entries)
1850- if trans_id == self._new_root:
1851- file_id = self._tree.path2id('')
1852- else:
1853- file_id = self.tree_file_id(trans_id)
1854- # File-id isn't really being deleted, just moved
1855- if file_id in self._r_new_id:
1856- continue
1857- path = self._tree_id_paths[trans_id]
1858- inventory_delta.append((path, None, file_id, None))
1859- new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1860- new_paths)
1861- for num, (path, trans_id) in enumerate(new_paths):
1862- if (num % 10) == 0:
1863- child_pb.update(gettext('adding file'),
1864- num + len(self._removed_id), total_entries)
1865- file_id = new_path_file_ids[trans_id]
1866- if file_id is None:
1867- continue
1868- kind = self.final_kind(trans_id)
1869- if kind is None:
1870- kind = self._tree.stored_kind(self._tree.id2path(file_id))
1871- parent_trans_id = self.final_parent(trans_id)
1872- parent_file_id = new_path_file_ids.get(parent_trans_id)
1873- if parent_file_id is None:
1874- parent_file_id = self.final_file_id(parent_trans_id)
1875- if trans_id in self._new_reference_revision:
1876- new_entry = inventory.TreeReference(
1877- file_id,
1878- self._new_name[trans_id],
1879- self.final_file_id(self._new_parent[trans_id]),
1880- None, self._new_reference_revision[trans_id])
1881- else:
1882- new_entry = inventory.make_entry(kind,
1883- self.final_name(trans_id),
1884- parent_file_id, file_id)
1885- try:
1886- old_path = self._tree.id2path(new_entry.file_id)
1887- except errors.NoSuchId:
1888- old_path = None
1889- new_executability = self._new_executability.get(trans_id)
1890- if new_executability is not None:
1891- new_entry.executable = new_executability
1892- inventory_delta.append(
1893- (old_path, path, new_entry.file_id, new_entry))
1894- return inventory_delta
1895-
1896- def _apply_removals(self, mover):
1897- """Perform tree operations that remove directory/inventory names.
1898-
1899- That is, delete files that are to be deleted, and put any files that
1900- need renaming into limbo. This must be done in strict child-to-parent
1901- order.
1902-
1903- If inventory_delta is None, no inventory delta generation is performed.
1904- """
1905- tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
1906- with ui.ui_factory.nested_progress_bar() as child_pb:
1907- for num, (path, trans_id) in enumerate(tree_paths):
1908- # do not attempt to move root into a subdirectory of itself.
1909- if path == '':
1910- continue
1911- child_pb.update(gettext('removing file'), num, len(tree_paths))
1912- full_path = self._tree.abspath(path)
1913- if trans_id in self._removed_contents:
1914- delete_path = os.path.join(self._deletiondir, trans_id)
1915- mover.pre_delete(full_path, delete_path)
1916- elif (trans_id in self._new_name or
1917- trans_id in self._new_parent):
1918- try:
1919- mover.rename(full_path, self._limbo_name(trans_id))
1920- except errors.TransformRenameFailed as e:
1921- if e.errno != errno.ENOENT:
1922- raise
1923- else:
1924- self.rename_count += 1
1925-
1926- def _apply_insertions(self, mover):
1927- """Perform tree operations that insert directory/inventory names.
1928-
1929- That is, create any files that need to be created, and restore from
1930- limbo any files that needed renaming. This must be done in strict
1931- parent-to-child order.
1932-
1933- If inventory_delta is None, no inventory delta is calculated, and
1934- no list of modified paths is returned.
1935- """
1936- new_paths = self.new_paths(filesystem_only=True)
1937- modified_paths = []
1938- with ui.ui_factory.nested_progress_bar() as child_pb:
1939- for num, (path, trans_id) in enumerate(new_paths):
1940- if (num % 10) == 0:
1941- child_pb.update(gettext('adding file'),
1942- num, len(new_paths))
1943- full_path = self._tree.abspath(path)
1944- if trans_id in self._needs_rename:
1945- try:
1946- mover.rename(self._limbo_name(trans_id), full_path)
1947- except errors.TransformRenameFailed as e:
1948- # We may be renaming a dangling inventory id
1949- if e.errno != errno.ENOENT:
1950- raise
1951- else:
1952- self.rename_count += 1
1953- # TODO: if trans_id in self._observed_sha1s, we should
1954- # re-stat the final target, since ctime will be
1955- # updated by the change.
1956- if (trans_id in self._new_contents
1957- or self.path_changed(trans_id)):
1958- if trans_id in self._new_contents:
1959- modified_paths.append(full_path)
1960- if trans_id in self._new_executability:
1961- self._set_executability(path, trans_id)
1962- if trans_id in self._observed_sha1s:
1963- o_sha1, o_st_val = self._observed_sha1s[trans_id]
1964- st = osutils.lstat(full_path)
1965- self._observed_sha1s[trans_id] = (o_sha1, st)
1966- for path, trans_id in new_paths:
1967- # new_paths includes stuff like workingtree conflicts. Only the
1968- # stuff in new_contents actually comes from limbo.
1969- if trans_id in self._limbo_files:
1970- del self._limbo_files[trans_id]
1971- self._new_contents.clear()
1972- return modified_paths
1973-
1974- def _apply_observed_sha1s(self):
1975- """After we have finished renaming everything, update observed sha1s
1976-
1977- This has to be done after self._tree.apply_inventory_delta, otherwise
1978- it doesn't know anything about the files we are updating. Also, we want
1979- to do this as late as possible, so that most entries end up cached.
1980- """
1981- # TODO: this doesn't update the stat information for directories. So
1982- # the first 'bzr status' will still need to rewrite
1983- # .bzr/checkout/dirstate. However, we at least don't need to
1984- # re-read all of the files.
1985- # TODO: If the operation took a while, we could do a time.sleep(3) here
1986- # to allow the clock to tick over and ensure we won't have any
1987- # problems. (we could observe start time, and finish time, and if
1988- # it is less than eg 10% overhead, add a sleep call.)
1989- paths = FinalPaths(self)
1990- for trans_id, observed in viewitems(self._observed_sha1s):
1991- path = paths.get_path(trans_id)
1992- self._tree._observed_sha1(path, observed)
1993-
1994-
1995-class TransformPreview(DiskTreeTransform):
1996- """A TreeTransform for generating preview trees.
1997-
1998- Unlike TreeTransform, this version works when the input tree is a
1999- RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
2000- unversioned files in the input tree.
2001- """
2002-
2003- def __init__(self, tree, pb=None, case_sensitive=True):
2004- tree.lock_read()
2005- limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
2006- DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
2007-
2008- def tree_kind(self, trans_id):
2009- path = self._tree_id_paths.get(trans_id)
2010- if path is None:
2011- return None
2012- kind = self._tree.path_content_summary(path)[0]
2013- if kind == 'missing':
2014- kind = None
2015- return kind
2016-
2017- def _set_mode(self, trans_id, mode_id, typefunc):
2018- """Set the mode of new file contents.
2019- The mode_id is the existing file to get the mode from (often the same
2020- as trans_id). The operation is only performed if there's a mode match
2021- according to typefunc.
2022- """
2023- # is it ok to ignore this? probably
2024- pass
2025-
2026- def iter_tree_children(self, parent_id):
2027- """Iterate through the entry's tree children, if any"""
2028- try:
2029- path = self._tree_id_paths[parent_id]
2030- except KeyError:
2031- return
2032- try:
2033- entry = next(self._tree.iter_entries_by_dir(
2034- specific_files=[path]))[1]
2035- except StopIteration:
2036- return
2037- children = getattr(entry, 'children', {})
2038- for child in children:
2039- childpath = joinpath(path, child)
2040- yield self.trans_id_tree_path(childpath)
2041-
2042- def new_orphan(self, trans_id, parent_id):
2043- raise NotImplementedError(self.new_orphan)
2044-
2045-
2046-class _PreviewTree(inventorytree.InventoryTree):
2047- """Partial implementation of Tree to support show_diff_trees"""
2048-
2049- def __init__(self, transform):
2050- self._transform = transform
2051- self._final_paths = FinalPaths(transform)
2052- self.__by_parent = None
2053- self._parent_ids = []
2054- self._all_children_cache = {}
2055- self._path2trans_id_cache = {}
2056- self._final_name_cache = {}
2057- self._iter_changes_cache = dict((c.file_id, c) for c in
2058- self._transform.iter_changes())
2059-
2060- def supports_tree_reference(self):
2061- # TODO(jelmer): Support tree references in _PreviewTree.
2062- # return self._transform._tree.supports_tree_reference()
2063- return False
2064-
2065- def _content_change(self, file_id):
2066- """Return True if the content of this file changed"""
2067- changes = self._iter_changes_cache.get(file_id)
2068- return (changes is not None and changes.changed_content)
2069-
2070- def _get_repository(self):
2071- repo = getattr(self._transform._tree, '_repository', None)
2072- if repo is None:
2073- repo = self._transform._tree.branch.repository
2074- return repo
2075-
2076- def _iter_parent_trees(self):
2077- for revision_id in self.get_parent_ids():
2078- try:
2079- yield self.revision_tree(revision_id)
2080- except errors.NoSuchRevisionInTree:
2081- yield self._get_repository().revision_tree(revision_id)
2082-
2083- def _get_file_revision(self, path, file_id, vf, tree_revision):
2084- parent_keys = [
2085- (file_id, t.get_file_revision(t.id2path(file_id)))
2086- for t in self._iter_parent_trees()]
2087- vf.add_lines((file_id, tree_revision), parent_keys,
2088- self.get_file_lines(path))
2089- repo = self._get_repository()
2090- base_vf = repo.texts
2091- if base_vf not in vf.fallback_versionedfiles:
2092- vf.fallback_versionedfiles.append(base_vf)
2093- return tree_revision
2094-
2095- def _stat_limbo_file(self, trans_id):
2096- name = self._transform._limbo_name(trans_id)
2097- return os.lstat(name)
2098-
2099- @property
2100- def _by_parent(self):
2101- if self.__by_parent is None:
2102- self.__by_parent = self._transform.by_parent()
2103- return self.__by_parent
2104-
2105- def _comparison_data(self, entry, path):
2106- kind, size, executable, link_or_sha1 = self.path_content_summary(path)
2107- if kind == 'missing':
2108- kind = None
2109- executable = False
2110- else:
2111- executable = self.is_executable(path)
2112- return kind, executable, None
2113-
2114- def is_locked(self):
2115- return False
2116-
2117- def lock_read(self):
2118- # Perhaps in theory, this should lock the TreeTransform?
2119- return lock.LogicalLockResult(self.unlock)
2120-
2121- def unlock(self):
2122- pass
2123-
2124- @property
2125- def root_inventory(self):
2126- """This Tree does not use inventory as its backing data."""
2127- raise NotImplementedError(_PreviewTree.root_inventory)
2128-
2129- def all_file_ids(self):
2130- tree_ids = set(self._transform._tree.all_file_ids())
2131- tree_ids.difference_update(self._transform.tree_file_id(t)
2132- for t in self._transform._removed_id)
2133- tree_ids.update(viewvalues(self._transform._new_id))
2134- return tree_ids
2135-
2136- def all_versioned_paths(self):
2137- tree_paths = set(self._transform._tree.all_versioned_paths())
2138-
2139- tree_paths.difference_update(
2140- self._transform.trans_id_tree_path(t)
2141- for t in self._transform._removed_id)
2142-
2143- tree_paths.update(
2144- self._final_paths._determine_path(t)
2145- for t in self._transform._new_id)
2146-
2147- return tree_paths
2148-
2149- def _path2trans_id(self, path):
2150- # We must not use None here, because that is a valid value to store.
2151- trans_id = self._path2trans_id_cache.get(path, object)
2152- if trans_id is not object:
2153- return trans_id
2154- segments = splitpath(path)
2155- cur_parent = self._transform.root
2156- for cur_segment in segments:
2157- for child in self._all_children(cur_parent):
2158- final_name = self._final_name_cache.get(child)
2159- if final_name is None:
2160- final_name = self._transform.final_name(child)
2161- self._final_name_cache[child] = final_name
2162- if final_name == cur_segment:
2163- cur_parent = child
2164- break
2165- else:
2166- self._path2trans_id_cache[path] = None
2167- return None
2168- self._path2trans_id_cache[path] = cur_parent
2169- return cur_parent
2170-
2171- def path2id(self, path):
2172- if isinstance(path, list):
2173- if path == []:
2174- path = [""]
2175- path = osutils.pathjoin(*path)
2176- return self._transform.final_file_id(self._path2trans_id(path))
2177-
2178- def id2path(self, file_id, recurse='down'):
2179- trans_id = self._transform.trans_id_file_id(file_id)
2180- try:
2181- return self._final_paths._determine_path(trans_id)
2182- except NoFinalPath:
2183- raise errors.NoSuchId(self, file_id)
2184-
2185- def _all_children(self, trans_id):
2186- children = self._all_children_cache.get(trans_id)
2187- if children is not None:
2188- return children
2189- children = set(self._transform.iter_tree_children(trans_id))
2190- # children in the _new_parent set are provided by _by_parent.
2191- children.difference_update(self._transform._new_parent)
2192- children.update(self._by_parent.get(trans_id, []))
2193- self._all_children_cache[trans_id] = children
2194- return children
2195-
2196- def extras(self):
2197- possible_extras = set(self._transform.trans_id_tree_path(p) for p
2198- in self._transform._tree.extras())
2199- possible_extras.update(self._transform._new_contents)
2200- possible_extras.update(self._transform._removed_id)
2201- for trans_id in possible_extras:
2202- if not self._transform.final_is_versioned(trans_id):
2203- yield self._final_paths._determine_path(trans_id)
2204-
2205- def _make_inv_entries(self, ordered_entries, specific_files=None):
2206- for trans_id, parent_file_id in ordered_entries:
2207- file_id = self._transform.final_file_id(trans_id)
2208- if file_id is None:
2209- continue
2210- if (specific_files is not None
2211- and self._final_paths.get_path(trans_id) not in specific_files):
2212- continue
2213- kind = self._transform.final_kind(trans_id)
2214- if kind is None:
2215- kind = self._transform._tree.stored_kind(
2216- self._transform._tree.id2path(file_id))
2217- new_entry = inventory.make_entry(
2218- kind,
2219- self._transform.final_name(trans_id),
2220- parent_file_id, file_id)
2221- yield new_entry, trans_id
2222-
2223- def _list_files_by_dir(self):
2224- todo = [ROOT_PARENT]
2225- ordered_ids = []
2226- while len(todo) > 0:
2227- parent = todo.pop()
2228- parent_file_id = self._transform.final_file_id(parent)
2229- children = list(self._all_children(parent))
2230- paths = dict(zip(children, self._final_paths.get_paths(children)))
2231- children.sort(key=paths.get)
2232- todo.extend(reversed(children))
2233- for trans_id in children:
2234- ordered_ids.append((trans_id, parent_file_id))
2235- return ordered_ids
2236-
2237- def iter_child_entries(self, path):
2238- trans_id = self._path2trans_id(path)
2239- if trans_id is None:
2240- raise errors.NoSuchFile(path)
2241- todo = [(child_trans_id, trans_id) for child_trans_id in
2242- self._all_children(trans_id)]
2243- for entry, trans_id in self._make_inv_entries(todo):
2244- yield entry
2245-
2246- def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
2247- if recurse_nested:
2248- raise NotImplementedError(
2249- 'follow tree references not yet supported')
2250-
2251- # This may not be a maximally efficient implementation, but it is
2252- # reasonably straightforward. An implementation that grafts the
2253- # TreeTransform changes onto the tree's iter_entries_by_dir results
2254- # might be more efficient, but requires tricky inferences about stack
2255- # position.
2256- ordered_ids = self._list_files_by_dir()
2257- for entry, trans_id in self._make_inv_entries(ordered_ids,
2258- specific_files):
2259- yield self._final_paths.get_path(trans_id), entry
2260-
2261- def _iter_entries_for_dir(self, dir_path):
2262- """Return path, entry for items in a directory without recursing down."""
2263- ordered_ids = []
2264- dir_trans_id = self._path2trans_id(dir_path)
2265- dir_id = self._transform.final_file_id(dir_trans_id)
2266- for child_trans_id in self._all_children(dir_trans_id):
2267- ordered_ids.append((child_trans_id, dir_id))
2268- path_entries = []
2269- for entry, trans_id in self._make_inv_entries(ordered_ids):
2270- path_entries.append((self._final_paths.get_path(trans_id), entry))
2271- path_entries.sort()
2272- return path_entries
2273-
2274- def list_files(self, include_root=False, from_dir=None, recursive=True,
2275- recurse_nested=False):
2276- """See WorkingTree.list_files."""
2277- if recurse_nested:
2278- raise NotImplementedError(
2279- 'follow tree references not yet supported')
2280-
2281- # XXX This should behave like WorkingTree.list_files, but is really
2282- # more like RevisionTree.list_files.
2283- if from_dir == '.':
2284- from_dir = None
2285- if recursive:
2286- prefix = None
2287- if from_dir:
2288- prefix = from_dir + '/'
2289- entries = self.iter_entries_by_dir()
2290- for path, entry in entries:
2291- if entry.name == '' and not include_root:
2292- continue
2293- if prefix:
2294- if not path.startswith(prefix):
2295- continue
2296- path = path[len(prefix):]
2297- yield path, 'V', entry.kind, entry
2298- else:
2299- if from_dir is None and include_root is True:
2300- root_entry = inventory.make_entry(
2301- 'directory', '', ROOT_PARENT, self.path2id(''))
2302- yield '', 'V', 'directory', root_entry
2303- entries = self._iter_entries_for_dir(from_dir or '')
2304- for path, entry in entries:
2305- yield path, 'V', entry.kind, entry
2306-
2307- def kind(self, path):
2308- trans_id = self._path2trans_id(path)
2309- if trans_id is None:
2310- raise errors.NoSuchFile(path)
2311- return self._transform.final_kind(trans_id)
2312-
2313- def stored_kind(self, path):
2314- trans_id = self._path2trans_id(path)
2315- if trans_id is None:
2316- raise errors.NoSuchFile(path)
2317- try:
2318- return self._transform._new_contents[trans_id]
2319- except KeyError:
2320- return self._transform._tree.stored_kind(path)
2321-
2322- def get_file_mtime(self, path):
2323- """See Tree.get_file_mtime"""
2324- file_id = self.path2id(path)
2325- if file_id is None:
2326- raise errors.NoSuchFile(path)
2327- if not self._content_change(file_id):
2328- return self._transform._tree.get_file_mtime(
2329- self._transform._tree.id2path(file_id))
2330- trans_id = self._path2trans_id(path)
2331- return self._stat_limbo_file(trans_id).st_mtime
2332-
2333- def get_file_size(self, path):
2334- """See Tree.get_file_size"""
2335- trans_id = self._path2trans_id(path)
2336- if trans_id is None:
2337- raise errors.NoSuchFile(path)
2338- kind = self._transform.final_kind(trans_id)
2339- if kind != 'file':
2340- return None
2341- if trans_id in self._transform._new_contents:
2342- return self._stat_limbo_file(trans_id).st_size
2343- if self.kind(path) == 'file':
2344- return self._transform._tree.get_file_size(path)
2345- else:
2346- return None
2347-
2348- def get_file_verifier(self, path, stat_value=None):
2349- trans_id = self._path2trans_id(path)
2350- if trans_id is None:
2351- raise errors.NoSuchFile(path)
2352- kind = self._transform._new_contents.get(trans_id)
2353- if kind is None:
2354- return self._transform._tree.get_file_verifier(path)
2355- if kind == 'file':
2356- with self.get_file(path) as fileobj:
2357- return ("SHA1", sha_file(fileobj))
2358-
2359- def get_file_sha1(self, path, stat_value=None):
2360- trans_id = self._path2trans_id(path)
2361- if trans_id is None:
2362- raise errors.NoSuchFile(path)
2363- kind = self._transform._new_contents.get(trans_id)
2364- if kind is None:
2365- return self._transform._tree.get_file_sha1(path)
2366- if kind == 'file':
2367- with self.get_file(path) as fileobj:
2368- return sha_file(fileobj)
2369-
2370- def get_reference_revision(self, path):
2371- trans_id = self._path2trans_id(path)
2372- if trans_id is None:
2373- raise errors.NoSuchFile(path)
2374- reference_revision = self._transform._new_reference_revision.get(trans_id)
2375- if reference_revision is None:
2376- return self._transform._tree.get_reference_revision(path)
2377- return reference_revision
2378-
2379- def is_executable(self, path):
2380- trans_id = self._path2trans_id(path)
2381- if trans_id is None:
2382- return False
2383- try:
2384- return self._transform._new_executability[trans_id]
2385- except KeyError:
2386- try:
2387- return self._transform._tree.is_executable(path)
2388- except OSError as e:
2389- if e.errno == errno.ENOENT:
2390- return False
2391- raise
2392- except errors.NoSuchFile:
2393- return False
2394-
2395- def has_filename(self, path):
2396- trans_id = self._path2trans_id(path)
2397- if trans_id in self._transform._new_contents:
2398- return True
2399- elif trans_id in self._transform._removed_contents:
2400- return False
2401- else:
2402- return self._transform._tree.has_filename(path)
2403-
2404- def path_content_summary(self, path):
2405- trans_id = self._path2trans_id(path)
2406- tt = self._transform
2407- tree_path = tt._tree_id_paths.get(trans_id)
2408- kind = tt._new_contents.get(trans_id)
2409- if kind is None:
2410- if tree_path is None or trans_id in tt._removed_contents:
2411- return 'missing', None, None, None
2412- summary = tt._tree.path_content_summary(tree_path)
2413- kind, size, executable, link_or_sha1 = summary
2414- else:
2415- link_or_sha1 = None
2416- limbo_name = tt._limbo_name(trans_id)
2417- if trans_id in tt._new_reference_revision:
2418- kind = 'tree-reference'
2419- if kind == 'file':
2420- statval = os.lstat(limbo_name)
2421- size = statval.st_size
2422- if not tt._limbo_supports_executable():
2423- executable = False
2424- else:
2425- executable = statval.st_mode & S_IEXEC
2426- else:
2427- size = None
2428- executable = None
2429- if kind == 'symlink':
2430- link_or_sha1 = os.readlink(limbo_name)
2431- if not isinstance(link_or_sha1, text_type):
2432- link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
2433- executable = tt._new_executability.get(trans_id, executable)
2434- return kind, size, executable, link_or_sha1
2435-
2436- def iter_changes(self, from_tree, include_unchanged=False,
2437- specific_files=None, pb=None, extra_trees=None,
2438- require_versioned=True, want_unversioned=False):
2439- """See InterTree.iter_changes.
2440-
2441- This has a fast path that is only used when the from_tree matches
2442- the transform tree, and no fancy options are supplied.
2443- """
2444- if (from_tree is not self._transform._tree or include_unchanged
2445- or specific_files or want_unversioned):
2446- from .bzr.inventorytree import InterInventoryTree
2447- return InterInventoryTree(from_tree, self).iter_changes(
2448- include_unchanged=include_unchanged,
2449- specific_files=specific_files,
2450- pb=pb,
2451- extra_trees=extra_trees,
2452- require_versioned=require_versioned,
2453- want_unversioned=want_unversioned)
2454- if want_unversioned:
2455- raise ValueError('want_unversioned is not supported')
2456- return self._transform.iter_changes()
2457-
2458- def get_file(self, path):
2459- """See Tree.get_file"""
2460- file_id = self.path2id(path)
2461- if not self._content_change(file_id):
2462- return self._transform._tree.get_file(path)
2463- trans_id = self._path2trans_id(path)
2464- name = self._transform._limbo_name(trans_id)
2465- return open(name, 'rb')
2466-
2467- def get_file_with_stat(self, path):
2468- return self.get_file(path), None
2469-
2470- def annotate_iter(self, path,
2471- default_revision=_mod_revision.CURRENT_REVISION):
2472- file_id = self.path2id(path)
2473- changes = self._iter_changes_cache.get(file_id)
2474- if changes is None:
2475- get_old = True
2476- else:
2477- changed_content, versioned, kind = (
2478- changes.changed_content, changes.versioned, changes.kind)
2479- if kind[1] is None:
2480- return None
2481- get_old = (kind[0] == 'file' and versioned[0])
2482- if get_old:
2483- old_annotation = self._transform._tree.annotate_iter(
2484- path, default_revision=default_revision)
2485- else:
2486- old_annotation = []
2487- if changes is None:
2488- return old_annotation
2489- if not changed_content:
2490- return old_annotation
2491- # TODO: This is doing something similar to what WT.annotate_iter is
2492- # doing, however it fails slightly because it doesn't know what
2493- # the *other* revision_id is, so it doesn't know how to give the
2494- # other as the origin for some lines, they all get
2495- # 'default_revision'
2496- # It would be nice to be able to use the new Annotator based
2497- # approach, as well.
2498- return annotate.reannotate([old_annotation],
2499- self.get_file(path).readlines(),
2500- default_revision)
2501-
2502- def get_symlink_target(self, path):
2503- """See Tree.get_symlink_target"""
2504- file_id = self.path2id(path)
2505- if not self._content_change(file_id):
2506- return self._transform._tree.get_symlink_target(path)
2507- trans_id = self._path2trans_id(path)
2508- name = self._transform._limbo_name(trans_id)
2509- return osutils.readlink(name)
2510-
2511- def walkdirs(self, prefix=''):
2512- pending = [self._transform.root]
2513- while len(pending) > 0:
2514- parent_id = pending.pop()
2515- children = []
2516- subdirs = []
2517- prefix = prefix.rstrip('/')
2518- parent_path = self._final_paths.get_path(parent_id)
2519- parent_file_id = self._transform.final_file_id(parent_id)
2520- for child_id in self._all_children(parent_id):
2521- path_from_root = self._final_paths.get_path(child_id)
2522- basename = self._transform.final_name(child_id)
2523- file_id = self._transform.final_file_id(child_id)
2524- kind = self._transform.final_kind(child_id)
2525- if kind is not None:
2526- versioned_kind = kind
2527- else:
2528- kind = 'unknown'
2529- versioned_kind = self._transform._tree.stored_kind(
2530- self._transform._tree.id2path(file_id))
2531- if versioned_kind == 'directory':
2532- subdirs.append(child_id)
2533- children.append((path_from_root, basename, kind, None,
2534- file_id, versioned_kind))
2535- children.sort()
2536- if parent_path.startswith(prefix):
2537- yield (parent_path, parent_file_id), children
2538- pending.extend(sorted(subdirs, key=self._final_paths.get_path,
2539- reverse=True))
2540-
2541- def get_parent_ids(self):
2542- return self._parent_ids
2543-
2544- def set_parent_ids(self, parent_ids):
2545- self._parent_ids = parent_ids
2546-
2547- def get_revision_tree(self, revision_id):
2548- return self._transform._tree.get_revision_tree(revision_id)
2549+ raise NotImplementedError(self.apply)
2550
2551
2552 def joinpath(parent, child):
2553@@ -3278,7 +2481,7 @@
2554 raise errors.FileExists(to, str(e))
2555 # normal OSError doesn't include filenames so it's hard to see where
2556 # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
2557- raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
2558+ raise TransformRenameFailed(from_, to, str(e), e.errno)
2559 self.past_renames.append((from_, to))
2560
2561 def pre_delete(self, from_, to):
2562@@ -3297,7 +2500,7 @@
2563 try:
2564 os.rename(to, from_)
2565 except OSError as e:
2566- raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
2567+ raise TransformRenameFailed(to, from_, str(e), e.errno)
2568 # after rollback, don't reuse _FileMover
2569 self.past_renames = None
2570 self.pending_deletions = None

Subscribers

People subscribed via source and target branches