~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Robey Pointer
  • Date: 2006-09-03 00:28:18 UTC
  • mfrom: (1981 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1996.
  • Revision ID: robey@lag.net-20060903002818-71ca5c7bfea93a26
merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
import errno
19
19
from stat import S_ISREG
20
20
 
 
21
from bzrlib import bzrdir, errors
21
22
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
22
23
                           ReusingTransform, NotVersionedError, CantMoveRoot,
23
24
                           ExistingLimbo, ImmortalLimbo)
475
476
 
476
477
    def path_changed(self, trans_id):
477
478
        """Return True if a trans_id's path has changed."""
478
 
        return trans_id in self._new_name or trans_id in self._new_parent
 
479
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
 
480
 
 
481
    def new_contents(self, trans_id):
 
482
        return (trans_id in self._new_contents)
479
483
 
480
484
    def find_conflicts(self):
481
485
        """Find any violations of inventory or filesystem invariants"""
646
650
            last_name = None
647
651
            last_trans_id = None
648
652
            for name, trans_id in name_ids:
 
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 None and file_id is None:
 
659
                    continue
649
660
                if name == last_name:
650
661
                    conflicts.append(('duplicate', last_trans_id, trans_id,
651
662
                    name))
652
 
                try:
653
 
                    kind = self.final_kind(trans_id)
654
 
                except NoSuchFile:
655
 
                    kind = None
656
 
                file_id = self.final_file_id(trans_id)
657
 
                if kind is not None or file_id is not None:
658
 
                    last_name = name
659
 
                    last_trans_id = trans_id
 
663
                last_name = name
 
664
                last_trans_id = trans_id
660
665
        return conflicts
661
666
 
662
667
    def _duplicate_ids(self):
934
939
    file_ids.sort(key=tree.id2path)
935
940
    return file_ids
936
941
 
 
942
 
937
943
def build_tree(tree, wt):
938
 
    """Create working tree for a branch, using a Transaction."""
 
944
    """Create working tree for a branch, using a TreeTransform.
 
945
    
 
946
    This function should be used on empty trees, having a tree root at most.
 
947
    (see merge and revert functionality for working with existing trees)
 
948
 
 
949
    Existing files are handled like so:
 
950
    
 
951
    - Existing bzrdirs take precedence over creating new items.  They are
 
952
      created as '%s.diverted' % name.
 
953
    - Otherwise, if the content on disk matches the content we are building,
 
954
      it is silently replaced.
 
955
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
 
956
    """
 
957
    assert 2 > len(wt.inventory)
939
958
    file_trans_id = {}
940
959
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
941
960
    pp = ProgressPhase("Build phase", 2, top_pb)
942
961
    tt = TreeTransform(wt)
 
962
    divert = set()
943
963
    try:
944
964
        pp.next_phase()
945
 
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
946
 
        file_ids = topology_sorted_ids(tree)
 
965
        file_trans_id[wt.get_root_id()] = \
 
966
            tt.trans_id_tree_file_id(wt.get_root_id())
947
967
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
948
968
        try:
949
 
            for num, file_id in enumerate(file_ids):
950
 
                pb.update("Building tree", num, len(file_ids))
951
 
                entry = tree.inventory[file_id]
 
969
            for num, (tree_path, entry) in \
 
970
                enumerate(tree.inventory.iter_entries_by_dir()):
 
971
                pb.update("Building tree", num, len(tree.inventory))
952
972
                if entry.parent_id is None:
953
973
                    continue
 
974
                reparent = False
 
975
                file_id = entry.file_id
 
976
                target_path = wt.abspath(tree_path)
 
977
                try:
 
978
                    kind = file_kind(target_path)
 
979
                except NoSuchFile:
 
980
                    pass
 
981
                else:
 
982
                    if kind == "directory":
 
983
                        try:
 
984
                            bzrdir.BzrDir.open(target_path)
 
985
                        except errors.NotBranchError:
 
986
                            pass
 
987
                        else:
 
988
                            divert.add(file_id)
 
989
                    if (file_id not in divert and
 
990
                        _content_match(tree, entry, file_id, kind,
 
991
                        target_path)):
 
992
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
 
993
                        if kind == 'directory':
 
994
                            reparent = True
954
995
                if entry.parent_id not in file_trans_id:
955
996
                    raise repr(entry.parent_id)
956
997
                parent_id = file_trans_id[entry.parent_id]
957
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
 
998
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
958
999
                                                      tree)
 
1000
                if reparent:
 
1001
                    new_trans_id = file_trans_id[file_id]
 
1002
                    old_parent = tt.trans_id_tree_path(tree_path)
 
1003
                    _reparent_children(tt, old_parent, new_trans_id)
959
1004
        finally:
960
1005
            pb.finished()
961
1006
        pp.next_phase()
 
1007
        divert_trans = set(file_trans_id[f] for f in divert)
 
1008
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
 
1009
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
 
1010
        conflicts = cook_conflicts(raw_conflicts, tt)
 
1011
        for conflict in conflicts:
 
1012
            warning(conflict)
 
1013
        try:
 
1014
            wt.add_conflicts(conflicts)
 
1015
        except errors.UnsupportedOperation:
 
1016
            pass
962
1017
        tt.apply()
963
1018
    finally:
964
1019
        tt.finalize()
965
1020
        top_pb.finished()
966
1021
 
 
1022
 
 
1023
def _reparent_children(tt, old_parent, new_parent):
 
1024
    for child in tt.iter_tree_children(old_parent):
 
1025
        tt.adjust_path(tt.final_name(child), new_parent, child)
 
1026
 
 
1027
 
 
1028
def _content_match(tree, entry, file_id, kind, target_path):
 
1029
    if entry.kind != kind:
 
1030
        return False
 
1031
    if entry.kind == "directory":
 
1032
        return True
 
1033
    if entry.kind == "file":
 
1034
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
 
1035
            return True
 
1036
    elif entry.kind == "symlink":
 
1037
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
 
1038
            return True
 
1039
    return False
 
1040
 
 
1041
 
 
1042
def resolve_checkout(tt, conflicts, divert):
 
1043
    new_conflicts = set()
 
1044
    for c_type, conflict in ((c[0], c) for c in conflicts):
 
1045
        # Anything but a 'duplicate' would indicate programmer error
 
1046
        assert c_type == 'duplicate', c_type
 
1047
        # Now figure out which is new and which is old
 
1048
        if tt.new_contents(conflict[1]):
 
1049
            new_file = conflict[1]
 
1050
            old_file = conflict[2]
 
1051
        else:
 
1052
            new_file = conflict[2]
 
1053
            old_file = conflict[1]
 
1054
 
 
1055
        # We should only get here if the conflict wasn't completely
 
1056
        # resolved
 
1057
        final_parent = tt.final_parent(old_file)
 
1058
        if new_file in divert:
 
1059
            new_name = tt.final_name(old_file)+'.diverted'
 
1060
            tt.adjust_path(new_name, final_parent, new_file)
 
1061
            new_conflicts.add((c_type, 'Diverted to',
 
1062
                               new_file, old_file))
 
1063
        else:
 
1064
            new_name = tt.final_name(old_file)+'.moved'
 
1065
            tt.adjust_path(new_name, final_parent, old_file)
 
1066
            new_conflicts.add((c_type, 'Moved existing file to',
 
1067
                               old_file, new_file))
 
1068
    return new_conflicts
 
1069
 
 
1070
 
967
1071
def new_by_entry(tt, entry, parent_id, tree):
968
1072
    """Create a new file according to its inventory entry"""
969
1073
    name = entry.name
1085
1189
    """Revert a working tree's contents to those of a target tree."""
1086
1190
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
1087
1191
    def interesting(file_id):
1088
 
        return interesting_ids is None or file_id in interesting_ids
 
1192
        return interesting_ids is None or (file_id in interesting_ids)
1089
1193
 
1090
1194
    tt = TreeTransform(working_tree, pb)
1091
1195
    try:
1132
1236
                if file_id not in target_tree:
1133
1237
                    trans_id = tt.trans_id_tree_file_id(file_id)
1134
1238
                    tt.unversion_file(trans_id)
1135
 
                    if file_id in merge_modified:
 
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:
1136
1256
                        tt.delete_contents(trans_id)
 
1257
                    if delete_merge_modified:
1137
1258
                        del merge_modified[file_id]
1138
1259
        finally:
1139
1260
            child_pb.finished()
1155
1276
    return conflicts
1156
1277
 
1157
1278
 
1158
 
def resolve_conflicts(tt, pb=DummyProgress()):
 
1279
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1159
1280
    """Make many conflict-resolution attempts, but die if they fail"""
 
1281
    if pass_func is None:
 
1282
        pass_func = conflict_pass
1160
1283
    new_conflicts = set()
1161
1284
    try:
1162
1285
        for n in range(10):
1164
1287
            conflicts = tt.find_conflicts()
1165
1288
            if len(conflicts) == 0:
1166
1289
                return new_conflicts
1167
 
            new_conflicts.update(conflict_pass(tt, conflicts))
 
1290
            new_conflicts.update(pass_func(tt, conflicts))
1168
1291
        raise MalformedTransform(conflicts=conflicts)
1169
1292
    finally:
1170
1293
        pb.clear()