~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Martin Pool
  • Date: 2006-05-17 07:09:13 UTC
  • mfrom: (1668.1.15 bzr-0.8.mbp)
  • mto: This revision was merged to the branch mainline in revision 1710.
  • Revision ID: mbp@sourcefrog.net-20060517070913-723e387ac91d55c0
merge 0.8 fix branch, including register-branch plugin

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
41
 
import bzrlib.urlutils as urlutils
 
29
import bzrlib.ui 
42
30
 
43
31
 
44
32
ROOT_PARENT = "root-parent"
51
39
 
52
40
 
53
41
class _TransformResults(object):
54
 
    def __init__(self, modified_paths, rename_count):
 
42
    def __init__(self, modified_paths):
55
43
        object.__init__(self)
56
44
        self.modified_paths = modified_paths
57
 
        self.rename_count = rename_count
58
45
 
59
46
 
60
47
class TreeTransform(object):
61
48
    """Represent a tree transformation.
62
49
    
63
50
    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.
 
51
    in any order.  
69
52
    
70
53
    It is easy to produce malformed transforms, but they are generally
71
54
    harmless.  Attempting to apply a malformed transform will cause an
87
70
     * set_executability
88
71
    """
89
72
    def __init__(self, tree, pb=DummyProgress()):
90
 
        """Note: a tree_write lock is taken on the tree.
 
73
        """Note: a write lock is taken on the tree.
91
74
        
92
 
        Use TreeTransform.finalize() to release the lock (can be omitted if
93
 
        TreeTransform.apply() called).
 
75
        Use TreeTransform.finalize() to release the lock
94
76
        """
95
77
        object.__init__(self)
96
78
        self._tree = tree
97
 
        self._tree.lock_tree_write()
 
79
        self._tree.lock_write()
98
80
        try:
99
81
            control_files = self._tree._control_files
100
 
            self._limbodir = urlutils.local_path_from_url(
101
 
                control_files.controlfilename('limbo'))
 
82
            self._limbodir = control_files.controlfilename('limbo')
102
83
            try:
103
84
                os.mkdir(self._limbodir)
104
85
            except OSError, e:
112
93
        self._new_name = {}
113
94
        self._new_parent = {}
114
95
        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
96
        self._removed_contents = set()
125
97
        self._new_executability = {}
126
 
        self._new_reference_revision = {}
127
98
        self._new_id = {}
128
99
        self._non_present_ids = {}
129
100
        self._r_new_id = {}
130
101
        self._removed_id = set()
131
102
        self._tree_path_ids = {}
132
103
        self._tree_id_paths = {}
133
 
        # Cache of realpath results, to speed up canonical_path
134
 
        self._realpaths = {}
135
 
        # Cache of relpath results, to speed up canonical_path
136
 
        self._relpaths = {}
137
104
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
138
105
        self.__done = False
139
106
        self._pb = pb
140
 
        self.rename_count = 0
141
107
 
142
108
    def __get_root(self):
143
109
        return self._new_root
145
111
    root = property(__get_root)
146
112
 
147
113
    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
 
        """
 
114
        """Release the working tree lock, if held, clean up limbo dir."""
153
115
        if self._tree is None:
154
116
            return
155
117
        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:
 
118
            for trans_id, kind in self._new_contents.iteritems():
 
119
                path = self._limbo_name(trans_id)
160
120
                if kind == "directory":
161
121
                    os.rmdir(path)
162
122
                else:
187
147
        """Change the path that is assigned to a transaction id."""
188
148
        if trans_id == self._new_root:
189
149
            raise CantMoveRoot
190
 
        previous_parent = self._new_parent.get(trans_id)
191
 
        previous_name = self._new_name.get(trans_id)
192
150
        self._new_name[trans_id] = name
193
151
        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
152
 
218
153
    def adjust_root_path(self, name, parent):
219
154
        """Emulate moving the root by moving all children, instead.
276
211
    def canonical_path(self, path):
277
212
        """Get the canonical tree-relative path"""
278
213
        # don't follow final symlinks
279
 
        abs = self._tree.abspath(path)
280
 
        if abs in self._relpaths:
281
 
            return self._relpaths[abs]
282
 
        dirname, basename = os.path.split(abs)
283
 
        if dirname not in self._realpaths:
284
 
            self._realpaths[dirname] = os.path.realpath(dirname)
285
 
        dirname = self._realpaths[dirname]
286
 
        abs = pathjoin(dirname, basename)
287
 
        if dirname in self._relpaths:
288
 
            relpath = pathjoin(self._relpaths[dirname], basename)
289
 
            relpath = relpath.rstrip('/\\')
290
 
        else:
291
 
            relpath = self._tree.relpath(abs)
292
 
        self._relpaths[abs] = relpath
293
 
        return relpath
 
214
        dirname, basename = os.path.split(self._tree.abspath(path))
 
215
        dirname = os.path.realpath(dirname)
 
216
        return self._tree.relpath(pathjoin(dirname, basename))
294
217
 
295
218
    def trans_id_tree_path(self, path):
296
219
        """Determine (and maybe set) the transaction ID for a tree path."""
318
241
        New file takes the permissions of any existing file with that id,
319
242
        unless mode_id is specified.
320
243
        """
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()
 
244
        f = file(self._limbo_name(trans_id), 'wb')
 
245
        unique_add(self._new_contents, trans_id, 'file')
 
246
        for segment in contents:
 
247
            f.write(segment)
 
248
        f.close()
336
249
        self._set_mode(trans_id, mode_id, S_ISREG)
337
250
 
338
251
    def _set_mode(self, trans_id, mode_id, typefunc):
348
261
        except KeyError:
349
262
            return
350
263
        try:
351
 
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
264
            mode = os.stat(old_path).st_mode
352
265
        except OSError, e:
353
266
            if e.errno == errno.ENOENT:
354
267
                return
377
290
    def cancel_creation(self, trans_id):
378
291
        """Cancel the creation of new file contents."""
379
292
        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
293
        delete_any(self._limbo_name(trans_id))
388
294
 
389
295
    def delete_contents(self, trans_id):
413
319
        else:
414
320
            unique_add(self._new_executability, trans_id, executability)
415
321
 
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
322
    def version_file(self, file_id, trans_id):
421
323
        """Schedule a file to become versioned."""
422
324
        assert file_id is not None
524
426
        try:
525
427
            return self._new_name[trans_id]
526
428
        except KeyError:
527
 
            try:
528
 
                return os.path.basename(self._tree_id_paths[trans_id])
529
 
            except KeyError:
530
 
                raise NoFinalPath(trans_id, self)
 
429
            return os.path.basename(self._tree_id_paths[trans_id])
531
430
 
532
431
    def by_parent(self):
533
432
        """Return a map of parent: children for known parents.
546
445
 
547
446
    def path_changed(self, trans_id):
548
447
        """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)
 
448
        return trans_id in self._new_name or trans_id in self._new_parent
553
449
 
554
450
    def find_conflicts(self):
555
451
        """Find any violations of inventory or filesystem invariants"""
581
477
                        self.tree_kind(t) == 'directory'])
582
478
        for trans_id in self._removed_id:
583
479
            file_id = self.tree_file_id(trans_id)
584
 
            if self._tree.inventory[file_id].kind == 'directory':
 
480
            if self._tree.inventory[file_id].kind in ('directory', 
 
481
                                                      'root_directory'):
585
482
                parents.append(trans_id)
586
483
 
587
484
        for parent_id in parents:
624
521
        if child_id is None:
625
522
            return lexists(self._tree.abspath(childpath))
626
523
        else:
627
 
            if self.final_parent(child_id) != parent_id:
 
524
            if tt.final_parent(child_id) != parent_id:
628
525
                return False
629
 
            if child_id in self._removed_contents:
 
526
            if child_id in tt._removed_contents:
630
527
                # XXX What about dangling file-ids?
631
528
                return False
632
529
            else:
640
537
            parent_id = trans_id
641
538
            while parent_id is not ROOT_PARENT:
642
539
                seen.add(parent_id)
643
 
                try:
644
 
                    parent_id = self.final_parent(parent_id)
645
 
                except KeyError:
646
 
                    break
 
540
                parent_id = self.final_parent(parent_id)
647
541
                if parent_id == trans_id:
648
542
                    conflicts.append(('parent loop', trans_id))
649
543
                if parent_id in seen:
723
617
            last_name = None
724
618
            last_trans_id = None
725
619
            for name, trans_id in name_ids:
 
620
                if name == last_name:
 
621
                    conflicts.append(('duplicate', last_trans_id, trans_id,
 
622
                    name))
726
623
                try:
727
624
                    kind = self.final_kind(trans_id)
728
625
                except NoSuchFile:
729
626
                    kind = None
730
627
                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
 
628
                if kind is not None or file_id is not None:
 
629
                    last_name = name
 
630
                    last_trans_id = trans_id
738
631
        return conflicts
739
632
 
740
633
    def _duplicate_ids(self):
788
681
        
789
682
        If filesystem or inventory conflicts are present, MalformedTransform
790
683
        will be thrown.
791
 
 
792
 
        If apply succeeds, finalize is not necessary.
793
684
        """
794
685
        conflicts = self.find_conflicts()
795
686
        if len(conflicts) != 0:
796
687
            raise MalformedTransform(conflicts=conflicts)
 
688
        limbo_inv = {}
797
689
        inv = self._tree.inventory
798
 
        inventory_delta = []
799
690
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
800
691
        try:
801
692
            child_pb.update('Apply phase', 0, 2)
802
 
            self._apply_removals(inv, inventory_delta)
 
693
            self._apply_removals(inv, limbo_inv)
803
694
            child_pb.update('Apply phase', 1, 2)
804
 
            modified_paths = self._apply_insertions(inv, inventory_delta)
 
695
            modified_paths = self._apply_insertions(inv, limbo_inv)
805
696
        finally:
806
697
            child_pb.finished()
807
 
        self._tree.apply_inventory_delta(inventory_delta)
 
698
        self._tree._write_inventory(inv)
808
699
        self.__done = True
809
700
        self.finalize()
810
 
        return _TransformResults(modified_paths, self.rename_count)
 
701
        return _TransformResults(modified_paths)
811
702
 
812
703
    def _limbo_name(self, trans_id):
813
704
        """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
 
705
        return pathjoin(self._limbodir, trans_id)
844
706
 
845
 
    def _apply_removals(self, inv, inventory_delta):
 
707
    def _apply_removals(self, inv, limbo_inv):
846
708
        """Perform tree operations that remove directory/inventory names.
847
709
        
848
710
        That is, delete files that are to be deleted, and put any files that
866
728
                    except OSError, e:
867
729
                        if e.errno != errno.ENOENT:
868
730
                            raise
869
 
                    else:
870
 
                        self.rename_count += 1
871
731
                if trans_id in self._removed_id:
872
732
                    if trans_id == self._new_root:
873
733
                        file_id = self._tree.inventory.root.file_id
874
734
                    else:
875
735
                        file_id = self.tree_file_id(trans_id)
876
 
                    assert file_id is not None
877
 
                    inventory_delta.append((path, None, file_id, None))
 
736
                    del inv[file_id]
 
737
                elif trans_id in self._new_name or trans_id in self._new_parent:
 
738
                    file_id = self.tree_file_id(trans_id)
 
739
                    if file_id is not None:
 
740
                        limbo_inv[trans_id] = inv[file_id]
 
741
                        del inv[file_id]
878
742
        finally:
879
743
            child_pb.finished()
880
744
 
881
 
    def _apply_insertions(self, inv, inventory_delta):
 
745
    def _apply_insertions(self, inv, limbo_inv):
882
746
        """Perform tree operations that insert directory/inventory names.
883
747
        
884
748
        That is, create any files that need to be created, and restore from
890
754
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
891
755
        try:
892
756
            for num, (path, trans_id) in enumerate(new_paths):
893
 
                new_entry = None
894
757
                child_pb.update('adding file', num, len(new_paths))
895
758
                try:
896
759
                    kind = self._new_contents[trans_id]
899
762
                if trans_id in self._new_contents or \
900
763
                    self.path_changed(trans_id):
901
764
                    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
 
765
                    try:
 
766
                        os.rename(self._limbo_name(trans_id), full_path)
 
767
                    except OSError, e:
 
768
                        # We may be renaming a dangling inventory id
 
769
                        if e.errno != errno.ENOENT:
 
770
                            raise
911
771
                    if trans_id in self._new_contents:
912
772
                        modified_paths.append(full_path)
913
773
                        del self._new_contents[trans_id]
915
775
                if trans_id in self._new_id:
916
776
                    if kind is None:
917
777
                        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
 
 
 
778
                    inv.add_path(path, kind, self._new_id[trans_id])
 
779
                elif trans_id in self._new_name or trans_id in\
 
780
                    self._new_parent:
 
781
                    entry = limbo_inv.get(trans_id)
 
782
                    if entry is not None:
 
783
                        entry.name = self.final_name(trans_id)
 
784
                        parent_path = os.path.dirname(path)
 
785
                        entry.parent_id = \
 
786
                            self._tree.inventory.path2id(parent_path)
 
787
                        inv.add(entry)
 
788
 
 
789
                # requires files and inventory entries to be in place
946
790
                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))
 
791
                    self._set_executability(path, inv, trans_id)
956
792
        finally:
957
793
            child_pb.finished()
958
794
        return modified_paths
959
795
 
960
 
    def _set_executability(self, path, entry, trans_id):
 
796
    def _set_executability(self, path, inv, trans_id):
961
797
        """Set the executability of versioned files """
 
798
        file_id = inv.path2id(path)
962
799
        new_executability = self._new_executability[trans_id]
963
 
        entry.executable = new_executability
 
800
        inv[file_id].executable = new_executability
964
801
        if supports_executable():
965
802
            abspath = self._tree.abspath(path)
966
803
            current_mode = os.stat(abspath).st_mode
992
829
        parent_id is the transaction id of the parent directory of the file.
993
830
        contents is an iterator of bytestrings, which will be used to produce
994
831
        the file.
995
 
        :param file_id: The inventory ID of the file, if it is to be versioned.
996
 
        :param executable: Only valid when a file_id has been supplied.
 
832
        file_id is the inventory ID of the file, if it is to be versioned.
997
833
        """
998
834
        trans_id = self._new_entry(name, parent_id, file_id)
999
 
        # TODO: rather than scheduling a set_executable call,
1000
 
        # have create_file create the file with the right mode.
1001
835
        self.create_file(contents, trans_id)
1002
836
        if executable is not None:
1003
837
            self.set_executability(executable, trans_id)
1027
861
        self.create_symlink(target, trans_id)
1028
862
        return trans_id
1029
863
 
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
864
def joinpath(parent, child):
1171
865
    """Join tree-relative paths, handling the tree root specially"""
1172
866
    if parent is None or parent == "":
1208
902
    file_ids.sort(key=tree.id2path)
1209
903
    return file_ids
1210
904
 
1211
 
 
1212
905
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)
 
906
    """Create working tree for a branch, using a Transaction."""
1240
907
    file_trans_id = {}
1241
908
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1242
909
    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
910
    tt = TreeTransform(wt)
1255
 
    divert = set()
1256
911
    try:
1257
912
        pp.next_phase()
1258
 
        file_trans_id[wt.get_root_id()] = \
1259
 
            tt.trans_id_tree_file_id(wt.get_root_id())
 
913
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
 
914
        file_ids = topology_sorted_ids(tree)
1260
915
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
1261
916
        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))
 
917
            for num, file_id in enumerate(file_ids):
 
918
                pb.update("Building tree", num, len(file_ids))
 
919
                entry = tree.inventory[file_id]
1265
920
                if entry.parent_id is None:
1266
921
                    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
922
                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))
 
923
                    raise repr(entry.parent_id)
1292
924
                parent_id = file_trans_id[entry.parent_id]
1293
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
925
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
1294
926
                                                      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
927
        finally:
1300
928
            pb.finished()
1301
929
        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()
 
930
        tt.apply()
1313
931
    finally:
1314
932
        tt.finalize()
1315
933
        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
934
 
1367
935
def new_by_entry(tt, entry, parent_id, tree):
1368
936
    """Create a new file according to its inventory entry"""
1373
941
        executable = tree.is_executable(entry.file_id)
1374
942
        return tt.new_file(name, parent_id, contents, entry.file_id, 
1375
943
                           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 
 
944
    elif kind == 'directory':
 
945
        return tt.new_directory(name, parent_id, entry.file_id)
1381
946
    elif kind == 'symlink':
1382
947
        target = tree.get_symlink_target(entry.file_id)
1383
948
        return tt.new_symlink(name, parent_id, target, entry.file_id)
1384
 
    else:
1385
 
        raise errors.BadFileKindError(name, kind)
1386
949
 
1387
950
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1388
951
    """Create new file contents according to an inventory entry."""
1389
952
    if entry.kind == "file":
1390
 
        if lines is None:
 
953
        if lines == None:
1391
954
            lines = tree.get_file(entry.file_id).readlines()
1392
955
        tt.create_file(lines, trans_id, mode_id=mode_id)
1393
956
    elif entry.kind == "symlink":
1401
964
        tt.set_executability(entry.executable, trans_id)
1402
965
 
1403
966
 
1404
 
@deprecated_function(zero_fifteen)
1405
967
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()
 
968
    """Find the ids corresponding to specified filenames."""
 
969
    if not filenames:
 
970
        interesting_ids = None
 
971
    else:
 
972
        interesting_ids = set()
 
973
        for tree_path in filenames:
 
974
            not_found = True
 
975
            for tree in (working_tree, target_tree):
 
976
                file_id = tree.inventory.path2id(tree_path)
 
977
                if file_id is not None:
 
978
                    interesting_ids.add(file_id)
 
979
                    not_found = False
 
980
            if not_found:
 
981
                raise NotVersionedError(path=tree_path)
 
982
    return interesting_ids
1419
983
 
1420
984
 
1421
985
def change_entry(tt, file_id, working_tree, target_tree, 
1459
1023
 
1460
1024
 
1461
1025
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
1026
    """Produce a backup-style name that appears to be available"""
1467
1027
    def name_gen():
1468
1028
        counter = 1
1469
1029
        while True:
1470
 
            yield "%s.~%d~" % (name, counter)
 
1030
            yield "%s.~%d~" % (entry.name, counter)
1471
1031
            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
 
 
 
1032
    for name in name_gen():
 
1033
        if not tt.has_named_child(by_parent, parent_trans_id, name):
 
1034
            return name
1476
1035
 
1477
1036
def _entry_changes(file_id, entry, working_tree):
1478
1037
    """Determine in which ways the inventory entry has changed.
1486
1045
    try:
1487
1046
        working_kind = working_tree.kind(file_id)
1488
1047
        has_contents = True
1489
 
    except NoSuchFile:
 
1048
    except OSError, e:
 
1049
        if e.errno != errno.ENOENT:
 
1050
            raise
1490
1051
        has_contents = False
1491
1052
        contents_mod = True
1492
1053
        meta_mod = False
1493
1054
    if has_contents is True:
1494
 
        if entry.kind != working_kind:
 
1055
        real_e_kind = entry.kind
 
1056
        if real_e_kind == 'root_directory':
 
1057
            real_e_kind = 'directory'
 
1058
        if real_e_kind != working_kind:
1495
1059
            contents_mod, meta_mod = True, False
1496
1060
        else:
1497
1061
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
1501
1065
    return has_contents, contents_mod, meta_mod
1502
1066
 
1503
1067
 
1504
 
def revert(working_tree, target_tree, filenames, backups=False,
1505
 
           pb=DummyProgress(), change_reporter=None):
 
1068
def revert(working_tree, target_tree, filenames, backups=False, 
 
1069
           pb=DummyProgress()):
1506
1070
    """Revert a working tree's contents to those of a target tree."""
1507
 
    target_tree.lock_read()
 
1071
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
 
1072
    def interesting(file_id):
 
1073
        return interesting_ids is None or file_id in interesting_ids
 
1074
 
1508
1075
    tt = TreeTransform(working_tree, pb)
1509
1076
    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)
 
1077
        merge_modified = working_tree.merge_modified()
 
1078
        trans_id = {}
 
1079
        def trans_id_file_id(file_id):
 
1080
            try:
 
1081
                return trans_id[file_id]
 
1082
            except KeyError:
 
1083
                return tt.trans_id_tree_file_id(file_id)
 
1084
 
 
1085
        pp = ProgressPhase("Revert phase", 4, pb)
 
1086
        pp.next_phase()
 
1087
        sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
 
1088
                              interesting(i)]
 
1089
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1090
        try:
 
1091
            by_parent = tt.by_parent()
 
1092
            for id_num, file_id in enumerate(sorted_interesting):
 
1093
                child_pb.update("Reverting file", id_num+1, 
 
1094
                                len(sorted_interesting))
 
1095
                if file_id not in working_tree.inventory:
 
1096
                    entry = target_tree.inventory[file_id]
 
1097
                    parent_id = trans_id_file_id(entry.parent_id)
 
1098
                    e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
 
1099
                    trans_id[file_id] = e_trans_id
 
1100
                else:
 
1101
                    backup_this = backups
 
1102
                    if file_id in merge_modified:
 
1103
                        backup_this = False
 
1104
                        del merge_modified[file_id]
 
1105
                    change_entry(tt, file_id, working_tree, target_tree, 
 
1106
                                 trans_id_file_id, backup_this, trans_id,
 
1107
                                 by_parent)
 
1108
        finally:
 
1109
            child_pb.finished()
 
1110
        pp.next_phase()
 
1111
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
 
1112
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1113
        try:
 
1114
            for id_num, file_id in enumerate(wt_interesting):
 
1115
                child_pb.update("New file check", id_num+1, 
 
1116
                                len(sorted_interesting))
 
1117
                if file_id not in target_tree:
 
1118
                    trans_id = tt.trans_id_tree_file_id(file_id)
 
1119
                    tt.unversion_file(trans_id)
 
1120
                    if file_id in merge_modified:
 
1121
                        tt.delete_contents(trans_id)
 
1122
                        del merge_modified[file_id]
1516
1123
        finally:
1517
1124
            child_pb.finished()
1518
1125
        pp.next_phase()
1522
1129
        finally:
1523
1130
            child_pb.finished()
1524
1131
        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
1132
        for conflict in conflicts:
1530
1133
            warning(conflict)
1531
1134
        pp.next_phase()
1532
1135
        tt.apply()
1533
 
        working_tree.set_merge_modified(merge_modified)
 
1136
        working_tree.set_merge_modified({})
1534
1137
    finally:
1535
 
        target_tree.unlock()
1536
1138
        tt.finalize()
1537
1139
        pb.clear()
1538
1140
    return conflicts
1539
1141
 
1540
1142
 
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):
 
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1632
1144
    """Make many conflict-resolution attempts, but die if they fail"""
1633
 
    if pass_func is None:
1634
 
        pass_func = conflict_pass
1635
1145
    new_conflicts = set()
1636
1146
    try:
1637
1147
        for n in range(10):
1639
1149
            conflicts = tt.find_conflicts()
1640
1150
            if len(conflicts) == 0:
1641
1151
                return new_conflicts
1642
 
            new_conflicts.update(pass_func(tt, conflicts))
 
1152
            new_conflicts.update(conflict_pass(tt, conflicts))
1643
1153
        raise MalformedTransform(conflicts=conflicts)
1644
1154
    finally:
1645
1155
        pb.clear()
1678
1188
            trans_id = conflict[1]
1679
1189
            try:
1680
1190
                tt.cancel_deletion(trans_id)
1681
 
                new_conflicts.add(('deleting parent', 'Not deleting', 
1682
 
                                   trans_id))
 
1191
                new_conflicts.add((c_type, 'Not deleting', trans_id))
1683
1192
            except KeyError:
1684
1193
                tt.create_directory(trans_id)
1685
 
                new_conflicts.add((c_type, 'Created directory', trans_id))
 
1194
                new_conflicts.add((c_type, 'Created directory.', trans_id))
1686
1195
        elif c_type == 'unversioned parent':
1687
1196
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1688
1197
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))