~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Wouter van Heyst
  • Date: 2006-06-07 16:05:27 UTC
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: larstiq@larstiq.dyndns.org-20060607160527-2b3649154d0e2e84
more code cleanup

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2006 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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 deprecated_function, zero_fifteen
38
28
from bzrlib.trace import mutter, warning
39
 
from bzrlib import tree
40
 
import bzrlib.ui
 
29
import bzrlib.ui 
41
30
import bzrlib.urlutils as urlutils
42
31
 
43
32
 
51
40
 
52
41
 
53
42
class _TransformResults(object):
54
 
    def __init__(self, modified_paths, rename_count):
 
43
    def __init__(self, modified_paths):
55
44
        object.__init__(self)
56
45
        self.modified_paths = modified_paths
57
 
        self.rename_count = rename_count
58
46
 
59
47
 
60
48
class TreeTransform(object):
61
49
    """Represent a tree transformation.
62
50
    
63
51
    This object is designed to support incremental generation of the transform,
64
 
    in any order.
65
 
 
66
 
    However, it gives optimum performance when parent directories are created
67
 
    before their contents.  The transform is then able to put child files
68
 
    directly in their parent directory, avoiding later renames.
 
52
    in any order.  
69
53
    
70
54
    It is easy to produce malformed transforms, but they are generally
71
55
    harmless.  Attempting to apply a malformed transform will cause an
87
71
     * set_executability
88
72
    """
89
73
    def __init__(self, tree, pb=DummyProgress()):
90
 
        """Note: a tree_write lock is taken on the tree.
 
74
        """Note: a write lock is taken on the tree.
91
75
        
92
 
        Use TreeTransform.finalize() to release the lock (can be omitted if
93
 
        TreeTransform.apply() called).
 
76
        Use TreeTransform.finalize() to release the lock
94
77
        """
95
78
        object.__init__(self)
96
79
        self._tree = tree
97
 
        self._tree.lock_tree_write()
 
80
        self._tree.lock_write()
98
81
        try:
99
82
            control_files = self._tree._control_files
100
83
            self._limbodir = urlutils.local_path_from_url(
112
95
        self._new_name = {}
113
96
        self._new_parent = {}
114
97
        self._new_contents = {}
115
 
        # A mapping of transform ids to their limbo filename
116
 
        self._limbo_files = {}
117
 
        # A mapping of transform ids to a set of the transform ids of children
118
 
        # that their limbo directory has
119
 
        self._limbo_children = {}
120
 
        # Map transform ids to maps of child filename to child transform id
121
 
        self._limbo_children_names = {}
122
 
        # List of transform ids that need to be renamed from limbo into place
123
 
        self._needs_rename = set()
124
98
        self._removed_contents = set()
125
99
        self._new_executability = {}
126
 
        self._new_reference_revision = {}
127
100
        self._new_id = {}
128
101
        self._non_present_ids = {}
129
102
        self._r_new_id = {}
130
103
        self._removed_id = set()
131
104
        self._tree_path_ids = {}
132
105
        self._tree_id_paths = {}
 
106
        self._realpaths = {}
133
107
        # Cache of realpath results, to speed up canonical_path
134
 
        self._realpaths = {}
 
108
        self._relpaths = {}
135
109
        # Cache of relpath results, to speed up canonical_path
136
 
        self._relpaths = {}
137
110
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
138
111
        self.__done = False
139
112
        self._pb = pb
140
 
        self.rename_count = 0
141
113
 
142
114
    def __get_root(self):
143
115
        return self._new_root
145
117
    root = property(__get_root)
146
118
 
147
119
    def finalize(self):
148
 
        """Release the working tree lock, if held, clean up limbo dir.
149
 
 
150
 
        This is required if apply has not been invoked, but can be invoked
151
 
        even after apply.
152
 
        """
 
120
        """Release the working tree lock, if held, clean up limbo dir."""
153
121
        if self._tree is None:
154
122
            return
155
123
        try:
156
 
            entries = [(self._limbo_name(t), t, k) for t, k in
157
 
                       self._new_contents.iteritems()]
158
 
            entries.sort(reverse=True)
159
 
            for path, trans_id, kind in entries:
 
124
            for trans_id, kind in self._new_contents.iteritems():
 
125
                path = self._limbo_name(trans_id)
160
126
                if kind == "directory":
161
127
                    os.rmdir(path)
162
128
                else:
187
153
        """Change the path that is assigned to a transaction id."""
188
154
        if trans_id == self._new_root:
189
155
            raise CantMoveRoot
190
 
        previous_parent = self._new_parent.get(trans_id)
191
 
        previous_name = self._new_name.get(trans_id)
192
156
        self._new_name[trans_id] = name
193
157
        self._new_parent[trans_id] = parent
194
 
        if (trans_id in self._limbo_files and
195
 
            trans_id not in self._needs_rename):
196
 
            self._rename_in_limbo([trans_id])
197
 
            self._limbo_children[previous_parent].remove(trans_id)
198
 
            del self._limbo_children_names[previous_parent][previous_name]
199
 
 
200
 
    def _rename_in_limbo(self, trans_ids):
201
 
        """Fix limbo names so that the right final path is produced.
202
 
 
203
 
        This means we outsmarted ourselves-- we tried to avoid renaming
204
 
        these files later by creating them with their final names in their
205
 
        final parents.  But now the previous name or parent is no longer
206
 
        suitable, so we have to rename them.
207
 
 
208
 
        Even for trans_ids that have no new contents, we must remove their
209
 
        entries from _limbo_files, because they are now stale.
210
 
        """
211
 
        for trans_id in trans_ids:
212
 
            old_path = self._limbo_files.pop(trans_id)
213
 
            if trans_id not in self._new_contents:
214
 
                continue
215
 
            new_path = self._limbo_name(trans_id)
216
 
            os.rename(old_path, new_path)
217
158
 
218
159
    def adjust_root_path(self, name, parent):
219
160
        """Emulate moving the root by moving all children, instead.
318
259
        New file takes the permissions of any existing file with that id,
319
260
        unless mode_id is specified.
320
261
        """
321
 
        name = self._limbo_name(trans_id)
322
 
        f = open(name, 'wb')
323
 
        try:
324
 
            try:
325
 
                unique_add(self._new_contents, trans_id, 'file')
326
 
            except:
327
 
                # Clean up the file, it never got registered so
328
 
                # TreeTransform.finalize() won't clean it up.
329
 
                f.close()
330
 
                os.unlink(name)
331
 
                raise
332
 
 
333
 
            f.writelines(contents)
334
 
        finally:
335
 
            f.close()
 
262
        f = file(self._limbo_name(trans_id), 'wb')
 
263
        unique_add(self._new_contents, trans_id, 'file')
 
264
        for segment in contents:
 
265
            f.write(segment)
 
266
        f.close()
336
267
        self._set_mode(trans_id, mode_id, S_ISREG)
337
268
 
338
269
    def _set_mode(self, trans_id, mode_id, typefunc):
348
279
        except KeyError:
349
280
            return
350
281
        try:
351
 
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
282
            mode = os.stat(old_path).st_mode
352
283
        except OSError, e:
353
284
            if e.errno == errno.ENOENT:
354
285
                return
377
308
    def cancel_creation(self, trans_id):
378
309
        """Cancel the creation of new file contents."""
379
310
        del self._new_contents[trans_id]
380
 
        children = self._limbo_children.get(trans_id)
381
 
        # if this is a limbo directory with children, move them before removing
382
 
        # the directory
383
 
        if children is not None:
384
 
            self._rename_in_limbo(children)
385
 
            del self._limbo_children[trans_id]
386
 
            del self._limbo_children_names[trans_id]
387
311
        delete_any(self._limbo_name(trans_id))
388
312
 
389
313
    def delete_contents(self, trans_id):
413
337
        else:
414
338
            unique_add(self._new_executability, trans_id, executability)
415
339
 
416
 
    def set_tree_reference(self, revision_id, trans_id):
417
 
        """Set the reference associated with a directory"""
418
 
        unique_add(self._new_reference_revision, trans_id, revision_id)
419
 
 
420
340
    def version_file(self, file_id, trans_id):
421
341
        """Schedule a file to become versioned."""
422
342
        assert file_id is not None
524
444
        try:
525
445
            return self._new_name[trans_id]
526
446
        except KeyError:
527
 
            try:
528
 
                return os.path.basename(self._tree_id_paths[trans_id])
529
 
            except KeyError:
530
 
                raise NoFinalPath(trans_id, self)
 
447
            return os.path.basename(self._tree_id_paths[trans_id])
531
448
 
532
449
    def by_parent(self):
533
450
        """Return a map of parent: children for known parents.
546
463
 
547
464
    def path_changed(self, trans_id):
548
465
        """Return True if a trans_id's path has changed."""
549
 
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
550
 
 
551
 
    def new_contents(self, trans_id):
552
 
        return (trans_id in self._new_contents)
 
466
        return trans_id in self._new_name or trans_id in self._new_parent
553
467
 
554
468
    def find_conflicts(self):
555
469
        """Find any violations of inventory or filesystem invariants"""
581
495
                        self.tree_kind(t) == 'directory'])
582
496
        for trans_id in self._removed_id:
583
497
            file_id = self.tree_file_id(trans_id)
584
 
            if self._tree.inventory[file_id].kind == 'directory':
 
498
            if self._tree.inventory[file_id].kind in ('directory', 
 
499
                                                      'root_directory'):
585
500
                parents.append(trans_id)
586
501
 
587
502
        for parent_id in parents:
624
539
        if child_id is None:
625
540
            return lexists(self._tree.abspath(childpath))
626
541
        else:
627
 
            if self.final_parent(child_id) != parent_id:
 
542
            if tt.final_parent(child_id) != parent_id:
628
543
                return False
629
 
            if child_id in self._removed_contents:
 
544
            if child_id in tt._removed_contents:
630
545
                # XXX What about dangling file-ids?
631
546
                return False
632
547
            else:
640
555
            parent_id = trans_id
641
556
            while parent_id is not ROOT_PARENT:
642
557
                seen.add(parent_id)
643
 
                try:
644
 
                    parent_id = self.final_parent(parent_id)
645
 
                except KeyError:
646
 
                    break
 
558
                parent_id = self.final_parent(parent_id)
647
559
                if parent_id == trans_id:
648
560
                    conflicts.append(('parent loop', trans_id))
649
561
                if parent_id in seen:
723
635
            last_name = None
724
636
            last_trans_id = None
725
637
            for name, trans_id in name_ids:
 
638
                if name == last_name:
 
639
                    conflicts.append(('duplicate', last_trans_id, trans_id,
 
640
                    name))
726
641
                try:
727
642
                    kind = self.final_kind(trans_id)
728
643
                except NoSuchFile:
729
644
                    kind = None
730
645
                file_id = self.final_file_id(trans_id)
731
 
                if kind is None and file_id is None:
732
 
                    continue
733
 
                if name == last_name:
734
 
                    conflicts.append(('duplicate', last_trans_id, trans_id,
735
 
                    name))
736
 
                last_name = name
737
 
                last_trans_id = trans_id
 
646
                if kind is not None or file_id is not None:
 
647
                    last_name = name
 
648
                    last_trans_id = trans_id
738
649
        return conflicts
739
650
 
740
651
    def _duplicate_ids(self):
788
699
        
789
700
        If filesystem or inventory conflicts are present, MalformedTransform
790
701
        will be thrown.
791
 
 
792
 
        If apply succeeds, finalize is not necessary.
793
702
        """
794
703
        conflicts = self.find_conflicts()
795
704
        if len(conflicts) != 0:
796
705
            raise MalformedTransform(conflicts=conflicts)
 
706
        limbo_inv = {}
797
707
        inv = self._tree.inventory
798
 
        inventory_delta = []
799
708
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
800
709
        try:
801
710
            child_pb.update('Apply phase', 0, 2)
802
 
            self._apply_removals(inv, inventory_delta)
 
711
            self._apply_removals(inv, limbo_inv)
803
712
            child_pb.update('Apply phase', 1, 2)
804
 
            modified_paths = self._apply_insertions(inv, inventory_delta)
 
713
            modified_paths = self._apply_insertions(inv, limbo_inv)
805
714
        finally:
806
715
            child_pb.finished()
807
 
        self._tree.apply_inventory_delta(inventory_delta)
 
716
        self._tree._write_inventory(inv)
808
717
        self.__done = True
809
718
        self.finalize()
810
 
        return _TransformResults(modified_paths, self.rename_count)
 
719
        return _TransformResults(modified_paths)
811
720
 
812
721
    def _limbo_name(self, trans_id):
813
722
        """Generate the limbo name of a file"""
814
 
        limbo_name = self._limbo_files.get(trans_id)
815
 
        if limbo_name is not None:
816
 
            return limbo_name
817
 
        parent = self._new_parent.get(trans_id)
818
 
        # if the parent directory is already in limbo (e.g. when building a
819
 
        # tree), choose a limbo name inside the parent, to reduce further
820
 
        # renames.
821
 
        use_direct_path = False
822
 
        if self._new_contents.get(parent) == 'directory':
823
 
            filename = self._new_name.get(trans_id)
824
 
            if filename is not None:
825
 
                if parent not in self._limbo_children:
826
 
                    self._limbo_children[parent] = set()
827
 
                    self._limbo_children_names[parent] = {}
828
 
                    use_direct_path = True
829
 
                # the direct path can only be used if no other file has
830
 
                # already taken this pathname, i.e. if the name is unused, or
831
 
                # if it is already associated with this trans_id.
832
 
                elif (self._limbo_children_names[parent].get(filename)
833
 
                      in (trans_id, None)):
834
 
                    use_direct_path = True
835
 
        if use_direct_path:
836
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
837
 
            self._limbo_children[parent].add(trans_id)
838
 
            self._limbo_children_names[parent][filename] = trans_id
839
 
        else:
840
 
            limbo_name = pathjoin(self._limbodir, trans_id)
841
 
            self._needs_rename.add(trans_id)
842
 
        self._limbo_files[trans_id] = limbo_name
843
 
        return limbo_name
 
723
        return pathjoin(self._limbodir, trans_id)
844
724
 
845
 
    def _apply_removals(self, inv, inventory_delta):
 
725
    def _apply_removals(self, inv, limbo_inv):
846
726
        """Perform tree operations that remove directory/inventory names.
847
727
        
848
728
        That is, delete files that are to be deleted, and put any files that
866
746
                    except OSError, e:
867
747
                        if e.errno != errno.ENOENT:
868
748
                            raise
869
 
                    else:
870
 
                        self.rename_count += 1
871
749
                if trans_id in self._removed_id:
872
750
                    if trans_id == self._new_root:
873
751
                        file_id = self._tree.inventory.root.file_id
874
752
                    else:
875
753
                        file_id = self.tree_file_id(trans_id)
876
 
                    assert file_id is not None
877
 
                    inventory_delta.append((path, None, file_id, None))
 
754
                    del inv[file_id]
 
755
                elif trans_id in self._new_name or trans_id in self._new_parent:
 
756
                    file_id = self.tree_file_id(trans_id)
 
757
                    if file_id is not None:
 
758
                        limbo_inv[trans_id] = inv[file_id]
 
759
                        del inv[file_id]
878
760
        finally:
879
761
            child_pb.finished()
880
762
 
881
 
    def _apply_insertions(self, inv, inventory_delta):
 
763
    def _apply_insertions(self, inv, limbo_inv):
882
764
        """Perform tree operations that insert directory/inventory names.
883
765
        
884
766
        That is, create any files that need to be created, and restore from
890
772
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
891
773
        try:
892
774
            for num, (path, trans_id) in enumerate(new_paths):
893
 
                new_entry = None
894
775
                child_pb.update('adding file', num, len(new_paths))
895
776
                try:
896
777
                    kind = self._new_contents[trans_id]
899
780
                if trans_id in self._new_contents or \
900
781
                    self.path_changed(trans_id):
901
782
                    full_path = self._tree.abspath(path)
902
 
                    if trans_id in self._needs_rename:
903
 
                        try:
904
 
                            os.rename(self._limbo_name(trans_id), full_path)
905
 
                        except OSError, e:
906
 
                            # We may be renaming a dangling inventory id
907
 
                            if e.errno != errno.ENOENT:
908
 
                                raise
909
 
                        else:
910
 
                            self.rename_count += 1
 
783
                    try:
 
784
                        os.rename(self._limbo_name(trans_id), full_path)
 
785
                    except OSError, e:
 
786
                        # We may be renaming a dangling inventory id
 
787
                        if e.errno != errno.ENOENT:
 
788
                            raise
911
789
                    if trans_id in self._new_contents:
912
790
                        modified_paths.append(full_path)
913
791
                        del self._new_contents[trans_id]
915
793
                if trans_id in self._new_id:
916
794
                    if kind is None:
917
795
                        kind = file_kind(self._tree.abspath(path))
918
 
                    if trans_id in self._new_reference_revision:
919
 
                        new_entry = inventory.TreeReference(
920
 
                            self._new_id[trans_id],
921
 
                            self._new_name[trans_id], 
922
 
                            self.final_file_id(self._new_parent[trans_id]),
923
 
                            None, self._new_reference_revision[trans_id])
924
 
                    else:
925
 
                        new_entry = inventory.make_entry(kind,
926
 
                            self.final_name(trans_id),
927
 
                            self.final_file_id(self.final_parent(trans_id)),
928
 
                            self._new_id[trans_id])
929
 
                else:
930
 
                    if trans_id in self._new_name or trans_id in\
931
 
                        self._new_parent or\
932
 
                        trans_id in self._new_executability:
933
 
                        file_id = self.final_file_id(trans_id)
934
 
                        if file_id is not None:
935
 
                            entry = inv[file_id]
936
 
                            new_entry = entry.copy()
937
 
 
938
 
                    if trans_id in self._new_name or trans_id in\
939
 
                        self._new_parent:
940
 
                            if new_entry is not None:
941
 
                                new_entry.name = self.final_name(trans_id)
942
 
                                parent = self.final_parent(trans_id)
943
 
                                parent_id = self.final_file_id(parent)
944
 
                                new_entry.parent_id = parent_id
945
 
 
 
796
                    inv.add_path(path, kind, self._new_id[trans_id])
 
797
                elif trans_id in self._new_name or trans_id in\
 
798
                    self._new_parent:
 
799
                    entry = limbo_inv.get(trans_id)
 
800
                    if entry is not None:
 
801
                        entry.name = self.final_name(trans_id)
 
802
                        parent_path = os.path.dirname(path)
 
803
                        entry.parent_id = \
 
804
                            self._tree.inventory.path2id(parent_path)
 
805
                        inv.add(entry)
 
806
 
 
807
                # requires files and inventory entries to be in place
946
808
                if trans_id in self._new_executability:
947
 
                    self._set_executability(path, new_entry, trans_id)
948
 
                if new_entry is not None:
949
 
                    if new_entry.file_id in inv:
950
 
                        old_path = inv.id2path(new_entry.file_id)
951
 
                    else:
952
 
                        old_path = None
953
 
                    inventory_delta.append((old_path, path,
954
 
                                            new_entry.file_id,
955
 
                                            new_entry))
 
809
                    self._set_executability(path, inv, trans_id)
956
810
        finally:
957
811
            child_pb.finished()
958
812
        return modified_paths
959
813
 
960
 
    def _set_executability(self, path, entry, trans_id):
 
814
    def _set_executability(self, path, inv, trans_id):
961
815
        """Set the executability of versioned files """
 
816
        file_id = inv.path2id(path)
962
817
        new_executability = self._new_executability[trans_id]
963
 
        entry.executable = new_executability
 
818
        inv[file_id].executable = new_executability
964
819
        if supports_executable():
965
820
            abspath = self._tree.abspath(path)
966
821
            current_mode = os.stat(abspath).st_mode
1027
882
        self.create_symlink(target, trans_id)
1028
883
        return trans_id
1029
884
 
1030
 
    def _affected_ids(self):
1031
 
        """Return the set of transform ids affected by the transform"""
1032
 
        trans_ids = set(self._removed_id)
1033
 
        trans_ids.update(self._new_id.keys())
1034
 
        trans_ids.update(self._removed_contents)
1035
 
        trans_ids.update(self._new_contents.keys())
1036
 
        trans_ids.update(self._new_executability.keys())
1037
 
        trans_ids.update(self._new_name.keys())
1038
 
        trans_ids.update(self._new_parent.keys())
1039
 
        return trans_ids
1040
 
 
1041
 
    def _get_file_id_maps(self):
1042
 
        """Return mapping of file_ids to trans_ids in the to and from states"""
1043
 
        trans_ids = self._affected_ids()
1044
 
        from_trans_ids = {}
1045
 
        to_trans_ids = {}
1046
 
        # Build up two dicts: trans_ids associated with file ids in the
1047
 
        # FROM state, vs the TO state.
1048
 
        for trans_id in trans_ids:
1049
 
            from_file_id = self.tree_file_id(trans_id)
1050
 
            if from_file_id is not None:
1051
 
                from_trans_ids[from_file_id] = trans_id
1052
 
            to_file_id = self.final_file_id(trans_id)
1053
 
            if to_file_id is not None:
1054
 
                to_trans_ids[to_file_id] = trans_id
1055
 
        return from_trans_ids, to_trans_ids
1056
 
 
1057
 
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
1058
 
        """Get data about a file in the from (tree) state
1059
 
 
1060
 
        Return a (name, parent, kind, executable) tuple
1061
 
        """
1062
 
        from_path = self._tree_id_paths.get(from_trans_id)
1063
 
        if from_versioned:
1064
 
            # get data from working tree if versioned
1065
 
            from_entry = self._tree.inventory[file_id]
1066
 
            from_name = from_entry.name
1067
 
            from_parent = from_entry.parent_id
1068
 
        else:
1069
 
            from_entry = None
1070
 
            if from_path is None:
1071
 
                # File does not exist in FROM state
1072
 
                from_name = None
1073
 
                from_parent = None
1074
 
            else:
1075
 
                # File exists, but is not versioned.  Have to use path-
1076
 
                # splitting stuff
1077
 
                from_name = os.path.basename(from_path)
1078
 
                tree_parent = self.get_tree_parent(from_trans_id)
1079
 
                from_parent = self.tree_file_id(tree_parent)
1080
 
        if from_path is not None:
1081
 
            from_kind, from_executable, from_stats = \
1082
 
                self._tree._comparison_data(from_entry, from_path)
1083
 
        else:
1084
 
            from_kind = None
1085
 
            from_executable = False
1086
 
        return from_name, from_parent, from_kind, from_executable
1087
 
 
1088
 
    def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1089
 
        """Get data about a file in the to (target) state
1090
 
 
1091
 
        Return a (name, parent, kind, executable) tuple
1092
 
        """
1093
 
        to_name = self.final_name(to_trans_id)
1094
 
        try:
1095
 
            to_kind = self.final_kind(to_trans_id)
1096
 
        except NoSuchFile:
1097
 
            to_kind = None
1098
 
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
1099
 
        if to_trans_id in self._new_executability:
1100
 
            to_executable = self._new_executability[to_trans_id]
1101
 
        elif to_trans_id == from_trans_id:
1102
 
            to_executable = from_executable
1103
 
        else:
1104
 
            to_executable = False
1105
 
        return to_name, to_parent, to_kind, to_executable
1106
 
 
1107
 
    def _iter_changes(self):
1108
 
        """Produce output in the same format as Tree._iter_changes.
1109
 
 
1110
 
        Will produce nonsensical results if invoked while inventory/filesystem
1111
 
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
1112
 
 
1113
 
        This reads the Transform, but only reproduces changes involving a
1114
 
        file_id.  Files that are not versioned in either of the FROM or TO
1115
 
        states are not reflected.
1116
 
        """
1117
 
        final_paths = FinalPaths(self)
1118
 
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
1119
 
        results = []
1120
 
        # Now iterate through all active file_ids
1121
 
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1122
 
            modified = False
1123
 
            from_trans_id = from_trans_ids.get(file_id)
1124
 
            # find file ids, and determine versioning state
1125
 
            if from_trans_id is None:
1126
 
                from_versioned = False
1127
 
                from_trans_id = to_trans_ids[file_id]
1128
 
            else:
1129
 
                from_versioned = True
1130
 
            to_trans_id = to_trans_ids.get(file_id)
1131
 
            if to_trans_id is None:
1132
 
                to_versioned = False
1133
 
                to_trans_id = from_trans_id
1134
 
            else:
1135
 
                to_versioned = True
1136
 
 
1137
 
            from_name, from_parent, from_kind, from_executable = \
1138
 
                self._from_file_data(from_trans_id, from_versioned, file_id)
1139
 
 
1140
 
            to_name, to_parent, to_kind, to_executable = \
1141
 
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
1142
 
 
1143
 
            if not from_versioned:
1144
 
                from_path = None
1145
 
            else:
1146
 
                from_path = self._tree_id_paths.get(from_trans_id)
1147
 
            if not to_versioned:
1148
 
                to_path = None
1149
 
            else:
1150
 
                to_path = final_paths.get_path(to_trans_id)
1151
 
            if from_kind != to_kind:
1152
 
                modified = True
1153
 
            elif to_kind in ('file', 'symlink') and (
1154
 
                to_trans_id != from_trans_id or
1155
 
                to_trans_id in self._new_contents):
1156
 
                modified = True
1157
 
            if (not modified and from_versioned == to_versioned and
1158
 
                from_parent==to_parent and from_name == to_name and
1159
 
                from_executable == to_executable):
1160
 
                continue
1161
 
            results.append((file_id, (from_path, to_path), modified,
1162
 
                   (from_versioned, to_versioned),
1163
 
                   (from_parent, to_parent),
1164
 
                   (from_name, to_name),
1165
 
                   (from_kind, to_kind),
1166
 
                   (from_executable, to_executable)))
1167
 
        return iter(sorted(results, key=lambda x:x[1]))
1168
 
 
1169
 
 
1170
885
def joinpath(parent, child):
1171
886
    """Join tree-relative paths, handling the tree root specially"""
1172
887
    if parent is None or parent == "":
1208
923
    file_ids.sort(key=tree.id2path)
1209
924
    return file_ids
1210
925
 
1211
 
 
1212
926
def build_tree(tree, wt):
1213
 
    """Create working tree for a branch, using a TreeTransform.
1214
 
    
1215
 
    This function should be used on empty trees, having a tree root at most.
1216
 
    (see merge and revert functionality for working with existing trees)
1217
 
 
1218
 
    Existing files are handled like so:
1219
 
    
1220
 
    - Existing bzrdirs take precedence over creating new items.  They are
1221
 
      created as '%s.diverted' % name.
1222
 
    - Otherwise, if the content on disk matches the content we are building,
1223
 
      it is silently replaced.
1224
 
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1225
 
    """
1226
 
    wt.lock_tree_write()
1227
 
    try:
1228
 
        tree.lock_read()
1229
 
        try:
1230
 
            return _build_tree(tree, wt)
1231
 
        finally:
1232
 
            tree.unlock()
1233
 
    finally:
1234
 
        wt.unlock()
1235
 
 
1236
 
def _build_tree(tree, wt):
1237
 
    """See build_tree."""
1238
 
    if len(wt.inventory) > 1:  # more than just a root
1239
 
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
 
927
    """Create working tree for a branch, using a Transaction."""
1240
928
    file_trans_id = {}
1241
929
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1242
930
    pp = ProgressPhase("Build phase", 2, top_pb)
1243
 
    if tree.inventory.root is not None:
1244
 
        # This is kind of a hack: we should be altering the root
1245
 
        # as part of the regular tree shape diff logic.
1246
 
        # The conditional test here is to avoid doing an
1247
 
        # expensive operation (flush) every time the root id
1248
 
        # is set within the tree, nor setting the root and thus
1249
 
        # marking the tree as dirty, because we use two different
1250
 
        # idioms here: tree interfaces and inventory interfaces.
1251
 
        if wt.path2id('') != tree.inventory.root.file_id:
1252
 
            wt.set_root_id(tree.inventory.root.file_id)
1253
 
            wt.flush()
1254
931
    tt = TreeTransform(wt)
1255
 
    divert = set()
1256
932
    try:
1257
933
        pp.next_phase()
1258
 
        file_trans_id[wt.get_root_id()] = \
1259
 
            tt.trans_id_tree_file_id(wt.get_root_id())
 
934
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
 
935
        file_ids = topology_sorted_ids(tree)
1260
936
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
1261
937
        try:
1262
 
            for num, (tree_path, entry) in \
1263
 
                enumerate(tree.inventory.iter_entries_by_dir()):
1264
 
                pb.update("Building tree", num, len(tree.inventory))
 
938
            for num, file_id in enumerate(file_ids):
 
939
                pb.update("Building tree", num, len(file_ids))
 
940
                entry = tree.inventory[file_id]
1265
941
                if entry.parent_id is None:
1266
942
                    continue
1267
 
                reparent = False
1268
 
                file_id = entry.file_id
1269
 
                target_path = wt.abspath(tree_path)
1270
 
                try:
1271
 
                    kind = file_kind(target_path)
1272
 
                except NoSuchFile:
1273
 
                    pass
1274
 
                else:
1275
 
                    if kind == "directory":
1276
 
                        try:
1277
 
                            bzrdir.BzrDir.open(target_path)
1278
 
                        except errors.NotBranchError:
1279
 
                            pass
1280
 
                        else:
1281
 
                            divert.add(file_id)
1282
 
                    if (file_id not in divert and
1283
 
                        _content_match(tree, entry, file_id, kind,
1284
 
                        target_path)):
1285
 
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
1286
 
                        if kind == 'directory':
1287
 
                            reparent = True
1288
943
                if entry.parent_id not in file_trans_id:
1289
 
                    raise AssertionError(
1290
 
                        'entry %s parent id %r is not in file_trans_id %r'
1291
 
                        % (entry, entry.parent_id, file_trans_id))
 
944
                    raise repr(entry.parent_id)
1292
945
                parent_id = file_trans_id[entry.parent_id]
1293
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
946
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
1294
947
                                                      tree)
1295
 
                if reparent:
1296
 
                    new_trans_id = file_trans_id[file_id]
1297
 
                    old_parent = tt.trans_id_tree_path(tree_path)
1298
 
                    _reparent_children(tt, old_parent, new_trans_id)
1299
948
        finally:
1300
949
            pb.finished()
1301
950
        pp.next_phase()
1302
 
        divert_trans = set(file_trans_id[f] for f in divert)
1303
 
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1304
 
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1305
 
        conflicts = cook_conflicts(raw_conflicts, tt)
1306
 
        for conflict in conflicts:
1307
 
            warning(conflict)
1308
 
        try:
1309
 
            wt.add_conflicts(conflicts)
1310
 
        except errors.UnsupportedOperation:
1311
 
            pass
1312
 
        result = tt.apply()
 
951
        tt.apply()
1313
952
    finally:
1314
953
        tt.finalize()
1315
954
        top_pb.finished()
1316
 
    return result
1317
 
 
1318
 
 
1319
 
def _reparent_children(tt, old_parent, new_parent):
1320
 
    for child in tt.iter_tree_children(old_parent):
1321
 
        tt.adjust_path(tt.final_name(child), new_parent, child)
1322
 
 
1323
 
 
1324
 
def _content_match(tree, entry, file_id, kind, target_path):
1325
 
    if entry.kind != kind:
1326
 
        return False
1327
 
    if entry.kind == "directory":
1328
 
        return True
1329
 
    if entry.kind == "file":
1330
 
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1331
 
            return True
1332
 
    elif entry.kind == "symlink":
1333
 
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
1334
 
            return True
1335
 
    return False
1336
 
 
1337
 
 
1338
 
def resolve_checkout(tt, conflicts, divert):
1339
 
    new_conflicts = set()
1340
 
    for c_type, conflict in ((c[0], c) for c in conflicts):
1341
 
        # Anything but a 'duplicate' would indicate programmer error
1342
 
        assert c_type == 'duplicate', c_type
1343
 
        # Now figure out which is new and which is old
1344
 
        if tt.new_contents(conflict[1]):
1345
 
            new_file = conflict[1]
1346
 
            old_file = conflict[2]
1347
 
        else:
1348
 
            new_file = conflict[2]
1349
 
            old_file = conflict[1]
1350
 
 
1351
 
        # We should only get here if the conflict wasn't completely
1352
 
        # resolved
1353
 
        final_parent = tt.final_parent(old_file)
1354
 
        if new_file in divert:
1355
 
            new_name = tt.final_name(old_file)+'.diverted'
1356
 
            tt.adjust_path(new_name, final_parent, new_file)
1357
 
            new_conflicts.add((c_type, 'Diverted to',
1358
 
                               new_file, old_file))
1359
 
        else:
1360
 
            new_name = tt.final_name(old_file)+'.moved'
1361
 
            tt.adjust_path(new_name, final_parent, old_file)
1362
 
            new_conflicts.add((c_type, 'Moved existing file to',
1363
 
                               old_file, new_file))
1364
 
    return new_conflicts
1365
 
 
1366
955
 
1367
956
def new_by_entry(tt, entry, parent_id, tree):
1368
957
    """Create a new file according to its inventory entry"""
1373
962
        executable = tree.is_executable(entry.file_id)
1374
963
        return tt.new_file(name, parent_id, contents, entry.file_id, 
1375
964
                           executable)
1376
 
    elif kind in ('directory', 'tree-reference'):
1377
 
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
1378
 
        if kind == 'tree-reference':
1379
 
            tt.set_tree_reference(entry.reference_revision, trans_id)
1380
 
        return trans_id 
 
965
    elif kind == 'directory':
 
966
        return tt.new_directory(name, parent_id, entry.file_id)
1381
967
    elif kind == 'symlink':
1382
968
        target = tree.get_symlink_target(entry.file_id)
1383
969
        return tt.new_symlink(name, parent_id, target, entry.file_id)
1384
 
    else:
1385
 
        raise errors.BadFileKindError(name, kind)
1386
970
 
1387
971
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1388
972
    """Create new file contents according to an inventory entry."""
1389
973
    if entry.kind == "file":
1390
 
        if lines is None:
 
974
        if lines == None:
1391
975
            lines = tree.get_file(entry.file_id).readlines()
1392
976
        tt.create_file(lines, trans_id, mode_id=mode_id)
1393
977
    elif entry.kind == "symlink":
1401
985
        tt.set_executability(entry.executable, trans_id)
1402
986
 
1403
987
 
1404
 
@deprecated_function(zero_fifteen)
1405
988
def find_interesting(working_tree, target_tree, filenames):
1406
 
    """Find the ids corresponding to specified filenames.
1407
 
    
1408
 
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1409
 
    """
1410
 
    working_tree.lock_read()
1411
 
    try:
1412
 
        target_tree.lock_read()
1413
 
        try:
1414
 
            return working_tree.paths2ids(filenames, [target_tree])
1415
 
        finally:
1416
 
            target_tree.unlock()
1417
 
    finally:
1418
 
        working_tree.unlock()
 
989
    """Find the ids corresponding to specified filenames."""
 
990
    if not filenames:
 
991
        interesting_ids = None
 
992
    else:
 
993
        interesting_ids = set()
 
994
        for tree_path in filenames:
 
995
            not_found = True
 
996
            for tree in (working_tree, target_tree):
 
997
                file_id = tree.inventory.path2id(tree_path)
 
998
                if file_id is not None:
 
999
                    interesting_ids.add(file_id)
 
1000
                    not_found = False
 
1001
            if not_found:
 
1002
                raise NotVersionedError(path=tree_path)
 
1003
    return interesting_ids
1419
1004
 
1420
1005
 
1421
1006
def change_entry(tt, file_id, working_tree, target_tree, 
1459
1044
 
1460
1045
 
1461
1046
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1462
 
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1463
 
 
1464
 
 
1465
 
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1466
1047
    """Produce a backup-style name that appears to be available"""
1467
1048
    def name_gen():
1468
1049
        counter = 1
1469
1050
        while True:
1470
 
            yield "%s.~%d~" % (name, counter)
 
1051
            yield "%s.~%d~" % (entry.name, counter)
1471
1052
            counter += 1
1472
 
    for new_name in name_gen():
1473
 
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1474
 
            return new_name
1475
 
 
 
1053
    for name in name_gen():
 
1054
        if not tt.has_named_child(by_parent, parent_trans_id, name):
 
1055
            return name
1476
1056
 
1477
1057
def _entry_changes(file_id, entry, working_tree):
1478
1058
    """Determine in which ways the inventory entry has changed.
1486
1066
    try:
1487
1067
        working_kind = working_tree.kind(file_id)
1488
1068
        has_contents = True
1489
 
    except NoSuchFile:
 
1069
    except OSError, e:
 
1070
        if e.errno != errno.ENOENT:
 
1071
            raise
1490
1072
        has_contents = False
1491
1073
        contents_mod = True
1492
1074
        meta_mod = False
1493
1075
    if has_contents is True:
1494
 
        if entry.kind != working_kind:
 
1076
        real_e_kind = entry.kind
 
1077
        if real_e_kind == 'root_directory':
 
1078
            real_e_kind = 'directory'
 
1079
        if real_e_kind != working_kind:
1495
1080
            contents_mod, meta_mod = True, False
1496
1081
        else:
1497
1082
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
1501
1086
    return has_contents, contents_mod, meta_mod
1502
1087
 
1503
1088
 
1504
 
def revert(working_tree, target_tree, filenames, backups=False,
1505
 
           pb=DummyProgress(), change_reporter=None):
 
1089
def revert(working_tree, target_tree, filenames, backups=False, 
 
1090
           pb=DummyProgress()):
1506
1091
    """Revert a working tree's contents to those of a target tree."""
1507
 
    target_tree.lock_read()
 
1092
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
 
1093
    def interesting(file_id):
 
1094
        return interesting_ids is None or file_id in interesting_ids
 
1095
 
1508
1096
    tt = TreeTransform(working_tree, pb)
1509
1097
    try:
1510
 
        pp = ProgressPhase("Revert phase", 3, pb)
1511
 
        pp.next_phase()
1512
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1513
 
        try:
1514
 
            merge_modified = _alter_files(working_tree, target_tree, tt,
1515
 
                                          child_pb, filenames, backups)
 
1098
        merge_modified = working_tree.merge_modified()
 
1099
        trans_id = {}
 
1100
        def trans_id_file_id(file_id):
 
1101
            try:
 
1102
                return trans_id[file_id]
 
1103
            except KeyError:
 
1104
                return tt.trans_id_tree_file_id(file_id)
 
1105
 
 
1106
        pp = ProgressPhase("Revert phase", 4, pb)
 
1107
        pp.next_phase()
 
1108
        sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
 
1109
                              interesting(i)]
 
1110
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1111
        try:
 
1112
            by_parent = tt.by_parent()
 
1113
            for id_num, file_id in enumerate(sorted_interesting):
 
1114
                child_pb.update("Reverting file", id_num+1, 
 
1115
                                len(sorted_interesting))
 
1116
                if file_id not in working_tree.inventory:
 
1117
                    entry = target_tree.inventory[file_id]
 
1118
                    parent_id = trans_id_file_id(entry.parent_id)
 
1119
                    e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
 
1120
                    trans_id[file_id] = e_trans_id
 
1121
                else:
 
1122
                    backup_this = backups
 
1123
                    if file_id in merge_modified:
 
1124
                        backup_this = False
 
1125
                        del merge_modified[file_id]
 
1126
                    change_entry(tt, file_id, working_tree, target_tree, 
 
1127
                                 trans_id_file_id, backup_this, trans_id,
 
1128
                                 by_parent)
 
1129
        finally:
 
1130
            child_pb.finished()
 
1131
        pp.next_phase()
 
1132
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
 
1133
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1134
        try:
 
1135
            for id_num, file_id in enumerate(wt_interesting):
 
1136
                child_pb.update("New file check", id_num+1, 
 
1137
                                len(sorted_interesting))
 
1138
                if file_id not in target_tree:
 
1139
                    trans_id = tt.trans_id_tree_file_id(file_id)
 
1140
                    tt.unversion_file(trans_id)
 
1141
                    if file_id in merge_modified:
 
1142
                        tt.delete_contents(trans_id)
 
1143
                        del merge_modified[file_id]
1516
1144
        finally:
1517
1145
            child_pb.finished()
1518
1146
        pp.next_phase()
1522
1150
        finally:
1523
1151
            child_pb.finished()
1524
1152
        conflicts = cook_conflicts(raw_conflicts, tt)
1525
 
        if change_reporter:
1526
 
            change_reporter = delta._ChangeReporter(
1527
 
                unversioned_filter=working_tree.is_ignored)
1528
 
            delta.report_changes(tt._iter_changes(), change_reporter)
1529
1153
        for conflict in conflicts:
1530
1154
            warning(conflict)
1531
1155
        pp.next_phase()
1532
1156
        tt.apply()
1533
 
        working_tree.set_merge_modified(merge_modified)
 
1157
        working_tree.set_merge_modified({})
1534
1158
    finally:
1535
 
        target_tree.unlock()
1536
1159
        tt.finalize()
1537
1160
        pb.clear()
1538
1161
    return conflicts
1539
1162
 
1540
1163
 
1541
 
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1542
 
                 backups):
1543
 
    merge_modified = working_tree.merge_modified()
1544
 
    change_list = target_tree._iter_changes(working_tree,
1545
 
        specific_files=specific_files, pb=pb)
1546
 
    if target_tree.inventory.root is None:
1547
 
        skip_root = True
1548
 
    else:
1549
 
        skip_root = False
1550
 
    basis_tree = None
1551
 
    try:
1552
 
        for id_num, (file_id, path, changed_content, versioned, parent, name,
1553
 
                kind, executable) in enumerate(change_list):
1554
 
            if skip_root and file_id[0] is not None and parent[0] is None:
1555
 
                continue
1556
 
            trans_id = tt.trans_id_file_id(file_id)
1557
 
            mode_id = None
1558
 
            if changed_content:
1559
 
                keep_content = False
1560
 
                if kind[0] == 'file' and (backups or kind[1] is None):
1561
 
                    wt_sha1 = working_tree.get_file_sha1(file_id)
1562
 
                    if merge_modified.get(file_id) != wt_sha1:
1563
 
                        # acquire the basis tree lazily to prevent the
1564
 
                        # expense of accessing it when it's not needed ?
1565
 
                        # (Guessing, RBC, 200702)
1566
 
                        if basis_tree is None:
1567
 
                            basis_tree = working_tree.basis_tree()
1568
 
                            basis_tree.lock_read()
1569
 
                        if file_id in basis_tree:
1570
 
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
1571
 
                                keep_content = True
1572
 
                        elif kind[1] is None and not versioned[1]:
1573
 
                            keep_content = True
1574
 
                if kind[0] is not None:
1575
 
                    if not keep_content:
1576
 
                        tt.delete_contents(trans_id)
1577
 
                    elif kind[1] is not None:
1578
 
                        parent_trans_id = tt.trans_id_file_id(parent[0])
1579
 
                        by_parent = tt.by_parent()
1580
 
                        backup_name = _get_backup_name(name[0], by_parent,
1581
 
                                                       parent_trans_id, tt)
1582
 
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
1583
 
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
1584
 
                        if versioned == (True, True):
1585
 
                            tt.unversion_file(trans_id)
1586
 
                            tt.version_file(file_id, new_trans_id)
1587
 
                        # New contents should have the same unix perms as old
1588
 
                        # contents
1589
 
                        mode_id = trans_id
1590
 
                        trans_id = new_trans_id
1591
 
                if kind[1] == 'directory':
1592
 
                    tt.create_directory(trans_id)
1593
 
                elif kind[1] == 'symlink':
1594
 
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
1595
 
                                      trans_id)
1596
 
                elif kind[1] == 'file':
1597
 
                    tt.create_file(target_tree.get_file_lines(file_id),
1598
 
                                   trans_id, mode_id)
1599
 
                    if basis_tree is None:
1600
 
                        basis_tree = working_tree.basis_tree()
1601
 
                        basis_tree.lock_read()
1602
 
                    new_sha1 = target_tree.get_file_sha1(file_id)
1603
 
                    if (file_id in basis_tree and new_sha1 ==
1604
 
                        basis_tree.get_file_sha1(file_id)):
1605
 
                        if file_id in merge_modified:
1606
 
                            del merge_modified[file_id]
1607
 
                    else:
1608
 
                        merge_modified[file_id] = new_sha1
1609
 
 
1610
 
                    # preserve the execute bit when backing up
1611
 
                    if keep_content and executable[0] == executable[1]:
1612
 
                        tt.set_executability(executable[1], trans_id)
1613
 
                else:
1614
 
                    assert kind[1] is None
1615
 
            if versioned == (False, True):
1616
 
                tt.version_file(file_id, trans_id)
1617
 
            if versioned == (True, False):
1618
 
                tt.unversion_file(trans_id)
1619
 
            if (name[1] is not None and 
1620
 
                (name[0] != name[1] or parent[0] != parent[1])):
1621
 
                tt.adjust_path(
1622
 
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
1623
 
            if executable[0] != executable[1] and kind[1] == "file":
1624
 
                tt.set_executability(executable[1], trans_id)
1625
 
    finally:
1626
 
        if basis_tree is not None:
1627
 
            basis_tree.unlock()
1628
 
    return merge_modified
1629
 
 
1630
 
 
1631
 
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
 
1164
def resolve_conflicts(tt, pb=DummyProgress()):
1632
1165
    """Make many conflict-resolution attempts, but die if they fail"""
1633
 
    if pass_func is None:
1634
 
        pass_func = conflict_pass
1635
1166
    new_conflicts = set()
1636
1167
    try:
1637
1168
        for n in range(10):
1639
1170
            conflicts = tt.find_conflicts()
1640
1171
            if len(conflicts) == 0:
1641
1172
                return new_conflicts
1642
 
            new_conflicts.update(pass_func(tt, conflicts))
 
1173
            new_conflicts.update(conflict_pass(tt, conflicts))
1643
1174
        raise MalformedTransform(conflicts=conflicts)
1644
1175
    finally:
1645
1176
        pb.clear()
1678
1209
            trans_id = conflict[1]
1679
1210
            try:
1680
1211
                tt.cancel_deletion(trans_id)
1681
 
                new_conflicts.add(('deleting parent', 'Not deleting', 
1682
 
                                   trans_id))
 
1212
                new_conflicts.add((c_type, 'Not deleting', trans_id))
1683
1213
            except KeyError:
1684
1214
                tt.create_directory(trans_id)
1685
 
                new_conflicts.add((c_type, 'Created directory', trans_id))
 
1215
                new_conflicts.add((c_type, 'Created directory.', trans_id))
1686
1216
        elif c_type == 'unversioned parent':
1687
1217
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1688
1218
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))