1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
from stat import S_ISREG, S_IEXEC
24
config as _mod_config,
33
lazy_import.lazy_import(globals(), """
37
revision as _mod_revision,
41
from breezy.i18n import gettext
44
from .errors import (DuplicateKey,
45
BzrError, InternalBzrError)
46
from .filters import filtered_output_bytes, ContentFilterContext
47
from .mutabletree import MutableTree
48
from .osutils import (
56
from .progress import ProgressPhase
64
ROOT_PARENT = "root-parent"
67
class NoFinalPath(BzrError):
69
_fmt = ("No final name for trans_id %(trans_id)r\n"
70
"file-id: %(file_id)r\n"
71
"root trans-id: %(root_trans_id)r\n")
73
def __init__(self, trans_id, transform):
74
self.trans_id = trans_id
75
self.file_id = transform.final_file_id(trans_id)
76
self.root_trans_id = transform.root
79
class ReusingTransform(BzrError):
81
_fmt = "Attempt to reuse a transform that has already been applied."
84
class MalformedTransform(InternalBzrError):
86
_fmt = "Tree transform is malformed %(conflicts)r"
89
class CantMoveRoot(BzrError):
91
_fmt = "Moving the root directory is not supported at this time"
94
class ImmortalLimbo(BzrError):
96
_fmt = """Unable to delete transform temporary directory %(limbo_dir)s.
97
Please examine %(limbo_dir)s to see if it contains any files you wish to
98
keep, and delete it when you are done."""
100
def __init__(self, limbo_dir):
101
BzrError.__init__(self)
102
self.limbo_dir = limbo_dir
105
class TransformRenameFailed(BzrError):
107
_fmt = "Failed to rename %(from_path)s to %(to_path)s: %(why)s"
109
def __init__(self, from_path, to_path, why, errno):
110
self.from_path = from_path
111
self.to_path = to_path
116
def unique_add(map, key, value):
118
raise DuplicateKey(key=key)
122
class _TransformResults(object):
124
def __init__(self, modified_paths, rename_count):
125
object.__init__(self)
126
self.modified_paths = modified_paths
127
self.rename_count = rename_count
130
class TreeTransform(object):
131
"""Represent a tree transformation.
133
This object is designed to support incremental generation of the transform,
136
However, it gives optimum performance when parent directories are created
137
before their contents. The transform is then able to put child files
138
directly in their parent directory, avoiding later renames.
140
It is easy to produce malformed transforms, but they are generally
141
harmless. Attempting to apply a malformed transform will cause an
142
exception to be raised before any modifications are made to the tree.
144
Many kinds of malformed transforms can be corrected with the
145
resolve_conflicts function. The remaining ones indicate programming error,
146
such as trying to create a file with no path.
148
Two sets of file creation methods are supplied. Convenience methods are:
153
These are composed of the low-level methods:
155
* create_file or create_directory or create_symlink
159
Transform/Transaction ids
160
-------------------------
161
trans_ids are temporary ids assigned to all files involved in a transform.
162
It's possible, even common, that not all files in the Tree have trans_ids.
164
trans_ids are only valid for the TreeTransform that generated them.
167
def __init__(self, tree, pb=None):
172
# Mapping of path in old tree -> trans_id
173
self._tree_path_ids = {}
174
# Mapping trans_id -> path in old tree
175
self._tree_id_paths = {}
176
# mapping of trans_id -> new basename
178
# mapping of trans_id -> new parent trans_id
179
self._new_parent = {}
180
# mapping of trans_id with new contents -> new file_kind
181
self._new_contents = {}
182
# Set of trans_ids whose contents will be removed
183
self._removed_contents = set()
184
# Mapping of trans_id -> new execute-bit value
185
self._new_executability = {}
186
# Mapping of trans_id -> new tree-reference value
187
self._new_reference_revision = {}
188
# Set of trans_ids that will be removed
189
self._removed_id = set()
190
# Indicator of whether the transform has been applied
194
"""Support Context Manager API."""
197
def __exit__(self, exc_type, exc_val, exc_tb):
198
"""Support Context Manager API."""
201
def iter_tree_children(self, trans_id):
202
"""Iterate through the entry's tree children, if any.
204
:param trans_id: trans id to iterate
205
:returns: Iterator over paths
207
raise NotImplementedError(self.iter_tree_children)
209
def canonical_path(self, path):
212
def tree_kind(self, trans_id):
213
raise NotImplementedError(self.tree_kind)
216
"""Return a map of parent: children for known parents.
218
Only new paths and parents of tree files with assigned ids are used.
221
items = list(self._new_parent.items())
222
items.extend((t, self.final_parent(t))
223
for t in list(self._tree_id_paths))
224
for trans_id, parent_id in items:
225
if parent_id not in by_parent:
226
by_parent[parent_id] = set()
227
by_parent[parent_id].add(trans_id)
231
"""Release the working tree lock, if held.
233
This is required if apply has not been invoked, but can be invoked
236
raise NotImplementedError(self.finalize)
238
def create_path(self, name, parent):
239
"""Assign a transaction id to a new path"""
240
trans_id = self._assign_id()
241
unique_add(self._new_name, trans_id, name)
242
unique_add(self._new_parent, trans_id, parent)
245
def adjust_path(self, name, parent, trans_id):
246
"""Change the path that is assigned to a transaction id."""
248
raise ValueError("Parent trans-id may not be None")
249
if trans_id == self._new_root:
251
self._new_name[trans_id] = name
252
self._new_parent[trans_id] = parent
254
def adjust_root_path(self, name, parent):
255
"""Emulate moving the root by moving all children, instead.
257
We do this by undoing the association of root's transaction id with the
258
current tree. This allows us to create a new directory with that
259
transaction id. We unversion the root directory and version the
260
physically new directory, and hope someone versions the tree root
263
raise NotImplementedError(self.adjust_root_path)
265
def fixup_new_roots(self):
266
"""Reinterpret requests to change the root directory
268
Instead of creating a root directory, or moving an existing directory,
269
all the attributes and children of the new root are applied to the
270
existing root directory.
272
This means that the old root trans-id becomes obsolete, so it is
273
recommended only to invoke this after the root trans-id has become
276
raise NotImplementedError(self.fixup_new_roots)
278
def _assign_id(self):
279
"""Produce a new tranform id"""
280
new_id = "new-%s" % self._id_number
284
def trans_id_tree_path(self, path):
285
"""Determine (and maybe set) the transaction ID for a tree path."""
286
path = self.canonical_path(path)
287
if path not in self._tree_path_ids:
288
self._tree_path_ids[path] = self._assign_id()
289
self._tree_id_paths[self._tree_path_ids[path]] = path
290
return self._tree_path_ids[path]
292
def get_tree_parent(self, trans_id):
293
"""Determine id of the parent in the tree."""
294
path = self._tree_id_paths[trans_id]
297
return self.trans_id_tree_path(os.path.dirname(path))
299
def delete_contents(self, trans_id):
300
"""Schedule the contents of a path entry for deletion"""
301
kind = self.tree_kind(trans_id)
303
self._removed_contents.add(trans_id)
305
def cancel_deletion(self, trans_id):
306
"""Cancel a scheduled deletion"""
307
self._removed_contents.remove(trans_id)
309
def delete_versioned(self, trans_id):
310
"""Delete and unversion a versioned file"""
311
self.delete_contents(trans_id)
312
self.unversion_file(trans_id)
314
def set_executability(self, executability, trans_id):
315
"""Schedule setting of the 'execute' bit
316
To unschedule, set to None
318
if executability is None:
319
del self._new_executability[trans_id]
321
unique_add(self._new_executability, trans_id, executability)
323
def set_tree_reference(self, revision_id, trans_id):
324
"""Set the reference associated with a directory"""
325
unique_add(self._new_reference_revision, trans_id, revision_id)
327
def version_file(self, trans_id, file_id=None):
328
"""Schedule a file to become versioned."""
329
raise NotImplementedError(self.version_file)
331
def cancel_versioning(self, trans_id):
332
"""Undo a previous versioning of a file"""
333
raise NotImplementedError(self.cancel_versioning)
335
def unversion_file(self, trans_id):
336
"""Schedule a path entry to become unversioned"""
337
self._removed_id.add(trans_id)
339
def new_paths(self, filesystem_only=False):
340
"""Determine the paths of all new and changed files.
342
:param filesystem_only: if True, only calculate values for files
343
that require renames or execute bit changes.
345
raise NotImplementedError(self.new_paths)
347
def final_kind(self, trans_id):
348
"""Determine the final file kind, after any changes applied.
350
:return: None if the file does not exist/has no contents. (It is
351
conceivable that a path would be created without the corresponding
352
contents insertion command)
354
if trans_id in self._new_contents:
355
return self._new_contents[trans_id]
356
elif trans_id in self._removed_contents:
359
return self.tree_kind(trans_id)
361
def tree_path(self, trans_id):
362
"""Determine the tree path associated with the trans_id."""
363
return self._tree_id_paths.get(trans_id)
365
def final_is_versioned(self, trans_id):
366
raise NotImplementedError(self.final_is_versioned)
368
def final_parent(self, trans_id):
369
"""Determine the parent file_id, after any changes are applied.
371
ROOT_PARENT is returned for the tree root.
374
return self._new_parent[trans_id]
376
return self.get_tree_parent(trans_id)
378
def final_name(self, trans_id):
379
"""Determine the final filename, after all changes are applied."""
381
return self._new_name[trans_id]
384
return os.path.basename(self._tree_id_paths[trans_id])
386
raise NoFinalPath(trans_id, self)
388
def path_changed(self, trans_id):
389
"""Return True if a trans_id's path has changed."""
390
return (trans_id in self._new_name) or (trans_id in self._new_parent)
392
def new_contents(self, trans_id):
393
return (trans_id in self._new_contents)
395
def find_conflicts(self):
396
"""Find any violations of inventory or filesystem invariants"""
397
raise NotImplementedError(self.find_conflicts)
399
def new_file(self, name, parent_id, contents, file_id=None,
400
executable=None, sha1=None):
401
"""Convenience method to create files.
403
name is the name of the file to create.
404
parent_id is the transaction id of the parent directory of the file.
405
contents is an iterator of bytestrings, which will be used to produce
407
:param file_id: The inventory ID of the file, if it is to be versioned.
408
:param executable: Only valid when a file_id has been supplied.
410
raise NotImplementedError(self.new_file)
412
def new_directory(self, name, parent_id, file_id=None):
413
"""Convenience method to create directories.
415
name is the name of the directory to create.
416
parent_id is the transaction id of the parent directory of the
418
file_id is the inventory ID of the directory, if it is to be versioned.
420
raise NotImplementedError(self.new_directory)
422
def new_symlink(self, name, parent_id, target, file_id=None):
423
"""Convenience method to create symbolic link.
425
name is the name of the symlink to create.
426
parent_id is the transaction id of the parent directory of the symlink.
427
target is a bytestring of the target of the symlink.
428
file_id is the inventory ID of the file, if it is to be versioned.
430
raise NotImplementedError(self.new_symlink)
432
def new_orphan(self, trans_id, parent_id):
433
"""Schedule an item to be orphaned.
435
When a directory is about to be removed, its children, if they are not
436
versioned are moved out of the way: they don't have a parent anymore.
438
:param trans_id: The trans_id of the existing item.
439
:param parent_id: The parent trans_id of the item.
441
raise NotImplementedError(self.new_orphan)
443
def iter_changes(self):
444
"""Produce output in the same format as Tree.iter_changes.
446
Will produce nonsensical results if invoked while inventory/filesystem
447
conflicts (as reported by TreeTransform.find_conflicts()) are present.
449
This reads the Transform, but only reproduces changes involving a
450
file_id. Files that are not versioned in either of the FROM or TO
451
states are not reflected.
453
raise NotImplementedError(self.iter_changes)
455
def get_preview_tree(self):
456
"""Return a tree representing the result of the transform.
458
The tree is a snapshot, and altering the TreeTransform will invalidate
461
raise NotImplementedError(self.get_preview_tree)
463
def commit(self, branch, message, merge_parents=None, strict=False,
464
timestamp=None, timezone=None, committer=None, authors=None,
465
revprops=None, revision_id=None):
466
"""Commit the result of this TreeTransform to a branch.
468
:param branch: The branch to commit to.
469
:param message: The message to attach to the commit.
470
:param merge_parents: Additional parent revision-ids specified by
472
:param strict: If True, abort the commit if there are unversioned
474
:param timestamp: if not None, seconds-since-epoch for the time and
475
date. (May be a float.)
476
:param timezone: Optional timezone for timestamp, as an offset in
478
:param committer: Optional committer in email-id format.
479
(e.g. "J Random Hacker <jrandom@example.com>")
480
:param authors: Optional list of authors in email-id format.
481
:param revprops: Optional dictionary of revision properties.
482
:param revision_id: Optional revision id. (Specifying a revision-id
483
may reduce performance for some non-native formats.)
484
:return: The revision_id of the revision committed.
486
raise NotImplementedError(self.commit)
488
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
489
"""Schedule creation of a new file.
493
:param contents: an iterator of strings, all of which will be written
494
to the target destination.
495
:param trans_id: TreeTransform handle
496
:param mode_id: If not None, force the mode of the target file to match
497
the mode of the object referenced by mode_id.
498
Otherwise, we will try to preserve mode bits of an existing file.
499
:param sha1: If the sha1 of this content is already known, pass it in.
500
We can use it to prevent future sha1 computations.
502
raise NotImplementedError(self.create_file)
504
def create_directory(self, trans_id):
505
"""Schedule creation of a new directory.
507
See also new_directory.
509
raise NotImplementedError(self.create_directory)
511
def create_symlink(self, target, trans_id):
512
"""Schedule creation of a new symbolic link.
514
target is a bytestring.
515
See also new_symlink.
517
raise NotImplementedError(self.create_symlink)
519
def create_hardlink(self, path, trans_id):
520
"""Schedule creation of a hard link"""
521
raise NotImplementedError(self.create_hardlink)
523
def cancel_creation(self, trans_id):
524
"""Cancel the creation of new file contents."""
525
raise NotImplementedError(self.cancel_creation)
528
class OrphaningError(errors.BzrError):
530
# Only bugs could lead to such exception being seen by the user
531
internal_error = True
532
_fmt = "Error while orphaning %s in %s directory"
534
def __init__(self, orphan, parent):
535
errors.BzrError.__init__(self)
540
class OrphaningForbidden(OrphaningError):
542
_fmt = "Policy: %s doesn't allow creating orphans."
544
def __init__(self, policy):
545
errors.BzrError.__init__(self)
549
def move_orphan(tt, orphan_id, parent_id):
550
"""See TreeTransformBase.new_orphan.
552
This creates a new orphan in the `brz-orphans` dir at the root of the
555
:param tt: The TreeTransform orphaning `trans_id`.
557
:param orphan_id: The trans id that should be orphaned.
559
:param parent_id: The orphan parent trans id.
561
# Add the orphan dir if it doesn't exist
562
orphan_dir_basename = 'brz-orphans'
563
od_id = tt.trans_id_tree_path(orphan_dir_basename)
564
if tt.final_kind(od_id) is None:
565
tt.create_directory(od_id)
566
parent_path = tt._tree_id_paths[parent_id]
567
# Find a name that doesn't exist yet in the orphan dir
568
actual_name = tt.final_name(orphan_id)
569
new_name = tt._available_backup_name(actual_name, od_id)
570
tt.adjust_path(new_name, od_id, orphan_id)
571
trace.warning('%s has been orphaned in %s'
572
% (joinpath(parent_path, actual_name), orphan_dir_basename))
575
def refuse_orphan(tt, orphan_id, parent_id):
576
"""See TreeTransformBase.new_orphan.
578
This refuses to create orphan, letting the caller handle the conflict.
580
raise OrphaningForbidden('never')
583
orphaning_registry = registry.Registry()
584
orphaning_registry.register(
585
u'conflict', refuse_orphan,
586
'Leave orphans in place and create a conflict on the directory.')
587
orphaning_registry.register(
588
u'move', move_orphan,
589
'Move orphans into the brz-orphans directory.')
590
orphaning_registry._set_default_key(u'conflict')
593
opt_transform_orphan = _mod_config.RegistryOption(
594
'transform.orphan_policy', orphaning_registry,
595
help='Policy for orphaned files during transform operations.',
599
def joinpath(parent, child):
600
"""Join tree-relative paths, handling the tree root specially"""
601
if parent is None or parent == "":
604
return pathjoin(parent, child)
607
class FinalPaths(object):
608
"""Make path calculation cheap by memoizing paths.
610
The underlying tree must not be manipulated between calls, or else
611
the results will likely be incorrect.
614
def __init__(self, transform):
615
object.__init__(self)
616
self._known_paths = {}
617
self.transform = transform
619
def _determine_path(self, trans_id):
620
if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
622
name = self.transform.final_name(trans_id)
623
parent_id = self.transform.final_parent(trans_id)
624
if parent_id == self.transform.root:
627
return pathjoin(self.get_path(parent_id), name)
629
def get_path(self, trans_id):
630
"""Find the final path associated with a trans_id"""
631
if trans_id not in self._known_paths:
632
self._known_paths[trans_id] = self._determine_path(trans_id)
633
return self._known_paths[trans_id]
635
def get_paths(self, trans_ids):
636
return [(self.get_path(t), t) for t in trans_ids]
639
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
640
delta_from_tree=False):
641
"""Create working tree for a branch, using a TreeTransform.
643
This function should be used on empty trees, having a tree root at most.
644
(see merge and revert functionality for working with existing trees)
646
Existing files are handled like so:
648
- Existing bzrdirs take precedence over creating new items. They are
649
created as '%s.diverted' % name.
650
- Otherwise, if the content on disk matches the content we are building,
651
it is silently replaced.
652
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
654
:param tree: The tree to convert wt into a copy of
655
:param wt: The working tree that files will be placed into
656
:param accelerator_tree: A tree which can be used for retrieving file
657
contents more quickly than tree itself, i.e. a workingtree. tree
658
will be used for cases where accelerator_tree's content is different.
659
:param hardlink: If true, hard-link files to accelerator_tree, where
660
possible. accelerator_tree must implement abspath, i.e. be a
662
:param delta_from_tree: If true, build_tree may use the input Tree to
663
generate the inventory delta.
665
with contextlib.ExitStack() as exit_stack:
666
exit_stack.enter_context(wt.lock_tree_write())
667
exit_stack.enter_context(tree.lock_read())
668
if accelerator_tree is not None:
669
exit_stack.enter_context(accelerator_tree.lock_read())
670
return _build_tree(tree, wt, accelerator_tree, hardlink,
674
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
675
"""See build_tree."""
676
for num, _unused in enumerate(wt.all_versioned_paths()):
677
if num > 0: # more than just a root
678
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
680
top_pb = ui.ui_factory.nested_progress_bar()
681
pp = ProgressPhase("Build phase", 2, top_pb)
682
if tree.path2id('') is not None:
683
# This is kind of a hack: we should be altering the root
684
# as part of the regular tree shape diff logic.
685
# The conditional test here is to avoid doing an
686
# expensive operation (flush) every time the root id
687
# is set within the tree, nor setting the root and thus
688
# marking the tree as dirty, because we use two different
689
# idioms here: tree interfaces and inventory interfaces.
690
if wt.path2id('') != tree.path2id(''):
691
wt.set_root_id(tree.path2id(''))
697
file_trans_id[find_previous_path(wt, tree, '')] = tt.trans_id_tree_path('')
698
with ui.ui_factory.nested_progress_bar() as pb:
699
deferred_contents = []
701
total = len(tree.all_versioned_paths())
703
precomputed_delta = []
705
precomputed_delta = None
706
# Check if tree inventory has content. If so, we populate
707
# existing_files with the directory content. If there are no
708
# entries we skip populating existing_files as its not used.
709
# This improves performance and unncessary work on large
710
# directory trees. (#501307)
712
existing_files = set()
713
for dir, files in wt.walkdirs():
714
existing_files.update(f[0] for f in files)
715
for num, (tree_path, entry) in \
716
enumerate(tree.iter_entries_by_dir()):
717
pb.update(gettext("Building tree"), num
718
- len(deferred_contents), total)
719
if entry.parent_id is None:
722
file_id = entry.file_id
724
precomputed_delta.append((None, tree_path, file_id, entry))
725
if tree_path in existing_files:
726
target_path = wt.abspath(tree_path)
727
kind = file_kind(target_path)
728
if kind == "directory":
730
controldir.ControlDir.open(target_path)
731
except errors.NotBranchError:
734
divert.add(tree_path)
735
if (tree_path not in divert
737
tree, entry, tree_path, kind, target_path)):
738
tt.delete_contents(tt.trans_id_tree_path(tree_path))
739
if kind == 'directory':
741
parent_id = file_trans_id[osutils.dirname(tree_path)]
742
if entry.kind == 'file':
743
# We *almost* replicate new_by_entry, so that we can defer
744
# getting the file text, and get them all at once.
745
trans_id = tt.create_path(entry.name, parent_id)
746
file_trans_id[tree_path] = trans_id
747
tt.version_file(trans_id, file_id=file_id)
748
executable = tree.is_executable(tree_path)
750
tt.set_executability(executable, trans_id)
751
trans_data = (trans_id, tree_path, entry.text_sha1)
752
deferred_contents.append((tree_path, trans_data))
754
file_trans_id[tree_path] = new_by_entry(
755
tree_path, tt, entry, parent_id, tree)
757
new_trans_id = file_trans_id[tree_path]
758
old_parent = tt.trans_id_tree_path(tree_path)
759
_reparent_children(tt, old_parent, new_trans_id)
760
offset = num + 1 - len(deferred_contents)
761
_create_files(tt, tree, deferred_contents, pb, offset,
762
accelerator_tree, hardlink)
764
divert_trans = set(file_trans_id[f] for f in divert)
767
return resolve_checkout(t, c, divert_trans)
768
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
769
if len(raw_conflicts) > 0:
770
precomputed_delta = None
771
conflicts = cook_conflicts(raw_conflicts, tt)
772
for conflict in conflicts:
773
trace.warning(str(conflict))
775
wt.add_conflicts(conflicts)
776
except errors.UnsupportedOperation:
778
result = tt.apply(no_conflicts=True,
779
precomputed_delta=precomputed_delta)
786
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
788
total = len(desired_files) + offset
790
if accelerator_tree is None:
791
new_desired_files = desired_files
793
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
795
change.path for change in iter
796
if not (change.changed_content or change.executable[0] != change.executable[1])]
797
if accelerator_tree.supports_content_filtering():
798
unchanged = [(tp, ap) for (tp, ap) in unchanged
799
if not next(accelerator_tree.iter_search_rules([ap]))]
800
unchanged = dict(unchanged)
801
new_desired_files = []
803
for unused_tree_path, (trans_id, tree_path, text_sha1) in desired_files:
804
accelerator_path = unchanged.get(tree_path)
805
if accelerator_path is None:
806
new_desired_files.append((tree_path,
807
(trans_id, tree_path, text_sha1)))
809
pb.update(gettext('Adding file contents'), count + offset, total)
811
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
814
with accelerator_tree.get_file(accelerator_path) as f:
815
chunks = osutils.file_iterator(f)
816
if wt.supports_content_filtering():
817
filters = wt._content_filter_stack(tree_path)
818
chunks = filtered_output_bytes(chunks, filters,
819
ContentFilterContext(tree_path, tree))
820
tt.create_file(chunks, trans_id, sha1=text_sha1)
823
for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
824
tree.iter_files_bytes(new_desired_files)):
825
if wt.supports_content_filtering():
826
filters = wt._content_filter_stack(tree_path)
827
contents = filtered_output_bytes(contents, filters,
828
ContentFilterContext(tree_path, tree))
829
tt.create_file(contents, trans_id, sha1=text_sha1)
830
pb.update(gettext('Adding file contents'), count + offset, total)
833
def _reparent_children(tt, old_parent, new_parent):
834
for child in tt.iter_tree_children(old_parent):
835
tt.adjust_path(tt.final_name(child), new_parent, child)
838
def _reparent_transform_children(tt, old_parent, new_parent):
839
by_parent = tt.by_parent()
840
for child in by_parent[old_parent]:
841
tt.adjust_path(tt.final_name(child), new_parent, child)
842
return by_parent[old_parent]
845
def _content_match(tree, entry, tree_path, kind, target_path):
846
if entry.kind != kind:
848
if entry.kind == "directory":
850
if entry.kind == "file":
851
with open(target_path, 'rb') as f1, \
852
tree.get_file(tree_path) as f2:
853
if osutils.compare_files(f1, f2):
855
elif entry.kind == "symlink":
856
if tree.get_symlink_target(tree_path) == os.readlink(target_path):
861
def resolve_checkout(tt, conflicts, divert):
862
new_conflicts = set()
863
for c_type, conflict in ((c[0], c) for c in conflicts):
864
# Anything but a 'duplicate' would indicate programmer error
865
if c_type != 'duplicate':
866
raise AssertionError(c_type)
867
# Now figure out which is new and which is old
868
if tt.new_contents(conflict[1]):
869
new_file = conflict[1]
870
old_file = conflict[2]
872
new_file = conflict[2]
873
old_file = conflict[1]
875
# We should only get here if the conflict wasn't completely
877
final_parent = tt.final_parent(old_file)
878
if new_file in divert:
879
new_name = tt.final_name(old_file) + '.diverted'
880
tt.adjust_path(new_name, final_parent, new_file)
881
new_conflicts.add((c_type, 'Diverted to',
884
new_name = tt.final_name(old_file) + '.moved'
885
tt.adjust_path(new_name, final_parent, old_file)
886
new_conflicts.add((c_type, 'Moved existing file to',
891
def new_by_entry(path, tt, entry, parent_id, tree):
892
"""Create a new file according to its inventory entry"""
896
with tree.get_file(path) as f:
897
executable = tree.is_executable(path)
899
name, parent_id, osutils.file_iterator(f), entry.file_id,
901
elif kind in ('directory', 'tree-reference'):
902
trans_id = tt.new_directory(name, parent_id, entry.file_id)
903
if kind == 'tree-reference':
904
tt.set_tree_reference(entry.reference_revision, trans_id)
906
elif kind == 'symlink':
907
target = tree.get_symlink_target(path)
908
return tt.new_symlink(name, parent_id, target, entry.file_id)
910
raise errors.BadFileKindError(name, kind)
913
def create_from_tree(tt, trans_id, tree, path, chunks=None,
914
filter_tree_path=None):
915
"""Create new file contents according to tree contents.
917
:param filter_tree_path: the tree path to use to lookup
918
content filters to apply to the bytes output in the working tree.
919
This only applies if the working tree supports content filtering.
921
kind = tree.kind(path)
922
if kind == 'directory':
923
tt.create_directory(trans_id)
926
f = tree.get_file(path)
927
chunks = osutils.file_iterator(f)
932
if wt.supports_content_filtering() and filter_tree_path is not None:
933
filters = wt._content_filter_stack(filter_tree_path)
934
chunks = filtered_output_bytes(
936
ContentFilterContext(filter_tree_path, tree))
937
tt.create_file(chunks, trans_id)
941
elif kind == "symlink":
942
tt.create_symlink(tree.get_symlink_target(path), trans_id)
944
raise AssertionError('Unknown kind %r' % kind)
947
def create_entry_executability(tt, entry, trans_id):
948
"""Set the executability of a trans_id according to an inventory entry"""
949
if entry.kind == "file":
950
tt.set_executability(entry.executable, trans_id)
953
def revert(working_tree, target_tree, filenames, backups=False,
954
pb=None, change_reporter=None):
955
"""Revert a working tree's contents to those of a target tree."""
956
pb = ui.ui_factory.nested_progress_bar()
958
with target_tree.lock_read(), working_tree.transform(pb) as tt:
959
pp = ProgressPhase("Revert phase", 3, pb)
960
conflicts, merge_modified = _prepare_revert_transform(
961
working_tree, target_tree, tt, filenames, backups, pp)
964
change_reporter = delta._ChangeReporter(
965
unversioned_filter=working_tree.is_ignored)
966
delta.report_changes(tt.iter_changes(), change_reporter)
967
for conflict in conflicts:
968
trace.warning(str(conflict))
971
if working_tree.supports_merge_modified():
972
working_tree.set_merge_modified(merge_modified)
978
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
979
backups, pp, basis_tree=None,
980
merge_modified=None):
981
with ui.ui_factory.nested_progress_bar() as child_pb:
982
if merge_modified is None:
983
merge_modified = working_tree.merge_modified()
984
merge_modified = _alter_files(working_tree, target_tree, tt,
985
child_pb, filenames, backups,
986
merge_modified, basis_tree)
987
with ui.ui_factory.nested_progress_bar() as child_pb:
988
raw_conflicts = resolve_conflicts(
989
tt, child_pb, lambda t, c: conflict_pass(t, c, target_tree))
990
conflicts = cook_conflicts(raw_conflicts, tt)
991
return conflicts, merge_modified
994
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
995
backups, merge_modified, basis_tree=None):
996
if basis_tree is not None:
997
basis_tree.lock_read()
998
# We ask the working_tree for its changes relative to the target, rather
999
# than the target changes relative to the working tree. Because WT4 has an
1000
# optimizer to compare itself to a target, but no optimizer for the
1002
change_list = working_tree.iter_changes(
1003
target_tree, specific_files=specific_files, pb=pb)
1004
if not target_tree.is_versioned(u''):
1010
for id_num, change in enumerate(change_list):
1011
file_id = change.file_id
1012
target_path, wt_path = change.path
1013
target_versioned, wt_versioned = change.versioned
1014
target_parent, wt_parent = change.parent_id
1015
target_name, wt_name = change.name
1016
target_kind, wt_kind = change.kind
1017
target_executable, wt_executable = change.executable
1018
if skip_root and wt_parent is None:
1020
trans_id = tt.trans_id_file_id(file_id)
1022
if change.changed_content:
1023
keep_content = False
1024
if wt_kind == 'file' and (backups or target_kind is None):
1025
wt_sha1 = working_tree.get_file_sha1(wt_path)
1026
if merge_modified.get(wt_path) != wt_sha1:
1027
# acquire the basis tree lazily to prevent the
1028
# expense of accessing it when it's not needed ?
1029
# (Guessing, RBC, 200702)
1030
if basis_tree is None:
1031
basis_tree = working_tree.basis_tree()
1032
basis_tree.lock_read()
1033
basis_inter = InterTree.get(basis_tree, working_tree)
1034
basis_path = basis_inter.find_source_path(wt_path)
1035
if basis_path is None:
1036
if target_kind is None and not target_versioned:
1039
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
1041
if wt_kind is not None:
1042
if not keep_content:
1043
tt.delete_contents(trans_id)
1044
elif target_kind is not None:
1045
parent_trans_id = tt.trans_id_file_id(wt_parent)
1046
backup_name = tt._available_backup_name(
1047
wt_name, parent_trans_id)
1048
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1049
new_trans_id = tt.create_path(wt_name, parent_trans_id)
1050
if wt_versioned and target_versioned:
1051
tt.unversion_file(trans_id)
1052
tt.version_file(new_trans_id, file_id=file_id)
1053
# New contents should have the same unix perms as old
1056
trans_id = new_trans_id
1057
if target_kind in ('directory', 'tree-reference'):
1058
tt.create_directory(trans_id)
1059
if target_kind == 'tree-reference':
1060
revision = target_tree.get_reference_revision(
1062
tt.set_tree_reference(revision, trans_id)
1063
elif target_kind == 'symlink':
1064
tt.create_symlink(target_tree.get_symlink_target(
1065
target_path), trans_id)
1066
elif target_kind == 'file':
1067
deferred_files.append(
1068
(target_path, (trans_id, mode_id, file_id)))
1069
if basis_tree is None:
1070
basis_tree = working_tree.basis_tree()
1071
basis_tree.lock_read()
1072
new_sha1 = target_tree.get_file_sha1(target_path)
1073
basis_inter = InterTree.get(basis_tree, target_tree)
1074
basis_path = basis_inter.find_source_path(target_path)
1075
if (basis_path is not None and
1076
new_sha1 == basis_tree.get_file_sha1(basis_path)):
1077
# If the new contents of the file match what is in basis,
1078
# then there is no need to store in merge_modified.
1079
if basis_path in merge_modified:
1080
del merge_modified[basis_path]
1082
merge_modified[target_path] = new_sha1
1084
# preserve the execute bit when backing up
1085
if keep_content and wt_executable == target_executable:
1086
tt.set_executability(target_executable, trans_id)
1087
elif target_kind is not None:
1088
raise AssertionError(target_kind)
1089
if not wt_versioned and target_versioned:
1090
tt.version_file(trans_id, file_id=file_id)
1091
if wt_versioned and not target_versioned:
1092
tt.unversion_file(trans_id)
1093
if (target_name is not None
1094
and (wt_name != target_name or wt_parent != target_parent)):
1095
if target_name == '' and target_parent is None:
1096
parent_trans = ROOT_PARENT
1098
parent_trans = tt.trans_id_file_id(target_parent)
1099
if wt_parent is None and wt_versioned:
1100
tt.adjust_root_path(target_name, parent_trans)
1102
tt.adjust_path(target_name, parent_trans, trans_id)
1103
if wt_executable != target_executable and target_kind == "file":
1104
tt.set_executability(target_executable, trans_id)
1105
if working_tree.supports_content_filtering():
1106
for (trans_id, mode_id, file_id), bytes in (
1107
target_tree.iter_files_bytes(deferred_files)):
1108
# We're reverting a tree to the target tree so using the
1109
# target tree to find the file path seems the best choice
1110
# here IMO - Ian C 27/Oct/2009
1111
filter_tree_path = target_tree.id2path(file_id)
1112
filters = working_tree._content_filter_stack(filter_tree_path)
1113
bytes = filtered_output_bytes(
1115
ContentFilterContext(filter_tree_path, working_tree))
1116
tt.create_file(bytes, trans_id, mode_id)
1118
for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
1120
tt.create_file(bytes, trans_id, mode_id)
1121
tt.fixup_new_roots()
1123
if basis_tree is not None:
1125
return merge_modified
1128
def resolve_conflicts(tt, pb=None, pass_func=None):
1129
"""Make many conflict-resolution attempts, but die if they fail"""
1130
if pass_func is None:
1131
pass_func = conflict_pass
1132
new_conflicts = set()
1133
with ui.ui_factory.nested_progress_bar() as pb:
1135
pb.update(gettext('Resolution pass'), n + 1, 10)
1136
conflicts = tt.find_conflicts()
1137
if len(conflicts) == 0:
1138
return new_conflicts
1139
new_conflicts.update(pass_func(tt, conflicts))
1140
raise MalformedTransform(conflicts=conflicts)
1143
def conflict_pass(tt, conflicts, path_tree=None):
1144
"""Resolve some classes of conflicts.
1146
:param tt: The transform to resolve conflicts in
1147
:param conflicts: The conflicts to resolve
1148
:param path_tree: A Tree to get supplemental paths from
1150
new_conflicts = set()
1151
for c_type, conflict in ((c[0], c) for c in conflicts):
1152
if c_type == 'duplicate id':
1153
tt.unversion_file(conflict[1])
1154
new_conflicts.add((c_type, 'Unversioned existing file',
1155
conflict[1], conflict[2], ))
1156
elif c_type == 'duplicate':
1157
# files that were renamed take precedence
1158
final_parent = tt.final_parent(conflict[1])
1159
if tt.path_changed(conflict[1]):
1160
existing_file, new_file = conflict[2], conflict[1]
1162
existing_file, new_file = conflict[1], conflict[2]
1163
new_name = tt.final_name(existing_file) + '.moved'
1164
tt.adjust_path(new_name, final_parent, existing_file)
1165
new_conflicts.add((c_type, 'Moved existing file to',
1166
existing_file, new_file))
1167
elif c_type == 'parent loop':
1168
# break the loop by undoing one of the ops that caused the loop
1170
while not tt.path_changed(cur):
1171
cur = tt.final_parent(cur)
1172
new_conflicts.add((c_type, 'Cancelled move', cur,
1173
tt.final_parent(cur),))
1174
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1176
elif c_type == 'missing parent':
1177
trans_id = conflict[1]
1178
if trans_id in tt._removed_contents:
1179
cancel_deletion = True
1180
orphans = tt._get_potential_orphans(trans_id)
1182
cancel_deletion = False
1183
# All children are orphans
1186
tt.new_orphan(o, trans_id)
1187
except OrphaningError:
1188
# Something bad happened so we cancel the directory
1189
# deletion which will leave it in place with a
1190
# conflict. The user can deal with it from there.
1191
# Note that this also catch the case where we don't
1192
# want to create orphans and leave the directory in
1194
cancel_deletion = True
1197
# Cancel the directory deletion
1198
tt.cancel_deletion(trans_id)
1199
new_conflicts.add(('deleting parent', 'Not deleting',
1204
tt.final_name(trans_id)
1206
if path_tree is not None:
1207
file_id = tt.final_file_id(trans_id)
1209
file_id = tt.inactive_file_id(trans_id)
1210
_, entry = next(path_tree.iter_entries_by_dir(
1211
specific_files=[path_tree.id2path(file_id)]))
1212
# special-case the other tree root (move its
1213
# children to current root)
1214
if entry.parent_id is None:
1216
moved = _reparent_transform_children(
1217
tt, trans_id, tt.root)
1219
new_conflicts.add((c_type, 'Moved to root',
1222
parent_trans_id = tt.trans_id_file_id(
1224
tt.adjust_path(entry.name, parent_trans_id,
1227
tt.create_directory(trans_id)
1228
new_conflicts.add((c_type, 'Created directory', trans_id))
1229
elif c_type == 'unversioned parent':
1230
file_id = tt.inactive_file_id(conflict[1])
1231
# special-case the other tree root (move its children instead)
1232
if path_tree and path_tree.path2id('') == file_id:
1233
# This is the root entry, skip it
1235
tt.version_file(conflict[1], file_id=file_id)
1236
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1237
elif c_type == 'non-directory parent':
1238
parent_id = conflict[1]
1239
parent_parent = tt.final_parent(parent_id)
1240
parent_name = tt.final_name(parent_id)
1241
parent_file_id = tt.final_file_id(parent_id)
1242
new_parent_id = tt.new_directory(parent_name + '.new',
1243
parent_parent, parent_file_id)
1244
_reparent_transform_children(tt, parent_id, new_parent_id)
1245
if parent_file_id is not None:
1246
tt.unversion_file(parent_id)
1247
new_conflicts.add((c_type, 'Created directory', new_parent_id))
1248
elif c_type == 'versioning no contents':
1249
tt.cancel_versioning(conflict[1])
1250
return new_conflicts
1253
def cook_conflicts(raw_conflicts, tt):
1254
"""Generate a list of cooked conflicts, sorted by file path"""
1255
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1256
return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
1259
def iter_cook_conflicts(raw_conflicts, tt):
1261
for conflict in raw_conflicts:
1262
c_type = conflict[0]
1263
action = conflict[1]
1264
modified_path = fp.get_path(conflict[2])
1265
modified_id = tt.final_file_id(conflict[2])
1266
if len(conflict) == 3:
1267
yield conflicts.Conflict.factory(
1268
c_type, action=action, path=modified_path, file_id=modified_id)
1271
conflicting_path = fp.get_path(conflict[3])
1272
conflicting_id = tt.final_file_id(conflict[3])
1273
yield conflicts.Conflict.factory(
1274
c_type, action=action, path=modified_path,
1275
file_id=modified_id,
1276
conflict_path=conflicting_path,
1277
conflict_file_id=conflicting_id)
1280
class _FileMover(object):
1281
"""Moves and deletes files for TreeTransform, tracking operations"""
1284
self.past_renames = []
1285
self.pending_deletions = []
1287
def rename(self, from_, to):
1288
"""Rename a file from one path to another."""
1290
os.rename(from_, to)
1291
except OSError as e:
1292
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
1293
raise errors.FileExists(to, str(e))
1294
# normal OSError doesn't include filenames so it's hard to see where
1295
# the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
1296
raise TransformRenameFailed(from_, to, str(e), e.errno)
1297
self.past_renames.append((from_, to))
1299
def pre_delete(self, from_, to):
1300
"""Rename a file out of the way and mark it for deletion.
1302
Unlike os.unlink, this works equally well for files and directories.
1303
:param from_: The current file path
1304
:param to: A temporary path for the file
1306
self.rename(from_, to)
1307
self.pending_deletions.append(to)
1310
"""Reverse all renames that have been performed"""
1311
for from_, to in reversed(self.past_renames):
1313
os.rename(to, from_)
1314
except OSError as e:
1315
raise TransformRenameFailed(to, from_, str(e), e.errno)
1316
# after rollback, don't reuse _FileMover
1317
self.past_renames = None
1318
self.pending_deletions = None
1320
def apply_deletions(self):
1321
"""Apply all marked deletions"""
1322
for path in self.pending_deletions:
1324
# after apply_deletions, don't reuse _FileMover
1325
self.past_renames = None
1326
self.pending_deletions = None
1329
def link_tree(target_tree, source_tree):
1330
"""Where possible, hard-link files in a tree to those in another tree.
1332
:param target_tree: Tree to change
1333
:param source_tree: Tree to hard-link from
1335
with target_tree.transform() as tt:
1336
for change in target_tree.iter_changes(source_tree, include_unchanged=True):
1337
if change.changed_content:
1339
if change.kind != ('file', 'file'):
1341
if change.executable[0] != change.executable[1]:
1343
trans_id = tt.trans_id_tree_path(change.path[1])
1344
tt.delete_contents(trans_id)
1345
tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
1349
class PreviewTree(object):
1352
def __init__(self, transform):
1353
self._transform = transform
1354
self._parent_ids = []
1355
self.__by_parent = None
1356
self._path2trans_id_cache = {}
1357
self._all_children_cache = {}
1358
self._final_name_cache = {}
1361
def _by_parent(self):
1362
if self.__by_parent is None:
1363
self.__by_parent = self._transform.by_parent()
1364
return self.__by_parent
1366
def get_parent_ids(self):
1367
return self._parent_ids
1369
def set_parent_ids(self, parent_ids):
1370
self._parent_ids = parent_ids
1372
def get_revision_tree(self, revision_id):
1373
return self._transform._tree.get_revision_tree(revision_id)
1375
def is_locked(self):
1378
def lock_read(self):
1379
# Perhaps in theory, this should lock the TreeTransform?
1380
return lock.LogicalLockResult(self.unlock)
1385
def _path2trans_id(self, path):
1386
# We must not use None here, because that is a valid value to store.
1387
trans_id = self._path2trans_id_cache.get(path, object)
1388
if trans_id is not object:
1390
segments = osutils.splitpath(path)
1391
cur_parent = self._transform.root
1392
for cur_segment in segments:
1393
for child in self._all_children(cur_parent):
1394
final_name = self._final_name_cache.get(child)
1395
if final_name is None:
1396
final_name = self._transform.final_name(child)
1397
self._final_name_cache[child] = final_name
1398
if final_name == cur_segment:
1402
self._path2trans_id_cache[path] = None
1404
self._path2trans_id_cache[path] = cur_parent
1407
def _all_children(self, trans_id):
1408
children = self._all_children_cache.get(trans_id)
1409
if children is not None:
1411
children = set(self._transform.iter_tree_children(trans_id))
1412
# children in the _new_parent set are provided by _by_parent.
1413
children.difference_update(self._transform._new_parent)
1414
children.update(self._by_parent.get(trans_id, []))
1415
self._all_children_cache[trans_id] = children
1418
def get_file_with_stat(self, path):
1419
return self.get_file(path), None
1421
def is_executable(self, path):
1422
trans_id = self._path2trans_id(path)
1423
if trans_id is None:
1426
return self._transform._new_executability[trans_id]
1429
return self._transform._tree.is_executable(path)
1430
except OSError as e:
1431
if e.errno == errno.ENOENT:
1434
except errors.NoSuchFile:
1437
def has_filename(self, path):
1438
trans_id = self._path2trans_id(path)
1439
if trans_id in self._transform._new_contents:
1441
elif trans_id in self._transform._removed_contents:
1444
return self._transform._tree.has_filename(path)
1446
def get_file_sha1(self, path, stat_value=None):
1447
trans_id = self._path2trans_id(path)
1448
if trans_id is None:
1449
raise errors.NoSuchFile(path)
1450
kind = self._transform._new_contents.get(trans_id)
1452
return self._transform._tree.get_file_sha1(path)
1454
with self.get_file(path) as fileobj:
1455
return osutils.sha_file(fileobj)
1457
def get_file_verifier(self, path, stat_value=None):
1458
trans_id = self._path2trans_id(path)
1459
if trans_id is None:
1460
raise errors.NoSuchFile(path)
1461
kind = self._transform._new_contents.get(trans_id)
1463
return self._transform._tree.get_file_verifier(path)
1465
with self.get_file(path) as fileobj:
1466
return ("SHA1", osutils.sha_file(fileobj))
1468
def kind(self, path):
1469
trans_id = self._path2trans_id(path)
1470
if trans_id is None:
1471
raise errors.NoSuchFile(path)
1472
return self._transform.final_kind(trans_id)
1474
def stored_kind(self, path):
1475
trans_id = self._path2trans_id(path)
1476
if trans_id is None:
1477
raise errors.NoSuchFile(path)
1479
return self._transform._new_contents[trans_id]
1481
return self._transform._tree.stored_kind(path)
1483
def _get_repository(self):
1484
repo = getattr(self._transform._tree, '_repository', None)
1486
repo = self._transform._tree.branch.repository
1489
def _iter_parent_trees(self):
1490
for revision_id in self.get_parent_ids():
1492
yield self.revision_tree(revision_id)
1493
except errors.NoSuchRevisionInTree:
1494
yield self._get_repository().revision_tree(revision_id)
1496
def get_file_size(self, path):
1497
"""See Tree.get_file_size"""
1498
trans_id = self._path2trans_id(path)
1499
if trans_id is None:
1500
raise errors.NoSuchFile(path)
1501
kind = self._transform.final_kind(trans_id)
1504
if trans_id in self._transform._new_contents:
1505
return self._stat_limbo_file(trans_id).st_size
1506
if self.kind(path) == 'file':
1507
return self._transform._tree.get_file_size(path)
1511
def get_reference_revision(self, path):
1512
trans_id = self._path2trans_id(path)
1513
if trans_id is None:
1514
raise errors.NoSuchFile(path)
1515
reference_revision = self._transform._new_reference_revision.get(trans_id)
1516
if reference_revision is None:
1517
return self._transform._tree.get_reference_revision(path)
1518
return reference_revision
1520
def tree_kind(self, trans_id):
1521
path = self._tree_id_paths.get(trans_id)
1524
kind = self._tree.path_content_summary(path)[0]
1525
if kind == 'missing':