~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Martin Pool
  • Date: 2006-08-10 01:16:16 UTC
  • mto: (1904.1.2 0.9)
  • mto: This revision was merged to the branch mainline in revision 1913.
  • Revision ID: mbp@sourcefrog.net-20060810011616-d74881eba696e746
compare_trees is deprecated in 0.9 not 0.10

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(
273
272
                os.unlink(name)
274
273
                raise
275
274
 
276
 
            f.writelines(contents)
 
275
            for segment in contents:
 
276
                f.write(segment)
277
277
        finally:
278
278
            f.close()
279
279
        self._set_mode(trans_id, mode_id, S_ISREG)
291
291
        except KeyError:
292
292
            return
293
293
        try:
294
 
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
294
            mode = os.stat(old_path).st_mode
295
295
        except OSError, e:
296
296
            if e.errno == errno.ENOENT:
297
297
                return
456
456
        try:
457
457
            return self._new_name[trans_id]
458
458
        except KeyError:
459
 
            try:
460
 
                return os.path.basename(self._tree_id_paths[trans_id])
461
 
            except KeyError:
462
 
                raise NoFinalPath(trans_id, self)
 
459
            return os.path.basename(self._tree_id_paths[trans_id])
463
460
 
464
461
    def by_parent(self):
465
462
        """Return a map of parent: children for known parents.
478
475
 
479
476
    def path_changed(self, trans_id):
480
477
        """Return True if a trans_id's path has changed."""
481
 
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
482
 
 
483
 
    def new_contents(self, trans_id):
484
 
        return (trans_id in self._new_contents)
 
478
        return trans_id in self._new_name or trans_id in self._new_parent
485
479
 
486
480
    def find_conflicts(self):
487
481
        """Find any violations of inventory or filesystem invariants"""
513
507
                        self.tree_kind(t) == 'directory'])
514
508
        for trans_id in self._removed_id:
515
509
            file_id = self.tree_file_id(trans_id)
516
 
            if self._tree.inventory[file_id].kind == 'directory':
 
510
            if self._tree.inventory[file_id].kind in ('directory', 
 
511
                                                      'root_directory'):
517
512
                parents.append(trans_id)
518
513
 
519
514
        for parent_id in parents:
572
567
            parent_id = trans_id
573
568
            while parent_id is not ROOT_PARENT:
574
569
                seen.add(parent_id)
575
 
                try:
576
 
                    parent_id = self.final_parent(parent_id)
577
 
                except KeyError:
578
 
                    break
 
570
                parent_id = self.final_parent(parent_id)
579
571
                if parent_id == trans_id:
580
572
                    conflicts.append(('parent loop', trans_id))
581
573
                if parent_id in seen:
655
647
            last_name = None
656
648
            last_trans_id = None
657
649
            for name, trans_id in name_ids:
 
650
                if name == last_name:
 
651
                    conflicts.append(('duplicate', last_trans_id, trans_id,
 
652
                    name))
658
653
                try:
659
654
                    kind = self.final_kind(trans_id)
660
655
                except NoSuchFile:
661
656
                    kind = None
662
657
                file_id = self.final_file_id(trans_id)
663
 
                if kind is None and file_id is None:
664
 
                    continue
665
 
                if name == last_name:
666
 
                    conflicts.append(('duplicate', last_trans_id, trans_id,
667
 
                    name))
668
 
                last_name = name
669
 
                last_trans_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
670
661
        return conflicts
671
662
 
672
663
    def _duplicate_ids(self):
944
935
    file_ids.sort(key=tree.id2path)
945
936
    return file_ids
946
937
 
947
 
 
948
938
def build_tree(tree, wt):
949
 
    """Create working tree for a branch, using a TreeTransform.
950
 
    
951
 
    This function should be used on empty trees, having a tree root at most.
952
 
    (see merge and revert functionality for working with existing trees)
953
 
 
954
 
    Existing files are handled like so:
955
 
    
956
 
    - Existing bzrdirs take precedence over creating new items.  They are
957
 
      created as '%s.diverted' % name.
958
 
    - Otherwise, if the content on disk matches the content we are building,
959
 
      it is silently replaced.
960
 
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
961
 
    """
962
 
    if len(wt.inventory) > 1:  # more than just a root
963
 
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
 
939
    """Create working tree for a branch, using a Transaction."""
964
940
    file_trans_id = {}
965
941
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
966
942
    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
943
    tt = TreeTransform(wt)
970
 
    divert = set()
971
944
    try:
972
945
        pp.next_phase()
973
 
        file_trans_id[wt.get_root_id()] = \
974
 
            tt.trans_id_tree_file_id(wt.get_root_id())
 
946
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
 
947
        file_ids = topology_sorted_ids(tree)
975
948
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
976
949
        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))
 
950
            for num, file_id in enumerate(file_ids):
 
951
                pb.update("Building tree", num, len(file_ids))
 
952
                entry = tree.inventory[file_id]
980
953
                if entry.parent_id is None:
981
954
                    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
955
                if entry.parent_id not in file_trans_id:
1004
956
                    raise repr(entry.parent_id)
1005
957
                parent_id = file_trans_id[entry.parent_id]
1006
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
958
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
1007
959
                                                      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
960
        finally:
1013
961
            pb.finished()
1014
962
        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
963
        tt.apply()
1026
964
    finally:
1027
965
        tt.finalize()
1028
966
        top_pb.finished()
1029
967
 
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
968
def new_by_entry(tt, entry, parent_id, tree):
1080
969
    """Create a new file according to its inventory entry"""
1081
970
    name = entry.name
1094
983
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1095
984
    """Create new file contents according to an inventory entry."""
1096
985
    if entry.kind == "file":
1097
 
        if lines is None:
 
986
        if lines == None:
1098
987
            lines = tree.get_file(entry.file_id).readlines()
1099
988
        tt.create_file(lines, trans_id, mode_id=mode_id)
1100
989
    elif entry.kind == "symlink":
1155
1044
 
1156
1045
 
1157
1046
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1158
 
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1159
 
 
1160
 
 
1161
 
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1162
1047
    """Produce a backup-style name that appears to be available"""
1163
1048
    def name_gen():
1164
1049
        counter = 1
1165
1050
        while True:
1166
 
            yield "%s.~%d~" % (name, counter)
 
1051
            yield "%s.~%d~" % (entry.name, counter)
1167
1052
            counter += 1
1168
 
    for new_name in name_gen():
1169
 
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1170
 
            return new_name
1171
 
 
 
1053
    for name in name_gen():
 
1054
        if not tt.has_named_child(by_parent, parent_trans_id, name):
 
1055
            return name
1172
1056
 
1173
1057
def _entry_changes(file_id, entry, working_tree):
1174
1058
    """Determine in which ways the inventory entry has changed.
1187
1071
        contents_mod = True
1188
1072
        meta_mod = False
1189
1073
    if has_contents is True:
1190
 
        if entry.kind != working_kind:
 
1074
        real_e_kind = entry.kind
 
1075
        if real_e_kind == 'root_directory':
 
1076
            real_e_kind = 'directory'
 
1077
        if real_e_kind != working_kind:
1191
1078
            contents_mod, meta_mod = True, False
1192
1079
        else:
1193
1080
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
1198
1085
 
1199
1086
 
1200
1087
def revert(working_tree, target_tree, filenames, backups=False, 
1201
 
           pb=DummyProgress(), change_reporter=None):
 
1088
           pb=DummyProgress()):
1202
1089
    """Revert a working tree's contents to those of a target tree."""
1203
1090
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
 
1091
    def interesting(file_id):
 
1092
        return interesting_ids is None or file_id in interesting_ids
 
1093
 
1204
1094
    tt = TreeTransform(working_tree, pb)
1205
1095
    try:
1206
 
        pp = ProgressPhase("Revert phase", 3, pb)
1207
 
        pp.next_phase()
1208
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1209
 
        try:
1210
 
            _alter_files(working_tree, target_tree, tt, child_pb, 
1211
 
                         interesting_ids, backups, change_reporter)
 
1096
        merge_modified = working_tree.merge_modified()
 
1097
        trans_id = {}
 
1098
        def trans_id_file_id(file_id):
 
1099
            try:
 
1100
                return trans_id[file_id]
 
1101
            except KeyError:
 
1102
                return tt.trans_id_tree_file_id(file_id)
 
1103
 
 
1104
        pp = ProgressPhase("Revert phase", 4, pb)
 
1105
        pp.next_phase()
 
1106
        sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
 
1107
                              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()
 
1130
        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]
1212
1142
        finally:
1213
1143
            child_pb.finished()
1214
1144
        pp.next_phase()
1229
1159
    return conflicts
1230
1160
 
1231
1161
 
1232
 
def _alter_files(working_tree, target_tree, tt, pb, interesting_ids, backups,
1233
 
                 report_changes):
1234
 
    from bzrlib import delta
1235
 
    merge_modified = working_tree.merge_modified()
1236
 
    change_list = list(target_tree._iter_changes(working_tree,
1237
 
        specific_file_ids=interesting_ids, pb=pb))
1238
 
    if target_tree.inventory.root is None:
1239
 
        skip_root = True
1240
 
    else:
1241
 
        skip_root = False
1242
 
    basis_tree = None
1243
 
    if report_changes:
1244
 
        change_reporter = delta.ChangeReporter(working_tree.inventory)
1245
 
        delta.report_changes(change_list, change_reporter)
1246
 
    for id_num, (file_id, path, changed_content, versioned, parent, name, kind,
1247
 
                 executable) in enumerate(change_list):
1248
 
        if skip_root and file_id[0] is not None and parent[0] is None:
1249
 
            continue
1250
 
        trans_id = tt.trans_id_file_id(file_id)
1251
 
        mode_id = None
1252
 
        if changed_content:
1253
 
            keep_content = False
1254
 
            if kind[0] == 'file' and (backups or kind[1] is None):
1255
 
                wt_sha1 = working_tree.get_file_sha1(file_id)
1256
 
                if merge_modified.get(file_id) != wt_sha1:
1257
 
                    if basis_tree is None:
1258
 
                        basis_tree = working_tree.basis_tree()
1259
 
                    if file_id in basis_tree:
1260
 
                        if wt_sha1 != basis_tree.get_file_sha1(file_id):
1261
 
                            keep_content = True
1262
 
                    elif kind[1] is None and not versioned[1]:
1263
 
                        keep_content = True
1264
 
            if kind[0] is not None:
1265
 
                if not keep_content:
1266
 
                    tt.delete_contents(trans_id)
1267
 
                elif kind[1] is not None:
1268
 
                    parent_trans_id = tt.trans_id_file_id(parent[0])
1269
 
                    by_parent = tt.by_parent()
1270
 
                    backup_name = _get_backup_name(name[0], by_parent,
1271
 
                                                   parent_trans_id, tt)
1272
 
                    tt.adjust_path(backup_name, parent_trans_id, trans_id)
1273
 
                    new_trans_id = tt.create_path(name[0], parent_trans_id)
1274
 
                    if versioned == (True, True):
1275
 
                        tt.unversion_file(trans_id)
1276
 
                        tt.version_file(file_id, new_trans_id)
1277
 
                    # New contents should have the same unix perms as old
1278
 
                    # contents
1279
 
                    mode_id = trans_id
1280
 
                    trans_id = new_trans_id
1281
 
            if kind[1] == 'directory':
1282
 
                tt.create_directory(trans_id)
1283
 
            elif kind[1] == 'symlink':
1284
 
                tt.create_symlink(target_tree.get_symlink_target(file_id),
1285
 
                                  trans_id)
1286
 
            elif kind[1] == 'file':
1287
 
                tt.create_file(target_tree.get_file_lines(file_id),
1288
 
                               trans_id, mode_id)
1289
 
                # preserve the execute bit when backing up
1290
 
                if keep_content and executable[0] == executable[1]:
1291
 
                    tt.set_executability(executable[1], trans_id)
1292
 
            else:
1293
 
                assert kind[1] is None
1294
 
        if versioned == (False, True):
1295
 
            tt.version_file(file_id, trans_id)
1296
 
        if versioned == (True, False):
1297
 
            tt.unversion_file(trans_id)
1298
 
        if (name[1] is not None and 
1299
 
            (name[0] != name[1] or parent[0] != parent[1])):
1300
 
            tt.adjust_path(name[1], tt.trans_id_file_id(parent[1]), trans_id)
1301
 
        if executable[0] != executable[1] and kind[1] == "file":
1302
 
            tt.set_executability(executable[1], trans_id)
1303
 
 
1304
 
 
1305
 
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
 
1162
def resolve_conflicts(tt, pb=DummyProgress()):
1306
1163
    """Make many conflict-resolution attempts, but die if they fail"""
1307
 
    if pass_func is None:
1308
 
        pass_func = conflict_pass
1309
1164
    new_conflicts = set()
1310
1165
    try:
1311
1166
        for n in range(10):
1313
1168
            conflicts = tt.find_conflicts()
1314
1169
            if len(conflicts) == 0:
1315
1170
                return new_conflicts
1316
 
            new_conflicts.update(pass_func(tt, conflicts))
 
1171
            new_conflicts.update(conflict_pass(tt, conflicts))
1317
1172
        raise MalformedTransform(conflicts=conflicts)
1318
1173
    finally:
1319
1174
        pb.clear()
1352
1207
            trans_id = conflict[1]
1353
1208
            try:
1354
1209
                tt.cancel_deletion(trans_id)
1355
 
                new_conflicts.add(('deleting parent', 'Not deleting', 
1356
 
                                   trans_id))
 
1210
                new_conflicts.add((c_type, 'Not deleting', trans_id))
1357
1211
            except KeyError:
1358
1212
                tt.create_directory(trans_id)
1359
 
                new_conflicts.add((c_type, 'Created directory', trans_id))
 
1213
                new_conflicts.add((c_type, 'Created directory.', trans_id))
1360
1214
        elif c_type == 'unversioned parent':
1361
1215
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1362
1216
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))