1
# Copyright (C) 2006, 2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from stat import S_ISREG
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
31
ReusingTransform, NotVersionedError, CantMoveRoot,
32
ExistingLimbo, ImmortalLimbo, NoFinalPath,
34
from bzrlib.inventory import InventoryEntry
35
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
36
delete_any, has_symlinks)
37
from bzrlib.progress import DummyProgress, ProgressPhase
38
from bzrlib.symbol_versioning import (
43
from bzrlib.trace import mutter, warning
44
from bzrlib import tree
46
import bzrlib.urlutils as urlutils
49
ROOT_PARENT = "root-parent"
52
def unique_add(map, key, value):
54
raise DuplicateKey(key=key)
58
class _TransformResults(object):
59
def __init__(self, modified_paths, rename_count):
61
self.modified_paths = modified_paths
62
self.rename_count = rename_count
65
class TreeTransform(object):
66
"""Represent a tree transformation.
68
This object is designed to support incremental generation of the transform,
71
However, it gives optimum performance when parent directories are created
72
before their contents. The transform is then able to put child files
73
directly in their parent directory, avoiding later renames.
75
It is easy to produce malformed transforms, but they are generally
76
harmless. Attempting to apply a malformed transform will cause an
77
exception to be raised before any modifications are made to the tree.
79
Many kinds of malformed transforms can be corrected with the
80
resolve_conflicts function. The remaining ones indicate programming error,
81
such as trying to create a file with no path.
83
Two sets of file creation methods are supplied. Convenience methods are:
88
These are composed of the low-level methods:
90
* create_file or create_directory or create_symlink
94
Transform/Transaction ids
95
-------------------------
96
trans_ids are temporary ids assigned to all files involved in a transform.
97
It's possible, even common, that not all files in the Tree have trans_ids.
99
trans_ids are used because filenames and file_ids are not good enough
100
identifiers; filenames change, and not all files have file_ids. File-ids
101
are also associated with trans-ids, so that moving a file moves its
104
trans_ids are only valid for the TreeTransform that generated them.
108
Limbo is a temporary directory use to hold new versions of files.
109
Files are added to limbo by new_file, new_directory, new_symlink, and their
110
convenience variants (create_*). Files may be removed from limbo using
111
cancel_creation. Files are renamed from limbo into their final location as
112
part of TreeTransform.apply
114
Limbo must be cleaned up, by either calling TreeTransform.apply or
115
calling TreeTransform.finalize.
117
Files are placed into limbo inside their parent directories, where
118
possible. This reduces subsequent renames, and makes operations involving
119
lots of files faster. This is only possible if the parent directory
120
is created *before* creating any of its children.
124
This temporary directory is used by _FileMover for storing files that are
125
about to be deleted. FileMover does not delete files until it is
126
sure that a rollback will not happen. In case of rollback, the files
129
def __init__(self, tree, pb=DummyProgress()):
130
"""Note: a tree_write lock is taken on the tree.
132
Use TreeTransform.finalize() to release the lock (can be omitted if
133
TreeTransform.apply() called).
135
object.__init__(self)
137
self._tree.lock_tree_write()
139
control_files = self._tree._control_files
140
self._limbodir = urlutils.local_path_from_url(
141
control_files.controlfilename('limbo'))
143
os.mkdir(self._limbodir)
145
if e.errno == errno.EEXIST:
146
raise ExistingLimbo(self._limbodir)
147
self._deletiondir = urlutils.local_path_from_url(
148
control_files.controlfilename('pending-deletion'))
150
os.mkdir(self._deletiondir)
152
if e.errno == errno.EEXIST:
153
raise errors.ExistingPendingDeletion(self._deletiondir)
159
# counter used to generate trans-ids (which are locally unique)
161
# mapping of trans_id -> new basename
163
# mapping of trans_id -> new parent trans_id
164
self._new_parent = {}
165
# mapping of trans_id with new contents -> new file_kind
166
self._new_contents = {}
167
# A mapping of transform ids to their limbo filename
168
self._limbo_files = {}
169
# A mapping of transform ids to a set of the transform ids of children
170
# that their limbo directory has
171
self._limbo_children = {}
172
# Map transform ids to maps of child filename to child transform id
173
self._limbo_children_names = {}
174
# List of transform ids that need to be renamed from limbo into place
175
self._needs_rename = set()
176
# Set of trans_ids whose contents will be removed
177
self._removed_contents = set()
178
# Mapping of trans_id -> new execute-bit value
179
self._new_executability = {}
180
# Mapping of trans_id -> new tree-reference value
181
self._new_reference_revision = {}
182
# Mapping of trans_id -> new file_id
184
# Mapping of old file-id -> trans_id
185
self._non_present_ids = {}
186
# Mapping of new file_id -> trans_id
188
# Set of file_ids that will be removed
189
self._removed_id = set()
190
# Mapping of path in old tree -> trans_id
191
self._tree_path_ids = {}
192
# Mapping trans_id -> path in old tree
193
self._tree_id_paths = {}
194
# Cache of realpath results, to speed up canonical_path
196
# Cache of relpath results, to speed up canonical_path
198
# The trans_id that will be used as the tree root
199
self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
200
# Indictor of whether the transform has been applied
204
# A counter of how many files have been renamed
205
self.rename_count = 0
207
def __get_root(self):
208
return self._new_root
210
root = property(__get_root)
213
"""Release the working tree lock, if held, clean up limbo dir.
215
This is required if apply has not been invoked, but can be invoked
218
if self._tree is None:
221
entries = [(self._limbo_name(t), t, k) for t, k in
222
self._new_contents.iteritems()]
223
entries.sort(reverse=True)
224
for path, trans_id, kind in entries:
225
if kind == "directory":
230
os.rmdir(self._limbodir)
232
# We don't especially care *why* the dir is immortal.
233
raise ImmortalLimbo(self._limbodir)
235
os.rmdir(self._deletiondir)
237
raise errors.ImmortalPendingDeletion(self._deletiondir)
242
def _assign_id(self):
243
"""Produce a new tranform id"""
244
new_id = "new-%s" % self._id_number
248
def create_path(self, name, parent):
249
"""Assign a transaction id to a new path"""
250
trans_id = self._assign_id()
251
unique_add(self._new_name, trans_id, name)
252
unique_add(self._new_parent, trans_id, parent)
255
def adjust_path(self, name, parent, trans_id):
256
"""Change the path that is assigned to a transaction id."""
257
if trans_id == self._new_root:
259
previous_parent = self._new_parent.get(trans_id)
260
previous_name = self._new_name.get(trans_id)
261
self._new_name[trans_id] = name
262
self._new_parent[trans_id] = parent
263
if (trans_id in self._limbo_files and
264
trans_id not in self._needs_rename):
265
self._rename_in_limbo([trans_id])
266
self._limbo_children[previous_parent].remove(trans_id)
267
del self._limbo_children_names[previous_parent][previous_name]
269
def _rename_in_limbo(self, trans_ids):
270
"""Fix limbo names so that the right final path is produced.
272
This means we outsmarted ourselves-- we tried to avoid renaming
273
these files later by creating them with their final names in their
274
final parents. But now the previous name or parent is no longer
275
suitable, so we have to rename them.
277
Even for trans_ids that have no new contents, we must remove their
278
entries from _limbo_files, because they are now stale.
280
for trans_id in trans_ids:
281
old_path = self._limbo_files.pop(trans_id)
282
if trans_id not in self._new_contents:
284
new_path = self._limbo_name(trans_id)
285
os.rename(old_path, new_path)
287
def adjust_root_path(self, name, parent):
288
"""Emulate moving the root by moving all children, instead.
290
We do this by undoing the association of root's transaction id with the
291
current tree. This allows us to create a new directory with that
292
transaction id. We unversion the root directory and version the
293
physically new directory, and hope someone versions the tree root
296
old_root = self._new_root
297
old_root_file_id = self.final_file_id(old_root)
298
# force moving all children of root
299
for child_id in self.iter_tree_children(old_root):
300
if child_id != parent:
301
self.adjust_path(self.final_name(child_id),
302
self.final_parent(child_id), child_id)
303
file_id = self.final_file_id(child_id)
304
if file_id is not None:
305
self.unversion_file(child_id)
306
self.version_file(file_id, child_id)
308
# the physical root needs a new transaction id
309
self._tree_path_ids.pop("")
310
self._tree_id_paths.pop(old_root)
311
self._new_root = self.trans_id_tree_file_id(self._tree.get_root_id())
312
if parent == old_root:
313
parent = self._new_root
314
self.adjust_path(name, parent, old_root)
315
self.create_directory(old_root)
316
self.version_file(old_root_file_id, old_root)
317
self.unversion_file(self._new_root)
319
def trans_id_tree_file_id(self, inventory_id):
320
"""Determine the transaction id of a working tree file.
322
This reflects only files that already exist, not ones that will be
323
added by transactions.
325
path = self._tree.inventory.id2path(inventory_id)
326
return self.trans_id_tree_path(path)
328
def trans_id_file_id(self, file_id):
329
"""Determine or set the transaction id associated with a file ID.
330
A new id is only created for file_ids that were never present. If
331
a transaction has been unversioned, it is deliberately still returned.
332
(this will likely lead to an unversioned parent conflict.)
334
if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
335
return self._r_new_id[file_id]
336
elif file_id in self._tree.inventory:
337
return self.trans_id_tree_file_id(file_id)
338
elif file_id in self._non_present_ids:
339
return self._non_present_ids[file_id]
341
trans_id = self._assign_id()
342
self._non_present_ids[file_id] = trans_id
345
def canonical_path(self, path):
346
"""Get the canonical tree-relative path"""
347
# don't follow final symlinks
348
abs = self._tree.abspath(path)
349
if abs in self._relpaths:
350
return self._relpaths[abs]
351
dirname, basename = os.path.split(abs)
352
if dirname not in self._realpaths:
353
self._realpaths[dirname] = os.path.realpath(dirname)
354
dirname = self._realpaths[dirname]
355
abs = pathjoin(dirname, basename)
356
if dirname in self._relpaths:
357
relpath = pathjoin(self._relpaths[dirname], basename)
358
relpath = relpath.rstrip('/\\')
360
relpath = self._tree.relpath(abs)
361
self._relpaths[abs] = relpath
364
def trans_id_tree_path(self, path):
365
"""Determine (and maybe set) the transaction ID for a tree path."""
366
path = self.canonical_path(path)
367
if path not in self._tree_path_ids:
368
self._tree_path_ids[path] = self._assign_id()
369
self._tree_id_paths[self._tree_path_ids[path]] = path
370
return self._tree_path_ids[path]
372
def get_tree_parent(self, trans_id):
373
"""Determine id of the parent in the tree."""
374
path = self._tree_id_paths[trans_id]
377
return self.trans_id_tree_path(os.path.dirname(path))
379
def create_file(self, contents, trans_id, mode_id=None):
380
"""Schedule creation of a new file.
384
Contents is an iterator of strings, all of which will be written
385
to the target destination.
387
New file takes the permissions of any existing file with that id,
388
unless mode_id is specified.
390
name = self._limbo_name(trans_id)
394
unique_add(self._new_contents, trans_id, 'file')
396
# Clean up the file, it never got registered so
397
# TreeTransform.finalize() won't clean it up.
402
f.writelines(contents)
405
self._set_mode(trans_id, mode_id, S_ISREG)
407
def _set_mode(self, trans_id, mode_id, typefunc):
408
"""Set the mode of new file contents.
409
The mode_id is the existing file to get the mode from (often the same
410
as trans_id). The operation is only performed if there's a mode match
411
according to typefunc.
416
old_path = self._tree_id_paths[mode_id]
420
mode = os.stat(self._tree.abspath(old_path)).st_mode
422
if e.errno == errno.ENOENT:
427
os.chmod(self._limbo_name(trans_id), mode)
429
def create_directory(self, trans_id):
430
"""Schedule creation of a new directory.
432
See also new_directory.
434
os.mkdir(self._limbo_name(trans_id))
435
unique_add(self._new_contents, trans_id, 'directory')
437
def create_symlink(self, target, trans_id):
438
"""Schedule creation of a new symbolic link.
440
target is a bytestring.
441
See also new_symlink.
444
os.symlink(target, self._limbo_name(trans_id))
445
unique_add(self._new_contents, trans_id, 'symlink')
448
path = FinalPaths(self).get_path(trans_id)
451
raise UnableCreateSymlink(path=path)
453
def cancel_creation(self, trans_id):
454
"""Cancel the creation of new file contents."""
455
del self._new_contents[trans_id]
456
children = self._limbo_children.get(trans_id)
457
# if this is a limbo directory with children, move them before removing
459
if children is not None:
460
self._rename_in_limbo(children)
461
del self._limbo_children[trans_id]
462
del self._limbo_children_names[trans_id]
463
delete_any(self._limbo_name(trans_id))
465
def delete_contents(self, trans_id):
466
"""Schedule the contents of a path entry for deletion"""
467
self.tree_kind(trans_id)
468
self._removed_contents.add(trans_id)
470
def cancel_deletion(self, trans_id):
471
"""Cancel a scheduled deletion"""
472
self._removed_contents.remove(trans_id)
474
def unversion_file(self, trans_id):
475
"""Schedule a path entry to become unversioned"""
476
self._removed_id.add(trans_id)
478
def delete_versioned(self, trans_id):
479
"""Delete and unversion a versioned file"""
480
self.delete_contents(trans_id)
481
self.unversion_file(trans_id)
483
def set_executability(self, executability, trans_id):
484
"""Schedule setting of the 'execute' bit
485
To unschedule, set to None
487
if executability is None:
488
del self._new_executability[trans_id]
490
unique_add(self._new_executability, trans_id, executability)
492
def set_tree_reference(self, revision_id, trans_id):
493
"""Set the reference associated with a directory"""
494
unique_add(self._new_reference_revision, trans_id, revision_id)
496
def version_file(self, file_id, trans_id):
497
"""Schedule a file to become versioned."""
498
assert file_id is not None
499
unique_add(self._new_id, trans_id, file_id)
500
unique_add(self._r_new_id, file_id, trans_id)
502
def cancel_versioning(self, trans_id):
503
"""Undo a previous versioning of a file"""
504
file_id = self._new_id[trans_id]
505
del self._new_id[trans_id]
506
del self._r_new_id[file_id]
509
"""Determine the paths of all new and changed files"""
511
fp = FinalPaths(self)
512
for id_set in (self._new_name, self._new_parent, self._new_contents,
513
self._new_id, self._new_executability):
514
new_ids.update(id_set)
515
new_paths = [(fp.get_path(t), t) for t in new_ids]
519
def tree_kind(self, trans_id):
520
"""Determine the file kind in the working tree.
522
Raises NoSuchFile if the file does not exist
524
path = self._tree_id_paths.get(trans_id)
526
raise NoSuchFile(None)
528
return file_kind(self._tree.abspath(path))
530
if e.errno != errno.ENOENT:
533
raise NoSuchFile(path)
535
def final_kind(self, trans_id):
536
"""Determine the final file kind, after any changes applied.
538
Raises NoSuchFile if the file does not exist/has no contents.
539
(It is conceivable that a path would be created without the
540
corresponding contents insertion command)
542
if trans_id in self._new_contents:
543
return self._new_contents[trans_id]
544
elif trans_id in self._removed_contents:
545
raise NoSuchFile(None)
547
return self.tree_kind(trans_id)
549
def tree_file_id(self, trans_id):
550
"""Determine the file id associated with the trans_id in the tree"""
552
path = self._tree_id_paths[trans_id]
554
# the file is a new, unversioned file, or invalid trans_id
556
# the file is old; the old id is still valid
557
if self._new_root == trans_id:
558
return self._tree.get_root_id()
559
return self._tree.inventory.path2id(path)
561
def final_file_id(self, trans_id):
562
"""Determine the file id after any changes are applied, or None.
564
None indicates that the file will not be versioned after changes are
568
# there is a new id for this file
569
assert self._new_id[trans_id] is not None
570
return self._new_id[trans_id]
572
if trans_id in self._removed_id:
574
return self.tree_file_id(trans_id)
576
def inactive_file_id(self, trans_id):
577
"""Return the inactive file_id associated with a transaction id.
578
That is, the one in the tree or in non_present_ids.
579
The file_id may actually be active, too.
581
file_id = self.tree_file_id(trans_id)
582
if file_id is not None:
584
for key, value in self._non_present_ids.iteritems():
585
if value == trans_id:
588
def final_parent(self, trans_id):
589
"""Determine the parent file_id, after any changes are applied.
591
ROOT_PARENT is returned for the tree root.
594
return self._new_parent[trans_id]
596
return self.get_tree_parent(trans_id)
598
def final_name(self, trans_id):
599
"""Determine the final filename, after all changes are applied."""
601
return self._new_name[trans_id]
604
return os.path.basename(self._tree_id_paths[trans_id])
606
raise NoFinalPath(trans_id, self)
609
"""Return a map of parent: children for known parents.
611
Only new paths and parents of tree files with assigned ids are used.
614
items = list(self._new_parent.iteritems())
615
items.extend((t, self.final_parent(t)) for t in
616
self._tree_id_paths.keys())
617
for trans_id, parent_id in items:
618
if parent_id not in by_parent:
619
by_parent[parent_id] = set()
620
by_parent[parent_id].add(trans_id)
623
def path_changed(self, trans_id):
624
"""Return True if a trans_id's path has changed."""
625
return (trans_id in self._new_name) or (trans_id in self._new_parent)
627
def new_contents(self, trans_id):
628
return (trans_id in self._new_contents)
630
def find_conflicts(self):
631
"""Find any violations of inventory or filesystem invariants"""
632
if self.__done is True:
633
raise ReusingTransform()
635
# ensure all children of all existent parents are known
636
# all children of non-existent parents are known, by definition.
637
self._add_tree_children()
638
by_parent = self.by_parent()
639
conflicts.extend(self._unversioned_parents(by_parent))
640
conflicts.extend(self._parent_loops())
641
conflicts.extend(self._duplicate_entries(by_parent))
642
conflicts.extend(self._duplicate_ids())
643
conflicts.extend(self._parent_type_conflicts(by_parent))
644
conflicts.extend(self._improper_versioning())
645
conflicts.extend(self._executability_conflicts())
646
conflicts.extend(self._overwrite_conflicts())
649
def _add_tree_children(self):
650
"""Add all the children of all active parents to the known paths.
652
Active parents are those which gain children, and those which are
653
removed. This is a necessary first step in detecting conflicts.
655
parents = self.by_parent().keys()
656
parents.extend([t for t in self._removed_contents if
657
self.tree_kind(t) == 'directory'])
658
for trans_id in self._removed_id:
659
file_id = self.tree_file_id(trans_id)
660
if self._tree.inventory[file_id].kind == 'directory':
661
parents.append(trans_id)
663
for parent_id in parents:
664
# ensure that all children are registered with the transaction
665
list(self.iter_tree_children(parent_id))
667
def iter_tree_children(self, parent_id):
668
"""Iterate through the entry's tree children, if any"""
670
path = self._tree_id_paths[parent_id]
674
children = os.listdir(self._tree.abspath(path))
676
if e.errno != errno.ENOENT and e.errno != errno.ESRCH:
680
for child in children:
681
childpath = joinpath(path, child)
682
if self._tree.is_control_filename(childpath):
684
yield self.trans_id_tree_path(childpath)
686
def has_named_child(self, by_parent, parent_id, name):
688
children = by_parent[parent_id]
691
for child in children:
692
if self.final_name(child) == name:
695
path = self._tree_id_paths[parent_id]
698
childpath = joinpath(path, name)
699
child_id = self._tree_path_ids.get(childpath)
701
return lexists(self._tree.abspath(childpath))
703
if self.final_parent(child_id) != parent_id:
705
if child_id in self._removed_contents:
706
# XXX What about dangling file-ids?
711
def _parent_loops(self):
712
"""No entry should be its own ancestor"""
714
for trans_id in self._new_parent:
717
while parent_id is not ROOT_PARENT:
720
parent_id = self.final_parent(parent_id)
723
if parent_id == trans_id:
724
conflicts.append(('parent loop', trans_id))
725
if parent_id in seen:
729
def _unversioned_parents(self, by_parent):
730
"""If parent directories are versioned, children must be versioned."""
732
for parent_id, children in by_parent.iteritems():
733
if parent_id is ROOT_PARENT:
735
if self.final_file_id(parent_id) is not None:
737
for child_id in children:
738
if self.final_file_id(child_id) is not None:
739
conflicts.append(('unversioned parent', parent_id))
743
def _improper_versioning(self):
744
"""Cannot version a file with no contents, or a bad type.
746
However, existing entries with no contents are okay.
749
for trans_id in self._new_id.iterkeys():
751
kind = self.final_kind(trans_id)
753
conflicts.append(('versioning no contents', trans_id))
755
if not InventoryEntry.versionable_kind(kind):
756
conflicts.append(('versioning bad kind', trans_id, kind))
759
def _executability_conflicts(self):
760
"""Check for bad executability changes.
762
Only versioned files may have their executability set, because
763
1. only versioned entries can have executability under windows
764
2. only files can be executable. (The execute bit on a directory
765
does not indicate searchability)
768
for trans_id in self._new_executability:
769
if self.final_file_id(trans_id) is None:
770
conflicts.append(('unversioned executability', trans_id))
773
non_file = self.final_kind(trans_id) != "file"
777
conflicts.append(('non-file executability', trans_id))
780
def _overwrite_conflicts(self):
781
"""Check for overwrites (not permitted on Win32)"""
783
for trans_id in self._new_contents:
785
self.tree_kind(trans_id)
788
if trans_id not in self._removed_contents:
789
conflicts.append(('overwrite', trans_id,
790
self.final_name(trans_id)))
793
def _duplicate_entries(self, by_parent):
794
"""No directory may have two entries with the same name."""
796
if (self._new_name, self._new_parent) == ({}, {}):
798
for children in by_parent.itervalues():
799
name_ids = [(self.final_name(t), t) for t in children]
803
for name, trans_id in name_ids:
805
kind = self.final_kind(trans_id)
808
file_id = self.final_file_id(trans_id)
809
if kind is None and file_id is None:
811
if name == last_name:
812
conflicts.append(('duplicate', last_trans_id, trans_id,
815
last_trans_id = trans_id
818
def _duplicate_ids(self):
819
"""Each inventory id may only be used once"""
821
removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
823
active_tree_ids = set((f for f in self._tree.inventory if
824
f not in removed_tree_ids))
825
for trans_id, file_id in self._new_id.iteritems():
826
if file_id in active_tree_ids:
827
old_trans_id = self.trans_id_tree_file_id(file_id)
828
conflicts.append(('duplicate id', old_trans_id, trans_id))
831
def _parent_type_conflicts(self, by_parent):
832
"""parents must have directory 'contents'."""
834
for parent_id, children in by_parent.iteritems():
835
if parent_id is ROOT_PARENT:
837
if not self._any_contents(children):
839
for child in children:
841
self.final_kind(child)
845
kind = self.final_kind(parent_id)
849
conflicts.append(('missing parent', parent_id))
850
elif kind != "directory":
851
conflicts.append(('non-directory parent', parent_id))
854
def _any_contents(self, trans_ids):
855
"""Return true if any of the trans_ids, will have contents."""
856
for trans_id in trans_ids:
858
kind = self.final_kind(trans_id)
864
def apply(self, no_conflicts=False, _mover=None):
865
"""Apply all changes to the inventory and filesystem.
867
If filesystem or inventory conflicts are present, MalformedTransform
870
If apply succeeds, finalize is not necessary.
872
:param no_conflicts: if True, the caller guarantees there are no
873
conflicts, so no check is made.
874
:param _mover: Supply an alternate FileMover, for testing
877
conflicts = self.find_conflicts()
878
if len(conflicts) != 0:
879
raise MalformedTransform(conflicts=conflicts)
880
inv = self._tree.inventory
882
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
889
child_pb.update('Apply phase', 0, 2)
890
self._apply_removals(inv, inventory_delta, mover)
891
child_pb.update('Apply phase', 1, 2)
892
modified_paths = self._apply_insertions(inv, inventory_delta,
898
mover.apply_deletions()
901
self._tree.apply_inventory_delta(inventory_delta)
904
return _TransformResults(modified_paths, self.rename_count)
906
def _limbo_name(self, trans_id):
907
"""Generate the limbo name of a file"""
908
limbo_name = self._limbo_files.get(trans_id)
909
if limbo_name is not None:
911
parent = self._new_parent.get(trans_id)
912
# if the parent directory is already in limbo (e.g. when building a
913
# tree), choose a limbo name inside the parent, to reduce further
915
use_direct_path = False
916
if self._new_contents.get(parent) == 'directory':
917
filename = self._new_name.get(trans_id)
918
if filename is not None:
919
if parent not in self._limbo_children:
920
self._limbo_children[parent] = set()
921
self._limbo_children_names[parent] = {}
922
use_direct_path = True
923
# the direct path can only be used if no other file has
924
# already taken this pathname, i.e. if the name is unused, or
925
# if it is already associated with this trans_id.
926
elif (self._limbo_children_names[parent].get(filename)
927
in (trans_id, None)):
928
use_direct_path = True
930
limbo_name = pathjoin(self._limbo_files[parent], filename)
931
self._limbo_children[parent].add(trans_id)
932
self._limbo_children_names[parent][filename] = trans_id
934
limbo_name = pathjoin(self._limbodir, trans_id)
935
self._needs_rename.add(trans_id)
936
self._limbo_files[trans_id] = limbo_name
939
def _apply_removals(self, inv, inventory_delta, mover):
940
"""Perform tree operations that remove directory/inventory names.
942
That is, delete files that are to be deleted, and put any files that
943
need renaming into limbo. This must be done in strict child-to-parent
946
tree_paths = list(self._tree_path_ids.iteritems())
947
tree_paths.sort(reverse=True)
948
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
950
for num, data in enumerate(tree_paths):
951
path, trans_id = data
952
child_pb.update('removing file', num, len(tree_paths))
953
full_path = self._tree.abspath(path)
954
if trans_id in self._removed_contents:
955
mover.pre_delete(full_path, os.path.join(self._deletiondir,
957
elif trans_id in self._new_name or trans_id in \
960
mover.rename(full_path, self._limbo_name(trans_id))
962
if e.errno != errno.ENOENT:
965
self.rename_count += 1
966
if trans_id in self._removed_id:
967
if trans_id == self._new_root:
968
file_id = self._tree.get_root_id()
970
file_id = self.tree_file_id(trans_id)
971
assert file_id is not None
972
inventory_delta.append((path, None, file_id, None))
976
def _apply_insertions(self, inv, inventory_delta, mover):
977
"""Perform tree operations that insert directory/inventory names.
979
That is, create any files that need to be created, and restore from
980
limbo any files that needed renaming. This must be done in strict
981
parent-to-child order.
983
new_paths = self.new_paths()
985
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
987
for num, (path, trans_id) in enumerate(new_paths):
989
child_pb.update('adding file', num, len(new_paths))
991
kind = self._new_contents[trans_id]
993
kind = contents = None
994
if trans_id in self._new_contents or \
995
self.path_changed(trans_id):
996
full_path = self._tree.abspath(path)
997
if trans_id in self._needs_rename:
999
mover.rename(self._limbo_name(trans_id), full_path)
1001
# We may be renaming a dangling inventory id
1002
if e.errno != errno.ENOENT:
1005
self.rename_count += 1
1006
if trans_id in self._new_contents:
1007
modified_paths.append(full_path)
1008
del self._new_contents[trans_id]
1010
if trans_id in self._new_id:
1012
kind = file_kind(self._tree.abspath(path))
1013
if trans_id in self._new_reference_revision:
1014
new_entry = inventory.TreeReference(
1015
self._new_id[trans_id],
1016
self._new_name[trans_id],
1017
self.final_file_id(self._new_parent[trans_id]),
1018
None, self._new_reference_revision[trans_id])
1020
new_entry = inventory.make_entry(kind,
1021
self.final_name(trans_id),
1022
self.final_file_id(self.final_parent(trans_id)),
1023
self._new_id[trans_id])
1025
if trans_id in self._new_name or trans_id in\
1026
self._new_parent or\
1027
trans_id in self._new_executability:
1028
file_id = self.final_file_id(trans_id)
1029
if file_id is not None:
1030
entry = inv[file_id]
1031
new_entry = entry.copy()
1033
if trans_id in self._new_name or trans_id in\
1035
if new_entry is not None:
1036
new_entry.name = self.final_name(trans_id)
1037
parent = self.final_parent(trans_id)
1038
parent_id = self.final_file_id(parent)
1039
new_entry.parent_id = parent_id
1041
if trans_id in self._new_executability:
1042
self._set_executability(path, new_entry, trans_id)
1043
if new_entry is not None:
1044
if new_entry.file_id in inv:
1045
old_path = inv.id2path(new_entry.file_id)
1048
inventory_delta.append((old_path, path,
1053
return modified_paths
1055
def _set_executability(self, path, entry, trans_id):
1056
"""Set the executability of versioned files """
1057
new_executability = self._new_executability[trans_id]
1058
entry.executable = new_executability
1059
if supports_executable():
1060
abspath = self._tree.abspath(path)
1061
current_mode = os.stat(abspath).st_mode
1062
if new_executability:
1065
to_mode = current_mode | (0100 & ~umask)
1066
# Enable x-bit for others only if they can read it.
1067
if current_mode & 0004:
1068
to_mode |= 0001 & ~umask
1069
if current_mode & 0040:
1070
to_mode |= 0010 & ~umask
1072
to_mode = current_mode & ~0111
1073
os.chmod(abspath, to_mode)
1075
def _new_entry(self, name, parent_id, file_id):
1076
"""Helper function to create a new filesystem entry."""
1077
trans_id = self.create_path(name, parent_id)
1078
if file_id is not None:
1079
self.version_file(file_id, trans_id)
1082
def new_file(self, name, parent_id, contents, file_id=None,
1084
"""Convenience method to create files.
1086
name is the name of the file to create.
1087
parent_id is the transaction id of the parent directory of the file.
1088
contents is an iterator of bytestrings, which will be used to produce
1090
:param file_id: The inventory ID of the file, if it is to be versioned.
1091
:param executable: Only valid when a file_id has been supplied.
1093
trans_id = self._new_entry(name, parent_id, file_id)
1094
# TODO: rather than scheduling a set_executable call,
1095
# have create_file create the file with the right mode.
1096
self.create_file(contents, trans_id)
1097
if executable is not None:
1098
self.set_executability(executable, trans_id)
1101
def new_directory(self, name, parent_id, file_id=None):
1102
"""Convenience method to create directories.
1104
name is the name of the directory to create.
1105
parent_id is the transaction id of the parent directory of the
1107
file_id is the inventory ID of the directory, if it is to be versioned.
1109
trans_id = self._new_entry(name, parent_id, file_id)
1110
self.create_directory(trans_id)
1113
def new_symlink(self, name, parent_id, target, file_id=None):
1114
"""Convenience method to create symbolic link.
1116
name is the name of the symlink to create.
1117
parent_id is the transaction id of the parent directory of the symlink.
1118
target is a bytestring of the target of the symlink.
1119
file_id is the inventory ID of the file, if it is to be versioned.
1121
trans_id = self._new_entry(name, parent_id, file_id)
1122
self.create_symlink(target, trans_id)
1125
def _affected_ids(self):
1126
"""Return the set of transform ids affected by the transform"""
1127
trans_ids = set(self._removed_id)
1128
trans_ids.update(self._new_id.keys())
1129
trans_ids.update(self._removed_contents)
1130
trans_ids.update(self._new_contents.keys())
1131
trans_ids.update(self._new_executability.keys())
1132
trans_ids.update(self._new_name.keys())
1133
trans_ids.update(self._new_parent.keys())
1136
def _get_file_id_maps(self):
1137
"""Return mapping of file_ids to trans_ids in the to and from states"""
1138
trans_ids = self._affected_ids()
1141
# Build up two dicts: trans_ids associated with file ids in the
1142
# FROM state, vs the TO state.
1143
for trans_id in trans_ids:
1144
from_file_id = self.tree_file_id(trans_id)
1145
if from_file_id is not None:
1146
from_trans_ids[from_file_id] = trans_id
1147
to_file_id = self.final_file_id(trans_id)
1148
if to_file_id is not None:
1149
to_trans_ids[to_file_id] = trans_id
1150
return from_trans_ids, to_trans_ids
1152
def _from_file_data(self, from_trans_id, from_versioned, file_id):
1153
"""Get data about a file in the from (tree) state
1155
Return a (name, parent, kind, executable) tuple
1157
from_path = self._tree_id_paths.get(from_trans_id)
1159
# get data from working tree if versioned
1160
from_entry = self._tree.inventory[file_id]
1161
from_name = from_entry.name
1162
from_parent = from_entry.parent_id
1165
if from_path is None:
1166
# File does not exist in FROM state
1170
# File exists, but is not versioned. Have to use path-
1172
from_name = os.path.basename(from_path)
1173
tree_parent = self.get_tree_parent(from_trans_id)
1174
from_parent = self.tree_file_id(tree_parent)
1175
if from_path is not None:
1176
from_kind, from_executable, from_stats = \
1177
self._tree._comparison_data(from_entry, from_path)
1180
from_executable = False
1181
return from_name, from_parent, from_kind, from_executable
1183
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1184
"""Get data about a file in the to (target) state
1186
Return a (name, parent, kind, executable) tuple
1188
to_name = self.final_name(to_trans_id)
1190
to_kind = self.final_kind(to_trans_id)
1193
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1194
if to_trans_id in self._new_executability:
1195
to_executable = self._new_executability[to_trans_id]
1196
elif to_trans_id == from_trans_id:
1197
to_executable = from_executable
1199
to_executable = False
1200
return to_name, to_parent, to_kind, to_executable
1202
def _iter_changes(self):
1203
"""Produce output in the same format as Tree._iter_changes.
1205
Will produce nonsensical results if invoked while inventory/filesystem
1206
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1208
This reads the Transform, but only reproduces changes involving a
1209
file_id. Files that are not versioned in either of the FROM or TO
1210
states are not reflected.
1212
final_paths = FinalPaths(self)
1213
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1215
# Now iterate through all active file_ids
1216
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1218
from_trans_id = from_trans_ids.get(file_id)
1219
# find file ids, and determine versioning state
1220
if from_trans_id is None:
1221
from_versioned = False
1222
from_trans_id = to_trans_ids[file_id]
1224
from_versioned = True
1225
to_trans_id = to_trans_ids.get(file_id)
1226
if to_trans_id is None:
1227
to_versioned = False
1228
to_trans_id = from_trans_id
1232
from_name, from_parent, from_kind, from_executable = \
1233
self._from_file_data(from_trans_id, from_versioned, file_id)
1235
to_name, to_parent, to_kind, to_executable = \
1236
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1238
if not from_versioned:
1241
from_path = self._tree_id_paths.get(from_trans_id)
1242
if not to_versioned:
1245
to_path = final_paths.get_path(to_trans_id)
1246
if from_kind != to_kind:
1248
elif to_kind in ('file', 'symlink') and (
1249
to_trans_id != from_trans_id or
1250
to_trans_id in self._new_contents):
1252
if (not modified and from_versioned == to_versioned and
1253
from_parent==to_parent and from_name == to_name and
1254
from_executable == to_executable):
1256
results.append((file_id, (from_path, to_path), modified,
1257
(from_versioned, to_versioned),
1258
(from_parent, to_parent),
1259
(from_name, to_name),
1260
(from_kind, to_kind),
1261
(from_executable, to_executable)))
1262
return iter(sorted(results, key=lambda x:x[1]))
1265
def joinpath(parent, child):
1266
"""Join tree-relative paths, handling the tree root specially"""
1267
if parent is None or parent == "":
1270
return pathjoin(parent, child)
1273
class FinalPaths(object):
1274
"""Make path calculation cheap by memoizing paths.
1276
The underlying tree must not be manipulated between calls, or else
1277
the results will likely be incorrect.
1279
def __init__(self, transform):
1280
object.__init__(self)
1281
self._known_paths = {}
1282
self.transform = transform
1284
def _determine_path(self, trans_id):
1285
if trans_id == self.transform.root:
1287
name = self.transform.final_name(trans_id)
1288
parent_id = self.transform.final_parent(trans_id)
1289
if parent_id == self.transform.root:
1292
return pathjoin(self.get_path(parent_id), name)
1294
def get_path(self, trans_id):
1295
"""Find the final path associated with a trans_id"""
1296
if trans_id not in self._known_paths:
1297
self._known_paths[trans_id] = self._determine_path(trans_id)
1298
return self._known_paths[trans_id]
1301
def topology_sorted_ids(tree):
1302
"""Determine the topological order of the ids in a tree"""
1303
file_ids = list(tree)
1304
file_ids.sort(key=tree.id2path)
1308
def build_tree(tree, wt):
1309
"""Create working tree for a branch, using a TreeTransform.
1311
This function should be used on empty trees, having a tree root at most.
1312
(see merge and revert functionality for working with existing trees)
1314
Existing files are handled like so:
1316
- Existing bzrdirs take precedence over creating new items. They are
1317
created as '%s.diverted' % name.
1318
- Otherwise, if the content on disk matches the content we are building,
1319
it is silently replaced.
1320
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1322
wt.lock_tree_write()
1326
return _build_tree(tree, wt)
1333
def _build_tree(tree, wt):
1334
"""See build_tree."""
1335
if len(wt.inventory) > 1: # more than just a root
1336
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1338
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1339
pp = ProgressPhase("Build phase", 2, top_pb)
1340
if tree.inventory.root is not None:
1341
# This is kind of a hack: we should be altering the root
1342
# as part of the regular tree shape diff logic.
1343
# The conditional test here is to avoid doing an
1344
# expensive operation (flush) every time the root id
1345
# is set within the tree, nor setting the root and thus
1346
# marking the tree as dirty, because we use two different
1347
# idioms here: tree interfaces and inventory interfaces.
1348
if wt.get_root_id() != tree.get_root_id():
1349
wt.set_root_id(tree.get_root_id())
1351
tt = TreeTransform(wt)
1355
file_trans_id[wt.get_root_id()] = \
1356
tt.trans_id_tree_file_id(wt.get_root_id())
1357
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1359
deferred_contents = []
1360
for num, (tree_path, entry) in \
1361
enumerate(tree.inventory.iter_entries_by_dir()):
1362
pb.update("Building tree", num - len(deferred_contents),
1363
len(tree.inventory))
1364
if entry.parent_id is None:
1367
file_id = entry.file_id
1368
target_path = wt.abspath(tree_path)
1370
kind = file_kind(target_path)
1374
if kind == "directory":
1376
bzrdir.BzrDir.open(target_path)
1377
except errors.NotBranchError:
1381
if (file_id not in divert and
1382
_content_match(tree, entry, file_id, kind,
1384
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1385
if kind == 'directory':
1387
if entry.parent_id not in file_trans_id:
1388
raise AssertionError(
1389
'entry %s parent id %r is not in file_trans_id %r'
1390
% (entry, entry.parent_id, file_trans_id))
1391
parent_id = file_trans_id[entry.parent_id]
1392
if entry.kind == 'file':
1393
# We *almost* replicate new_by_entry, so that we can defer
1394
# getting the file text, and get them all at once.
1395
trans_id = tt.create_path(entry.name, parent_id)
1396
file_trans_id[file_id] = trans_id
1397
tt.version_file(entry.file_id, trans_id)
1398
executable = tree.is_executable(entry.file_id, tree_path)
1399
if executable is not None:
1400
tt.set_executability(executable, trans_id)
1401
deferred_contents.append((entry.file_id, trans_id))
1403
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1406
new_trans_id = file_trans_id[file_id]
1407
old_parent = tt.trans_id_tree_path(tree_path)
1408
_reparent_children(tt, old_parent, new_trans_id)
1409
for num, (trans_id, bytes) in enumerate(
1410
tree.iter_files_bytes(deferred_contents)):
1411
tt.create_file(bytes, trans_id)
1412
pb.update('Adding file contents',
1413
(num + len(tree.inventory) - len(deferred_contents)),
1414
len(tree.inventory))
1418
divert_trans = set(file_trans_id[f] for f in divert)
1419
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1420
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1421
conflicts = cook_conflicts(raw_conflicts, tt)
1422
for conflict in conflicts:
1425
wt.add_conflicts(conflicts)
1426
except errors.UnsupportedOperation:
1435
def _reparent_children(tt, old_parent, new_parent):
1436
for child in tt.iter_tree_children(old_parent):
1437
tt.adjust_path(tt.final_name(child), new_parent, child)
1440
def _content_match(tree, entry, file_id, kind, target_path):
1441
if entry.kind != kind:
1443
if entry.kind == "directory":
1445
if entry.kind == "file":
1446
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1448
elif entry.kind == "symlink":
1449
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1454
def resolve_checkout(tt, conflicts, divert):
1455
new_conflicts = set()
1456
for c_type, conflict in ((c[0], c) for c in conflicts):
1457
# Anything but a 'duplicate' would indicate programmer error
1458
assert c_type == 'duplicate', c_type
1459
# Now figure out which is new and which is old
1460
if tt.new_contents(conflict[1]):
1461
new_file = conflict[1]
1462
old_file = conflict[2]
1464
new_file = conflict[2]
1465
old_file = conflict[1]
1467
# We should only get here if the conflict wasn't completely
1469
final_parent = tt.final_parent(old_file)
1470
if new_file in divert:
1471
new_name = tt.final_name(old_file)+'.diverted'
1472
tt.adjust_path(new_name, final_parent, new_file)
1473
new_conflicts.add((c_type, 'Diverted to',
1474
new_file, old_file))
1476
new_name = tt.final_name(old_file)+'.moved'
1477
tt.adjust_path(new_name, final_parent, old_file)
1478
new_conflicts.add((c_type, 'Moved existing file to',
1479
old_file, new_file))
1480
return new_conflicts
1483
def new_by_entry(tt, entry, parent_id, tree):
1484
"""Create a new file according to its inventory entry"""
1488
contents = tree.get_file(entry.file_id).readlines()
1489
executable = tree.is_executable(entry.file_id)
1490
return tt.new_file(name, parent_id, contents, entry.file_id,
1492
elif kind in ('directory', 'tree-reference'):
1493
trans_id = tt.new_directory(name, parent_id, entry.file_id)
1494
if kind == 'tree-reference':
1495
tt.set_tree_reference(entry.reference_revision, trans_id)
1497
elif kind == 'symlink':
1498
target = tree.get_symlink_target(entry.file_id)
1499
return tt.new_symlink(name, parent_id, target, entry.file_id)
1501
raise errors.BadFileKindError(name, kind)
1504
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1505
"""Create new file contents according to an inventory entry."""
1506
if entry.kind == "file":
1508
lines = tree.get_file(entry.file_id).readlines()
1509
tt.create_file(lines, trans_id, mode_id=mode_id)
1510
elif entry.kind == "symlink":
1511
tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
1512
elif entry.kind == "directory":
1513
tt.create_directory(trans_id)
1516
def create_entry_executability(tt, entry, trans_id):
1517
"""Set the executability of a trans_id according to an inventory entry"""
1518
if entry.kind == "file":
1519
tt.set_executability(entry.executable, trans_id)
1522
@deprecated_function(zero_fifteen)
1523
def find_interesting(working_tree, target_tree, filenames):
1524
"""Find the ids corresponding to specified filenames.
1526
Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1528
working_tree.lock_read()
1530
target_tree.lock_read()
1532
return working_tree.paths2ids(filenames, [target_tree])
1534
target_tree.unlock()
1536
working_tree.unlock()
1539
@deprecated_function(zero_ninety)
1540
def change_entry(tt, file_id, working_tree, target_tree,
1541
trans_id_file_id, backups, trans_id, by_parent):
1542
"""Replace a file_id's contents with those from a target tree."""
1543
if file_id is None and target_tree is None:
1544
# skip the logic altogether in the deprecation test
1546
e_trans_id = trans_id_file_id(file_id)
1547
entry = target_tree.inventory[file_id]
1548
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1551
mode_id = e_trans_id
1554
tt.delete_contents(e_trans_id)
1556
parent_trans_id = trans_id_file_id(entry.parent_id)
1557
backup_name = get_backup_name(entry, by_parent,
1558
parent_trans_id, tt)
1559
tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
1560
tt.unversion_file(e_trans_id)
1561
e_trans_id = tt.create_path(entry.name, parent_trans_id)
1562
tt.version_file(file_id, e_trans_id)
1563
trans_id[file_id] = e_trans_id
1564
create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id)
1565
create_entry_executability(tt, entry, e_trans_id)
1568
tt.set_executability(entry.executable, e_trans_id)
1569
if tt.final_name(e_trans_id) != entry.name:
1572
parent_id = tt.final_parent(e_trans_id)
1573
parent_file_id = tt.final_file_id(parent_id)
1574
if parent_file_id != entry.parent_id:
1579
parent_trans_id = trans_id_file_id(entry.parent_id)
1580
tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1583
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1584
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1587
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1588
"""Produce a backup-style name that appears to be available"""
1592
yield "%s.~%d~" % (name, counter)
1594
for new_name in name_gen():
1595
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1599
def _entry_changes(file_id, entry, working_tree):
1600
"""Determine in which ways the inventory entry has changed.
1602
Returns booleans: has_contents, content_mod, meta_mod
1603
has_contents means there are currently contents, but they differ
1604
contents_mod means contents need to be modified
1605
meta_mod means the metadata needs to be modified
1607
cur_entry = working_tree.inventory[file_id]
1609
working_kind = working_tree.kind(file_id)
1612
has_contents = False
1615
if has_contents is True:
1616
if entry.kind != working_kind:
1617
contents_mod, meta_mod = True, False
1619
cur_entry._read_tree_state(working_tree.id2path(file_id),
1621
contents_mod, meta_mod = entry.detect_changes(cur_entry)
1622
cur_entry._forget_tree_state()
1623
return has_contents, contents_mod, meta_mod
1626
def revert(working_tree, target_tree, filenames, backups=False,
1627
pb=DummyProgress(), change_reporter=None):
1628
"""Revert a working tree's contents to those of a target tree."""
1629
target_tree.lock_read()
1630
tt = TreeTransform(working_tree, pb)
1632
pp = ProgressPhase("Revert phase", 3, pb)
1634
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1636
merge_modified = _alter_files(working_tree, target_tree, tt,
1637
child_pb, filenames, backups)
1641
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1643
raw_conflicts = resolve_conflicts(tt, child_pb)
1646
conflicts = cook_conflicts(raw_conflicts, tt)
1648
change_reporter = delta._ChangeReporter(
1649
unversioned_filter=working_tree.is_ignored)
1650
delta.report_changes(tt._iter_changes(), change_reporter)
1651
for conflict in conflicts:
1655
working_tree.set_merge_modified(merge_modified)
1657
target_tree.unlock()
1663
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1665
merge_modified = working_tree.merge_modified()
1666
change_list = target_tree._iter_changes(working_tree,
1667
specific_files=specific_files, pb=pb)
1668
if target_tree.inventory.root is None:
1675
for id_num, (file_id, path, changed_content, versioned, parent, name,
1676
kind, executable) in enumerate(change_list):
1677
if skip_root and file_id[0] is not None and parent[0] is None:
1679
trans_id = tt.trans_id_file_id(file_id)
1682
keep_content = False
1683
if kind[0] == 'file' and (backups or kind[1] is None):
1684
wt_sha1 = working_tree.get_file_sha1(file_id)
1685
if merge_modified.get(file_id) != wt_sha1:
1686
# acquire the basis tree lazily to prevent the
1687
# expense of accessing it when it's not needed ?
1688
# (Guessing, RBC, 200702)
1689
if basis_tree is None:
1690
basis_tree = working_tree.basis_tree()
1691
basis_tree.lock_read()
1692
if file_id in basis_tree:
1693
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1695
elif kind[1] is None and not versioned[1]:
1697
if kind[0] is not None:
1698
if not keep_content:
1699
tt.delete_contents(trans_id)
1700
elif kind[1] is not None:
1701
parent_trans_id = tt.trans_id_file_id(parent[0])
1702
by_parent = tt.by_parent()
1703
backup_name = _get_backup_name(name[0], by_parent,
1704
parent_trans_id, tt)
1705
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1706
new_trans_id = tt.create_path(name[0], parent_trans_id)
1707
if versioned == (True, True):
1708
tt.unversion_file(trans_id)
1709
tt.version_file(file_id, new_trans_id)
1710
# New contents should have the same unix perms as old
1713
trans_id = new_trans_id
1714
if kind[1] == 'directory':
1715
tt.create_directory(trans_id)
1716
elif kind[1] == 'symlink':
1717
tt.create_symlink(target_tree.get_symlink_target(file_id),
1719
elif kind[1] == 'file':
1720
deferred_files.append((file_id, (trans_id, mode_id)))
1721
if basis_tree is None:
1722
basis_tree = working_tree.basis_tree()
1723
basis_tree.lock_read()
1724
new_sha1 = target_tree.get_file_sha1(file_id)
1725
if (file_id in basis_tree and new_sha1 ==
1726
basis_tree.get_file_sha1(file_id)):
1727
if file_id in merge_modified:
1728
del merge_modified[file_id]
1730
merge_modified[file_id] = new_sha1
1732
# preserve the execute bit when backing up
1733
if keep_content and executable[0] == executable[1]:
1734
tt.set_executability(executable[1], trans_id)
1736
assert kind[1] is None
1737
if versioned == (False, True):
1738
tt.version_file(file_id, trans_id)
1739
if versioned == (True, False):
1740
tt.unversion_file(trans_id)
1741
if (name[1] is not None and
1742
(name[0] != name[1] or parent[0] != parent[1])):
1744
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1745
if executable[0] != executable[1] and kind[1] == "file":
1746
tt.set_executability(executable[1], trans_id)
1747
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1749
tt.create_file(bytes, trans_id, mode_id)
1751
if basis_tree is not None:
1753
return merge_modified
1756
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1757
"""Make many conflict-resolution attempts, but die if they fail"""
1758
if pass_func is None:
1759
pass_func = conflict_pass
1760
new_conflicts = set()
1763
pb.update('Resolution pass', n+1, 10)
1764
conflicts = tt.find_conflicts()
1765
if len(conflicts) == 0:
1766
return new_conflicts
1767
new_conflicts.update(pass_func(tt, conflicts))
1768
raise MalformedTransform(conflicts=conflicts)
1773
def conflict_pass(tt, conflicts, path_tree=None):
1774
"""Resolve some classes of conflicts.
1776
:param tt: The transform to resolve conflicts in
1777
:param conflicts: The conflicts to resolve
1778
:param path_tree: A Tree to get supplemental paths from
1780
new_conflicts = set()
1781
for c_type, conflict in ((c[0], c) for c in conflicts):
1782
if c_type == 'duplicate id':
1783
tt.unversion_file(conflict[1])
1784
new_conflicts.add((c_type, 'Unversioned existing file',
1785
conflict[1], conflict[2], ))
1786
elif c_type == 'duplicate':
1787
# files that were renamed take precedence
1788
new_name = tt.final_name(conflict[1])+'.moved'
1789
final_parent = tt.final_parent(conflict[1])
1790
if tt.path_changed(conflict[1]):
1791
tt.adjust_path(new_name, final_parent, conflict[2])
1792
new_conflicts.add((c_type, 'Moved existing file to',
1793
conflict[2], conflict[1]))
1795
tt.adjust_path(new_name, final_parent, conflict[1])
1796
new_conflicts.add((c_type, 'Moved existing file to',
1797
conflict[1], conflict[2]))
1798
elif c_type == 'parent loop':
1799
# break the loop by undoing one of the ops that caused the loop
1801
while not tt.path_changed(cur):
1802
cur = tt.final_parent(cur)
1803
new_conflicts.add((c_type, 'Cancelled move', cur,
1804
tt.final_parent(cur),))
1805
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1807
elif c_type == 'missing parent':
1808
trans_id = conflict[1]
1810
tt.cancel_deletion(trans_id)
1811
new_conflicts.add(('deleting parent', 'Not deleting',
1814
tt.create_directory(trans_id)
1815
new_conflicts.add((c_type, 'Created directory', trans_id))
1817
tt.final_name(trans_id)
1819
file_id = tt.final_file_id(trans_id)
1820
entry = path_tree.inventory[file_id]
1821
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1822
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1823
elif c_type == 'unversioned parent':
1824
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1825
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1826
return new_conflicts
1829
def cook_conflicts(raw_conflicts, tt):
1830
"""Generate a list of cooked conflicts, sorted by file path"""
1831
from bzrlib.conflicts import Conflict
1832
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1833
return sorted(conflict_iter, key=Conflict.sort_key)
1836
def iter_cook_conflicts(raw_conflicts, tt):
1837
from bzrlib.conflicts import Conflict
1839
for conflict in raw_conflicts:
1840
c_type = conflict[0]
1841
action = conflict[1]
1842
modified_path = fp.get_path(conflict[2])
1843
modified_id = tt.final_file_id(conflict[2])
1844
if len(conflict) == 3:
1845
yield Conflict.factory(c_type, action=action, path=modified_path,
1846
file_id=modified_id)
1849
conflicting_path = fp.get_path(conflict[3])
1850
conflicting_id = tt.final_file_id(conflict[3])
1851
yield Conflict.factory(c_type, action=action, path=modified_path,
1852
file_id=modified_id,
1853
conflict_path=conflicting_path,
1854
conflict_file_id=conflicting_id)
1857
class _FileMover(object):
1858
"""Moves and deletes files for TreeTransform, tracking operations"""
1861
self.past_renames = []
1862
self.pending_deletions = []
1864
def rename(self, from_, to):
1865
"""Rename a file from one path to another. Functions like os.rename"""
1866
os.rename(from_, to)
1867
self.past_renames.append((from_, to))
1869
def pre_delete(self, from_, to):
1870
"""Rename a file out of the way and mark it for deletion.
1872
Unlike os.unlink, this works equally well for files and directories.
1873
:param from_: The current file path
1874
:param to: A temporary path for the file
1876
self.rename(from_, to)
1877
self.pending_deletions.append(to)
1880
"""Reverse all renames that have been performed"""
1881
for from_, to in reversed(self.past_renames):
1882
os.rename(to, from_)
1883
# after rollback, don't reuse _FileMover
1885
pending_deletions = None
1887
def apply_deletions(self):
1888
"""Apply all marked deletions"""
1889
for path in self.pending_deletions:
1891
# after apply_deletions, don't reuse _FileMover
1893
pending_deletions = None