~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-17 13:49:05 UTC
  • mfrom: (1711.2.129 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060817134905-0dec610d2fcd6663
(bialix) 'make html-docs' produces html documentation

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
import errno
19
19
from stat import S_ISREG
20
20
 
21
 
from bzrlib.lazy_import import lazy_import
22
 
lazy_import(globals(), """
23
 
from bzrlib import (
24
 
    bzrdir,
25
 
    delta,
26
 
    errors,
27
 
    inventory
28
 
    )
29
 
""")
30
21
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
31
22
                           ReusingTransform, NotVersionedError, CantMoveRoot,
32
 
                           ExistingLimbo, ImmortalLimbo, NoFinalPath)
 
23
                           ExistingLimbo, ImmortalLimbo)
33
24
from bzrlib.inventory import InventoryEntry
34
25
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
35
26
                            delete_any)
36
27
from bzrlib.progress import DummyProgress, ProgressPhase
37
 
from bzrlib.symbol_versioning import (
38
 
        deprecated_function,
39
 
        zero_fifteen,
40
 
        zero_ninety,
41
 
        )
42
28
from bzrlib.trace import mutter, warning
43
29
from bzrlib import tree
44
 
import bzrlib.ui
 
30
import bzrlib.ui 
45
31
import bzrlib.urlutils as urlutils
46
32
 
47
33
 
55
41
 
56
42
 
57
43
class _TransformResults(object):
58
 
    def __init__(self, modified_paths, rename_count):
 
44
    def __init__(self, modified_paths):
59
45
        object.__init__(self)
60
46
        self.modified_paths = modified_paths
61
 
        self.rename_count = rename_count
62
47
 
63
48
 
64
49
class TreeTransform(object):
65
50
    """Represent a tree transformation.
66
51
    
67
52
    This object is designed to support incremental generation of the transform,
68
 
    in any order.
69
 
 
70
 
    However, it gives optimum performance when parent directories are created
71
 
    before their contents.  The transform is then able to put child files
72
 
    directly in their parent directory, avoiding later renames.
 
53
    in any order.  
73
54
    
74
55
    It is easy to produce malformed transforms, but they are generally
75
56
    harmless.  Attempting to apply a malformed transform will cause an
91
72
     * set_executability
92
73
    """
93
74
    def __init__(self, tree, pb=DummyProgress()):
94
 
        """Note: a tree_write lock is taken on the tree.
 
75
        """Note: a write lock is taken on the tree.
95
76
        
96
 
        Use TreeTransform.finalize() to release the lock (can be omitted if
97
 
        TreeTransform.apply() called).
 
77
        Use TreeTransform.finalize() to release the lock
98
78
        """
99
79
        object.__init__(self)
100
80
        self._tree = tree
101
 
        self._tree.lock_tree_write()
 
81
        self._tree.lock_write()
102
82
        try:
103
83
            control_files = self._tree._control_files
104
84
            self._limbodir = urlutils.local_path_from_url(
108
88
            except OSError, e:
109
89
                if e.errno == errno.EEXIST:
110
90
                    raise ExistingLimbo(self._limbodir)
111
 
            self._deletiondir = urlutils.local_path_from_url(
112
 
                control_files.controlfilename('pending-deletion'))
113
 
            try:
114
 
                os.mkdir(self._deletiondir)
115
 
            except OSError, e:
116
 
                if e.errno == errno.EEXIST:
117
 
                    raise errors.ExistingPendingDeletion(self._deletiondir)
118
 
 
119
91
        except: 
120
92
            self._tree.unlock()
121
93
            raise
124
96
        self._new_name = {}
125
97
        self._new_parent = {}
126
98
        self._new_contents = {}
127
 
        # A mapping of transform ids to their limbo filename
128
 
        self._limbo_files = {}
129
 
        # A mapping of transform ids to a set of the transform ids of children
130
 
        # that their limbo directory has
131
 
        self._limbo_children = {}
132
 
        # Map transform ids to maps of child filename to child transform id
133
 
        self._limbo_children_names = {}
134
 
        # List of transform ids that need to be renamed from limbo into place
135
 
        self._needs_rename = set()
136
99
        self._removed_contents = set()
137
100
        self._new_executability = {}
138
 
        self._new_reference_revision = {}
139
101
        self._new_id = {}
140
102
        self._non_present_ids = {}
141
103
        self._r_new_id = {}
142
104
        self._removed_id = set()
143
105
        self._tree_path_ids = {}
144
106
        self._tree_id_paths = {}
 
107
        self._realpaths = {}
145
108
        # Cache of realpath results, to speed up canonical_path
146
 
        self._realpaths = {}
 
109
        self._relpaths = {}
147
110
        # Cache of relpath results, to speed up canonical_path
148
 
        self._relpaths = {}
149
111
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
150
112
        self.__done = False
151
113
        self._pb = pb
152
 
        self.rename_count = 0
153
114
 
154
115
    def __get_root(self):
155
116
        return self._new_root
157
118
    root = property(__get_root)
158
119
 
159
120
    def finalize(self):
160
 
        """Release the working tree lock, if held, clean up limbo dir.
161
 
 
162
 
        This is required if apply has not been invoked, but can be invoked
163
 
        even after apply.
164
 
        """
 
121
        """Release the working tree lock, if held, clean up limbo dir."""
165
122
        if self._tree is None:
166
123
            return
167
124
        try:
168
 
            entries = [(self._limbo_name(t), t, k) for t, k in
169
 
                       self._new_contents.iteritems()]
170
 
            entries.sort(reverse=True)
171
 
            for path, trans_id, kind in entries:
 
125
            for trans_id, kind in self._new_contents.iteritems():
 
126
                path = self._limbo_name(trans_id)
172
127
                if kind == "directory":
173
128
                    os.rmdir(path)
174
129
                else:
178
133
            except OSError:
179
134
                # We don't especially care *why* the dir is immortal.
180
135
                raise ImmortalLimbo(self._limbodir)
181
 
            try:
182
 
                os.rmdir(self._deletiondir)
183
 
            except OSError:
184
 
                raise errors.ImmortalPendingDeletion(self._deletiondir)
185
136
        finally:
186
137
            self._tree.unlock()
187
138
            self._tree = None
203
154
        """Change the path that is assigned to a transaction id."""
204
155
        if trans_id == self._new_root:
205
156
            raise CantMoveRoot
206
 
        previous_parent = self._new_parent.get(trans_id)
207
 
        previous_name = self._new_name.get(trans_id)
208
157
        self._new_name[trans_id] = name
209
158
        self._new_parent[trans_id] = parent
210
 
        if (trans_id in self._limbo_files and
211
 
            trans_id not in self._needs_rename):
212
 
            self._rename_in_limbo([trans_id])
213
 
            self._limbo_children[previous_parent].remove(trans_id)
214
 
            del self._limbo_children_names[previous_parent][previous_name]
215
 
 
216
 
    def _rename_in_limbo(self, trans_ids):
217
 
        """Fix limbo names so that the right final path is produced.
218
 
 
219
 
        This means we outsmarted ourselves-- we tried to avoid renaming
220
 
        these files later by creating them with their final names in their
221
 
        final parents.  But now the previous name or parent is no longer
222
 
        suitable, so we have to rename them.
223
 
 
224
 
        Even for trans_ids that have no new contents, we must remove their
225
 
        entries from _limbo_files, because they are now stale.
226
 
        """
227
 
        for trans_id in trans_ids:
228
 
            old_path = self._limbo_files.pop(trans_id)
229
 
            if trans_id not in self._new_contents:
230
 
                continue
231
 
            new_path = self._limbo_name(trans_id)
232
 
            os.rename(old_path, new_path)
233
159
 
234
160
    def adjust_root_path(self, name, parent):
235
161
        """Emulate moving the root by moving all children, instead.
346
272
                os.unlink(name)
347
273
                raise
348
274
 
349
 
            f.writelines(contents)
 
275
            for segment in contents:
 
276
                f.write(segment)
350
277
        finally:
351
278
            f.close()
352
279
        self._set_mode(trans_id, mode_id, S_ISREG)
364
291
        except KeyError:
365
292
            return
366
293
        try:
367
 
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
294
            mode = os.stat(old_path).st_mode
368
295
        except OSError, e:
369
296
            if e.errno == errno.ENOENT:
370
297
                return
393
320
    def cancel_creation(self, trans_id):
394
321
        """Cancel the creation of new file contents."""
395
322
        del self._new_contents[trans_id]
396
 
        children = self._limbo_children.get(trans_id)
397
 
        # if this is a limbo directory with children, move them before removing
398
 
        # the directory
399
 
        if children is not None:
400
 
            self._rename_in_limbo(children)
401
 
            del self._limbo_children[trans_id]
402
 
            del self._limbo_children_names[trans_id]
403
323
        delete_any(self._limbo_name(trans_id))
404
324
 
405
325
    def delete_contents(self, trans_id):
429
349
        else:
430
350
            unique_add(self._new_executability, trans_id, executability)
431
351
 
432
 
    def set_tree_reference(self, revision_id, trans_id):
433
 
        """Set the reference associated with a directory"""
434
 
        unique_add(self._new_reference_revision, trans_id, revision_id)
435
 
 
436
352
    def version_file(self, file_id, trans_id):
437
353
        """Schedule a file to become versioned."""
438
354
        assert file_id is not None
540
456
        try:
541
457
            return self._new_name[trans_id]
542
458
        except KeyError:
543
 
            try:
544
 
                return os.path.basename(self._tree_id_paths[trans_id])
545
 
            except KeyError:
546
 
                raise NoFinalPath(trans_id, self)
 
459
            return os.path.basename(self._tree_id_paths[trans_id])
547
460
 
548
461
    def by_parent(self):
549
462
        """Return a map of parent: children for known parents.
562
475
 
563
476
    def path_changed(self, trans_id):
564
477
        """Return True if a trans_id's path has changed."""
565
 
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
566
 
 
567
 
    def new_contents(self, trans_id):
568
 
        return (trans_id in self._new_contents)
 
478
        return trans_id in self._new_name or trans_id in self._new_parent
569
479
 
570
480
    def find_conflicts(self):
571
481
        """Find any violations of inventory or filesystem invariants"""
656
566
            parent_id = trans_id
657
567
            while parent_id is not ROOT_PARENT:
658
568
                seen.add(parent_id)
659
 
                try:
660
 
                    parent_id = self.final_parent(parent_id)
661
 
                except KeyError:
662
 
                    break
 
569
                parent_id = self.final_parent(parent_id)
663
570
                if parent_id == trans_id:
664
571
                    conflicts.append(('parent loop', trans_id))
665
572
                if parent_id in seen:
733
640
    def _duplicate_entries(self, by_parent):
734
641
        """No directory may have two entries with the same name."""
735
642
        conflicts = []
736
 
        if (self._new_name, self._new_parent) == ({}, {}):
737
 
            return conflicts
738
643
        for children in by_parent.itervalues():
739
644
            name_ids = [(self.final_name(t), t) for t in children]
740
645
            name_ids.sort()
741
646
            last_name = None
742
647
            last_trans_id = None
743
648
            for name, trans_id in name_ids:
 
649
                if name == last_name:
 
650
                    conflicts.append(('duplicate', last_trans_id, trans_id,
 
651
                    name))
744
652
                try:
745
653
                    kind = self.final_kind(trans_id)
746
654
                except NoSuchFile:
747
655
                    kind = None
748
656
                file_id = self.final_file_id(trans_id)
749
 
                if kind is None and file_id is None:
750
 
                    continue
751
 
                if name == last_name:
752
 
                    conflicts.append(('duplicate', last_trans_id, trans_id,
753
 
                    name))
754
 
                last_name = name
755
 
                last_trans_id = trans_id
 
657
                if kind is not None or file_id is not None:
 
658
                    last_name = name
 
659
                    last_trans_id = trans_id
756
660
        return conflicts
757
661
 
758
662
    def _duplicate_ids(self):
801
705
            return True
802
706
        return False
803
707
            
804
 
    def apply(self, no_conflicts=False, _mover=None):
 
708
    def apply(self):
805
709
        """Apply all changes to the inventory and filesystem.
806
710
        
807
711
        If filesystem or inventory conflicts are present, MalformedTransform
808
712
        will be thrown.
809
 
 
810
 
        If apply succeeds, finalize is not necessary.
811
 
 
812
 
        :param no_conflicts: if True, the caller guarantees there are no
813
 
            conflicts, so no check is made.
814
 
        :param _mover: Supply an alternate FileMover, for testing
815
713
        """
816
 
        if not no_conflicts:
817
 
            conflicts = self.find_conflicts()
818
 
            if len(conflicts) != 0:
819
 
                raise MalformedTransform(conflicts=conflicts)
 
714
        conflicts = self.find_conflicts()
 
715
        if len(conflicts) != 0:
 
716
            raise MalformedTransform(conflicts=conflicts)
 
717
        limbo_inv = {}
820
718
        inv = self._tree.inventory
821
 
        inventory_delta = []
822
719
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
823
720
        try:
824
 
            if _mover is None:
825
 
                mover = _FileMover()
826
 
            else:
827
 
                mover = _mover
828
 
            try:
829
 
                child_pb.update('Apply phase', 0, 2)
830
 
                self._apply_removals(inv, inventory_delta, mover)
831
 
                child_pb.update('Apply phase', 1, 2)
832
 
                modified_paths = self._apply_insertions(inv, inventory_delta,
833
 
                                                        mover)
834
 
            except:
835
 
                mover.rollback()
836
 
                raise
837
 
            else:
838
 
                mover.apply_deletions()
 
721
            child_pb.update('Apply phase', 0, 2)
 
722
            self._apply_removals(inv, limbo_inv)
 
723
            child_pb.update('Apply phase', 1, 2)
 
724
            modified_paths = self._apply_insertions(inv, limbo_inv)
839
725
        finally:
840
726
            child_pb.finished()
841
 
        self._tree.apply_inventory_delta(inventory_delta)
 
727
        self._tree._write_inventory(inv)
842
728
        self.__done = True
843
729
        self.finalize()
844
 
        return _TransformResults(modified_paths, self.rename_count)
 
730
        return _TransformResults(modified_paths)
845
731
 
846
732
    def _limbo_name(self, trans_id):
847
733
        """Generate the limbo name of a file"""
848
 
        limbo_name = self._limbo_files.get(trans_id)
849
 
        if limbo_name is not None:
850
 
            return limbo_name
851
 
        parent = self._new_parent.get(trans_id)
852
 
        # if the parent directory is already in limbo (e.g. when building a
853
 
        # tree), choose a limbo name inside the parent, to reduce further
854
 
        # renames.
855
 
        use_direct_path = False
856
 
        if self._new_contents.get(parent) == 'directory':
857
 
            filename = self._new_name.get(trans_id)
858
 
            if filename is not None:
859
 
                if parent not in self._limbo_children:
860
 
                    self._limbo_children[parent] = set()
861
 
                    self._limbo_children_names[parent] = {}
862
 
                    use_direct_path = True
863
 
                # the direct path can only be used if no other file has
864
 
                # already taken this pathname, i.e. if the name is unused, or
865
 
                # if it is already associated with this trans_id.
866
 
                elif (self._limbo_children_names[parent].get(filename)
867
 
                      in (trans_id, None)):
868
 
                    use_direct_path = True
869
 
        if use_direct_path:
870
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
871
 
            self._limbo_children[parent].add(trans_id)
872
 
            self._limbo_children_names[parent][filename] = trans_id
873
 
        else:
874
 
            limbo_name = pathjoin(self._limbodir, trans_id)
875
 
            self._needs_rename.add(trans_id)
876
 
        self._limbo_files[trans_id] = limbo_name
877
 
        return limbo_name
 
734
        return pathjoin(self._limbodir, trans_id)
878
735
 
879
 
    def _apply_removals(self, inv, inventory_delta, mover):
 
736
    def _apply_removals(self, inv, limbo_inv):
880
737
        """Perform tree operations that remove directory/inventory names.
881
738
        
882
739
        That is, delete files that are to be deleted, and put any files that
892
749
                child_pb.update('removing file', num, len(tree_paths))
893
750
                full_path = self._tree.abspath(path)
894
751
                if trans_id in self._removed_contents:
895
 
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
896
 
                                     trans_id))
 
752
                    delete_any(full_path)
897
753
                elif trans_id in self._new_name or trans_id in \
898
754
                    self._new_parent:
899
755
                    try:
900
 
                        mover.rename(full_path, self._limbo_name(trans_id))
 
756
                        os.rename(full_path, self._limbo_name(trans_id))
901
757
                    except OSError, e:
902
758
                        if e.errno != errno.ENOENT:
903
759
                            raise
904
 
                    else:
905
 
                        self.rename_count += 1
906
760
                if trans_id in self._removed_id:
907
761
                    if trans_id == self._new_root:
908
762
                        file_id = self._tree.inventory.root.file_id
909
763
                    else:
910
764
                        file_id = self.tree_file_id(trans_id)
911
 
                    assert file_id is not None
912
 
                    inventory_delta.append((path, None, file_id, None))
 
765
                    del inv[file_id]
 
766
                elif trans_id in self._new_name or trans_id in self._new_parent:
 
767
                    file_id = self.tree_file_id(trans_id)
 
768
                    if file_id is not None:
 
769
                        limbo_inv[trans_id] = inv[file_id]
 
770
                        del inv[file_id]
913
771
        finally:
914
772
            child_pb.finished()
915
773
 
916
 
    def _apply_insertions(self, inv, inventory_delta, mover):
 
774
    def _apply_insertions(self, inv, limbo_inv):
917
775
        """Perform tree operations that insert directory/inventory names.
918
776
        
919
777
        That is, create any files that need to be created, and restore from
925
783
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
926
784
        try:
927
785
            for num, (path, trans_id) in enumerate(new_paths):
928
 
                new_entry = None
929
786
                child_pb.update('adding file', num, len(new_paths))
930
787
                try:
931
788
                    kind = self._new_contents[trans_id]
934
791
                if trans_id in self._new_contents or \
935
792
                    self.path_changed(trans_id):
936
793
                    full_path = self._tree.abspath(path)
937
 
                    if trans_id in self._needs_rename:
938
 
                        try:
939
 
                            mover.rename(self._limbo_name(trans_id), full_path)
940
 
                        except OSError, e:
941
 
                            # We may be renaming a dangling inventory id
942
 
                            if e.errno != errno.ENOENT:
943
 
                                raise
944
 
                        else:
945
 
                            self.rename_count += 1
 
794
                    try:
 
795
                        os.rename(self._limbo_name(trans_id), full_path)
 
796
                    except OSError, e:
 
797
                        # We may be renaming a dangling inventory id
 
798
                        if e.errno != errno.ENOENT:
 
799
                            raise
946
800
                    if trans_id in self._new_contents:
947
801
                        modified_paths.append(full_path)
948
802
                        del self._new_contents[trans_id]
950
804
                if trans_id in self._new_id:
951
805
                    if kind is None:
952
806
                        kind = file_kind(self._tree.abspath(path))
953
 
                    if trans_id in self._new_reference_revision:
954
 
                        new_entry = inventory.TreeReference(
955
 
                            self._new_id[trans_id],
956
 
                            self._new_name[trans_id], 
957
 
                            self.final_file_id(self._new_parent[trans_id]),
958
 
                            None, self._new_reference_revision[trans_id])
959
 
                    else:
960
 
                        new_entry = inventory.make_entry(kind,
961
 
                            self.final_name(trans_id),
962
 
                            self.final_file_id(self.final_parent(trans_id)),
963
 
                            self._new_id[trans_id])
964
 
                else:
965
 
                    if trans_id in self._new_name or trans_id in\
966
 
                        self._new_parent or\
967
 
                        trans_id in self._new_executability:
968
 
                        file_id = self.final_file_id(trans_id)
969
 
                        if file_id is not None:
970
 
                            entry = inv[file_id]
971
 
                            new_entry = entry.copy()
972
 
 
973
 
                    if trans_id in self._new_name or trans_id in\
974
 
                        self._new_parent:
975
 
                            if new_entry is not None:
976
 
                                new_entry.name = self.final_name(trans_id)
977
 
                                parent = self.final_parent(trans_id)
978
 
                                parent_id = self.final_file_id(parent)
979
 
                                new_entry.parent_id = parent_id
980
 
 
 
807
                    inv.add_path(path, kind, self._new_id[trans_id])
 
808
                elif trans_id in self._new_name or trans_id in\
 
809
                    self._new_parent:
 
810
                    entry = limbo_inv.get(trans_id)
 
811
                    if entry is not None:
 
812
                        entry.name = self.final_name(trans_id)
 
813
                        parent_path = os.path.dirname(path)
 
814
                        entry.parent_id = \
 
815
                            self._tree.inventory.path2id(parent_path)
 
816
                        inv.add(entry)
 
817
 
 
818
                # requires files and inventory entries to be in place
981
819
                if trans_id in self._new_executability:
982
 
                    self._set_executability(path, new_entry, trans_id)
983
 
                if new_entry is not None:
984
 
                    if new_entry.file_id in inv:
985
 
                        old_path = inv.id2path(new_entry.file_id)
986
 
                    else:
987
 
                        old_path = None
988
 
                    inventory_delta.append((old_path, path,
989
 
                                            new_entry.file_id,
990
 
                                            new_entry))
 
820
                    self._set_executability(path, inv, trans_id)
991
821
        finally:
992
822
            child_pb.finished()
993
823
        return modified_paths
994
824
 
995
 
    def _set_executability(self, path, entry, trans_id):
 
825
    def _set_executability(self, path, inv, trans_id):
996
826
        """Set the executability of versioned files """
 
827
        file_id = inv.path2id(path)
997
828
        new_executability = self._new_executability[trans_id]
998
 
        entry.executable = new_executability
 
829
        inv[file_id].executable = new_executability
999
830
        if supports_executable():
1000
831
            abspath = self._tree.abspath(path)
1001
832
            current_mode = os.stat(abspath).st_mode
1062
893
        self.create_symlink(target, trans_id)
1063
894
        return trans_id
1064
895
 
1065
 
    def _affected_ids(self):
1066
 
        """Return the set of transform ids affected by the transform"""
1067
 
        trans_ids = set(self._removed_id)
1068
 
        trans_ids.update(self._new_id.keys())
1069
 
        trans_ids.update(self._removed_contents)
1070
 
        trans_ids.update(self._new_contents.keys())
1071
 
        trans_ids.update(self._new_executability.keys())
1072
 
        trans_ids.update(self._new_name.keys())
1073
 
        trans_ids.update(self._new_parent.keys())
1074
 
        return trans_ids
1075
 
 
1076
 
    def _get_file_id_maps(self):
1077
 
        """Return mapping of file_ids to trans_ids in the to and from states"""
1078
 
        trans_ids = self._affected_ids()
1079
 
        from_trans_ids = {}
1080
 
        to_trans_ids = {}
1081
 
        # Build up two dicts: trans_ids associated with file ids in the
1082
 
        # FROM state, vs the TO state.
1083
 
        for trans_id in trans_ids:
1084
 
            from_file_id = self.tree_file_id(trans_id)
1085
 
            if from_file_id is not None:
1086
 
                from_trans_ids[from_file_id] = trans_id
1087
 
            to_file_id = self.final_file_id(trans_id)
1088
 
            if to_file_id is not None:
1089
 
                to_trans_ids[to_file_id] = trans_id
1090
 
        return from_trans_ids, to_trans_ids
1091
 
 
1092
 
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
1093
 
        """Get data about a file in the from (tree) state
1094
 
 
1095
 
        Return a (name, parent, kind, executable) tuple
1096
 
        """
1097
 
        from_path = self._tree_id_paths.get(from_trans_id)
1098
 
        if from_versioned:
1099
 
            # get data from working tree if versioned
1100
 
            from_entry = self._tree.inventory[file_id]
1101
 
            from_name = from_entry.name
1102
 
            from_parent = from_entry.parent_id
1103
 
        else:
1104
 
            from_entry = None
1105
 
            if from_path is None:
1106
 
                # File does not exist in FROM state
1107
 
                from_name = None
1108
 
                from_parent = None
1109
 
            else:
1110
 
                # File exists, but is not versioned.  Have to use path-
1111
 
                # splitting stuff
1112
 
                from_name = os.path.basename(from_path)
1113
 
                tree_parent = self.get_tree_parent(from_trans_id)
1114
 
                from_parent = self.tree_file_id(tree_parent)
1115
 
        if from_path is not None:
1116
 
            from_kind, from_executable, from_stats = \
1117
 
                self._tree._comparison_data(from_entry, from_path)
1118
 
        else:
1119
 
            from_kind = None
1120
 
            from_executable = False
1121
 
        return from_name, from_parent, from_kind, from_executable
1122
 
 
1123
 
    def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1124
 
        """Get data about a file in the to (target) state
1125
 
 
1126
 
        Return a (name, parent, kind, executable) tuple
1127
 
        """
1128
 
        to_name = self.final_name(to_trans_id)
1129
 
        try:
1130
 
            to_kind = self.final_kind(to_trans_id)
1131
 
        except NoSuchFile:
1132
 
            to_kind = None
1133
 
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
1134
 
        if to_trans_id in self._new_executability:
1135
 
            to_executable = self._new_executability[to_trans_id]
1136
 
        elif to_trans_id == from_trans_id:
1137
 
            to_executable = from_executable
1138
 
        else:
1139
 
            to_executable = False
1140
 
        return to_name, to_parent, to_kind, to_executable
1141
 
 
1142
 
    def _iter_changes(self):
1143
 
        """Produce output in the same format as Tree._iter_changes.
1144
 
 
1145
 
        Will produce nonsensical results if invoked while inventory/filesystem
1146
 
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
1147
 
 
1148
 
        This reads the Transform, but only reproduces changes involving a
1149
 
        file_id.  Files that are not versioned in either of the FROM or TO
1150
 
        states are not reflected.
1151
 
        """
1152
 
        final_paths = FinalPaths(self)
1153
 
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
1154
 
        results = []
1155
 
        # Now iterate through all active file_ids
1156
 
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1157
 
            modified = False
1158
 
            from_trans_id = from_trans_ids.get(file_id)
1159
 
            # find file ids, and determine versioning state
1160
 
            if from_trans_id is None:
1161
 
                from_versioned = False
1162
 
                from_trans_id = to_trans_ids[file_id]
1163
 
            else:
1164
 
                from_versioned = True
1165
 
            to_trans_id = to_trans_ids.get(file_id)
1166
 
            if to_trans_id is None:
1167
 
                to_versioned = False
1168
 
                to_trans_id = from_trans_id
1169
 
            else:
1170
 
                to_versioned = True
1171
 
 
1172
 
            from_name, from_parent, from_kind, from_executable = \
1173
 
                self._from_file_data(from_trans_id, from_versioned, file_id)
1174
 
 
1175
 
            to_name, to_parent, to_kind, to_executable = \
1176
 
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
1177
 
 
1178
 
            if not from_versioned:
1179
 
                from_path = None
1180
 
            else:
1181
 
                from_path = self._tree_id_paths.get(from_trans_id)
1182
 
            if not to_versioned:
1183
 
                to_path = None
1184
 
            else:
1185
 
                to_path = final_paths.get_path(to_trans_id)
1186
 
            if from_kind != to_kind:
1187
 
                modified = True
1188
 
            elif to_kind in ('file', 'symlink') and (
1189
 
                to_trans_id != from_trans_id or
1190
 
                to_trans_id in self._new_contents):
1191
 
                modified = True
1192
 
            if (not modified and from_versioned == to_versioned and
1193
 
                from_parent==to_parent and from_name == to_name and
1194
 
                from_executable == to_executable):
1195
 
                continue
1196
 
            results.append((file_id, (from_path, to_path), modified,
1197
 
                   (from_versioned, to_versioned),
1198
 
                   (from_parent, to_parent),
1199
 
                   (from_name, to_name),
1200
 
                   (from_kind, to_kind),
1201
 
                   (from_executable, to_executable)))
1202
 
        return iter(sorted(results, key=lambda x:x[1]))
1203
 
 
1204
 
 
1205
896
def joinpath(parent, child):
1206
897
    """Join tree-relative paths, handling the tree root specially"""
1207
898
    if parent is None or parent == "":
1243
934
    file_ids.sort(key=tree.id2path)
1244
935
    return file_ids
1245
936
 
1246
 
 
1247
937
def build_tree(tree, wt):
1248
 
    """Create working tree for a branch, using a TreeTransform.
1249
 
    
1250
 
    This function should be used on empty trees, having a tree root at most.
1251
 
    (see merge and revert functionality for working with existing trees)
1252
 
 
1253
 
    Existing files are handled like so:
1254
 
    
1255
 
    - Existing bzrdirs take precedence over creating new items.  They are
1256
 
      created as '%s.diverted' % name.
1257
 
    - Otherwise, if the content on disk matches the content we are building,
1258
 
      it is silently replaced.
1259
 
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1260
 
    """
1261
 
    wt.lock_tree_write()
1262
 
    try:
1263
 
        tree.lock_read()
1264
 
        try:
1265
 
            return _build_tree(tree, wt)
1266
 
        finally:
1267
 
            tree.unlock()
1268
 
    finally:
1269
 
        wt.unlock()
1270
 
 
1271
 
def _build_tree(tree, wt):
1272
 
    """See build_tree."""
1273
 
    if len(wt.inventory) > 1:  # more than just a root
1274
 
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
 
938
    """Create working tree for a branch, using a Transaction."""
1275
939
    file_trans_id = {}
1276
940
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1277
941
    pp = ProgressPhase("Build phase", 2, top_pb)
1278
 
    if tree.inventory.root is not None:
1279
 
        # This is kind of a hack: we should be altering the root
1280
 
        # as part of the regular tree shape diff logic.
1281
 
        # The conditional test here is to avoid doing an
1282
 
        # expensive operation (flush) every time the root id
1283
 
        # is set within the tree, nor setting the root and thus
1284
 
        # marking the tree as dirty, because we use two different
1285
 
        # idioms here: tree interfaces and inventory interfaces.
1286
 
        if wt.path2id('') != tree.inventory.root.file_id:
1287
 
            wt.set_root_id(tree.inventory.root.file_id)
1288
 
            wt.flush()
1289
942
    tt = TreeTransform(wt)
1290
 
    divert = set()
1291
943
    try:
1292
944
        pp.next_phase()
1293
 
        file_trans_id[wt.get_root_id()] = \
1294
 
            tt.trans_id_tree_file_id(wt.get_root_id())
 
945
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
 
946
        file_ids = topology_sorted_ids(tree)
1295
947
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
1296
948
        try:
1297
 
            deferred_contents = []
1298
 
            for num, (tree_path, entry) in \
1299
 
                enumerate(tree.inventory.iter_entries_by_dir()):
1300
 
                pb.update("Building tree", num - len(deferred_contents),
1301
 
                          len(tree.inventory))
 
949
            for num, file_id in enumerate(file_ids):
 
950
                pb.update("Building tree", num, len(file_ids))
 
951
                entry = tree.inventory[file_id]
1302
952
                if entry.parent_id is None:
1303
953
                    continue
1304
 
                reparent = False
1305
 
                file_id = entry.file_id
1306
 
                target_path = wt.abspath(tree_path)
1307
 
                try:
1308
 
                    kind = file_kind(target_path)
1309
 
                except NoSuchFile:
1310
 
                    pass
1311
 
                else:
1312
 
                    if kind == "directory":
1313
 
                        try:
1314
 
                            bzrdir.BzrDir.open(target_path)
1315
 
                        except errors.NotBranchError:
1316
 
                            pass
1317
 
                        else:
1318
 
                            divert.add(file_id)
1319
 
                    if (file_id not in divert and
1320
 
                        _content_match(tree, entry, file_id, kind,
1321
 
                        target_path)):
1322
 
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
1323
 
                        if kind == 'directory':
1324
 
                            reparent = True
1325
954
                if entry.parent_id not in file_trans_id:
1326
 
                    raise AssertionError(
1327
 
                        'entry %s parent id %r is not in file_trans_id %r'
1328
 
                        % (entry, entry.parent_id, file_trans_id))
 
955
                    raise repr(entry.parent_id)
1329
956
                parent_id = file_trans_id[entry.parent_id]
1330
 
                if entry.kind == 'file':
1331
 
                    # We *almost* replicate new_by_entry, so that we can defer
1332
 
                    # getting the file text, and get them all at once.
1333
 
                    trans_id = tt.create_path(entry.name, parent_id)
1334
 
                    file_trans_id[file_id] = trans_id
1335
 
                    tt.version_file(entry.file_id, trans_id)
1336
 
                    executable = tree.is_executable(entry.file_id, tree_path)
1337
 
                    if executable is not None:
1338
 
                        tt.set_executability(executable, trans_id)
1339
 
                    deferred_contents.append((entry.file_id, trans_id))
1340
 
                else:
1341
 
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1342
 
                                                          tree)
1343
 
                if reparent:
1344
 
                    new_trans_id = file_trans_id[file_id]
1345
 
                    old_parent = tt.trans_id_tree_path(tree_path)
1346
 
                    _reparent_children(tt, old_parent, new_trans_id)
1347
 
            for num, (trans_id, bytes) in enumerate(
1348
 
                tree.iter_files_bytes(deferred_contents)):
1349
 
                tt.create_file(bytes, trans_id)
1350
 
                pb.update('Adding file contents',
1351
 
                          (num + len(tree.inventory) - len(deferred_contents)),
1352
 
                          len(tree.inventory))
 
957
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
 
958
                                                      tree)
1353
959
        finally:
1354
960
            pb.finished()
1355
961
        pp.next_phase()
1356
 
        divert_trans = set(file_trans_id[f] for f in divert)
1357
 
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1358
 
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1359
 
        conflicts = cook_conflicts(raw_conflicts, tt)
1360
 
        for conflict in conflicts:
1361
 
            warning(conflict)
1362
 
        try:
1363
 
            wt.add_conflicts(conflicts)
1364
 
        except errors.UnsupportedOperation:
1365
 
            pass
1366
 
        result = tt.apply()
 
962
        tt.apply()
1367
963
    finally:
1368
964
        tt.finalize()
1369
965
        top_pb.finished()
1370
 
    return result
1371
 
 
1372
 
 
1373
 
def _reparent_children(tt, old_parent, new_parent):
1374
 
    for child in tt.iter_tree_children(old_parent):
1375
 
        tt.adjust_path(tt.final_name(child), new_parent, child)
1376
 
 
1377
 
 
1378
 
def _content_match(tree, entry, file_id, kind, target_path):
1379
 
    if entry.kind != kind:
1380
 
        return False
1381
 
    if entry.kind == "directory":
1382
 
        return True
1383
 
    if entry.kind == "file":
1384
 
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1385
 
            return True
1386
 
    elif entry.kind == "symlink":
1387
 
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
1388
 
            return True
1389
 
    return False
1390
 
 
1391
 
 
1392
 
def resolve_checkout(tt, conflicts, divert):
1393
 
    new_conflicts = set()
1394
 
    for c_type, conflict in ((c[0], c) for c in conflicts):
1395
 
        # Anything but a 'duplicate' would indicate programmer error
1396
 
        assert c_type == 'duplicate', c_type
1397
 
        # Now figure out which is new and which is old
1398
 
        if tt.new_contents(conflict[1]):
1399
 
            new_file = conflict[1]
1400
 
            old_file = conflict[2]
1401
 
        else:
1402
 
            new_file = conflict[2]
1403
 
            old_file = conflict[1]
1404
 
 
1405
 
        # We should only get here if the conflict wasn't completely
1406
 
        # resolved
1407
 
        final_parent = tt.final_parent(old_file)
1408
 
        if new_file in divert:
1409
 
            new_name = tt.final_name(old_file)+'.diverted'
1410
 
            tt.adjust_path(new_name, final_parent, new_file)
1411
 
            new_conflicts.add((c_type, 'Diverted to',
1412
 
                               new_file, old_file))
1413
 
        else:
1414
 
            new_name = tt.final_name(old_file)+'.moved'
1415
 
            tt.adjust_path(new_name, final_parent, old_file)
1416
 
            new_conflicts.add((c_type, 'Moved existing file to',
1417
 
                               old_file, new_file))
1418
 
    return new_conflicts
1419
 
 
1420
966
 
1421
967
def new_by_entry(tt, entry, parent_id, tree):
1422
968
    """Create a new file according to its inventory entry"""
1427
973
        executable = tree.is_executable(entry.file_id)
1428
974
        return tt.new_file(name, parent_id, contents, entry.file_id, 
1429
975
                           executable)
1430
 
    elif kind in ('directory', 'tree-reference'):
1431
 
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
1432
 
        if kind == 'tree-reference':
1433
 
            tt.set_tree_reference(entry.reference_revision, trans_id)
1434
 
        return trans_id 
 
976
    elif kind == 'directory':
 
977
        return tt.new_directory(name, parent_id, entry.file_id)
1435
978
    elif kind == 'symlink':
1436
979
        target = tree.get_symlink_target(entry.file_id)
1437
980
        return tt.new_symlink(name, parent_id, target, entry.file_id)
1438
 
    else:
1439
 
        raise errors.BadFileKindError(name, kind)
1440
981
 
1441
982
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1442
983
    """Create new file contents according to an inventory entry."""
1443
984
    if entry.kind == "file":
1444
 
        if lines is None:
 
985
        if lines == None:
1445
986
            lines = tree.get_file(entry.file_id).readlines()
1446
987
        tt.create_file(lines, trans_id, mode_id=mode_id)
1447
988
    elif entry.kind == "symlink":
1455
996
        tt.set_executability(entry.executable, trans_id)
1456
997
 
1457
998
 
1458
 
@deprecated_function(zero_fifteen)
1459
999
def find_interesting(working_tree, target_tree, filenames):
1460
 
    """Find the ids corresponding to specified filenames.
1461
 
    
1462
 
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1463
 
    """
1464
 
    working_tree.lock_read()
1465
 
    try:
1466
 
        target_tree.lock_read()
1467
 
        try:
1468
 
            return working_tree.paths2ids(filenames, [target_tree])
1469
 
        finally:
1470
 
            target_tree.unlock()
1471
 
    finally:
1472
 
        working_tree.unlock()
1473
 
 
1474
 
 
1475
 
@deprecated_function(zero_ninety)
 
1000
    """Find the ids corresponding to specified filenames."""
 
1001
    trees = (working_tree, target_tree)
 
1002
    return tree.find_ids_across_trees(filenames, trees)
 
1003
 
 
1004
 
1476
1005
def change_entry(tt, file_id, working_tree, target_tree, 
1477
1006
                 trans_id_file_id, backups, trans_id, by_parent):
1478
1007
    """Replace a file_id's contents with those from a target tree."""
1479
 
    if file_id is None and target_tree is None:
1480
 
        # skip the logic altogether in the deprecation test
1481
 
        return
1482
1008
    e_trans_id = trans_id_file_id(file_id)
1483
1009
    entry = target_tree.inventory[file_id]
1484
1010
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
1517
1043
 
1518
1044
 
1519
1045
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1520
 
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1521
 
 
1522
 
 
1523
 
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1524
1046
    """Produce a backup-style name that appears to be available"""
1525
1047
    def name_gen():
1526
1048
        counter = 1
1527
1049
        while True:
1528
 
            yield "%s.~%d~" % (name, counter)
 
1050
            yield "%s.~%d~" % (entry.name, counter)
1529
1051
            counter += 1
1530
 
    for new_name in name_gen():
1531
 
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1532
 
            return new_name
1533
 
 
 
1052
    for name in name_gen():
 
1053
        if not tt.has_named_child(by_parent, parent_trans_id, name):
 
1054
            return name
1534
1055
 
1535
1056
def _entry_changes(file_id, entry, working_tree):
1536
1057
    """Determine in which ways the inventory entry has changed.
1559
1080
    return has_contents, contents_mod, meta_mod
1560
1081
 
1561
1082
 
1562
 
def revert(working_tree, target_tree, filenames, backups=False,
1563
 
           pb=DummyProgress(), change_reporter=None):
 
1083
def revert(working_tree, target_tree, filenames, backups=False, 
 
1084
           pb=DummyProgress()):
1564
1085
    """Revert a working tree's contents to those of a target tree."""
1565
 
    target_tree.lock_read()
 
1086
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
 
1087
    def interesting(file_id):
 
1088
        return interesting_ids is None or file_id in interesting_ids
 
1089
 
1566
1090
    tt = TreeTransform(working_tree, pb)
1567
1091
    try:
1568
 
        pp = ProgressPhase("Revert phase", 3, pb)
1569
 
        pp.next_phase()
1570
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1571
 
        try:
1572
 
            merge_modified = _alter_files(working_tree, target_tree, tt,
1573
 
                                          child_pb, filenames, backups)
 
1092
        merge_modified = working_tree.merge_modified()
 
1093
        trans_id = {}
 
1094
        def trans_id_file_id(file_id):
 
1095
            try:
 
1096
                return trans_id[file_id]
 
1097
            except KeyError:
 
1098
                return tt.trans_id_tree_file_id(file_id)
 
1099
 
 
1100
        pp = ProgressPhase("Revert phase", 4, pb)
 
1101
        pp.next_phase()
 
1102
        sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
 
1103
                              interesting(i)]
 
1104
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1105
        try:
 
1106
            by_parent = tt.by_parent()
 
1107
            for id_num, file_id in enumerate(sorted_interesting):
 
1108
                child_pb.update("Reverting file", id_num+1, 
 
1109
                                len(sorted_interesting))
 
1110
                if file_id not in working_tree.inventory:
 
1111
                    entry = target_tree.inventory[file_id]
 
1112
                    parent_id = trans_id_file_id(entry.parent_id)
 
1113
                    e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
 
1114
                    trans_id[file_id] = e_trans_id
 
1115
                else:
 
1116
                    backup_this = backups
 
1117
                    if file_id in merge_modified:
 
1118
                        backup_this = False
 
1119
                        del merge_modified[file_id]
 
1120
                    change_entry(tt, file_id, working_tree, target_tree, 
 
1121
                                 trans_id_file_id, backup_this, trans_id,
 
1122
                                 by_parent)
 
1123
        finally:
 
1124
            child_pb.finished()
 
1125
        pp.next_phase()
 
1126
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
 
1127
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1128
        try:
 
1129
            for id_num, file_id in enumerate(wt_interesting):
 
1130
                child_pb.update("New file check", id_num+1, 
 
1131
                                len(sorted_interesting))
 
1132
                if file_id not in target_tree:
 
1133
                    trans_id = tt.trans_id_tree_file_id(file_id)
 
1134
                    tt.unversion_file(trans_id)
 
1135
                    if file_id in merge_modified:
 
1136
                        tt.delete_contents(trans_id)
 
1137
                        del merge_modified[file_id]
1574
1138
        finally:
1575
1139
            child_pb.finished()
1576
1140
        pp.next_phase()
1580
1144
        finally:
1581
1145
            child_pb.finished()
1582
1146
        conflicts = cook_conflicts(raw_conflicts, tt)
1583
 
        if change_reporter:
1584
 
            change_reporter = delta._ChangeReporter(
1585
 
                unversioned_filter=working_tree.is_ignored)
1586
 
            delta.report_changes(tt._iter_changes(), change_reporter)
1587
1147
        for conflict in conflicts:
1588
1148
            warning(conflict)
1589
1149
        pp.next_phase()
1590
1150
        tt.apply()
1591
 
        working_tree.set_merge_modified(merge_modified)
 
1151
        working_tree.set_merge_modified({})
1592
1152
    finally:
1593
 
        target_tree.unlock()
1594
1153
        tt.finalize()
1595
1154
        pb.clear()
1596
1155
    return conflicts
1597
1156
 
1598
1157
 
1599
 
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1600
 
                 backups):
1601
 
    merge_modified = working_tree.merge_modified()
1602
 
    change_list = target_tree._iter_changes(working_tree,
1603
 
        specific_files=specific_files, pb=pb)
1604
 
    if target_tree.inventory.root is None:
1605
 
        skip_root = True
1606
 
    else:
1607
 
        skip_root = False
1608
 
    basis_tree = None
1609
 
    try:
1610
 
        deferred_files = []
1611
 
        for id_num, (file_id, path, changed_content, versioned, parent, name,
1612
 
                kind, executable) in enumerate(change_list):
1613
 
            if skip_root and file_id[0] is not None and parent[0] is None:
1614
 
                continue
1615
 
            trans_id = tt.trans_id_file_id(file_id)
1616
 
            mode_id = None
1617
 
            if changed_content:
1618
 
                keep_content = False
1619
 
                if kind[0] == 'file' and (backups or kind[1] is None):
1620
 
                    wt_sha1 = working_tree.get_file_sha1(file_id)
1621
 
                    if merge_modified.get(file_id) != wt_sha1:
1622
 
                        # acquire the basis tree lazily to prevent the
1623
 
                        # expense of accessing it when it's not needed ?
1624
 
                        # (Guessing, RBC, 200702)
1625
 
                        if basis_tree is None:
1626
 
                            basis_tree = working_tree.basis_tree()
1627
 
                            basis_tree.lock_read()
1628
 
                        if file_id in basis_tree:
1629
 
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
1630
 
                                keep_content = True
1631
 
                        elif kind[1] is None and not versioned[1]:
1632
 
                            keep_content = True
1633
 
                if kind[0] is not None:
1634
 
                    if not keep_content:
1635
 
                        tt.delete_contents(trans_id)
1636
 
                    elif kind[1] is not None:
1637
 
                        parent_trans_id = tt.trans_id_file_id(parent[0])
1638
 
                        by_parent = tt.by_parent()
1639
 
                        backup_name = _get_backup_name(name[0], by_parent,
1640
 
                                                       parent_trans_id, tt)
1641
 
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
1642
 
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
1643
 
                        if versioned == (True, True):
1644
 
                            tt.unversion_file(trans_id)
1645
 
                            tt.version_file(file_id, new_trans_id)
1646
 
                        # New contents should have the same unix perms as old
1647
 
                        # contents
1648
 
                        mode_id = trans_id
1649
 
                        trans_id = new_trans_id
1650
 
                if kind[1] == 'directory':
1651
 
                    tt.create_directory(trans_id)
1652
 
                elif kind[1] == 'symlink':
1653
 
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
1654
 
                                      trans_id)
1655
 
                elif kind[1] == 'file':
1656
 
                    deferred_files.append((file_id, (trans_id, mode_id)))
1657
 
                    if basis_tree is None:
1658
 
                        basis_tree = working_tree.basis_tree()
1659
 
                        basis_tree.lock_read()
1660
 
                    new_sha1 = target_tree.get_file_sha1(file_id)
1661
 
                    if (file_id in basis_tree and new_sha1 ==
1662
 
                        basis_tree.get_file_sha1(file_id)):
1663
 
                        if file_id in merge_modified:
1664
 
                            del merge_modified[file_id]
1665
 
                    else:
1666
 
                        merge_modified[file_id] = new_sha1
1667
 
 
1668
 
                    # preserve the execute bit when backing up
1669
 
                    if keep_content and executable[0] == executable[1]:
1670
 
                        tt.set_executability(executable[1], trans_id)
1671
 
                else:
1672
 
                    assert kind[1] is None
1673
 
            if versioned == (False, True):
1674
 
                tt.version_file(file_id, trans_id)
1675
 
            if versioned == (True, False):
1676
 
                tt.unversion_file(trans_id)
1677
 
            if (name[1] is not None and 
1678
 
                (name[0] != name[1] or parent[0] != parent[1])):
1679
 
                tt.adjust_path(
1680
 
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
1681
 
            if executable[0] != executable[1] and kind[1] == "file":
1682
 
                tt.set_executability(executable[1], trans_id)
1683
 
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1684
 
            deferred_files):
1685
 
            tt.create_file(bytes, trans_id, mode_id)
1686
 
    finally:
1687
 
        if basis_tree is not None:
1688
 
            basis_tree.unlock()
1689
 
    return merge_modified
1690
 
 
1691
 
 
1692
 
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
 
1158
def resolve_conflicts(tt, pb=DummyProgress()):
1693
1159
    """Make many conflict-resolution attempts, but die if they fail"""
1694
 
    if pass_func is None:
1695
 
        pass_func = conflict_pass
1696
1160
    new_conflicts = set()
1697
1161
    try:
1698
1162
        for n in range(10):
1700
1164
            conflicts = tt.find_conflicts()
1701
1165
            if len(conflicts) == 0:
1702
1166
                return new_conflicts
1703
 
            new_conflicts.update(pass_func(tt, conflicts))
 
1167
            new_conflicts.update(conflict_pass(tt, conflicts))
1704
1168
        raise MalformedTransform(conflicts=conflicts)
1705
1169
    finally:
1706
1170
        pb.clear()
1707
1171
 
1708
1172
 
1709
 
def conflict_pass(tt, conflicts, path_tree=None):
1710
 
    """Resolve some classes of conflicts.
1711
 
 
1712
 
    :param tt: The transform to resolve conflicts in
1713
 
    :param conflicts: The conflicts to resolve
1714
 
    :param path_tree: A Tree to get supplemental paths from
1715
 
    """
 
1173
def conflict_pass(tt, conflicts):
 
1174
    """Resolve some classes of conflicts."""
1716
1175
    new_conflicts = set()
1717
1176
    for c_type, conflict in ((c[0], c) for c in conflicts):
1718
1177
        if c_type == 'duplicate id':
1744
1203
            trans_id = conflict[1]
1745
1204
            try:
1746
1205
                tt.cancel_deletion(trans_id)
1747
 
                new_conflicts.add(('deleting parent', 'Not deleting', 
1748
 
                                   trans_id))
 
1206
                new_conflicts.add((c_type, 'Not deleting', trans_id))
1749
1207
            except KeyError:
1750
1208
                tt.create_directory(trans_id)
1751
 
                new_conflicts.add((c_type, 'Created directory', trans_id))
1752
 
                try:
1753
 
                    tt.final_name(trans_id)
1754
 
                except NoFinalPath:
1755
 
                    file_id = tt.final_file_id(trans_id)
1756
 
                    entry = path_tree.inventory[file_id]
1757
 
                    parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1758
 
                    tt.adjust_path(entry.name, parent_trans_id, trans_id)
 
1209
                new_conflicts.add((c_type, 'Created directory.', trans_id))
1759
1210
        elif c_type == 'unversioned parent':
1760
1211
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1761
1212
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1788
1239
                                   file_id=modified_id, 
1789
1240
                                   conflict_path=conflicting_path,
1790
1241
                                   conflict_file_id=conflicting_id)
1791
 
 
1792
 
 
1793
 
class _FileMover(object):
1794
 
    """Moves and deletes files for TreeTransform, tracking operations"""
1795
 
 
1796
 
    def __init__(self):
1797
 
        self.past_renames = []
1798
 
        self.pending_deletions = []
1799
 
 
1800
 
    def rename(self, from_, to):
1801
 
        """Rename a file from one path to another.  Functions like os.rename"""
1802
 
        os.rename(from_, to)
1803
 
        self.past_renames.append((from_, to))
1804
 
 
1805
 
    def pre_delete(self, from_, to):
1806
 
        """Rename a file out of the way and mark it for deletion.
1807
 
 
1808
 
        Unlike os.unlink, this works equally well for files and directories.
1809
 
        :param from_: The current file path
1810
 
        :param to: A temporary path for the file
1811
 
        """
1812
 
        self.rename(from_, to)
1813
 
        self.pending_deletions.append(to)
1814
 
 
1815
 
    def rollback(self):
1816
 
        """Reverse all renames that have been performed"""
1817
 
        for from_, to in reversed(self.past_renames):
1818
 
            os.rename(to, from_)
1819
 
        # after rollback, don't reuse _FileMover
1820
 
        past_renames = None
1821
 
        pending_deletions = None
1822
 
 
1823
 
    def apply_deletions(self):
1824
 
        """Apply all marked deletions"""
1825
 
        for path in self.pending_deletions:
1826
 
            delete_any(path)
1827
 
        # after apply_deletions, don't reuse _FileMover
1828
 
        past_renames = None
1829
 
        pending_deletions = None