~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Michael Ellerman
  • Date: 2006-02-28 14:45:51 UTC
  • mto: (1558.1.18 Aaron's integration)
  • mto: This revision was merged to the branch mainline in revision 1586.
  • Revision ID: michael@ellerman.id.au-20060228144551-3d9941ecde4a0b0a
Update contrib/pwk for -p1 diffs from bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
                           ReusingTransform, NotVersionedError, CantMoveRoot,
23
23
                           ExistingLimbo, ImmortalLimbo)
24
24
from bzrlib.inventory import InventoryEntry
25
 
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
26
 
                            delete_any)
27
 
from bzrlib.progress import DummyProgress, ProgressPhase
 
25
from bzrlib.osutils import file_kind, supports_executable, pathjoin
 
26
from bzrlib.progress import DummyProgress
28
27
from bzrlib.trace import mutter, warning
29
 
from bzrlib import tree
30
 
import bzrlib.ui 
31
 
import bzrlib.urlutils as urlutils
32
28
 
33
29
 
34
30
ROOT_PARENT = "root-parent"
40
36
    map[key] = value
41
37
 
42
38
 
43
 
class _TransformResults(object):
44
 
    def __init__(self, modified_paths):
45
 
        object.__init__(self)
46
 
        self.modified_paths = modified_paths
47
 
 
48
 
 
49
39
class TreeTransform(object):
50
40
    """Represent a tree transformation.
51
41
    
81
71
        self._tree.lock_write()
82
72
        try:
83
73
            control_files = self._tree._control_files
84
 
            self._limbodir = urlutils.local_path_from_url(
85
 
                control_files.controlfilename('limbo'))
 
74
            self._limbodir = control_files.controlfilename('limbo')
86
75
            try:
87
76
                os.mkdir(self._limbodir)
88
77
            except OSError, e:
104
93
        self._removed_id = set()
105
94
        self._tree_path_ids = {}
106
95
        self._tree_id_paths = {}
107
 
        self._realpaths = {}
108
 
        # Cache of realpath results, to speed up canonical_path
109
 
        self._relpaths = {}
110
 
        # Cache of relpath results, to speed up canonical_path
111
96
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
112
97
        self.__done = False
113
98
        self._pb = pb
218
203
    def canonical_path(self, path):
219
204
        """Get the canonical tree-relative path"""
220
205
        # don't follow final symlinks
221
 
        abs = self._tree.abspath(path)
222
 
        if abs in self._relpaths:
223
 
            return self._relpaths[abs]
224
 
        dirname, basename = os.path.split(abs)
225
 
        if dirname not in self._realpaths:
226
 
            self._realpaths[dirname] = os.path.realpath(dirname)
227
 
        dirname = self._realpaths[dirname]
228
 
        abs = pathjoin(dirname, basename)
229
 
        if dirname in self._relpaths:
230
 
            relpath = pathjoin(self._relpaths[dirname], basename)
231
 
            relpath = relpath.rstrip('/\\')
232
 
        else:
233
 
            relpath = self._tree.relpath(abs)
234
 
        self._relpaths[abs] = relpath
235
 
        return relpath
 
206
        dirname, basename = os.path.split(self._tree.abspath(path))
 
207
        dirname = os.path.realpath(dirname)
 
208
        return self._tree.relpath(pathjoin(dirname, basename))
236
209
 
237
210
    def trans_id_tree_path(self, path):
238
211
        """Determine (and maybe set) the transaction ID for a tree path."""
260
233
        New file takes the permissions of any existing file with that id,
261
234
        unless mode_id is specified.
262
235
        """
263
 
        name = self._limbo_name(trans_id)
264
 
        f = open(name, 'wb')
265
 
        try:
266
 
            try:
267
 
                unique_add(self._new_contents, trans_id, 'file')
268
 
            except:
269
 
                # Clean up the file, it never got registered so
270
 
                # TreeTransform.finalize() won't clean it up.
271
 
                f.close()
272
 
                os.unlink(name)
273
 
                raise
274
 
 
275
 
            for segment in contents:
276
 
                f.write(segment)
277
 
        finally:
278
 
            f.close()
 
236
        f = file(self._limbo_name(trans_id), 'wb')
 
237
        unique_add(self._new_contents, trans_id, 'file')
 
238
        for segment in contents:
 
239
            f.write(segment)
 
240
        f.close()
279
241
        self._set_mode(trans_id, mode_id, S_ISREG)
280
242
 
281
243
    def _set_mode(self, trans_id, mode_id, typefunc):
317
279
        os.symlink(target, self._limbo_name(trans_id))
318
280
        unique_add(self._new_contents, trans_id, 'symlink')
319
281
 
 
282
    @staticmethod
 
283
    def delete_any(full_path):
 
284
        """Delete a file or directory."""
 
285
        try:
 
286
            os.unlink(full_path)
 
287
        except OSError, e:
 
288
        # We may be renaming a dangling inventory id
 
289
            if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
 
290
                raise
 
291
            os.rmdir(full_path)
 
292
 
320
293
    def cancel_creation(self, trans_id):
321
294
        """Cancel the creation of new file contents."""
322
295
        del self._new_contents[trans_id]
323
 
        delete_any(self._limbo_name(trans_id))
 
296
        self.delete_any(self._limbo_name(trans_id))
324
297
 
325
298
    def delete_contents(self, trans_id):
326
299
        """Schedule the contents of a path entry for deletion"""
458
431
        except KeyError:
459
432
            return os.path.basename(self._tree_id_paths[trans_id])
460
433
 
461
 
    def by_parent(self):
 
434
    def _by_parent(self):
462
435
        """Return a map of parent: children for known parents.
463
436
        
464
437
        Only new paths and parents of tree files with assigned ids are used.
485
458
        # ensure all children of all existent parents are known
486
459
        # all children of non-existent parents are known, by definition.
487
460
        self._add_tree_children()
488
 
        by_parent = self.by_parent()
 
461
        by_parent = self._by_parent()
489
462
        conflicts.extend(self._unversioned_parents(by_parent))
490
463
        conflicts.extend(self._parent_loops())
491
464
        conflicts.extend(self._duplicate_entries(by_parent))
502
475
        Active parents are those which gain children, and those which are
503
476
        removed.  This is a necessary first step in detecting conflicts.
504
477
        """
505
 
        parents = self.by_parent().keys()
 
478
        parents = self._by_parent().keys()
506
479
        parents.extend([t for t in self._removed_contents if 
507
480
                        self.tree_kind(t) == 'directory'])
508
481
        for trans_id in self._removed_id:
534
507
                continue
535
508
            yield self.trans_id_tree_path(childpath)
536
509
 
537
 
    def has_named_child(self, by_parent, parent_id, name):
538
 
        try:
539
 
            children = by_parent[parent_id]
540
 
        except KeyError:
541
 
            children = []
542
 
        for child in children:
543
 
            if self.final_name(child) == name:
544
 
                return True
545
 
        try:
546
 
            path = self._tree_id_paths[parent_id]
547
 
        except KeyError:
548
 
            return False
549
 
        childpath = joinpath(path, name)
550
 
        child_id = self._tree_path_ids.get(childpath)
551
 
        if child_id is None:
552
 
            return lexists(self._tree.abspath(childpath))
553
 
        else:
554
 
            if self.final_parent(child_id) != parent_id:
555
 
                return False
556
 
            if child_id in self._removed_contents:
557
 
                # XXX What about dangling file-ids?
558
 
                return False
559
 
            else:
560
 
                return True
561
 
 
562
510
    def _parent_loops(self):
563
511
        """No entry should be its own ancestor"""
564
512
        conflicts = []
650
598
                if name == last_name:
651
599
                    conflicts.append(('duplicate', last_trans_id, trans_id,
652
600
                    name))
653
 
                try:
654
 
                    kind = self.final_kind(trans_id)
655
 
                except NoSuchFile:
656
 
                    kind = None
657
 
                file_id = self.final_file_id(trans_id)
658
 
                if kind is not None or file_id is not None:
659
 
                    last_name = name
660
 
                    last_trans_id = trans_id
 
601
                last_name = name
 
602
                last_trans_id = trans_id
661
603
        return conflicts
662
604
 
663
605
    def _duplicate_ids(self):
717
659
            raise MalformedTransform(conflicts=conflicts)
718
660
        limbo_inv = {}
719
661
        inv = self._tree.inventory
720
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
721
 
        try:
722
 
            child_pb.update('Apply phase', 0, 2)
723
 
            self._apply_removals(inv, limbo_inv)
724
 
            child_pb.update('Apply phase', 1, 2)
725
 
            modified_paths = self._apply_insertions(inv, limbo_inv)
726
 
        finally:
727
 
            child_pb.finished()
 
662
        self._apply_removals(inv, limbo_inv)
 
663
        self._apply_insertions(inv, limbo_inv)
728
664
        self._tree._write_inventory(inv)
729
665
        self.__done = True
730
666
        self.finalize()
731
 
        return _TransformResults(modified_paths)
732
667
 
733
668
    def _limbo_name(self, trans_id):
734
669
        """Generate the limbo name of a file"""
743
678
        """
744
679
        tree_paths = list(self._tree_path_ids.iteritems())
745
680
        tree_paths.sort(reverse=True)
746
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
747
 
        try:
748
 
            for num, data in enumerate(tree_paths):
749
 
                path, trans_id = data
750
 
                child_pb.update('removing file', num, len(tree_paths))
751
 
                full_path = self._tree.abspath(path)
752
 
                if trans_id in self._removed_contents:
753
 
                    delete_any(full_path)
754
 
                elif trans_id in self._new_name or trans_id in \
755
 
                    self._new_parent:
756
 
                    try:
757
 
                        os.rename(full_path, self._limbo_name(trans_id))
758
 
                    except OSError, e:
759
 
                        if e.errno != errno.ENOENT:
760
 
                            raise
761
 
                if trans_id in self._removed_id:
762
 
                    if trans_id == self._new_root:
763
 
                        file_id = self._tree.inventory.root.file_id
764
 
                    else:
765
 
                        file_id = self.tree_file_id(trans_id)
 
681
        for num, data in enumerate(tree_paths):
 
682
            path, trans_id = data
 
683
            self._pb.update('removing file', num+1, len(tree_paths))
 
684
            full_path = self._tree.abspath(path)
 
685
            if trans_id in self._removed_contents:
 
686
                self.delete_any(full_path)
 
687
            elif trans_id in self._new_name or trans_id in self._new_parent:
 
688
                try:
 
689
                    os.rename(full_path, self._limbo_name(trans_id))
 
690
                except OSError, e:
 
691
                    if e.errno != errno.ENOENT:
 
692
                        raise
 
693
            if trans_id in self._removed_id:
 
694
                if trans_id == self._new_root:
 
695
                    file_id = self._tree.inventory.root.file_id
 
696
                else:
 
697
                    file_id = self.tree_file_id(trans_id)
 
698
                del inv[file_id]
 
699
            elif trans_id in self._new_name or trans_id in self._new_parent:
 
700
                file_id = self.tree_file_id(trans_id)
 
701
                if file_id is not None:
 
702
                    limbo_inv[trans_id] = inv[file_id]
766
703
                    del inv[file_id]
767
 
                elif trans_id in self._new_name or trans_id in self._new_parent:
768
 
                    file_id = self.tree_file_id(trans_id)
769
 
                    if file_id is not None:
770
 
                        limbo_inv[trans_id] = inv[file_id]
771
 
                        del inv[file_id]
772
 
        finally:
773
 
            child_pb.finished()
 
704
        self._pb.clear()
774
705
 
775
706
    def _apply_insertions(self, inv, limbo_inv):
776
707
        """Perform tree operations that insert directory/inventory names.
780
711
        parent-to-child order.
781
712
        """
782
713
        new_paths = self.new_paths()
783
 
        modified_paths = []
784
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
785
 
        try:
786
 
            for num, (path, trans_id) in enumerate(new_paths):
787
 
                child_pb.update('adding file', num, len(new_paths))
 
714
        for num, (path, trans_id) in enumerate(new_paths):
 
715
            self._pb.update('adding file', num+1, len(new_paths))
 
716
            try:
 
717
                kind = self._new_contents[trans_id]
 
718
            except KeyError:
 
719
                kind = contents = None
 
720
            if trans_id in self._new_contents or self.path_changed(trans_id):
 
721
                full_path = self._tree.abspath(path)
788
722
                try:
789
 
                    kind = self._new_contents[trans_id]
790
 
                except KeyError:
791
 
                    kind = contents = None
792
 
                if trans_id in self._new_contents or \
793
 
                    self.path_changed(trans_id):
794
 
                    full_path = self._tree.abspath(path)
795
 
                    try:
796
 
                        os.rename(self._limbo_name(trans_id), full_path)
797
 
                    except OSError, e:
798
 
                        # We may be renaming a dangling inventory id
799
 
                        if e.errno != errno.ENOENT:
800
 
                            raise
801
 
                    if trans_id in self._new_contents:
802
 
                        modified_paths.append(full_path)
803
 
                        del self._new_contents[trans_id]
804
 
 
805
 
                if trans_id in self._new_id:
806
 
                    if kind is None:
807
 
                        kind = file_kind(self._tree.abspath(path))
808
 
                    inv.add_path(path, kind, self._new_id[trans_id])
809
 
                elif trans_id in self._new_name or trans_id in\
810
 
                    self._new_parent:
811
 
                    entry = limbo_inv.get(trans_id)
812
 
                    if entry is not None:
813
 
                        entry.name = self.final_name(trans_id)
814
 
                        parent_path = os.path.dirname(path)
815
 
                        entry.parent_id = \
816
 
                            self._tree.inventory.path2id(parent_path)
817
 
                        inv.add(entry)
818
 
 
819
 
                # requires files and inventory entries to be in place
820
 
                if trans_id in self._new_executability:
821
 
                    self._set_executability(path, inv, trans_id)
822
 
        finally:
823
 
            child_pb.finished()
824
 
        return modified_paths
 
723
                    os.rename(self._limbo_name(trans_id), full_path)
 
724
                except OSError, e:
 
725
                    # We may be renaming a dangling inventory id
 
726
                    if e.errno != errno.ENOENT:
 
727
                        raise
 
728
                if trans_id in self._new_contents:
 
729
                    del self._new_contents[trans_id]
 
730
 
 
731
            if trans_id in self._new_id:
 
732
                if kind is None:
 
733
                    kind = file_kind(self._tree.abspath(path))
 
734
                inv.add_path(path, kind, self._new_id[trans_id])
 
735
            elif trans_id in self._new_name or trans_id in self._new_parent:
 
736
                entry = limbo_inv.get(trans_id)
 
737
                if entry is not None:
 
738
                    entry.name = self.final_name(trans_id)
 
739
                    parent_path = os.path.dirname(path)
 
740
                    entry.parent_id = self._tree.inventory.path2id(parent_path)
 
741
                    inv.add(entry)
 
742
 
 
743
            # requires files and inventory entries to be in place
 
744
            if trans_id in self._new_executability:
 
745
                self._set_executability(path, inv, trans_id)
 
746
        self._pb.clear()
825
747
 
826
748
    def _set_executability(self, path, inv, trans_id):
827
749
        """Set the executability of versioned files """
859
781
        parent_id is the transaction id of the parent directory of the file.
860
782
        contents is an iterator of bytestrings, which will be used to produce
861
783
        the file.
862
 
        :param file_id: The inventory ID of the file, if it is to be versioned.
863
 
        :param executable: Only valid when a file_id has been supplied.
 
784
        file_id is the inventory ID of the file, if it is to be versioned.
864
785
        """
865
786
        trans_id = self._new_entry(name, parent_id, file_id)
866
 
        # TODO: rather than scheduling a set_executable call,
867
 
        # have create_file create the file with the right mode.
868
787
        self.create_file(contents, trans_id)
869
788
        if executable is not None:
870
789
            self.set_executability(executable, trans_id)
938
857
def build_tree(tree, wt):
939
858
    """Create working tree for a branch, using a Transaction."""
940
859
    file_trans_id = {}
941
 
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
942
 
    pp = ProgressPhase("Build phase", 2, top_pb)
943
860
    tt = TreeTransform(wt)
944
861
    try:
945
 
        pp.next_phase()
946
862
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
947
863
        file_ids = topology_sorted_ids(tree)
948
 
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
949
 
        try:
950
 
            for num, file_id in enumerate(file_ids):
951
 
                pb.update("Building tree", num, len(file_ids))
952
 
                entry = tree.inventory[file_id]
953
 
                if entry.parent_id is None:
954
 
                    continue
955
 
                if entry.parent_id not in file_trans_id:
956
 
                    raise repr(entry.parent_id)
957
 
                parent_id = file_trans_id[entry.parent_id]
958
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
959
 
                                                      tree)
960
 
        finally:
961
 
            pb.finished()
962
 
        pp.next_phase()
 
864
        for file_id in file_ids:
 
865
            entry = tree.inventory[file_id]
 
866
            if entry.parent_id is None:
 
867
                continue
 
868
            if entry.parent_id not in file_trans_id:
 
869
                raise repr(entry.parent_id)
 
870
            parent_id = file_trans_id[entry.parent_id]
 
871
            file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree)
963
872
        tt.apply()
964
873
    finally:
965
874
        tt.finalize()
966
 
        top_pb.finished()
967
875
 
968
876
def new_by_entry(tt, entry, parent_id, tree):
969
877
    """Create a new file according to its inventory entry"""
999
907
 
1000
908
def find_interesting(working_tree, target_tree, filenames):
1001
909
    """Find the ids corresponding to specified filenames."""
1002
 
    trees = (working_tree, target_tree)
1003
 
    return tree.find_ids_across_trees(filenames, trees)
 
910
    if not filenames:
 
911
        interesting_ids = None
 
912
    else:
 
913
        interesting_ids = set()
 
914
        for tree_path in filenames:
 
915
            for tree in (working_tree, target_tree):
 
916
                not_found = True
 
917
                file_id = tree.inventory.path2id(tree_path)
 
918
                if file_id is not None:
 
919
                    interesting_ids.add(file_id)
 
920
                    not_found = False
 
921
                if not_found:
 
922
                    raise NotVersionedError(path=tree_path)
 
923
    return interesting_ids
1004
924
 
1005
925
 
1006
926
def change_entry(tt, file_id, working_tree, target_tree, 
1007
 
                 trans_id_file_id, backups, trans_id, by_parent):
 
927
                 trans_id_file_id, backups, trans_id):
1008
928
    """Replace a file_id's contents with those from a target tree."""
1009
929
    e_trans_id = trans_id_file_id(file_id)
1010
930
    entry = target_tree.inventory[file_id]
1017
937
                tt.delete_contents(e_trans_id)
1018
938
            else:
1019
939
                parent_trans_id = trans_id_file_id(entry.parent_id)
1020
 
                backup_name = get_backup_name(entry, by_parent,
1021
 
                                              parent_trans_id, tt)
1022
 
                tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
 
940
                tt.adjust_path(entry.name+"~", parent_trans_id, e_trans_id)
1023
941
                tt.unversion_file(e_trans_id)
1024
942
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
1025
943
                tt.version_file(file_id, e_trans_id)
1043
961
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1044
962
 
1045
963
 
1046
 
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1047
 
    """Produce a backup-style name that appears to be available"""
1048
 
    def name_gen():
1049
 
        counter = 1
1050
 
        while True:
1051
 
            yield "%s.~%d~" % (entry.name, counter)
1052
 
            counter += 1
1053
 
    for name in name_gen():
1054
 
        if not tt.has_named_child(by_parent, parent_trans_id, name):
1055
 
            return name
1056
 
 
1057
964
def _entry_changes(file_id, entry, working_tree):
1058
965
    """Determine in which ways the inventory entry has changed.
1059
966
 
1066
973
    try:
1067
974
        working_kind = working_tree.kind(file_id)
1068
975
        has_contents = True
1069
 
    except NoSuchFile:
 
976
    except OSError, e:
 
977
        if e.errno != errno.ENOENT:
 
978
            raise
1070
979
        has_contents = False
1071
980
        contents_mod = True
1072
981
        meta_mod = False
1093
1002
 
1094
1003
    tt = TreeTransform(working_tree, pb)
1095
1004
    try:
1096
 
        merge_modified = working_tree.merge_modified()
1097
1005
        trans_id = {}
1098
1006
        def trans_id_file_id(file_id):
1099
1007
            try:
1101
1009
            except KeyError:
1102
1010
                return tt.trans_id_tree_file_id(file_id)
1103
1011
 
1104
 
        pp = ProgressPhase("Revert phase", 4, pb)
1105
 
        pp.next_phase()
1106
1012
        sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1107
1013
                              interesting(i)]
1108
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1109
 
        try:
1110
 
            by_parent = tt.by_parent()
1111
 
            for id_num, file_id in enumerate(sorted_interesting):
1112
 
                child_pb.update("Reverting file", id_num+1, 
1113
 
                                len(sorted_interesting))
1114
 
                if file_id not in working_tree.inventory:
1115
 
                    entry = target_tree.inventory[file_id]
1116
 
                    parent_id = trans_id_file_id(entry.parent_id)
1117
 
                    e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1118
 
                    trans_id[file_id] = e_trans_id
1119
 
                else:
1120
 
                    backup_this = backups
1121
 
                    if file_id in merge_modified:
1122
 
                        backup_this = False
1123
 
                        del merge_modified[file_id]
1124
 
                    change_entry(tt, file_id, working_tree, target_tree, 
1125
 
                                 trans_id_file_id, backup_this, trans_id,
1126
 
                                 by_parent)
1127
 
        finally:
1128
 
            child_pb.finished()
1129
 
        pp.next_phase()
 
1014
        for id_num, file_id in enumerate(sorted_interesting):
 
1015
            pb.update("Reverting file", id_num+1, len(sorted_interesting))
 
1016
            if file_id not in working_tree.inventory:
 
1017
                entry = target_tree.inventory[file_id]
 
1018
                parent_id = trans_id_file_id(entry.parent_id)
 
1019
                e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
 
1020
                trans_id[file_id] = e_trans_id
 
1021
            else:
 
1022
                change_entry(tt, file_id, working_tree, target_tree, 
 
1023
                             trans_id_file_id, backups, trans_id)
1130
1024
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1131
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1132
 
        try:
1133
 
            for id_num, file_id in enumerate(wt_interesting):
1134
 
                child_pb.update("New file check", id_num+1, 
1135
 
                                len(sorted_interesting))
1136
 
                if file_id not in target_tree:
1137
 
                    trans_id = tt.trans_id_tree_file_id(file_id)
1138
 
                    tt.unversion_file(trans_id)
1139
 
                    if file_id in merge_modified:
1140
 
                        tt.delete_contents(trans_id)
1141
 
                        del merge_modified[file_id]
1142
 
        finally:
1143
 
            child_pb.finished()
1144
 
        pp.next_phase()
1145
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1146
 
        try:
1147
 
            raw_conflicts = resolve_conflicts(tt, child_pb)
1148
 
        finally:
1149
 
            child_pb.finished()
1150
 
        conflicts = cook_conflicts(raw_conflicts, tt)
1151
 
        for conflict in conflicts:
1152
 
            warning(conflict)
1153
 
        pp.next_phase()
 
1025
        for id_num, file_id in enumerate(wt_interesting):
 
1026
            pb.update("New file check", id_num+1, len(sorted_interesting))
 
1027
            if file_id not in target_tree:
 
1028
                tt.unversion_file(tt.trans_id_tree_file_id(file_id))
 
1029
        raw_conflicts = resolve_conflicts(tt, pb)
 
1030
        for line in conflicts_strings(cook_conflicts(raw_conflicts, tt)):
 
1031
            warning(line)
1154
1032
        tt.apply()
1155
 
        working_tree.set_merge_modified({})
1156
1033
    finally:
1157
1034
        tt.finalize()
1158
1035
        pb.clear()
1159
 
    return conflicts
1160
1036
 
1161
1037
 
1162
1038
def resolve_conflicts(tt, pb=DummyProgress()):
1216
1092
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1217
1093
    return new_conflicts
1218
1094
 
1219
 
 
1220
1095
def cook_conflicts(raw_conflicts, tt):
1221
1096
    """Generate a list of cooked conflicts, sorted by file path"""
1222
 
    from bzrlib.conflicts import Conflict
1223
 
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1224
 
    return sorted(conflict_iter, key=Conflict.sort_key)
 
1097
    def key(conflict):
 
1098
        if conflict[2] is not None:
 
1099
            return conflict[2], conflict[0]
 
1100
        elif len(conflict) == 6:
 
1101
            return conflict[4], conflict[0]
 
1102
        else:
 
1103
            return None, conflict[0]
1225
1104
 
 
1105
    return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1226
1106
 
1227
1107
def iter_cook_conflicts(raw_conflicts, tt):
1228
 
    from bzrlib.conflicts import Conflict
 
1108
    cooked_conflicts = []
1229
1109
    fp = FinalPaths(tt)
1230
1110
    for conflict in raw_conflicts:
1231
1111
        c_type = conflict[0]
1233
1113
        modified_path = fp.get_path(conflict[2])
1234
1114
        modified_id = tt.final_file_id(conflict[2])
1235
1115
        if len(conflict) == 3:
1236
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
1237
 
                                     file_id=modified_id)
1238
 
             
 
1116
            yield c_type, action, modified_path, modified_id
1239
1117
        else:
1240
1118
            conflicting_path = fp.get_path(conflict[3])
1241
1119
            conflicting_id = tt.final_file_id(conflict[3])
1242
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
1243
 
                                   file_id=modified_id, 
1244
 
                                   conflict_path=conflicting_path,
1245
 
                                   conflict_file_id=conflicting_id)
 
1120
            yield (c_type, action, modified_path, modified_id, 
 
1121
                   conflicting_path, conflicting_id)
 
1122
 
 
1123
 
 
1124
def conflicts_strings(conflicts):
 
1125
    """Generate strings for the provided conflicts"""
 
1126
    for conflict in conflicts:
 
1127
        conflict_type = conflict[0]
 
1128
        if conflict_type == 'text conflict':
 
1129
            yield 'Text conflict in %s' % conflict[2]
 
1130
        elif conflict_type == 'contents conflict':
 
1131
            yield 'Contents conflict in %s' % conflict[2]
 
1132
        elif conflict_type == 'path conflict':
 
1133
            yield 'Path conflict: %s / %s' % conflict[2:]
 
1134
        elif conflict_type == 'duplicate id':
 
1135
            vals = (conflict[4], conflict[1], conflict[2])
 
1136
            yield 'Conflict adding id to %s.  %s %s.' % vals
 
1137
        elif conflict_type == 'duplicate':
 
1138
            vals = (conflict[4], conflict[1], conflict[2])
 
1139
            yield 'Conflict adding file %s.  %s %s.' % vals
 
1140
        elif conflict_type == 'parent loop':
 
1141
            vals = (conflict[4], conflict[2], conflict[1])
 
1142
            yield 'Conflict moving %s into %s.  %s.' % vals
 
1143
        elif conflict_type == 'unversioned parent':
 
1144
            vals = (conflict[2], conflict[1])
 
1145
            yield 'Conflict adding versioned files to %s.  %s.' % vals
 
1146
        elif conflict_type == 'missing parent':
 
1147
            vals = (conflict[2], conflict[1])
 
1148
            yield 'Conflict adding files to %s.  %s.' % vals