~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: John Arbash Meinel
  • Date: 2006-08-14 16:16:53 UTC
  • mto: (1946.2.6 reduce-knit-churn)
  • mto: This revision was merged to the branch mainline in revision 1919.
  • Revision ID: john@arbash-meinel.com-20060814161653-54cdcdadcd4e9003
Remove bogus entry from BRANCH.TODO

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
22
21
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
22
                           ReusingTransform, NotVersionedError, CantMoveRoot,
24
 
                           ExistingLimbo, ImmortalLimbo, NoFinalPath)
 
23
                           ExistingLimbo, ImmortalLimbo)
25
24
from bzrlib.inventory import InventoryEntry
26
25
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
27
26
                            delete_any)
73
72
     * set_executability
74
73
    """
75
74
    def __init__(self, tree, pb=DummyProgress()):
76
 
        """Note: a tree_write lock is taken on the tree.
 
75
        """Note: a write lock is taken on the tree.
77
76
        
78
77
        Use TreeTransform.finalize() to release the lock
79
78
        """
80
79
        object.__init__(self)
81
80
        self._tree = tree
82
 
        self._tree.lock_tree_write()
 
81
        self._tree.lock_write()
83
82
        try:
84
83
            control_files = self._tree._control_files
85
84
            self._limbodir = urlutils.local_path_from_url(
292
291
        except KeyError:
293
292
            return
294
293
        try:
295
 
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
294
            mode = os.stat(old_path).st_mode
296
295
        except OSError, e:
297
296
            if e.errno == errno.ENOENT:
298
297
                return
457
456
        try:
458
457
            return self._new_name[trans_id]
459
458
        except KeyError:
460
 
            try:
461
 
                return os.path.basename(self._tree_id_paths[trans_id])
462
 
            except KeyError:
463
 
                raise NoFinalPath(trans_id, self)
 
459
            return os.path.basename(self._tree_id_paths[trans_id])
464
460
 
465
461
    def by_parent(self):
466
462
        """Return a map of parent: children for known parents.
479
475
 
480
476
    def path_changed(self, trans_id):
481
477
        """Return True if a trans_id's path has changed."""
482
 
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
483
 
 
484
 
    def new_contents(self, trans_id):
485
 
        return (trans_id in self._new_contents)
 
478
        return trans_id in self._new_name or trans_id in self._new_parent
486
479
 
487
480
    def find_conflicts(self):
488
481
        """Find any violations of inventory or filesystem invariants"""
573
566
            parent_id = trans_id
574
567
            while parent_id is not ROOT_PARENT:
575
568
                seen.add(parent_id)
576
 
                try:
577
 
                    parent_id = self.final_parent(parent_id)
578
 
                except KeyError:
579
 
                    break
 
569
                parent_id = self.final_parent(parent_id)
580
570
                if parent_id == trans_id:
581
571
                    conflicts.append(('parent loop', trans_id))
582
572
                if parent_id in seen:
656
646
            last_name = None
657
647
            last_trans_id = None
658
648
            for name, trans_id in name_ids:
 
649
                if name == last_name:
 
650
                    conflicts.append(('duplicate', last_trans_id, trans_id,
 
651
                    name))
659
652
                try:
660
653
                    kind = self.final_kind(trans_id)
661
654
                except NoSuchFile:
662
655
                    kind = None
663
656
                file_id = self.final_file_id(trans_id)
664
 
                if kind is None and file_id is None:
665
 
                    continue
666
 
                if name == last_name:
667
 
                    conflicts.append(('duplicate', last_trans_id, trans_id,
668
 
                    name))
669
 
                last_name = name
670
 
                last_trans_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
671
660
        return conflicts
672
661
 
673
662
    def _duplicate_ids(self):
945
934
    file_ids.sort(key=tree.id2path)
946
935
    return file_ids
947
936
 
948
 
 
949
937
def build_tree(tree, wt):
950
 
    """Create working tree for a branch, using a TreeTransform.
951
 
    
952
 
    This function should be used on empty trees, having a tree root at most.
953
 
    (see merge and revert functionality for working with existing trees)
954
 
 
955
 
    Existing files are handled like so:
956
 
    
957
 
    - Existing bzrdirs take precedence over creating new items.  They are
958
 
      created as '%s.diverted' % name.
959
 
    - Otherwise, if the content on disk matches the content we are building,
960
 
      it is silently replaced.
961
 
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
962
 
    """
963
 
    assert 2 > len(wt.inventory)
 
938
    """Create working tree for a branch, using a Transaction."""
964
939
    file_trans_id = {}
965
940
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
966
941
    pp = ProgressPhase("Build phase", 2, top_pb)
967
 
    if tree.inventory.root is not None:
968
 
        wt.set_root_id(tree.inventory.root.file_id)
969
942
    tt = TreeTransform(wt)
970
 
    divert = set()
971
943
    try:
972
944
        pp.next_phase()
973
 
        file_trans_id[wt.get_root_id()] = \
974
 
            tt.trans_id_tree_file_id(wt.get_root_id())
 
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)
975
947
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
976
948
        try:
977
 
            for num, (tree_path, entry) in \
978
 
                enumerate(tree.inventory.iter_entries_by_dir()):
979
 
                pb.update("Building tree", num, len(tree.inventory))
 
949
            for num, file_id in enumerate(file_ids):
 
950
                pb.update("Building tree", num, len(file_ids))
 
951
                entry = tree.inventory[file_id]
980
952
                if entry.parent_id is None:
981
953
                    continue
982
 
                reparent = False
983
 
                file_id = entry.file_id
984
 
                target_path = wt.abspath(tree_path)
985
 
                try:
986
 
                    kind = file_kind(target_path)
987
 
                except NoSuchFile:
988
 
                    pass
989
 
                else:
990
 
                    if kind == "directory":
991
 
                        try:
992
 
                            bzrdir.BzrDir.open(target_path)
993
 
                        except errors.NotBranchError:
994
 
                            pass
995
 
                        else:
996
 
                            divert.add(file_id)
997
 
                    if (file_id not in divert and
998
 
                        _content_match(tree, entry, file_id, kind,
999
 
                        target_path)):
1000
 
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
1001
 
                        if kind == 'directory':
1002
 
                            reparent = True
1003
954
                if entry.parent_id not in file_trans_id:
1004
955
                    raise repr(entry.parent_id)
1005
956
                parent_id = file_trans_id[entry.parent_id]
1006
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
957
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
1007
958
                                                      tree)
1008
 
                if reparent:
1009
 
                    new_trans_id = file_trans_id[file_id]
1010
 
                    old_parent = tt.trans_id_tree_path(tree_path)
1011
 
                    _reparent_children(tt, old_parent, new_trans_id)
1012
959
        finally:
1013
960
            pb.finished()
1014
961
        pp.next_phase()
1015
 
        divert_trans = set(file_trans_id[f] for f in divert)
1016
 
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1017
 
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1018
 
        conflicts = cook_conflicts(raw_conflicts, tt)
1019
 
        for conflict in conflicts:
1020
 
            warning(conflict)
1021
 
        try:
1022
 
            wt.add_conflicts(conflicts)
1023
 
        except errors.UnsupportedOperation:
1024
 
            pass
1025
962
        tt.apply()
1026
963
    finally:
1027
964
        tt.finalize()
1028
965
        top_pb.finished()
1029
966
 
1030
 
 
1031
 
def _reparent_children(tt, old_parent, new_parent):
1032
 
    for child in tt.iter_tree_children(old_parent):
1033
 
        tt.adjust_path(tt.final_name(child), new_parent, child)
1034
 
 
1035
 
 
1036
 
def _content_match(tree, entry, file_id, kind, target_path):
1037
 
    if entry.kind != kind:
1038
 
        return False
1039
 
    if entry.kind == "directory":
1040
 
        return True
1041
 
    if entry.kind == "file":
1042
 
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1043
 
            return True
1044
 
    elif entry.kind == "symlink":
1045
 
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
1046
 
            return True
1047
 
    return False
1048
 
 
1049
 
 
1050
 
def resolve_checkout(tt, conflicts, divert):
1051
 
    new_conflicts = set()
1052
 
    for c_type, conflict in ((c[0], c) for c in conflicts):
1053
 
        # Anything but a 'duplicate' would indicate programmer error
1054
 
        assert c_type == 'duplicate', c_type
1055
 
        # Now figure out which is new and which is old
1056
 
        if tt.new_contents(conflict[1]):
1057
 
            new_file = conflict[1]
1058
 
            old_file = conflict[2]
1059
 
        else:
1060
 
            new_file = conflict[2]
1061
 
            old_file = conflict[1]
1062
 
 
1063
 
        # We should only get here if the conflict wasn't completely
1064
 
        # resolved
1065
 
        final_parent = tt.final_parent(old_file)
1066
 
        if new_file in divert:
1067
 
            new_name = tt.final_name(old_file)+'.diverted'
1068
 
            tt.adjust_path(new_name, final_parent, new_file)
1069
 
            new_conflicts.add((c_type, 'Diverted to',
1070
 
                               new_file, old_file))
1071
 
        else:
1072
 
            new_name = tt.final_name(old_file)+'.moved'
1073
 
            tt.adjust_path(new_name, final_parent, old_file)
1074
 
            new_conflicts.add((c_type, 'Moved existing file to',
1075
 
                               old_file, new_file))
1076
 
    return new_conflicts
1077
 
 
1078
 
 
1079
967
def new_by_entry(tt, entry, parent_id, tree):
1080
968
    """Create a new file according to its inventory entry"""
1081
969
    name = entry.name
1094
982
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1095
983
    """Create new file contents according to an inventory entry."""
1096
984
    if entry.kind == "file":
1097
 
        if lines is None:
 
985
        if lines == None:
1098
986
            lines = tree.get_file(entry.file_id).readlines()
1099
987
        tt.create_file(lines, trans_id, mode_id=mode_id)
1100
988
    elif entry.kind == "symlink":
1197
1085
    """Revert a working tree's contents to those of a target tree."""
1198
1086
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
1199
1087
    def interesting(file_id):
1200
 
        return interesting_ids is None or (file_id in interesting_ids)
 
1088
        return interesting_ids is None or file_id in interesting_ids
1201
1089
 
1202
1090
    tt = TreeTransform(working_tree, pb)
1203
1091
    try:
1237
1125
        pp.next_phase()
1238
1126
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1239
1127
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1240
 
        basis_tree = None
1241
1128
        try:
1242
1129
            for id_num, file_id in enumerate(wt_interesting):
1243
 
                if (working_tree.inventory.is_root(file_id) and 
1244
 
                    len(target_tree.inventory) == 0):
1245
 
                    continue
1246
1130
                child_pb.update("New file check", id_num+1, 
1247
1131
                                len(sorted_interesting))
1248
1132
                if file_id not in target_tree:
1249
1133
                    trans_id = tt.trans_id_tree_file_id(file_id)
1250
1134
                    tt.unversion_file(trans_id)
1251
 
                    try:
1252
 
                        file_kind = working_tree.kind(file_id)
1253
 
                    except NoSuchFile:
1254
 
                        file_kind = None
1255
 
                    delete_merge_modified = (file_id in merge_modified)
1256
 
                    if file_kind != 'file' and file_kind is not None:
1257
 
                        keep_contents = False
1258
 
                    else:
1259
 
                        if basis_tree is None:
1260
 
                            basis_tree = working_tree.basis_tree()
1261
 
                        wt_sha1 = working_tree.get_file_sha1(file_id)
1262
 
                        if (file_id in merge_modified and 
1263
 
                            merge_modified[file_id] == wt_sha1):
1264
 
                            keep_contents = False
1265
 
                        elif (file_id in basis_tree and 
1266
 
                            basis_tree.get_file_sha1(file_id) == wt_sha1):
1267
 
                            keep_contents = False
1268
 
                        else:
1269
 
                            keep_contents = True
1270
 
                    if not keep_contents:
 
1135
                    if file_id in merge_modified:
1271
1136
                        tt.delete_contents(trans_id)
1272
 
                    if delete_merge_modified:
1273
1137
                        del merge_modified[file_id]
1274
1138
        finally:
1275
1139
            child_pb.finished()
1291
1155
    return conflicts
1292
1156
 
1293
1157
 
1294
 
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
 
1158
def resolve_conflicts(tt, pb=DummyProgress()):
1295
1159
    """Make many conflict-resolution attempts, but die if they fail"""
1296
 
    if pass_func is None:
1297
 
        pass_func = conflict_pass
1298
1160
    new_conflicts = set()
1299
1161
    try:
1300
1162
        for n in range(10):
1302
1164
            conflicts = tt.find_conflicts()
1303
1165
            if len(conflicts) == 0:
1304
1166
                return new_conflicts
1305
 
            new_conflicts.update(pass_func(tt, conflicts))
 
1167
            new_conflicts.update(conflict_pass(tt, conflicts))
1306
1168
        raise MalformedTransform(conflicts=conflicts)
1307
1169
    finally:
1308
1170
        pb.clear()
1341
1203
            trans_id = conflict[1]
1342
1204
            try:
1343
1205
                tt.cancel_deletion(trans_id)
1344
 
                new_conflicts.add(('deleting parent', 'Not deleting', 
1345
 
                                   trans_id))
 
1206
                new_conflicts.add((c_type, 'Not deleting', trans_id))
1346
1207
            except KeyError:
1347
1208
                tt.create_directory(trans_id)
1348
 
                new_conflicts.add((c_type, 'Created directory', trans_id))
 
1209
                new_conflicts.add((c_type, 'Created directory.', trans_id))
1349
1210
        elif c_type == 'unversioned parent':
1350
1211
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1351
1212
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))