/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Alexander Belchenko
  • Date: 2007-06-05 08:02:04 UTC
  • mto: This revision was merged to the branch mainline in revision 2512.
  • Revision ID: bialix@ukr.net-20070605080204-hvhqw69njlpxcscb
sanitizeĀ developersĀ docs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
import errno
19
19
from stat import S_ISREG
20
20
 
21
 
from bzrlib import bzrdir, errors
 
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
""")
22
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
31
                           ReusingTransform, NotVersionedError, CantMoveRoot,
24
 
                           ExistingLimbo, ImmortalLimbo)
 
32
                           ExistingLimbo, ImmortalLimbo, NoFinalPath)
25
33
from bzrlib.inventory import InventoryEntry
26
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
27
35
                            delete_any)
28
36
from bzrlib.progress import DummyProgress, ProgressPhase
 
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
29
38
from bzrlib.trace import mutter, warning
30
39
from bzrlib import tree
31
 
import bzrlib.ui 
 
40
import bzrlib.ui
32
41
import bzrlib.urlutils as urlutils
33
42
 
34
43
 
99
108
        self._new_contents = {}
100
109
        self._removed_contents = set()
101
110
        self._new_executability = {}
 
111
        self._new_reference_revision = {}
102
112
        self._new_id = {}
103
113
        self._non_present_ids = {}
104
114
        self._r_new_id = {}
273
283
                os.unlink(name)
274
284
                raise
275
285
 
276
 
            for segment in contents:
277
 
                f.write(segment)
 
286
            f.writelines(contents)
278
287
        finally:
279
288
            f.close()
280
289
        self._set_mode(trans_id, mode_id, S_ISREG)
292
301
        except KeyError:
293
302
            return
294
303
        try:
295
 
            mode = os.stat(old_path).st_mode
 
304
            mode = os.stat(self._tree.abspath(old_path)).st_mode
296
305
        except OSError, e:
297
306
            if e.errno == errno.ENOENT:
298
307
                return
350
359
        else:
351
360
            unique_add(self._new_executability, trans_id, executability)
352
361
 
 
362
    def set_tree_reference(self, revision_id, trans_id):
 
363
        """Set the reference associated with a directory"""
 
364
        unique_add(self._new_reference_revision, trans_id, revision_id)
 
365
 
353
366
    def version_file(self, file_id, trans_id):
354
367
        """Schedule a file to become versioned."""
355
368
        assert file_id is not None
457
470
        try:
458
471
            return self._new_name[trans_id]
459
472
        except KeyError:
460
 
            return os.path.basename(self._tree_id_paths[trans_id])
 
473
            try:
 
474
                return os.path.basename(self._tree_id_paths[trans_id])
 
475
            except KeyError:
 
476
                raise NoFinalPath(trans_id, self)
461
477
 
462
478
    def by_parent(self):
463
479
        """Return a map of parent: children for known parents.
570
586
            parent_id = trans_id
571
587
            while parent_id is not ROOT_PARENT:
572
588
                seen.add(parent_id)
573
 
                parent_id = self.final_parent(parent_id)
 
589
                try:
 
590
                    parent_id = self.final_parent(parent_id)
 
591
                except KeyError:
 
592
                    break
574
593
                if parent_id == trans_id:
575
594
                    conflicts.append(('parent loop', trans_id))
576
595
                if parent_id in seen:
719
738
        conflicts = self.find_conflicts()
720
739
        if len(conflicts) != 0:
721
740
            raise MalformedTransform(conflicts=conflicts)
722
 
        limbo_inv = {}
723
741
        inv = self._tree.inventory
 
742
        inventory_delta = []
724
743
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
725
744
        try:
726
745
            child_pb.update('Apply phase', 0, 2)
727
 
            self._apply_removals(inv, limbo_inv)
 
746
            self._apply_removals(inv, inventory_delta)
728
747
            child_pb.update('Apply phase', 1, 2)
729
 
            modified_paths = self._apply_insertions(inv, limbo_inv)
 
748
            modified_paths = self._apply_insertions(inv, inventory_delta)
730
749
        finally:
731
750
            child_pb.finished()
732
 
        self._tree._write_inventory(inv)
 
751
        self._tree.apply_inventory_delta(inventory_delta)
733
752
        self.__done = True
734
753
        self.finalize()
735
754
        return _TransformResults(modified_paths)
738
757
        """Generate the limbo name of a file"""
739
758
        return pathjoin(self._limbodir, trans_id)
740
759
 
741
 
    def _apply_removals(self, inv, limbo_inv):
 
760
    def _apply_removals(self, inv, inventory_delta):
742
761
        """Perform tree operations that remove directory/inventory names.
743
762
        
744
763
        That is, delete files that are to be deleted, and put any files that
767
786
                        file_id = self._tree.inventory.root.file_id
768
787
                    else:
769
788
                        file_id = self.tree_file_id(trans_id)
770
 
                    del inv[file_id]
771
 
                elif trans_id in self._new_name or trans_id in self._new_parent:
772
 
                    file_id = self.tree_file_id(trans_id)
773
 
                    if file_id is not None:
774
 
                        limbo_inv[trans_id] = inv[file_id]
775
 
                        del inv[file_id]
 
789
                    assert file_id is not None
 
790
                    inventory_delta.append((path, None, file_id, None))
776
791
        finally:
777
792
            child_pb.finished()
778
793
 
779
 
    def _apply_insertions(self, inv, limbo_inv):
 
794
    def _apply_insertions(self, inv, inventory_delta):
780
795
        """Perform tree operations that insert directory/inventory names.
781
796
        
782
797
        That is, create any files that need to be created, and restore from
788
803
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
789
804
        try:
790
805
            for num, (path, trans_id) in enumerate(new_paths):
 
806
                new_entry = None
791
807
                child_pb.update('adding file', num, len(new_paths))
792
808
                try:
793
809
                    kind = self._new_contents[trans_id]
809
825
                if trans_id in self._new_id:
810
826
                    if kind is None:
811
827
                        kind = file_kind(self._tree.abspath(path))
812
 
                    inv.add_path(path, kind, self._new_id[trans_id])
813
 
                elif trans_id in self._new_name or trans_id in\
814
 
                    self._new_parent:
815
 
                    entry = limbo_inv.get(trans_id)
816
 
                    if entry is not None:
817
 
                        entry.name = self.final_name(trans_id)
818
 
                        parent_path = os.path.dirname(path)
819
 
                        entry.parent_id = \
820
 
                            self._tree.inventory.path2id(parent_path)
821
 
                        inv.add(entry)
822
 
 
823
 
                # requires files and inventory entries to be in place
 
828
                    if trans_id in self._new_reference_revision:
 
829
                        new_entry = inventory.TreeReference(
 
830
                            self._new_id[trans_id],
 
831
                            self._new_name[trans_id], 
 
832
                            self.final_file_id(self._new_parent[trans_id]),
 
833
                            None, self._new_reference_revision[trans_id])
 
834
                    else:
 
835
                        new_entry = inventory.make_entry(kind,
 
836
                            self.final_name(trans_id),
 
837
                            self.final_file_id(self.final_parent(trans_id)),
 
838
                            self._new_id[trans_id])
 
839
                else:
 
840
                    if trans_id in self._new_name or trans_id in\
 
841
                        self._new_parent or\
 
842
                        trans_id in self._new_executability:
 
843
                        file_id = self.final_file_id(trans_id)
 
844
                        if file_id is not None:
 
845
                            entry = inv[file_id]
 
846
                            new_entry = entry.copy()
 
847
 
 
848
                    if trans_id in self._new_name or trans_id in\
 
849
                        self._new_parent:
 
850
                            if new_entry is not None:
 
851
                                new_entry.name = self.final_name(trans_id)
 
852
                                parent = self.final_parent(trans_id)
 
853
                                parent_id = self.final_file_id(parent)
 
854
                                new_entry.parent_id = parent_id
 
855
 
824
856
                if trans_id in self._new_executability:
825
 
                    self._set_executability(path, inv, trans_id)
 
857
                    self._set_executability(path, new_entry, trans_id)
 
858
                if new_entry is not None:
 
859
                    if new_entry.file_id in inv:
 
860
                        old_path = inv.id2path(new_entry.file_id)
 
861
                    else:
 
862
                        old_path = None
 
863
                    inventory_delta.append((old_path, path,
 
864
                                            new_entry.file_id,
 
865
                                            new_entry))
826
866
        finally:
827
867
            child_pb.finished()
828
868
        return modified_paths
829
869
 
830
 
    def _set_executability(self, path, inv, trans_id):
 
870
    def _set_executability(self, path, entry, trans_id):
831
871
        """Set the executability of versioned files """
832
 
        file_id = inv.path2id(path)
833
872
        new_executability = self._new_executability[trans_id]
834
 
        inv[file_id].executable = new_executability
 
873
        entry.executable = new_executability
835
874
        if supports_executable():
836
875
            abspath = self._tree.abspath(path)
837
876
            current_mode = os.stat(abspath).st_mode
898
937
        self.create_symlink(target, trans_id)
899
938
        return trans_id
900
939
 
 
940
    def _affected_ids(self):
 
941
        """Return the set of transform ids affected by the transform"""
 
942
        trans_ids = set(self._removed_id)
 
943
        trans_ids.update(self._new_id.keys())
 
944
        trans_ids.update(self._removed_contents)
 
945
        trans_ids.update(self._new_contents.keys())
 
946
        trans_ids.update(self._new_executability.keys())
 
947
        trans_ids.update(self._new_name.keys())
 
948
        trans_ids.update(self._new_parent.keys())
 
949
        return trans_ids
 
950
 
 
951
    def _get_file_id_maps(self):
 
952
        """Return mapping of file_ids to trans_ids in the to and from states"""
 
953
        trans_ids = self._affected_ids()
 
954
        from_trans_ids = {}
 
955
        to_trans_ids = {}
 
956
        # Build up two dicts: trans_ids associated with file ids in the
 
957
        # FROM state, vs the TO state.
 
958
        for trans_id in trans_ids:
 
959
            from_file_id = self.tree_file_id(trans_id)
 
960
            if from_file_id is not None:
 
961
                from_trans_ids[from_file_id] = trans_id
 
962
            to_file_id = self.final_file_id(trans_id)
 
963
            if to_file_id is not None:
 
964
                to_trans_ids[to_file_id] = trans_id
 
965
        return from_trans_ids, to_trans_ids
 
966
 
 
967
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
 
968
        """Get data about a file in the from (tree) state
 
969
 
 
970
        Return a (name, parent, kind, executable) tuple
 
971
        """
 
972
        from_path = self._tree_id_paths.get(from_trans_id)
 
973
        if from_versioned:
 
974
            # get data from working tree if versioned
 
975
            from_entry = self._tree.inventory[file_id]
 
976
            from_name = from_entry.name
 
977
            from_parent = from_entry.parent_id
 
978
        else:
 
979
            from_entry = None
 
980
            if from_path is None:
 
981
                # File does not exist in FROM state
 
982
                from_name = None
 
983
                from_parent = None
 
984
            else:
 
985
                # File exists, but is not versioned.  Have to use path-
 
986
                # splitting stuff
 
987
                from_name = os.path.basename(from_path)
 
988
                tree_parent = self.get_tree_parent(from_trans_id)
 
989
                from_parent = self.tree_file_id(tree_parent)
 
990
        if from_path is not None:
 
991
            from_kind, from_executable, from_stats = \
 
992
                self._tree._comparison_data(from_entry, from_path)
 
993
        else:
 
994
            from_kind = None
 
995
            from_executable = False
 
996
        return from_name, from_parent, from_kind, from_executable
 
997
 
 
998
    def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
 
999
        """Get data about a file in the to (target) state
 
1000
 
 
1001
        Return a (name, parent, kind, executable) tuple
 
1002
        """
 
1003
        to_name = self.final_name(to_trans_id)
 
1004
        try:
 
1005
            to_kind = self.final_kind(to_trans_id)
 
1006
        except NoSuchFile:
 
1007
            to_kind = None
 
1008
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
 
1009
        if to_trans_id in self._new_executability:
 
1010
            to_executable = self._new_executability[to_trans_id]
 
1011
        elif to_trans_id == from_trans_id:
 
1012
            to_executable = from_executable
 
1013
        else:
 
1014
            to_executable = False
 
1015
        return to_name, to_parent, to_kind, to_executable
 
1016
 
 
1017
    def _iter_changes(self):
 
1018
        """Produce output in the same format as Tree._iter_changes.
 
1019
 
 
1020
        Will produce nonsensical results if invoked while inventory/filesystem
 
1021
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
 
1022
 
 
1023
        This reads the Transform, but only reproduces changes involving a
 
1024
        file_id.  Files that are not versioned in either of the FROM or TO
 
1025
        states are not reflected.
 
1026
        """
 
1027
        final_paths = FinalPaths(self)
 
1028
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
 
1029
        results = []
 
1030
        # Now iterate through all active file_ids
 
1031
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
 
1032
            modified = False
 
1033
            from_trans_id = from_trans_ids.get(file_id)
 
1034
            # find file ids, and determine versioning state
 
1035
            if from_trans_id is None:
 
1036
                from_versioned = False
 
1037
                from_trans_id = to_trans_ids[file_id]
 
1038
            else:
 
1039
                from_versioned = True
 
1040
            to_trans_id = to_trans_ids.get(file_id)
 
1041
            if to_trans_id is None:
 
1042
                to_versioned = False
 
1043
                to_trans_id = from_trans_id
 
1044
            else:
 
1045
                to_versioned = True
 
1046
 
 
1047
            from_name, from_parent, from_kind, from_executable = \
 
1048
                self._from_file_data(from_trans_id, from_versioned, file_id)
 
1049
 
 
1050
            to_name, to_parent, to_kind, to_executable = \
 
1051
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
 
1052
 
 
1053
            if not from_versioned:
 
1054
                from_path = None
 
1055
            else:
 
1056
                from_path = self._tree_id_paths.get(from_trans_id)
 
1057
            if not to_versioned:
 
1058
                to_path = None
 
1059
            else:
 
1060
                to_path = final_paths.get_path(to_trans_id)
 
1061
            if from_kind != to_kind:
 
1062
                modified = True
 
1063
            elif to_kind in ('file', 'symlink') and (
 
1064
                to_trans_id != from_trans_id or
 
1065
                to_trans_id in self._new_contents):
 
1066
                modified = True
 
1067
            if (not modified and from_versioned == to_versioned and
 
1068
                from_parent==to_parent and from_name == to_name and
 
1069
                from_executable == to_executable):
 
1070
                continue
 
1071
            results.append((file_id, (from_path, to_path), modified,
 
1072
                   (from_versioned, to_versioned),
 
1073
                   (from_parent, to_parent),
 
1074
                   (from_name, to_name),
 
1075
                   (from_kind, to_kind),
 
1076
                   (from_executable, to_executable)))
 
1077
        return iter(sorted(results, key=lambda x:x[1]))
 
1078
 
 
1079
 
901
1080
def joinpath(parent, child):
902
1081
    """Join tree-relative paths, handling the tree root specially"""
903
1082
    if parent is None or parent == "":
954
1133
      it is silently replaced.
955
1134
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
956
1135
    """
957
 
    assert 2 > len(wt.inventory)
 
1136
    wt.lock_tree_write()
 
1137
    try:
 
1138
        tree.lock_read()
 
1139
        try:
 
1140
            return _build_tree(tree, wt)
 
1141
        finally:
 
1142
            tree.unlock()
 
1143
    finally:
 
1144
        wt.unlock()
 
1145
 
 
1146
def _build_tree(tree, wt):
 
1147
    """See build_tree."""
 
1148
    if len(wt.inventory) > 1:  # more than just a root
 
1149
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
958
1150
    file_trans_id = {}
959
1151
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
960
1152
    pp = ProgressPhase("Build phase", 2, top_pb)
 
1153
    if tree.inventory.root is not None:
 
1154
        # this is kindof a hack: we should be altering the root 
 
1155
        # as partof the regular tree shape diff logic.
 
1156
        # the conditional test hereis to avoid doing an
 
1157
        # expensive operation (flush) every time the root id
 
1158
        # is set within the tree, nor setting the root and thus
 
1159
        # marking the tree as dirty, because we use two different
 
1160
        # idioms here: tree interfaces and inventory interfaces.
 
1161
        if wt.path2id('') != tree.inventory.root.file_id:
 
1162
            wt.set_root_id(tree.inventory.root.file_id)
 
1163
            wt.flush()
961
1164
    tt = TreeTransform(wt)
962
1165
    divert = set()
963
1166
    try:
993
1196
                        if kind == 'directory':
994
1197
                            reparent = True
995
1198
                if entry.parent_id not in file_trans_id:
996
 
                    raise repr(entry.parent_id)
 
1199
                    raise AssertionError(
 
1200
                        'entry %s parent id %r is not in file_trans_id %r'
 
1201
                        % (entry, entry.parent_id, file_trans_id))
997
1202
                parent_id = file_trans_id[entry.parent_id]
998
1203
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
999
1204
                                                      tree)
1077
1282
        executable = tree.is_executable(entry.file_id)
1078
1283
        return tt.new_file(name, parent_id, contents, entry.file_id, 
1079
1284
                           executable)
1080
 
    elif kind == 'directory':
1081
 
        return tt.new_directory(name, parent_id, entry.file_id)
 
1285
    elif kind in ('directory', 'tree-reference'):
 
1286
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
 
1287
        if kind == 'tree-reference':
 
1288
            tt.set_tree_reference(entry.reference_revision, trans_id)
 
1289
        return trans_id 
1082
1290
    elif kind == 'symlink':
1083
1291
        target = tree.get_symlink_target(entry.file_id)
1084
1292
        return tt.new_symlink(name, parent_id, target, entry.file_id)
 
1293
    else:
 
1294
        raise errors.BadFileKindError(name, kind)
1085
1295
 
1086
1296
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1087
1297
    """Create new file contents according to an inventory entry."""
1100
1310
        tt.set_executability(entry.executable, trans_id)
1101
1311
 
1102
1312
 
 
1313
@deprecated_function(zero_fifteen)
1103
1314
def find_interesting(working_tree, target_tree, filenames):
1104
 
    """Find the ids corresponding to specified filenames."""
1105
 
    trees = (working_tree, target_tree)
1106
 
    return tree.find_ids_across_trees(filenames, trees)
 
1315
    """Find the ids corresponding to specified filenames.
 
1316
    
 
1317
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
 
1318
    """
 
1319
    working_tree.lock_read()
 
1320
    try:
 
1321
        target_tree.lock_read()
 
1322
        try:
 
1323
            return working_tree.paths2ids(filenames, [target_tree])
 
1324
        finally:
 
1325
            target_tree.unlock()
 
1326
    finally:
 
1327
        working_tree.unlock()
1107
1328
 
1108
1329
 
1109
1330
def change_entry(tt, file_id, working_tree, target_tree, 
1147
1368
 
1148
1369
 
1149
1370
def get_backup_name(entry, by_parent, parent_trans_id, tt):
 
1371
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
 
1372
 
 
1373
 
 
1374
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1150
1375
    """Produce a backup-style name that appears to be available"""
1151
1376
    def name_gen():
1152
1377
        counter = 1
1153
1378
        while True:
1154
 
            yield "%s.~%d~" % (entry.name, counter)
 
1379
            yield "%s.~%d~" % (name, counter)
1155
1380
            counter += 1
1156
 
    for name in name_gen():
1157
 
        if not tt.has_named_child(by_parent, parent_trans_id, name):
1158
 
            return name
 
1381
    for new_name in name_gen():
 
1382
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
 
1383
            return new_name
 
1384
 
1159
1385
 
1160
1386
def _entry_changes(file_id, entry, working_tree):
1161
1387
    """Determine in which ways the inventory entry has changed.
1184
1410
    return has_contents, contents_mod, meta_mod
1185
1411
 
1186
1412
 
1187
 
def revert(working_tree, target_tree, filenames, backups=False, 
1188
 
           pb=DummyProgress()):
 
1413
def revert(working_tree, target_tree, filenames, backups=False,
 
1414
           pb=DummyProgress(), change_reporter=None):
1189
1415
    """Revert a working tree's contents to those of a target tree."""
1190
 
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
1191
 
    def interesting(file_id):
1192
 
        return interesting_ids is None or (file_id in interesting_ids)
1193
 
 
 
1416
    target_tree.lock_read()
1194
1417
    tt = TreeTransform(working_tree, pb)
1195
1418
    try:
1196
 
        merge_modified = working_tree.merge_modified()
1197
 
        trans_id = {}
1198
 
        def trans_id_file_id(file_id):
1199
 
            try:
1200
 
                return trans_id[file_id]
1201
 
            except KeyError:
1202
 
                return tt.trans_id_tree_file_id(file_id)
1203
 
 
1204
 
        pp = ProgressPhase("Revert phase", 4, pb)
1205
 
        pp.next_phase()
1206
 
        sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1207
 
                              interesting(i)]
1208
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1209
 
        try:
1210
 
            by_parent = tt.by_parent()
1211
 
            for id_num, file_id in enumerate(sorted_interesting):
1212
 
                child_pb.update("Reverting file", id_num+1, 
1213
 
                                len(sorted_interesting))
1214
 
                if file_id not in working_tree.inventory:
1215
 
                    entry = target_tree.inventory[file_id]
1216
 
                    parent_id = trans_id_file_id(entry.parent_id)
1217
 
                    e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1218
 
                    trans_id[file_id] = e_trans_id
1219
 
                else:
1220
 
                    backup_this = backups
1221
 
                    if file_id in merge_modified:
1222
 
                        backup_this = False
1223
 
                        del merge_modified[file_id]
1224
 
                    change_entry(tt, file_id, working_tree, target_tree, 
1225
 
                                 trans_id_file_id, backup_this, trans_id,
1226
 
                                 by_parent)
1227
 
        finally:
1228
 
            child_pb.finished()
1229
 
        pp.next_phase()
1230
 
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1231
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1232
 
        try:
1233
 
            for id_num, file_id in enumerate(wt_interesting):
1234
 
                child_pb.update("New file check", id_num+1, 
1235
 
                                len(sorted_interesting))
1236
 
                if file_id not in target_tree:
1237
 
                    trans_id = tt.trans_id_tree_file_id(file_id)
1238
 
                    tt.unversion_file(trans_id)
1239
 
                    try:
1240
 
                        file_kind = working_tree.kind(file_id)
1241
 
                    except NoSuchFile:
1242
 
                        file_kind = None
1243
 
                    if file_kind != 'file' and file_kind is not None:
1244
 
                        keep_contents = False
1245
 
                        delete_merge_modified = False
1246
 
                    else:
1247
 
                        if (file_id in merge_modified and 
1248
 
                            merge_modified[file_id] == 
1249
 
                            working_tree.get_file_sha1(file_id)):
1250
 
                            keep_contents = False
1251
 
                            delete_merge_modified = True
1252
 
                        else:
1253
 
                            keep_contents = True
1254
 
                            delete_merge_modified = False
1255
 
                    if not keep_contents:
1256
 
                        tt.delete_contents(trans_id)
1257
 
                    if delete_merge_modified:
1258
 
                        del merge_modified[file_id]
 
1419
        pp = ProgressPhase("Revert phase", 3, pb)
 
1420
        pp.next_phase()
 
1421
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1422
        try:
 
1423
            merge_modified = _alter_files(working_tree, target_tree, tt,
 
1424
                                          child_pb, filenames, backups)
1259
1425
        finally:
1260
1426
            child_pb.finished()
1261
1427
        pp.next_phase()
1265
1431
        finally:
1266
1432
            child_pb.finished()
1267
1433
        conflicts = cook_conflicts(raw_conflicts, tt)
 
1434
        if change_reporter:
 
1435
            change_reporter = delta._ChangeReporter(
 
1436
                unversioned_filter=working_tree.is_ignored)
 
1437
            delta.report_changes(tt._iter_changes(), change_reporter)
1268
1438
        for conflict in conflicts:
1269
1439
            warning(conflict)
1270
1440
        pp.next_phase()
1271
1441
        tt.apply()
1272
 
        working_tree.set_merge_modified({})
 
1442
        working_tree.set_merge_modified(merge_modified)
1273
1443
    finally:
 
1444
        target_tree.unlock()
1274
1445
        tt.finalize()
1275
1446
        pb.clear()
1276
1447
    return conflicts
1277
1448
 
1278
1449
 
 
1450
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
 
1451
                 backups):
 
1452
    merge_modified = working_tree.merge_modified()
 
1453
    change_list = target_tree._iter_changes(working_tree,
 
1454
        specific_files=specific_files, pb=pb)
 
1455
    if target_tree.inventory.root is None:
 
1456
        skip_root = True
 
1457
    else:
 
1458
        skip_root = False
 
1459
    basis_tree = None
 
1460
    try:
 
1461
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
1462
                kind, executable) in enumerate(change_list):
 
1463
            if skip_root and file_id[0] is not None and parent[0] is None:
 
1464
                continue
 
1465
            trans_id = tt.trans_id_file_id(file_id)
 
1466
            mode_id = None
 
1467
            if changed_content:
 
1468
                keep_content = False
 
1469
                if kind[0] == 'file' and (backups or kind[1] is None):
 
1470
                    wt_sha1 = working_tree.get_file_sha1(file_id)
 
1471
                    if merge_modified.get(file_id) != wt_sha1:
 
1472
                        # acquire the basis tree lazyily to prevent the expense
 
1473
                        # of accessing it when its not needed ? (Guessing, RBC,
 
1474
                        # 200702)
 
1475
                        if basis_tree is None:
 
1476
                            basis_tree = working_tree.basis_tree()
 
1477
                            basis_tree.lock_read()
 
1478
                        if file_id in basis_tree:
 
1479
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
 
1480
                                keep_content = True
 
1481
                        elif kind[1] is None and not versioned[1]:
 
1482
                            keep_content = True
 
1483
                if kind[0] is not None:
 
1484
                    if not keep_content:
 
1485
                        tt.delete_contents(trans_id)
 
1486
                    elif kind[1] is not None:
 
1487
                        parent_trans_id = tt.trans_id_file_id(parent[0])
 
1488
                        by_parent = tt.by_parent()
 
1489
                        backup_name = _get_backup_name(name[0], by_parent,
 
1490
                                                       parent_trans_id, tt)
 
1491
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
 
1492
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
 
1493
                        if versioned == (True, True):
 
1494
                            tt.unversion_file(trans_id)
 
1495
                            tt.version_file(file_id, new_trans_id)
 
1496
                        # New contents should have the same unix perms as old
 
1497
                        # contents
 
1498
                        mode_id = trans_id
 
1499
                        trans_id = new_trans_id
 
1500
                if kind[1] == 'directory':
 
1501
                    tt.create_directory(trans_id)
 
1502
                elif kind[1] == 'symlink':
 
1503
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
 
1504
                                      trans_id)
 
1505
                elif kind[1] == 'file':
 
1506
                    tt.create_file(target_tree.get_file_lines(file_id),
 
1507
                                   trans_id, mode_id)
 
1508
                    if basis_tree is None:
 
1509
                        basis_tree = working_tree.basis_tree()
 
1510
                        basis_tree.lock_read()
 
1511
                    new_sha1 = target_tree.get_file_sha1(file_id)
 
1512
                    if (file_id in basis_tree and new_sha1 ==
 
1513
                        basis_tree.get_file_sha1(file_id)):
 
1514
                        if file_id in merge_modified:
 
1515
                            del merge_modified[file_id]
 
1516
                    else:
 
1517
                        merge_modified[file_id] = new_sha1
 
1518
 
 
1519
                    # preserve the execute bit when backing up
 
1520
                    if keep_content and executable[0] == executable[1]:
 
1521
                        tt.set_executability(executable[1], trans_id)
 
1522
                else:
 
1523
                    assert kind[1] is None
 
1524
            if versioned == (False, True):
 
1525
                tt.version_file(file_id, trans_id)
 
1526
            if versioned == (True, False):
 
1527
                tt.unversion_file(trans_id)
 
1528
            if (name[1] is not None and 
 
1529
                (name[0] != name[1] or parent[0] != parent[1])):
 
1530
                tt.adjust_path(
 
1531
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
 
1532
            if executable[0] != executable[1] and kind[1] == "file":
 
1533
                tt.set_executability(executable[1], trans_id)
 
1534
    finally:
 
1535
        if basis_tree is not None:
 
1536
            basis_tree.unlock()
 
1537
    return merge_modified
 
1538
 
 
1539
 
1279
1540
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1280
1541
    """Make many conflict-resolution attempts, but die if they fail"""
1281
1542
    if pass_func is None: