151
80
* create_file or create_directory or create_symlink
153
82
* set_executability
155
Transform/Transaction ids
156
-------------------------
157
trans_ids are temporary ids assigned to all files involved in a transform.
158
It's possible, even common, that not all files in the Tree have trans_ids.
160
trans_ids are only valid for the TreeTransform that generated them.
163
def __init__(self, tree, pb=None):
84
def __init__(self, tree, pb=DummyProgress()):
85
"""Note: a tree_write lock is taken on the tree.
87
Use TreeTransform.finalize() to release the lock
91
self._tree.lock_tree_write()
93
control_files = self._tree._control_files
94
self._limbodir = urlutils.local_path_from_url(
95
control_files.controlfilename('limbo'))
97
os.mkdir(self._limbodir)
99
if e.errno == errno.EEXIST:
100
raise ExistingLimbo(self._limbodir)
167
105
self._id_number = 0
168
# Mapping of path in old tree -> trans_id
169
self._tree_path_ids = {}
170
# Mapping trans_id -> path in old tree
171
self._tree_id_paths = {}
172
# mapping of trans_id -> new basename
173
106
self._new_name = {}
174
# mapping of trans_id -> new parent trans_id
175
107
self._new_parent = {}
176
# mapping of trans_id with new contents -> new file_kind
177
108
self._new_contents = {}
178
# Set of trans_ids whose contents will be removed
179
109
self._removed_contents = set()
180
# Mapping of trans_id -> new execute-bit value
181
110
self._new_executability = {}
182
# Mapping of trans_id -> new tree-reference value
183
111
self._new_reference_revision = {}
184
# Set of trans_ids that will be removed
113
self._non_present_ids = {}
185
115
self._removed_id = set()
186
# Indicator of whether the transform has been applied
190
"""Support Context Manager API."""
193
def __exit__(self, exc_type, exc_val, exc_tb):
194
"""Support Context Manager API."""
197
def iter_tree_children(self, trans_id):
198
"""Iterate through the entry's tree children, if any.
200
:param trans_id: trans id to iterate
201
:returns: Iterator over paths
203
raise NotImplementedError(self.iter_tree_children)
205
def canonical_path(self, path):
208
def tree_kind(self, trans_id):
209
raise NotImplementedError(self.tree_kind)
212
"""Return a map of parent: children for known parents.
214
Only new paths and parents of tree files with assigned ids are used.
217
items = list(self._new_parent.items())
218
items.extend((t, self.final_parent(t))
219
for t in list(self._tree_id_paths))
220
for trans_id, parent_id in items:
221
if parent_id not in by_parent:
222
by_parent[parent_id] = set()
223
by_parent[parent_id].add(trans_id)
116
self._tree_path_ids = {}
117
self._tree_id_paths = {}
119
# Cache of realpath results, to speed up canonical_path
121
# Cache of relpath results, to speed up canonical_path
122
self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
126
def __get_root(self):
127
return self._new_root
129
root = property(__get_root)
226
131
def finalize(self):
227
"""Release the working tree lock, if held.
132
"""Release the working tree lock, if held, clean up limbo dir."""
133
if self._tree is None:
136
for trans_id, kind in self._new_contents.iteritems():
137
path = self._limbo_name(trans_id)
138
if kind == "directory":
143
os.rmdir(self._limbodir)
145
# We don't especially care *why* the dir is immortal.
146
raise ImmortalLimbo(self._limbodir)
229
This is required if apply has not been invoked, but can be invoked
232
raise NotImplementedError(self.finalize)
151
def _assign_id(self):
152
"""Produce a new tranform id"""
153
new_id = "new-%s" % self._id_number
234
157
def create_path(self, name, parent):
235
158
"""Assign a transaction id to a new path"""
236
trans_id = self.assign_id()
159
trans_id = self._assign_id()
237
160
unique_add(self._new_name, trans_id, name)
238
161
unique_add(self._new_parent, trans_id, parent)
241
164
def adjust_path(self, name, parent, trans_id):
242
165
"""Change the path that is assigned to a transaction id."""
244
raise ValueError("Parent trans-id may not be None")
245
if trans_id == self.root:
166
if trans_id == self._new_root:
246
167
raise CantMoveRoot
247
168
self._new_name[trans_id] = name
248
169
self._new_parent[trans_id] = parent
250
171
def adjust_root_path(self, name, parent):
251
172
"""Emulate moving the root by moving all children, instead.
253
174
We do this by undoing the association of root's transaction id with the
254
175
current tree. This allows us to create a new directory with that
255
transaction id. We unversion the root directory and version the
176
transaction id. We unversion the root directory and version the
256
177
physically new directory, and hope someone versions the tree root
259
raise NotImplementedError(self.adjust_root_path)
261
def fixup_new_roots(self):
262
"""Reinterpret requests to change the root directory
264
Instead of creating a root directory, or moving an existing directory,
265
all the attributes and children of the new root are applied to the
266
existing root directory.
268
This means that the old root trans-id becomes obsolete, so it is
269
recommended only to invoke this after the root trans-id has become
272
raise NotImplementedError(self.fixup_new_roots)
275
"""Produce a new tranform id"""
276
new_id = "new-%s" % self._id_number
180
old_root = self._new_root
181
old_root_file_id = self.final_file_id(old_root)
182
# force moving all children of root
183
for child_id in self.iter_tree_children(old_root):
184
if child_id != parent:
185
self.adjust_path(self.final_name(child_id),
186
self.final_parent(child_id), child_id)
187
file_id = self.final_file_id(child_id)
188
if file_id is not None:
189
self.unversion_file(child_id)
190
self.version_file(file_id, child_id)
192
# the physical root needs a new transaction id
193
self._tree_path_ids.pop("")
194
self._tree_id_paths.pop(old_root)
195
self._new_root = self.trans_id_tree_file_id(self._tree.get_root_id())
196
if parent == old_root:
197
parent = self._new_root
198
self.adjust_path(name, parent, old_root)
199
self.create_directory(old_root)
200
self.version_file(old_root_file_id, old_root)
201
self.unversion_file(self._new_root)
203
def trans_id_tree_file_id(self, inventory_id):
204
"""Determine the transaction id of a working tree file.
206
This reflects only files that already exist, not ones that will be
207
added by transactions.
209
path = self._tree.inventory.id2path(inventory_id)
210
return self.trans_id_tree_path(path)
212
def trans_id_file_id(self, file_id):
213
"""Determine or set the transaction id associated with a file ID.
214
A new id is only created for file_ids that were never present. If
215
a transaction has been unversioned, it is deliberately still returned.
216
(this will likely lead to an unversioned parent conflict.)
218
if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
219
return self._r_new_id[file_id]
220
elif file_id in self._tree.inventory:
221
return self.trans_id_tree_file_id(file_id)
222
elif file_id in self._non_present_ids:
223
return self._non_present_ids[file_id]
225
trans_id = self._assign_id()
226
self._non_present_ids[file_id] = trans_id
229
def canonical_path(self, path):
230
"""Get the canonical tree-relative path"""
231
# don't follow final symlinks
232
abs = self._tree.abspath(path)
233
if abs in self._relpaths:
234
return self._relpaths[abs]
235
dirname, basename = os.path.split(abs)
236
if dirname not in self._realpaths:
237
self._realpaths[dirname] = os.path.realpath(dirname)
238
dirname = self._realpaths[dirname]
239
abs = pathjoin(dirname, basename)
240
if dirname in self._relpaths:
241
relpath = pathjoin(self._relpaths[dirname], basename)
242
relpath = relpath.rstrip('/\\')
244
relpath = self._tree.relpath(abs)
245
self._relpaths[abs] = relpath
280
248
def trans_id_tree_path(self, path):
281
249
"""Determine (and maybe set) the transaction ID for a tree path."""
282
250
path = self.canonical_path(path)
283
251
if path not in self._tree_path_ids:
284
self._tree_path_ids[path] = self.assign_id()
252
self._tree_path_ids[path] = self._assign_id()
285
253
self._tree_id_paths[self._tree_path_ids[path]] = path
286
254
return self._tree_path_ids[path]
390
497
def new_contents(self, trans_id):
391
498
return (trans_id in self._new_contents)
393
def find_raw_conflicts(self):
500
def find_conflicts(self):
394
501
"""Find any violations of inventory or filesystem invariants"""
395
raise NotImplementedError(self.find_raw_conflicts)
397
def new_file(self, name, parent_id, contents, file_id=None,
398
executable=None, sha1=None):
502
if self.__done is True:
503
raise ReusingTransform()
505
# ensure all children of all existent parents are known
506
# all children of non-existent parents are known, by definition.
507
self._add_tree_children()
508
by_parent = self.by_parent()
509
conflicts.extend(self._unversioned_parents(by_parent))
510
conflicts.extend(self._parent_loops())
511
conflicts.extend(self._duplicate_entries(by_parent))
512
conflicts.extend(self._duplicate_ids())
513
conflicts.extend(self._parent_type_conflicts(by_parent))
514
conflicts.extend(self._improper_versioning())
515
conflicts.extend(self._executability_conflicts())
516
conflicts.extend(self._overwrite_conflicts())
519
def _add_tree_children(self):
520
"""Add all the children of all active parents to the known paths.
522
Active parents are those which gain children, and those which are
523
removed. This is a necessary first step in detecting conflicts.
525
parents = self.by_parent().keys()
526
parents.extend([t for t in self._removed_contents if
527
self.tree_kind(t) == 'directory'])
528
for trans_id in self._removed_id:
529
file_id = self.tree_file_id(trans_id)
530
if self._tree.inventory[file_id].kind == 'directory':
531
parents.append(trans_id)
533
for parent_id in parents:
534
# ensure that all children are registered with the transaction
535
list(self.iter_tree_children(parent_id))
537
def iter_tree_children(self, parent_id):
538
"""Iterate through the entry's tree children, if any"""
540
path = self._tree_id_paths[parent_id]
544
children = os.listdir(self._tree.abspath(path))
546
if e.errno != errno.ENOENT and e.errno != errno.ESRCH:
550
for child in children:
551
childpath = joinpath(path, child)
552
if self._tree.is_control_filename(childpath):
554
yield self.trans_id_tree_path(childpath)
556
def has_named_child(self, by_parent, parent_id, name):
558
children = by_parent[parent_id]
561
for child in children:
562
if self.final_name(child) == name:
565
path = self._tree_id_paths[parent_id]
568
childpath = joinpath(path, name)
569
child_id = self._tree_path_ids.get(childpath)
571
return lexists(self._tree.abspath(childpath))
573
if self.final_parent(child_id) != parent_id:
575
if child_id in self._removed_contents:
576
# XXX What about dangling file-ids?
581
def _parent_loops(self):
582
"""No entry should be its own ancestor"""
584
for trans_id in self._new_parent:
587
while parent_id is not ROOT_PARENT:
590
parent_id = self.final_parent(parent_id)
593
if parent_id == trans_id:
594
conflicts.append(('parent loop', trans_id))
595
if parent_id in seen:
599
def _unversioned_parents(self, by_parent):
600
"""If parent directories are versioned, children must be versioned."""
602
for parent_id, children in by_parent.iteritems():
603
if parent_id is ROOT_PARENT:
605
if self.final_file_id(parent_id) is not None:
607
for child_id in children:
608
if self.final_file_id(child_id) is not None:
609
conflicts.append(('unversioned parent', parent_id))
613
def _improper_versioning(self):
614
"""Cannot version a file with no contents, or a bad type.
616
However, existing entries with no contents are okay.
619
for trans_id in self._new_id.iterkeys():
621
kind = self.final_kind(trans_id)
623
conflicts.append(('versioning no contents', trans_id))
625
if not InventoryEntry.versionable_kind(kind):
626
conflicts.append(('versioning bad kind', trans_id, kind))
629
def _executability_conflicts(self):
630
"""Check for bad executability changes.
632
Only versioned files may have their executability set, because
633
1. only versioned entries can have executability under windows
634
2. only files can be executable. (The execute bit on a directory
635
does not indicate searchability)
638
for trans_id in self._new_executability:
639
if self.final_file_id(trans_id) is None:
640
conflicts.append(('unversioned executability', trans_id))
643
non_file = self.final_kind(trans_id) != "file"
647
conflicts.append(('non-file executability', trans_id))
650
def _overwrite_conflicts(self):
651
"""Check for overwrites (not permitted on Win32)"""
653
for trans_id in self._new_contents:
655
self.tree_kind(trans_id)
658
if trans_id not in self._removed_contents:
659
conflicts.append(('overwrite', trans_id,
660
self.final_name(trans_id)))
663
def _duplicate_entries(self, by_parent):
664
"""No directory may have two entries with the same name."""
666
for children in by_parent.itervalues():
667
name_ids = [(self.final_name(t), t) for t in children]
671
for name, trans_id in name_ids:
673
kind = self.final_kind(trans_id)
676
file_id = self.final_file_id(trans_id)
677
if kind is None and file_id is None:
679
if name == last_name:
680
conflicts.append(('duplicate', last_trans_id, trans_id,
683
last_trans_id = trans_id
686
def _duplicate_ids(self):
687
"""Each inventory id may only be used once"""
689
removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
691
active_tree_ids = set((f for f in self._tree.inventory if
692
f not in removed_tree_ids))
693
for trans_id, file_id in self._new_id.iteritems():
694
if file_id in active_tree_ids:
695
old_trans_id = self.trans_id_tree_file_id(file_id)
696
conflicts.append(('duplicate id', old_trans_id, trans_id))
699
def _parent_type_conflicts(self, by_parent):
700
"""parents must have directory 'contents'."""
702
for parent_id, children in by_parent.iteritems():
703
if parent_id is ROOT_PARENT:
705
if not self._any_contents(children):
707
for child in children:
709
self.final_kind(child)
713
kind = self.final_kind(parent_id)
717
conflicts.append(('missing parent', parent_id))
718
elif kind != "directory":
719
conflicts.append(('non-directory parent', parent_id))
722
def _any_contents(self, trans_ids):
723
"""Return true if any of the trans_ids, will have contents."""
724
for trans_id in trans_ids:
726
kind = self.final_kind(trans_id)
733
"""Apply all changes to the inventory and filesystem.
735
If filesystem or inventory conflicts are present, MalformedTransform
738
conflicts = self.find_conflicts()
739
if len(conflicts) != 0:
740
raise MalformedTransform(conflicts=conflicts)
741
inv = self._tree.inventory
743
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
745
child_pb.update('Apply phase', 0, 2)
746
self._apply_removals(inv, inventory_delta)
747
child_pb.update('Apply phase', 1, 2)
748
modified_paths = self._apply_insertions(inv, inventory_delta)
751
self._tree.apply_inventory_delta(inventory_delta)
754
return _TransformResults(modified_paths)
756
def _limbo_name(self, trans_id):
757
"""Generate the limbo name of a file"""
758
return pathjoin(self._limbodir, trans_id)
760
def _apply_removals(self, inv, inventory_delta):
761
"""Perform tree operations that remove directory/inventory names.
763
That is, delete files that are to be deleted, and put any files that
764
need renaming into limbo. This must be done in strict child-to-parent
767
tree_paths = list(self._tree_path_ids.iteritems())
768
tree_paths.sort(reverse=True)
769
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
771
for num, data in enumerate(tree_paths):
772
path, trans_id = data
773
child_pb.update('removing file', num, len(tree_paths))
774
full_path = self._tree.abspath(path)
775
if trans_id in self._removed_contents:
776
delete_any(full_path)
777
elif trans_id in self._new_name or trans_id in \
780
os.rename(full_path, self._limbo_name(trans_id))
782
if e.errno != errno.ENOENT:
784
if trans_id in self._removed_id:
785
if trans_id == self._new_root:
786
file_id = self._tree.inventory.root.file_id
788
file_id = self.tree_file_id(trans_id)
789
assert file_id is not None
790
inventory_delta.append((path, None, file_id, None))
794
def _apply_insertions(self, inv, inventory_delta):
795
"""Perform tree operations that insert directory/inventory names.
797
That is, create any files that need to be created, and restore from
798
limbo any files that needed renaming. This must be done in strict
799
parent-to-child order.
801
new_paths = self.new_paths()
803
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
805
for num, (path, trans_id) in enumerate(new_paths):
807
child_pb.update('adding file', num, len(new_paths))
809
kind = self._new_contents[trans_id]
811
kind = contents = None
812
if trans_id in self._new_contents or \
813
self.path_changed(trans_id):
814
full_path = self._tree.abspath(path)
816
os.rename(self._limbo_name(trans_id), full_path)
818
# We may be renaming a dangling inventory id
819
if e.errno != errno.ENOENT:
821
if trans_id in self._new_contents:
822
modified_paths.append(full_path)
823
del self._new_contents[trans_id]
825
if trans_id in self._new_id:
827
kind = file_kind(self._tree.abspath(path))
828
if trans_id in self._new_reference_revision:
829
new_entry = inventory.TreeReference(
830
self._new_id[trans_id],
831
self._new_name[trans_id],
832
self.final_file_id(self._new_parent[trans_id]),
833
None, self._new_reference_revision[trans_id])
835
new_entry = inventory.make_entry(kind,
836
self.final_name(trans_id),
837
self.final_file_id(self.final_parent(trans_id)),
838
self._new_id[trans_id])
840
if trans_id in self._new_name or trans_id in\
842
trans_id in self._new_executability:
843
file_id = self.final_file_id(trans_id)
844
if file_id is not None:
846
new_entry = entry.copy()
848
if trans_id in self._new_name or trans_id in\
850
if new_entry is not None:
851
new_entry.name = self.final_name(trans_id)
852
parent = self.final_parent(trans_id)
853
parent_id = self.final_file_id(parent)
854
new_entry.parent_id = parent_id
856
if trans_id in self._new_executability:
857
self._set_executability(path, new_entry, trans_id)
858
if new_entry is not None:
859
if new_entry.file_id in inv:
860
old_path = inv.id2path(new_entry.file_id)
863
inventory_delta.append((old_path, path,
868
return modified_paths
870
def _set_executability(self, path, entry, trans_id):
871
"""Set the executability of versioned files """
872
new_executability = self._new_executability[trans_id]
873
entry.executable = new_executability
874
if supports_executable():
875
abspath = self._tree.abspath(path)
876
current_mode = os.stat(abspath).st_mode
877
if new_executability:
880
to_mode = current_mode | (0100 & ~umask)
881
# Enable x-bit for others only if they can read it.
882
if current_mode & 0004:
883
to_mode |= 0001 & ~umask
884
if current_mode & 0040:
885
to_mode |= 0010 & ~umask
887
to_mode = current_mode & ~0111
888
os.chmod(abspath, to_mode)
890
def _new_entry(self, name, parent_id, file_id):
891
"""Helper function to create a new filesystem entry."""
892
trans_id = self.create_path(name, parent_id)
893
if file_id is not None:
894
self.version_file(file_id, trans_id)
897
def new_file(self, name, parent_id, contents, file_id=None,
399
899
"""Convenience method to create files.
401
901
name is the name of the file to create.
402
902
parent_id is the transaction id of the parent directory of the file.
403
903
contents is an iterator of bytestrings, which will be used to produce
416
922
file_id is the inventory ID of the directory, if it is to be versioned.
418
raise NotImplementedError(self.new_directory)
924
trans_id = self._new_entry(name, parent_id, file_id)
925
self.create_directory(trans_id)
420
928
def new_symlink(self, name, parent_id, target, file_id=None):
421
929
"""Convenience method to create symbolic link.
423
931
name is the name of the symlink to create.
424
932
parent_id is the transaction id of the parent directory of the symlink.
425
933
target is a bytestring of the target of the symlink.
426
934
file_id is the inventory ID of the file, if it is to be versioned.
428
raise NotImplementedError(self.new_symlink)
430
def new_orphan(self, trans_id, parent_id):
431
"""Schedule an item to be orphaned.
433
When a directory is about to be removed, its children, if they are not
434
versioned are moved out of the way: they don't have a parent anymore.
436
:param trans_id: The trans_id of the existing item.
437
:param parent_id: The parent trans_id of the item.
439
raise NotImplementedError(self.new_orphan)
441
def iter_changes(self):
442
"""Produce output in the same format as Tree.iter_changes.
936
trans_id = self._new_entry(name, parent_id, file_id)
937
self.create_symlink(target, trans_id)
940
def _affected_ids(self):
941
"""Return the set of transform ids affected by the transform"""
942
trans_ids = set(self._removed_id)
943
trans_ids.update(self._new_id.keys())
944
trans_ids.update(self._removed_contents)
945
trans_ids.update(self._new_contents.keys())
946
trans_ids.update(self._new_executability.keys())
947
trans_ids.update(self._new_name.keys())
948
trans_ids.update(self._new_parent.keys())
951
def _get_file_id_maps(self):
952
"""Return mapping of file_ids to trans_ids in the to and from states"""
953
trans_ids = self._affected_ids()
956
# Build up two dicts: trans_ids associated with file ids in the
957
# FROM state, vs the TO state.
958
for trans_id in trans_ids:
959
from_file_id = self.tree_file_id(trans_id)
960
if from_file_id is not None:
961
from_trans_ids[from_file_id] = trans_id
962
to_file_id = self.final_file_id(trans_id)
963
if to_file_id is not None:
964
to_trans_ids[to_file_id] = trans_id
965
return from_trans_ids, to_trans_ids
967
def _from_file_data(self, from_trans_id, from_versioned, file_id):
968
"""Get data about a file in the from (tree) state
970
Return a (name, parent, kind, executable) tuple
972
from_path = self._tree_id_paths.get(from_trans_id)
974
# get data from working tree if versioned
975
from_entry = self._tree.inventory[file_id]
976
from_name = from_entry.name
977
from_parent = from_entry.parent_id
980
if from_path is None:
981
# File does not exist in FROM state
985
# File exists, but is not versioned. Have to use path-
987
from_name = os.path.basename(from_path)
988
tree_parent = self.get_tree_parent(from_trans_id)
989
from_parent = self.tree_file_id(tree_parent)
990
if from_path is not None:
991
from_kind, from_executable, from_stats = \
992
self._tree._comparison_data(from_entry, from_path)
995
from_executable = False
996
return from_name, from_parent, from_kind, from_executable
998
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
999
"""Get data about a file in the to (target) state
1001
Return a (name, parent, kind, executable) tuple
1003
to_name = self.final_name(to_trans_id)
1005
to_kind = self.final_kind(to_trans_id)
1008
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1009
if to_trans_id in self._new_executability:
1010
to_executable = self._new_executability[to_trans_id]
1011
elif to_trans_id == from_trans_id:
1012
to_executable = from_executable
1014
to_executable = False
1015
return to_name, to_parent, to_kind, to_executable
1017
def _iter_changes(self):
1018
"""Produce output in the same format as Tree._iter_changes.
444
1020
Will produce nonsensical results if invoked while inventory/filesystem
445
conflicts (as reported by TreeTransform.find_raw_conflicts()) are present.
1021
conflicts (as reported by TreeTransform.find_conflicts()) are present.
447
1023
This reads the Transform, but only reproduces changes involving a
448
1024
file_id. Files that are not versioned in either of the FROM or TO
449
1025
states are not reflected.
451
raise NotImplementedError(self.iter_changes)
453
def get_preview_tree(self):
454
"""Return a tree representing the result of the transform.
456
The tree is a snapshot, and altering the TreeTransform will invalidate
459
raise NotImplementedError(self.get_preview_tree)
461
def commit(self, branch, message, merge_parents=None, strict=False,
462
timestamp=None, timezone=None, committer=None, authors=None,
463
revprops=None, revision_id=None):
464
"""Commit the result of this TreeTransform to a branch.
466
:param branch: The branch to commit to.
467
:param message: The message to attach to the commit.
468
:param merge_parents: Additional parent revision-ids specified by
470
:param strict: If True, abort the commit if there are unversioned
472
:param timestamp: if not None, seconds-since-epoch for the time and
473
date. (May be a float.)
474
:param timezone: Optional timezone for timestamp, as an offset in
476
:param committer: Optional committer in email-id format.
477
(e.g. "J Random Hacker <jrandom@example.com>")
478
:param authors: Optional list of authors in email-id format.
479
:param revprops: Optional dictionary of revision properties.
480
:param revision_id: Optional revision id. (Specifying a revision-id
481
may reduce performance for some non-native formats.)
482
:return: The revision_id of the revision committed.
484
raise NotImplementedError(self.commit)
486
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
487
"""Schedule creation of a new file.
491
:param contents: an iterator of strings, all of which will be written
492
to the target destination.
493
:param trans_id: TreeTransform handle
494
:param mode_id: If not None, force the mode of the target file to match
495
the mode of the object referenced by mode_id.
496
Otherwise, we will try to preserve mode bits of an existing file.
497
:param sha1: If the sha1 of this content is already known, pass it in.
498
We can use it to prevent future sha1 computations.
500
raise NotImplementedError(self.create_file)
502
def create_directory(self, trans_id):
503
"""Schedule creation of a new directory.
505
See also new_directory.
507
raise NotImplementedError(self.create_directory)
509
def create_symlink(self, target, trans_id):
510
"""Schedule creation of a new symbolic link.
512
target is a bytestring.
513
See also new_symlink.
515
raise NotImplementedError(self.create_symlink)
517
def create_hardlink(self, path, trans_id):
518
"""Schedule creation of a hard link"""
519
raise NotImplementedError(self.create_hardlink)
521
def cancel_creation(self, trans_id):
522
"""Cancel the creation of new file contents."""
523
raise NotImplementedError(self.cancel_creation)
525
def cook_conflicts(self, raw_conflicts):
528
raise NotImplementedError(self.cook_conflicts)
531
class OrphaningError(errors.BzrError):
533
# Only bugs could lead to such exception being seen by the user
534
internal_error = True
535
_fmt = "Error while orphaning %s in %s directory"
537
def __init__(self, orphan, parent):
538
errors.BzrError.__init__(self)
543
class OrphaningForbidden(OrphaningError):
545
_fmt = "Policy: %s doesn't allow creating orphans."
547
def __init__(self, policy):
548
errors.BzrError.__init__(self)
552
def move_orphan(tt, orphan_id, parent_id):
553
"""See TreeTransformBase.new_orphan.
555
This creates a new orphan in the `brz-orphans` dir at the root of the
558
:param tt: The TreeTransform orphaning `trans_id`.
560
:param orphan_id: The trans id that should be orphaned.
562
:param parent_id: The orphan parent trans id.
564
# Add the orphan dir if it doesn't exist
565
orphan_dir_basename = 'brz-orphans'
566
od_id = tt.trans_id_tree_path(orphan_dir_basename)
567
if tt.final_kind(od_id) is None:
568
tt.create_directory(od_id)
569
parent_path = tt._tree_id_paths[parent_id]
570
# Find a name that doesn't exist yet in the orphan dir
571
actual_name = tt.final_name(orphan_id)
572
new_name = tt._available_backup_name(actual_name, od_id)
573
tt.adjust_path(new_name, od_id, orphan_id)
574
trace.warning('%s has been orphaned in %s'
575
% (joinpath(parent_path, actual_name), orphan_dir_basename))
578
def refuse_orphan(tt, orphan_id, parent_id):
579
"""See TreeTransformBase.new_orphan.
581
This refuses to create orphan, letting the caller handle the conflict.
583
raise OrphaningForbidden('never')
586
orphaning_registry = registry.Registry()
587
orphaning_registry.register(
588
u'conflict', refuse_orphan,
589
'Leave orphans in place and create a conflict on the directory.')
590
orphaning_registry.register(
591
u'move', move_orphan,
592
'Move orphans into the brz-orphans directory.')
593
orphaning_registry._set_default_key(u'conflict')
596
opt_transform_orphan = _mod_config.RegistryOption(
597
'transform.orphan_policy', orphaning_registry,
598
help='Policy for orphaned files during transform operations.',
1027
final_paths = FinalPaths(self)
1028
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1030
# Now iterate through all active file_ids
1031
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1033
from_trans_id = from_trans_ids.get(file_id)
1034
# find file ids, and determine versioning state
1035
if from_trans_id is None:
1036
from_versioned = False
1037
from_trans_id = to_trans_ids[file_id]
1039
from_versioned = True
1040
to_trans_id = to_trans_ids.get(file_id)
1041
if to_trans_id is None:
1042
to_versioned = False
1043
to_trans_id = from_trans_id
1047
from_name, from_parent, from_kind, from_executable = \
1048
self._from_file_data(from_trans_id, from_versioned, file_id)
1050
to_name, to_parent, to_kind, to_executable = \
1051
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1053
if not from_versioned:
1056
from_path = self._tree_id_paths.get(from_trans_id)
1057
if not to_versioned:
1060
to_path = final_paths.get_path(to_trans_id)
1061
if from_kind != to_kind:
1063
elif to_kind in ('file', 'symlink') and (
1064
to_trans_id != from_trans_id or
1065
to_trans_id in self._new_contents):
1067
if (not modified and from_versioned == to_versioned and
1068
from_parent==to_parent and from_name == to_name and
1069
from_executable == to_executable):
1071
results.append((file_id, (from_path, to_path), modified,
1072
(from_versioned, to_versioned),
1073
(from_parent, to_parent),
1074
(from_name, to_name),
1075
(from_kind, to_kind),
1076
(from_executable, to_executable)))
1077
return iter(sorted(results, key=lambda x:x[1]))
602
1080
def joinpath(parent, child):
635
1112
self._known_paths[trans_id] = self._determine_path(trans_id)
636
1113
return self._known_paths[trans_id]
638
def get_paths(self, trans_ids):
639
return [(self.get_path(t), t) for t in trans_ids]
642
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
643
delta_from_tree=False):
1115
def topology_sorted_ids(tree):
1116
"""Determine the topological order of the ids in a tree"""
1117
file_ids = list(tree)
1118
file_ids.sort(key=tree.id2path)
1122
def build_tree(tree, wt):
644
1123
"""Create working tree for a branch, using a TreeTransform.
646
1125
This function should be used on empty trees, having a tree root at most.
647
1126
(see merge and revert functionality for working with existing trees)
649
1128
Existing files are handled like so:
651
1130
- Existing bzrdirs take precedence over creating new items. They are
652
1131
created as '%s.diverted' % name.
653
1132
- Otherwise, if the content on disk matches the content we are building,
654
1133
it is silently replaced.
655
1134
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
657
:param tree: The tree to convert wt into a copy of
658
:param wt: The working tree that files will be placed into
659
:param accelerator_tree: A tree which can be used for retrieving file
660
contents more quickly than tree itself, i.e. a workingtree. tree
661
will be used for cases where accelerator_tree's content is different.
662
:param hardlink: If true, hard-link files to accelerator_tree, where
663
possible. accelerator_tree must implement abspath, i.e. be a
665
:param delta_from_tree: If true, build_tree may use the input Tree to
666
generate the inventory delta.
668
with contextlib.ExitStack() as exit_stack:
669
exit_stack.enter_context(wt.lock_tree_write())
670
exit_stack.enter_context(tree.lock_read())
671
if accelerator_tree is not None:
672
exit_stack.enter_context(accelerator_tree.lock_read())
673
return _build_tree(tree, wt, accelerator_tree, hardlink,
677
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
1136
wt.lock_tree_write()
1140
return _build_tree(tree, wt)
1146
def _build_tree(tree, wt):
678
1147
"""See build_tree."""
679
for num, _unused in enumerate(wt.all_versioned_paths()):
680
if num > 0: # more than just a root
681
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1148
if len(wt.inventory) > 1: # more than just a root
1149
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
682
1150
file_trans_id = {}
683
top_pb = ui.ui_factory.nested_progress_bar()
1151
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
684
1152
pp = ProgressPhase("Build phase", 2, top_pb)
685
if tree.path2id('') is not None:
686
# This is kind of a hack: we should be altering the root
687
# as part of the regular tree shape diff logic.
688
# The conditional test here is to avoid doing an
1153
if tree.inventory.root is not None:
1154
# this is kindof a hack: we should be altering the root
1155
# as partof the regular tree shape diff logic.
1156
# the conditional test hereis to avoid doing an
689
1157
# expensive operation (flush) every time the root id
690
1158
# is set within the tree, nor setting the root and thus
691
1159
# marking the tree as dirty, because we use two different
692
1160
# idioms here: tree interfaces and inventory interfaces.
693
if wt.path2id('') != tree.path2id(''):
694
wt.set_root_id(tree.path2id(''))
1161
if wt.path2id('') != tree.inventory.root.file_id:
1162
wt.set_root_id(tree.inventory.root.file_id)
1164
tt = TreeTransform(wt)
700
file_trans_id[find_previous_path(wt, tree, '')] = tt.trans_id_tree_path('')
701
with ui.ui_factory.nested_progress_bar() as pb:
702
deferred_contents = []
704
total = len(tree.all_versioned_paths())
706
precomputed_delta = []
708
precomputed_delta = None
709
# Check if tree inventory has content. If so, we populate
710
# existing_files with the directory content. If there are no
711
# entries we skip populating existing_files as its not used.
712
# This improves performance and unncessary work on large
713
# directory trees. (#501307)
715
existing_files = set()
716
for dir, files in wt.walkdirs():
717
existing_files.update(f[0] for f in files)
1168
file_trans_id[wt.get_root_id()] = \
1169
tt.trans_id_tree_file_id(wt.get_root_id())
1170
pb = bzrlib.ui.ui_factory.nested_progress_bar()
718
1172
for num, (tree_path, entry) in \
719
enumerate(tree.iter_entries_by_dir()):
720
pb.update(gettext("Building tree"), num
721
- len(deferred_contents), total)
1173
enumerate(tree.inventory.iter_entries_by_dir()):
1174
pb.update("Building tree", num, len(tree.inventory))
722
1175
if entry.parent_id is None:
724
1177
reparent = False
725
1178
file_id = entry.file_id
727
precomputed_delta.append((None, tree_path, file_id, entry))
728
if tree_path in existing_files:
729
target_path = wt.abspath(tree_path)
1179
target_path = wt.abspath(tree_path)
730
1181
kind = file_kind(target_path)
731
1185
if kind == "directory":
733
controldir.ControlDir.open(target_path)
1187
bzrdir.BzrDir.open(target_path)
734
1188
except errors.NotBranchError:
737
divert.add(tree_path)
738
if (tree_path not in divert
740
tree, entry, tree_path, kind, target_path)):
1192
if (file_id not in divert and
1193
_content_match(tree, entry, file_id, kind,
741
1195
tt.delete_contents(tt.trans_id_tree_path(tree_path))
742
1196
if kind == 'directory':
744
parent_id = file_trans_id[osutils.dirname(tree_path)]
745
if entry.kind == 'file':
746
# We *almost* replicate new_by_entry, so that we can defer
747
# getting the file text, and get them all at once.
748
trans_id = tt.create_path(entry.name, parent_id)
749
file_trans_id[tree_path] = trans_id
750
tt.version_file(trans_id, file_id=file_id)
751
executable = tree.is_executable(tree_path)
753
tt.set_executability(executable, trans_id)
754
trans_data = (trans_id, tree_path, entry.text_sha1)
755
deferred_contents.append((tree_path, trans_data))
757
file_trans_id[tree_path] = new_by_entry(
758
tree_path, tt, entry, parent_id, tree)
1198
if entry.parent_id not in file_trans_id:
1199
raise AssertionError(
1200
'entry %s parent id %r is not in file_trans_id %r'
1201
% (entry, entry.parent_id, file_trans_id))
1202
parent_id = file_trans_id[entry.parent_id]
1203
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
760
new_trans_id = file_trans_id[tree_path]
1206
new_trans_id = file_trans_id[file_id]
761
1207
old_parent = tt.trans_id_tree_path(tree_path)
762
1208
_reparent_children(tt, old_parent, new_trans_id)
763
offset = num + 1 - len(deferred_contents)
764
_create_files(tt, tree, deferred_contents, pb, offset,
765
accelerator_tree, hardlink)
767
1212
divert_trans = set(file_trans_id[f] for f in divert)
770
return resolve_checkout(t, c, divert_trans)
1213
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
771
1214
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
772
if len(raw_conflicts) > 0:
773
precomputed_delta = None
774
conflicts = tt.cook_conflicts(raw_conflicts)
1215
conflicts = cook_conflicts(raw_conflicts, tt)
775
1216
for conflict in conflicts:
776
trace.warning(str(conflict))
778
1219
wt.add_conflicts(conflicts)
779
1220
except errors.UnsupportedOperation:
781
result = tt.apply(no_conflicts=True,
782
precomputed_delta=precomputed_delta)
785
1225
top_pb.finished()
789
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
791
total = len(desired_files) + offset
793
if accelerator_tree is None:
794
new_desired_files = desired_files
796
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
798
change.path for change in iter
799
if not (change.changed_content or change.executable[0] != change.executable[1])]
800
if accelerator_tree.supports_content_filtering():
801
unchanged = [(tp, ap) for (tp, ap) in unchanged
802
if not next(accelerator_tree.iter_search_rules([ap]))]
803
unchanged = dict(unchanged)
804
new_desired_files = []
806
for unused_tree_path, (trans_id, tree_path, text_sha1) in desired_files:
807
accelerator_path = unchanged.get(tree_path)
808
if accelerator_path is None:
809
new_desired_files.append((tree_path,
810
(trans_id, tree_path, text_sha1)))
812
pb.update(gettext('Adding file contents'), count + offset, total)
814
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
817
with accelerator_tree.get_file(accelerator_path) as f:
818
chunks = osutils.file_iterator(f)
819
if wt.supports_content_filtering():
820
filters = wt._content_filter_stack(tree_path)
821
chunks = filtered_output_bytes(chunks, filters,
822
ContentFilterContext(tree_path, tree))
823
tt.create_file(chunks, trans_id, sha1=text_sha1)
826
for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
827
tree.iter_files_bytes(new_desired_files)):
828
if wt.supports_content_filtering():
829
filters = wt._content_filter_stack(tree_path)
830
contents = filtered_output_bytes(contents, filters,
831
ContentFilterContext(tree_path, tree))
832
tt.create_file(contents, trans_id, sha1=text_sha1)
833
pb.update(gettext('Adding file contents'), count + offset, total)
836
1228
def _reparent_children(tt, old_parent, new_parent):
953
1310
tt.set_executability(entry.executable, trans_id)
1313
@deprecated_function(zero_fifteen)
1314
def find_interesting(working_tree, target_tree, filenames):
1315
"""Find the ids corresponding to specified filenames.
1317
Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1319
working_tree.lock_read()
1321
target_tree.lock_read()
1323
return working_tree.paths2ids(filenames, [target_tree])
1325
target_tree.unlock()
1327
working_tree.unlock()
1330
def change_entry(tt, file_id, working_tree, target_tree,
1331
trans_id_file_id, backups, trans_id, by_parent):
1332
"""Replace a file_id's contents with those from a target tree."""
1333
e_trans_id = trans_id_file_id(file_id)
1334
entry = target_tree.inventory[file_id]
1335
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1338
mode_id = e_trans_id
1341
tt.delete_contents(e_trans_id)
1343
parent_trans_id = trans_id_file_id(entry.parent_id)
1344
backup_name = get_backup_name(entry, by_parent,
1345
parent_trans_id, tt)
1346
tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
1347
tt.unversion_file(e_trans_id)
1348
e_trans_id = tt.create_path(entry.name, parent_trans_id)
1349
tt.version_file(file_id, e_trans_id)
1350
trans_id[file_id] = e_trans_id
1351
create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id)
1352
create_entry_executability(tt, entry, e_trans_id)
1355
tt.set_executability(entry.executable, e_trans_id)
1356
if tt.final_name(e_trans_id) != entry.name:
1359
parent_id = tt.final_parent(e_trans_id)
1360
parent_file_id = tt.final_file_id(parent_id)
1361
if parent_file_id != entry.parent_id:
1366
parent_trans_id = trans_id_file_id(entry.parent_id)
1367
tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1370
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1371
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1374
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1375
"""Produce a backup-style name that appears to be available"""
1379
yield "%s.~%d~" % (name, counter)
1381
for new_name in name_gen():
1382
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1386
def _entry_changes(file_id, entry, working_tree):
1387
"""Determine in which ways the inventory entry has changed.
1389
Returns booleans: has_contents, content_mod, meta_mod
1390
has_contents means there are currently contents, but they differ
1391
contents_mod means contents need to be modified
1392
meta_mod means the metadata needs to be modified
1394
cur_entry = working_tree.inventory[file_id]
1396
working_kind = working_tree.kind(file_id)
1399
has_contents = False
1402
if has_contents is True:
1403
if entry.kind != working_kind:
1404
contents_mod, meta_mod = True, False
1406
cur_entry._read_tree_state(working_tree.id2path(file_id),
1408
contents_mod, meta_mod = entry.detect_changes(cur_entry)
1409
cur_entry._forget_tree_state()
1410
return has_contents, contents_mod, meta_mod
956
1413
def revert(working_tree, target_tree, filenames, backups=False,
957
pb=None, change_reporter=None):
1414
pb=DummyProgress(), change_reporter=None):
958
1415
"""Revert a working tree's contents to those of a target tree."""
959
pb = ui.ui_factory.nested_progress_bar()
1416
target_tree.lock_read()
1417
tt = TreeTransform(working_tree, pb)
961
with target_tree.lock_read(), working_tree.transform(pb) as tt:
962
pp = ProgressPhase("Revert phase", 3, pb)
963
conflicts, merge_modified = _prepare_revert_transform(
964
working_tree, target_tree, tt, filenames, backups, pp)
967
change_reporter = delta._ChangeReporter(
968
unversioned_filter=working_tree.is_ignored)
969
delta.report_changes(tt.iter_changes(), change_reporter)
970
for conflict in conflicts:
971
trace.warning(str(conflict))
974
if working_tree.supports_merge_modified():
975
working_tree.set_merge_modified(merge_modified)
1419
pp = ProgressPhase("Revert phase", 3, pb)
1421
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1423
_alter_files(working_tree, target_tree, tt, child_pb,
1428
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1430
raw_conflicts = resolve_conflicts(tt, child_pb)
1433
conflicts = cook_conflicts(raw_conflicts, tt)
1435
change_reporter = delta._ChangeReporter(
1436
unversioned_filter=working_tree.is_ignored)
1437
delta.report_changes(tt._iter_changes(), change_reporter)
1438
for conflict in conflicts:
1442
working_tree.set_merge_modified({})
1444
target_tree.unlock()
978
1447
return conflicts
981
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
982
backups, pp, basis_tree=None,
983
merge_modified=None):
984
with ui.ui_factory.nested_progress_bar() as child_pb:
985
if merge_modified is None:
986
merge_modified = working_tree.merge_modified()
987
merge_modified = _alter_files(working_tree, target_tree, tt,
988
child_pb, filenames, backups,
989
merge_modified, basis_tree)
990
with ui.ui_factory.nested_progress_bar() as child_pb:
991
raw_conflicts = resolve_conflicts(
992
tt, child_pb, lambda t, c: conflict_pass(t, c, target_tree))
993
conflicts = tt.cook_conflicts(raw_conflicts)
994
return conflicts, merge_modified
997
1450
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
998
backups, merge_modified, basis_tree=None):
999
if basis_tree is not None:
1000
basis_tree.lock_read()
1001
# We ask the working_tree for its changes relative to the target, rather
1002
# than the target changes relative to the working tree. Because WT4 has an
1003
# optimizer to compare itself to a target, but no optimizer for the
1005
change_list = working_tree.iter_changes(
1006
target_tree, specific_files=specific_files, pb=pb)
1007
if not target_tree.is_versioned(u''):
1452
merge_modified = working_tree.merge_modified()
1453
change_list = target_tree._iter_changes(working_tree,
1454
specific_files=specific_files, pb=pb)
1455
if target_tree.inventory.root is None:
1008
1456
skip_root = True
1010
1458
skip_root = False
1013
for id_num, change in enumerate(change_list):
1014
target_path, wt_path = change.path
1015
target_versioned, wt_versioned = change.versioned
1016
target_parent = change.parent_id[0]
1017
target_name, wt_name = change.name
1018
target_kind, wt_kind = change.kind
1019
target_executable, wt_executable = change.executable
1020
if skip_root and wt_path == '':
1461
for id_num, (file_id, path, changed_content, versioned, parent, name,
1462
kind, executable) in enumerate(change_list):
1463
if skip_root and file_id[0] is not None and parent[0] is None:
1022
trans_id = tt.trans_id_file_id(change.file_id)
1465
trans_id = tt.trans_id_file_id(file_id)
1024
if change.changed_content:
1025
1468
keep_content = False
1026
if wt_kind == 'file' and (backups or target_kind is None):
1027
wt_sha1 = working_tree.get_file_sha1(wt_path)
1028
if merge_modified.get(wt_path) != wt_sha1:
1029
# acquire the basis tree lazily to prevent the
1030
# expense of accessing it when it's not needed ?
1031
# (Guessing, RBC, 200702)
1469
if kind[0] == 'file' and (backups or kind[1] is None):
1470
wt_sha1 = working_tree.get_file_sha1(file_id)
1471
if merge_modified.get(file_id) != wt_sha1:
1472
# acquire the basis tree lazyily to prevent the expense
1473
# of accessing it when its not needed ? (Guessing, RBC,
1032
1475
if basis_tree is None:
1033
1476
basis_tree = working_tree.basis_tree()
1034
1477
basis_tree.lock_read()
1035
basis_inter = InterTree.get(basis_tree, working_tree)
1036
basis_path = basis_inter.find_source_path(wt_path)
1037
if basis_path is None:
1038
if target_kind is None and not target_versioned:
1041
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
1043
if wt_kind is not None:
1478
if file_id in basis_tree:
1479
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1481
elif kind[1] is None and not versioned[1]:
1483
if kind[0] is not None:
1044
1484
if not keep_content:
1045
1485
tt.delete_contents(trans_id)
1046
elif target_kind is not None:
1047
parent_trans_id = tt.trans_id_tree_path(osutils.dirname(wt_path))
1048
backup_name = tt._available_backup_name(
1049
wt_name, parent_trans_id)
1486
elif kind[1] is not None:
1487
parent_trans_id = tt.trans_id_file_id(parent[0])
1488
by_parent = tt.by_parent()
1489
backup_name = _get_backup_name(name[0], by_parent,
1490
parent_trans_id, tt)
1050
1491
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1051
new_trans_id = tt.create_path(wt_name, parent_trans_id)
1052
if wt_versioned and target_versioned:
1492
new_trans_id = tt.create_path(name[0], parent_trans_id)
1493
if versioned == (True, True):
1053
1494
tt.unversion_file(trans_id)
1055
new_trans_id, file_id=getattr(change, 'file_id', None))
1495
tt.version_file(file_id, new_trans_id)
1056
1496
# New contents should have the same unix perms as old
1058
1498
mode_id = trans_id
1059
1499
trans_id = new_trans_id
1060
if target_kind in ('directory', 'tree-reference'):
1500
if kind[1] == 'directory':
1061
1501
tt.create_directory(trans_id)
1062
if target_kind == 'tree-reference':
1063
revision = target_tree.get_reference_revision(
1065
tt.set_tree_reference(revision, trans_id)
1066
elif target_kind == 'symlink':
1067
tt.create_symlink(target_tree.get_symlink_target(
1068
target_path), trans_id)
1069
elif target_kind == 'file':
1070
deferred_files.append(
1071
(target_path, (trans_id, mode_id, target_path)))
1072
if basis_tree is None:
1073
basis_tree = working_tree.basis_tree()
1074
basis_tree.lock_read()
1075
new_sha1 = target_tree.get_file_sha1(target_path)
1076
basis_inter = InterTree.get(basis_tree, target_tree)
1077
basis_path = basis_inter.find_source_path(target_path)
1078
if (basis_path is not None and
1079
new_sha1 == basis_tree.get_file_sha1(basis_path)):
1080
# If the new contents of the file match what is in basis,
1081
# then there is no need to store in merge_modified.
1082
if basis_path in merge_modified:
1083
del merge_modified[basis_path]
1085
merge_modified[target_path] = new_sha1
1502
elif kind[1] == 'symlink':
1503
tt.create_symlink(target_tree.get_symlink_target(file_id),
1505
elif kind[1] == 'file':
1506
tt.create_file(target_tree.get_file_lines(file_id),
1087
1508
# preserve the execute bit when backing up
1088
if keep_content and wt_executable == target_executable:
1089
tt.set_executability(target_executable, trans_id)
1090
elif target_kind is not None:
1091
raise AssertionError(target_kind)
1092
if not wt_versioned and target_versioned:
1094
trans_id, file_id=getattr(change, 'file_id', None))
1095
if wt_versioned and not target_versioned:
1509
if keep_content and executable[0] == executable[1]:
1510
tt.set_executability(executable[1], trans_id)
1512
assert kind[1] is None
1513
if versioned == (False, True):
1514
tt.version_file(file_id, trans_id)
1515
if versioned == (True, False):
1096
1516
tt.unversion_file(trans_id)
1097
if (target_name is not None
1098
and (wt_name != target_name or change.is_reparented())):
1099
if target_path == '':
1100
parent_trans = ROOT_PARENT
1102
parent_trans = tt.trans_id_file_id(target_parent)
1103
if wt_path == '' and wt_versioned:
1104
tt.adjust_root_path(target_name, parent_trans)
1106
tt.adjust_path(target_name, parent_trans, trans_id)
1107
if wt_executable != target_executable and target_kind == "file":
1108
tt.set_executability(target_executable, trans_id)
1109
if working_tree.supports_content_filtering():
1110
for (trans_id, mode_id, target_path), bytes in (
1111
target_tree.iter_files_bytes(deferred_files)):
1112
# We're reverting a tree to the target tree so using the
1113
# target tree to find the file path seems the best choice
1114
# here IMO - Ian C 27/Oct/2009
1115
filters = working_tree._content_filter_stack(target_path)
1116
bytes = filtered_output_bytes(
1118
ContentFilterContext(target_path, working_tree))
1119
tt.create_file(bytes, trans_id, mode_id)
1121
for (trans_id, mode_id, target_path), bytes in target_tree.iter_files_bytes(
1123
tt.create_file(bytes, trans_id, mode_id)
1124
tt.fixup_new_roots()
1517
if (name[1] is not None and
1518
(name[0] != name[1] or parent[0] != parent[1])):
1520
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1521
if executable[0] != executable[1] and kind[1] == "file":
1522
tt.set_executability(executable[1], trans_id)
1126
1524
if basis_tree is not None:
1127
1525
basis_tree.unlock()
1128
return merge_modified
1131
def resolve_conflicts(tt, pb=None, pass_func=None):
1528
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1132
1529
"""Make many conflict-resolution attempts, but die if they fail"""
1133
1530
if pass_func is None:
1134
1531
pass_func = conflict_pass
1135
1532
new_conflicts = set()
1136
with ui.ui_factory.nested_progress_bar() as pb:
1137
1534
for n in range(10):
1138
pb.update(gettext('Resolution pass'), n + 1, 10)
1139
conflicts = tt.find_raw_conflicts()
1535
pb.update('Resolution pass', n+1, 10)
1536
conflicts = tt.find_conflicts()
1140
1537
if len(conflicts) == 0:
1141
1538
return new_conflicts
1142
1539
new_conflicts.update(pass_func(tt, conflicts))
1143
1540
raise MalformedTransform(conflicts=conflicts)
1146
def resolve_duplicate_id(tt, path_tree, c_type, old_trans_id, trans_id):
1147
tt.unversion_file(old_trans_id)
1148
yield (c_type, 'Unversioned existing file', old_trans_id, trans_id)
1151
def resolve_duplicate(tt, path_tree, c_type, last_trans_id, trans_id, name):
1152
# files that were renamed take precedence
1153
final_parent = tt.final_parent(last_trans_id)
1154
if tt.path_changed(last_trans_id):
1155
existing_file, new_file = trans_id, last_trans_id
1157
existing_file, new_file = last_trans_id, trans_id
1158
new_name = tt.final_name(existing_file) + '.moved'
1159
tt.adjust_path(new_name, final_parent, existing_file)
1160
yield (c_type, 'Moved existing file to', existing_file, new_file)
1163
def resolve_parent_loop(tt, path_tree, c_type, cur):
1164
# break the loop by undoing one of the ops that caused the loop
1165
while not tt.path_changed(cur):
1166
cur = tt.final_parent(cur)
1167
yield (c_type, 'Cancelled move', cur, tt.final_parent(cur),)
1168
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1171
def resolve_missing_parent(tt, path_tree, c_type, trans_id):
1172
if trans_id in tt._removed_contents:
1173
cancel_deletion = True
1174
orphans = tt._get_potential_orphans(trans_id)
1176
cancel_deletion = False
1177
# All children are orphans
1180
tt.new_orphan(o, trans_id)
1181
except OrphaningError:
1182
# Something bad happened so we cancel the directory
1183
# deletion which will leave it in place with a
1184
# conflict. The user can deal with it from there.
1185
# Note that this also catch the case where we don't
1186
# want to create orphans and leave the directory in
1188
cancel_deletion = True
1191
# Cancel the directory deletion
1192
tt.cancel_deletion(trans_id)
1193
yield ('deleting parent', 'Not deleting', trans_id)
1197
tt.final_name(trans_id)
1199
if path_tree is not None:
1200
file_id = tt.final_file_id(trans_id)
1202
file_id = tt.inactive_file_id(trans_id)
1203
_, entry = next(path_tree.iter_entries_by_dir(
1204
specific_files=[path_tree.id2path(file_id)]))
1205
# special-case the other tree root (move its
1206
# children to current root)
1207
if entry.parent_id is None:
1209
moved = _reparent_transform_children(
1210
tt, trans_id, tt.root)
1212
yield (c_type, 'Moved to root', child)
1214
parent_trans_id = tt.trans_id_file_id(
1216
tt.adjust_path(entry.name, parent_trans_id,
1219
tt.create_directory(trans_id)
1220
yield (c_type, 'Created directory', trans_id)
1223
def resolve_unversioned_parent(tt, path_tree, c_type, trans_id):
1224
file_id = tt.inactive_file_id(trans_id)
1225
# special-case the other tree root (move its children instead)
1226
if path_tree and path_tree.path2id('') == file_id:
1227
# This is the root entry, skip it
1229
tt.version_file(trans_id, file_id=file_id)
1230
yield (c_type, 'Versioned directory', trans_id)
1233
def resolve_non_directory_parent(tt, path_tree, c_type, parent_id):
1234
parent_parent = tt.final_parent(parent_id)
1235
parent_name = tt.final_name(parent_id)
1236
# TODO(jelmer): Make this code transform-specific
1237
if tt._tree.supports_setting_file_ids():
1238
parent_file_id = tt.final_file_id(parent_id)
1240
parent_file_id = b'DUMMY'
1241
new_parent_id = tt.new_directory(parent_name + '.new',
1242
parent_parent, parent_file_id)
1243
_reparent_transform_children(tt, parent_id, new_parent_id)
1244
if parent_file_id is not None:
1245
tt.unversion_file(parent_id)
1246
yield (c_type, 'Created directory', new_parent_id)
1249
def resolve_versioning_no_contents(tt, path_tree, c_type, trans_id):
1250
tt.cancel_versioning(trans_id)
1254
CONFLICT_RESOLVERS = {
1255
'duplicate id': resolve_duplicate_id,
1256
'duplicate': resolve_duplicate,
1257
'parent loop': resolve_parent_loop,
1258
'missing parent': resolve_missing_parent,
1259
'unversioned parent': resolve_unversioned_parent,
1260
'non-directory parent': resolve_non_directory_parent,
1261
'versioning no contents': resolve_versioning_no_contents,
1265
def conflict_pass(tt, conflicts, path_tree=None):
1266
"""Resolve some classes of conflicts.
1268
:param tt: The transform to resolve conflicts in
1269
:param conflicts: The conflicts to resolve
1270
:param path_tree: A Tree to get supplemental paths from
1545
def conflict_pass(tt, conflicts):
1546
"""Resolve some classes of conflicts."""
1272
1547
new_conflicts = set()
1273
for conflict in conflicts:
1274
resolver = CONFLICT_RESOLVERS.get(conflict[0])
1275
if resolver is None:
1277
new_conflicts.update(resolver(tt, path_tree, *conflict))
1548
for c_type, conflict in ((c[0], c) for c in conflicts):
1549
if c_type == 'duplicate id':
1550
tt.unversion_file(conflict[1])
1551
new_conflicts.add((c_type, 'Unversioned existing file',
1552
conflict[1], conflict[2], ))
1553
elif c_type == 'duplicate':
1554
# files that were renamed take precedence
1555
new_name = tt.final_name(conflict[1])+'.moved'
1556
final_parent = tt.final_parent(conflict[1])
1557
if tt.path_changed(conflict[1]):
1558
tt.adjust_path(new_name, final_parent, conflict[2])
1559
new_conflicts.add((c_type, 'Moved existing file to',
1560
conflict[2], conflict[1]))
1562
tt.adjust_path(new_name, final_parent, conflict[1])
1563
new_conflicts.add((c_type, 'Moved existing file to',
1564
conflict[1], conflict[2]))
1565
elif c_type == 'parent loop':
1566
# break the loop by undoing one of the ops that caused the loop
1568
while not tt.path_changed(cur):
1569
cur = tt.final_parent(cur)
1570
new_conflicts.add((c_type, 'Cancelled move', cur,
1571
tt.final_parent(cur),))
1572
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1574
elif c_type == 'missing parent':
1575
trans_id = conflict[1]
1577
tt.cancel_deletion(trans_id)
1578
new_conflicts.add(('deleting parent', 'Not deleting',
1581
tt.create_directory(trans_id)
1582
new_conflicts.add((c_type, 'Created directory', trans_id))
1583
elif c_type == 'unversioned parent':
1584
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1585
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1278
1586
return new_conflicts
1281
class _FileMover(object):
1282
"""Moves and deletes files for TreeTransform, tracking operations"""
1285
self.past_renames = []
1286
self.pending_deletions = []
1288
def rename(self, from_, to):
1289
"""Rename a file from one path to another."""
1291
os.rename(from_, to)
1292
except OSError as e:
1293
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
1294
raise errors.FileExists(to, str(e))
1295
# normal OSError doesn't include filenames so it's hard to see where
1296
# the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
1297
raise TransformRenameFailed(from_, to, str(e), e.errno)
1298
self.past_renames.append((from_, to))
1300
def pre_delete(self, from_, to):
1301
"""Rename a file out of the way and mark it for deletion.
1303
Unlike os.unlink, this works equally well for files and directories.
1304
:param from_: The current file path
1305
:param to: A temporary path for the file
1307
self.rename(from_, to)
1308
self.pending_deletions.append(to)
1311
"""Reverse all renames that have been performed"""
1312
for from_, to in reversed(self.past_renames):
1314
os.rename(to, from_)
1315
except OSError as e:
1316
raise TransformRenameFailed(to, from_, str(e), e.errno)
1317
# after rollback, don't reuse _FileMover
1318
self.past_renames = None
1319
self.pending_deletions = None
1321
def apply_deletions(self):
1322
"""Apply all marked deletions"""
1323
for path in self.pending_deletions:
1325
# after apply_deletions, don't reuse _FileMover
1326
self.past_renames = None
1327
self.pending_deletions = None
1330
def link_tree(target_tree, source_tree):
1331
"""Where possible, hard-link files in a tree to those in another tree.
1333
:param target_tree: Tree to change
1334
:param source_tree: Tree to hard-link from
1336
with target_tree.transform() as tt:
1337
for change in target_tree.iter_changes(source_tree, include_unchanged=True):
1338
if change.changed_content:
1340
if change.kind != ('file', 'file'):
1342
if change.executable[0] != change.executable[1]:
1344
trans_id = tt.trans_id_tree_path(change.path[1])
1345
tt.delete_contents(trans_id)
1346
tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
1350
class PreviewTree(object):
1353
def __init__(self, transform):
1354
self._transform = transform
1355
self._parent_ids = []
1356
self.__by_parent = None
1357
self._path2trans_id_cache = {}
1358
self._all_children_cache = {}
1359
self._final_name_cache = {}
1361
def supports_setting_file_ids(self):
1362
raise NotImplementedError(self.supports_setting_file_ids)
1365
def _by_parent(self):
1366
if self.__by_parent is None:
1367
self.__by_parent = self._transform.by_parent()
1368
return self.__by_parent
1370
def get_parent_ids(self):
1371
return self._parent_ids
1373
def set_parent_ids(self, parent_ids):
1374
self._parent_ids = parent_ids
1376
def get_revision_tree(self, revision_id):
1377
return self._transform._tree.get_revision_tree(revision_id)
1379
def is_locked(self):
1382
def lock_read(self):
1383
# Perhaps in theory, this should lock the TreeTransform?
1384
return lock.LogicalLockResult(self.unlock)
1389
def _path2trans_id(self, path):
1390
"""Look up the trans id associated with a path.
1392
:param path: path to look up, None when the path does not exist
1395
# We must not use None here, because that is a valid value to store.
1396
trans_id = self._path2trans_id_cache.get(path, object)
1397
if trans_id is not object:
1399
segments = osutils.splitpath(path)
1400
cur_parent = self._transform.root
1401
for cur_segment in segments:
1402
for child in self._all_children(cur_parent):
1403
final_name = self._final_name_cache.get(child)
1404
if final_name is None:
1405
final_name = self._transform.final_name(child)
1406
self._final_name_cache[child] = final_name
1407
if final_name == cur_segment:
1411
self._path2trans_id_cache[path] = None
1413
self._path2trans_id_cache[path] = cur_parent
1416
def _all_children(self, trans_id):
1417
children = self._all_children_cache.get(trans_id)
1418
if children is not None:
1420
children = set(self._transform.iter_tree_children(trans_id))
1421
# children in the _new_parent set are provided by _by_parent.
1422
children.difference_update(self._transform._new_parent)
1423
children.update(self._by_parent.get(trans_id, []))
1424
self._all_children_cache[trans_id] = children
1427
def get_file_with_stat(self, path):
1428
return self.get_file(path), None
1430
def is_executable(self, path):
1431
trans_id = self._path2trans_id(path)
1432
if trans_id is None:
1435
return self._transform._new_executability[trans_id]
1438
return self._transform._tree.is_executable(path)
1439
except OSError as e:
1440
if e.errno == errno.ENOENT:
1443
except errors.NoSuchFile:
1446
def has_filename(self, path):
1447
trans_id = self._path2trans_id(path)
1448
if trans_id in self._transform._new_contents:
1450
elif trans_id in self._transform._removed_contents:
1453
return self._transform._tree.has_filename(path)
1455
def get_file_sha1(self, path, stat_value=None):
1456
trans_id = self._path2trans_id(path)
1457
if trans_id is None:
1458
raise errors.NoSuchFile(path)
1459
kind = self._transform._new_contents.get(trans_id)
1461
return self._transform._tree.get_file_sha1(path)
1463
with self.get_file(path) as fileobj:
1464
return osutils.sha_file(fileobj)
1466
def get_file_verifier(self, path, stat_value=None):
1467
trans_id = self._path2trans_id(path)
1468
if trans_id is None:
1469
raise errors.NoSuchFile(path)
1470
kind = self._transform._new_contents.get(trans_id)
1472
return self._transform._tree.get_file_verifier(path)
1474
with self.get_file(path) as fileobj:
1475
return ("SHA1", osutils.sha_file(fileobj))
1477
def kind(self, path):
1478
trans_id = self._path2trans_id(path)
1479
if trans_id is None:
1480
raise errors.NoSuchFile(path)
1481
return self._transform.final_kind(trans_id)
1483
def stored_kind(self, path):
1484
trans_id = self._path2trans_id(path)
1485
if trans_id is None:
1486
raise errors.NoSuchFile(path)
1488
return self._transform._new_contents[trans_id]
1490
return self._transform._tree.stored_kind(path)
1492
def _get_repository(self):
1493
repo = getattr(self._transform._tree, '_repository', None)
1495
repo = self._transform._tree.branch.repository
1498
def _iter_parent_trees(self):
1499
for revision_id in self.get_parent_ids():
1501
yield self.revision_tree(revision_id)
1502
except errors.NoSuchRevisionInTree:
1503
yield self._get_repository().revision_tree(revision_id)
1505
def get_file_size(self, path):
1506
"""See Tree.get_file_size"""
1507
trans_id = self._path2trans_id(path)
1508
if trans_id is None:
1509
raise errors.NoSuchFile(path)
1510
kind = self._transform.final_kind(trans_id)
1513
if trans_id in self._transform._new_contents:
1514
return self._stat_limbo_file(trans_id).st_size
1515
if self.kind(path) == 'file':
1516
return self._transform._tree.get_file_size(path)
1520
def get_reference_revision(self, path):
1521
trans_id = self._path2trans_id(path)
1522
if trans_id is None:
1523
raise errors.NoSuchFile(path)
1524
reference_revision = self._transform._new_reference_revision.get(trans_id)
1525
if reference_revision is None:
1526
return self._transform._tree.get_reference_revision(path)
1527
return reference_revision
1529
def tree_kind(self, trans_id):
1530
path = self._tree_id_paths.get(trans_id)
1533
kind = self._tree.path_content_summary(path)[0]
1534
if kind == 'missing':
1589
def cook_conflicts(raw_conflicts, tt):
1590
"""Generate a list of cooked conflicts, sorted by file path"""
1591
from bzrlib.conflicts import Conflict
1592
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1593
return sorted(conflict_iter, key=Conflict.sort_key)
1596
def iter_cook_conflicts(raw_conflicts, tt):
1597
from bzrlib.conflicts import Conflict
1599
for conflict in raw_conflicts:
1600
c_type = conflict[0]
1601
action = conflict[1]
1602
modified_path = fp.get_path(conflict[2])
1603
modified_id = tt.final_file_id(conflict[2])
1604
if len(conflict) == 3:
1605
yield Conflict.factory(c_type, action=action, path=modified_path,
1606
file_id=modified_id)
1609
conflicting_path = fp.get_path(conflict[3])
1610
conflicting_id = tt.final_file_id(conflict[3])
1611
yield Conflict.factory(c_type, action=action, path=modified_path,
1612
file_id=modified_id,
1613
conflict_path=conflicting_path,
1614
conflict_file_id=conflicting_id)