~ubuntu-branches/ubuntu/hardy/bzr/hardy-updates

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Bazaar Package Importer
  • Author(s): Etienne Goyer
  • Date: 2007-03-07 08:55:49 UTC
  • mto: This revision was merged to the branch mainline in revision 35.
  • Revision ID: james.westby@ubuntu.com-20070307085549-2y0nzag1hotluiah
Tags: upstream-0.15~rc1
ImportĀ upstreamĀ versionĀ 0.15~rc1

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
32
                           ExistingLimbo, ImmortalLimbo, NoFinalPath)
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)
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
778
791
                    file_id = self.tree_file_id(trans_id)
779
792
                    if file_id is not None:
780
793
                        limbo_inv[trans_id] = inv[file_id]
781
 
                        del inv[file_id]
 
794
                        inv.remove_recursive_id(file_id)
782
795
        finally:
783
796
            child_pb.finished()
784
797
 
815
828
                if trans_id in self._new_id:
816
829
                    if kind is None:
817
830
                        kind = file_kind(self._tree.abspath(path))
818
 
                    inv.add_path(path, kind, self._new_id[trans_id])
 
831
                    if trans_id in self._new_reference_revision:
 
832
                        entry = inventory.TreeReference(self._new_id[trans_id], 
 
833
                            self._new_name[trans_id], 
 
834
                            self.final_file_id(self._new_parent[trans_id]),
 
835
                            None, self._new_reference_revision[trans_id])
 
836
                        inv.add(entry)
 
837
                    else:
 
838
                        inv.add_path(path, kind, self._new_id[trans_id])
819
839
                elif trans_id in self._new_name or trans_id in\
820
840
                    self._new_parent:
821
841
                    entry = limbo_inv.get(trans_id)
904
924
        self.create_symlink(target, trans_id)
905
925
        return trans_id
906
926
 
 
927
    def _affected_ids(self):
 
928
        """Return the set of transform ids affected by the transform"""
 
929
        trans_ids = set(self._removed_id)
 
930
        trans_ids.update(self._new_id.keys())
 
931
        trans_ids.update(self._removed_contents)
 
932
        trans_ids.update(self._new_contents.keys())
 
933
        trans_ids.update(self._new_executability.keys())
 
934
        trans_ids.update(self._new_name.keys())
 
935
        trans_ids.update(self._new_parent.keys())
 
936
        return trans_ids
 
937
 
 
938
    def _get_file_id_maps(self):
 
939
        """Return mapping of file_ids to trans_ids in the to and from states"""
 
940
        trans_ids = self._affected_ids()
 
941
        from_trans_ids = {}
 
942
        to_trans_ids = {}
 
943
        # Build up two dicts: trans_ids associated with file ids in the
 
944
        # FROM state, vs the TO state.
 
945
        for trans_id in trans_ids:
 
946
            from_file_id = self.tree_file_id(trans_id)
 
947
            if from_file_id is not None:
 
948
                from_trans_ids[from_file_id] = trans_id
 
949
            to_file_id = self.final_file_id(trans_id)
 
950
            if to_file_id is not None:
 
951
                to_trans_ids[to_file_id] = trans_id
 
952
        return from_trans_ids, to_trans_ids
 
953
 
 
954
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
 
955
        """Get data about a file in the from (tree) state
 
956
 
 
957
        Return a (name, parent, kind, executable) tuple
 
958
        """
 
959
        from_path = self._tree_id_paths.get(from_trans_id)
 
960
        if from_versioned:
 
961
            # get data from working tree if versioned
 
962
            from_entry = self._tree.inventory[file_id]
 
963
            from_name = from_entry.name
 
964
            from_parent = from_entry.parent_id
 
965
        else:
 
966
            from_entry = None
 
967
            if from_path is None:
 
968
                # File does not exist in FROM state
 
969
                from_name = None
 
970
                from_parent = None
 
971
            else:
 
972
                # File exists, but is not versioned.  Have to use path-
 
973
                # splitting stuff
 
974
                from_name = os.path.basename(from_path)
 
975
                tree_parent = self.get_tree_parent(from_trans_id)
 
976
                from_parent = self.tree_file_id(tree_parent)
 
977
        if from_path is not None:
 
978
            from_kind, from_executable, from_stats = \
 
979
                self._tree._comparison_data(from_entry, from_path)
 
980
        else:
 
981
            from_kind = None
 
982
            from_executable = False
 
983
        return from_name, from_parent, from_kind, from_executable
 
984
 
 
985
    def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
 
986
        """Get data about a file in the to (target) state
 
987
 
 
988
        Return a (name, parent, kind, executable) tuple
 
989
        """
 
990
        to_name = self.final_name(to_trans_id)
 
991
        try:
 
992
            to_kind = self.final_kind(to_trans_id)
 
993
        except NoSuchFile:
 
994
            to_kind = None
 
995
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
 
996
        if to_trans_id in self._new_executability:
 
997
            to_executable = self._new_executability[to_trans_id]
 
998
        elif to_trans_id == from_trans_id:
 
999
            to_executable = from_executable
 
1000
        else:
 
1001
            to_executable = False
 
1002
        return to_name, to_parent, to_kind, to_executable
 
1003
 
 
1004
    def _iter_changes(self):
 
1005
        """Produce output in the same format as Tree._iter_changes.
 
1006
 
 
1007
        Will produce nonsensical results if invoked while inventory/filesystem
 
1008
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
 
1009
 
 
1010
        This reads the Transform, but only reproduces changes involving a
 
1011
        file_id.  Files that are not versioned in either of the FROM or TO
 
1012
        states are not reflected.
 
1013
        """
 
1014
        final_paths = FinalPaths(self)
 
1015
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
 
1016
        results = []
 
1017
        # Now iterate through all active file_ids
 
1018
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
 
1019
            modified = False
 
1020
            from_trans_id = from_trans_ids.get(file_id)
 
1021
            # find file ids, and determine versioning state
 
1022
            if from_trans_id is None:
 
1023
                from_versioned = False
 
1024
                from_trans_id = to_trans_ids[file_id]
 
1025
            else:
 
1026
                from_versioned = True
 
1027
            to_trans_id = to_trans_ids.get(file_id)
 
1028
            if to_trans_id is None:
 
1029
                to_versioned = False
 
1030
                to_trans_id = from_trans_id
 
1031
            else:
 
1032
                to_versioned = True
 
1033
 
 
1034
            from_name, from_parent, from_kind, from_executable = \
 
1035
                self._from_file_data(from_trans_id, from_versioned, file_id)
 
1036
 
 
1037
            to_name, to_parent, to_kind, to_executable = \
 
1038
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
 
1039
 
 
1040
            if not from_versioned:
 
1041
                from_path = None
 
1042
            else:
 
1043
                from_path = self._tree_id_paths.get(from_trans_id)
 
1044
            if not to_versioned:
 
1045
                to_path = None
 
1046
            else:
 
1047
                to_path = final_paths.get_path(to_trans_id)
 
1048
            if from_kind != to_kind:
 
1049
                modified = True
 
1050
            elif to_kind in ('file' or 'symlink') and (
 
1051
                to_trans_id != from_trans_id or
 
1052
                to_trans_id in self._new_contents):
 
1053
                modified = True
 
1054
            if (not modified and from_versioned == to_versioned and
 
1055
                from_parent==to_parent and from_name == to_name and
 
1056
                from_executable == to_executable):
 
1057
                continue
 
1058
            results.append((file_id, (from_path, to_path), modified,
 
1059
                   (from_versioned, to_versioned),
 
1060
                   (from_parent, to_parent),
 
1061
                   (from_name, to_name),
 
1062
                   (from_kind, to_kind),
 
1063
                   (from_executable, to_executable)))
 
1064
        return iter(sorted(results, key=lambda x:x[1]))
 
1065
 
 
1066
 
907
1067
def joinpath(parent, child):
908
1068
    """Join tree-relative paths, handling the tree root specially"""
909
1069
    if parent is None or parent == "":
960
1120
      it is silently replaced.
961
1121
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
962
1122
    """
 
1123
    wt.lock_tree_write()
 
1124
    try:
 
1125
        tree.lock_read()
 
1126
        try:
 
1127
            return _build_tree(tree, wt)
 
1128
        finally:
 
1129
            tree.unlock()
 
1130
    finally:
 
1131
        wt.unlock()
 
1132
 
 
1133
def _build_tree(tree, wt):
 
1134
    """See build_tree."""
963
1135
    if len(wt.inventory) > 1:  # more than just a root
964
1136
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
965
1137
    file_trans_id = {}
966
1138
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
967
1139
    pp = ProgressPhase("Build phase", 2, top_pb)
968
1140
    if tree.inventory.root is not None:
969
 
        wt.set_root_id(tree.inventory.root.file_id)
 
1141
        # this is kindof a hack: we should be altering the root 
 
1142
        # as partof the regular tree shape diff logic.
 
1143
        # the conditional test hereis to avoid doing an
 
1144
        # expensive operation (flush) every time the root id
 
1145
        # is set within the tree, nor setting the root and thus
 
1146
        # marking the tree as dirty, because we use two different
 
1147
        # idioms here: tree interfaces and inventory interfaces.
 
1148
        if wt.path2id('') != tree.inventory.root.file_id:
 
1149
            wt.set_root_id(tree.inventory.root.file_id)
 
1150
            wt.flush()
970
1151
    tt = TreeTransform(wt)
971
1152
    divert = set()
972
1153
    try:
1002
1183
                        if kind == 'directory':
1003
1184
                            reparent = True
1004
1185
                if entry.parent_id not in file_trans_id:
1005
 
                    raise repr(entry.parent_id)
 
1186
                    raise AssertionError(
 
1187
                        'entry %s parent id %r is not in file_trans_id %r'
 
1188
                        % (entry, entry.parent_id, file_trans_id))
1006
1189
                parent_id = file_trans_id[entry.parent_id]
1007
1190
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1008
1191
                                                      tree)
1086
1269
        executable = tree.is_executable(entry.file_id)
1087
1270
        return tt.new_file(name, parent_id, contents, entry.file_id, 
1088
1271
                           executable)
1089
 
    elif kind == 'directory':
1090
 
        return tt.new_directory(name, parent_id, entry.file_id)
 
1272
    elif kind in ('directory', 'tree-reference'):
 
1273
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
 
1274
        if kind == 'tree-reference':
 
1275
            tt.set_tree_reference(entry.reference_revision, trans_id)
 
1276
        return trans_id 
1091
1277
    elif kind == 'symlink':
1092
1278
        target = tree.get_symlink_target(entry.file_id)
1093
1279
        return tt.new_symlink(name, parent_id, target, entry.file_id)
 
1280
    else:
 
1281
        raise errors.BadFileKindError(name, kind)
1094
1282
 
1095
1283
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1096
1284
    """Create new file contents according to an inventory entry."""
1109
1297
        tt.set_executability(entry.executable, trans_id)
1110
1298
 
1111
1299
 
 
1300
@deprecated_function(zero_fifteen)
1112
1301
def find_interesting(working_tree, target_tree, filenames):
1113
 
    """Find the ids corresponding to specified filenames."""
1114
 
    trees = (working_tree, target_tree)
1115
 
    return tree.find_ids_across_trees(filenames, trees)
 
1302
    """Find the ids corresponding to specified filenames.
 
1303
    
 
1304
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
 
1305
    """
 
1306
    working_tree.lock_read()
 
1307
    try:
 
1308
        target_tree.lock_read()
 
1309
        try:
 
1310
            return working_tree.paths2ids(filenames, [target_tree])
 
1311
        finally:
 
1312
            target_tree.unlock()
 
1313
    finally:
 
1314
        working_tree.unlock()
1116
1315
 
1117
1316
 
1118
1317
def change_entry(tt, file_id, working_tree, target_tree, 
1198
1397
    return has_contents, contents_mod, meta_mod
1199
1398
 
1200
1399
 
1201
 
def revert(working_tree, target_tree, filenames, backups=False, 
1202
 
           pb=DummyProgress()):
 
1400
def revert(working_tree, target_tree, filenames, backups=False,
 
1401
           pb=DummyProgress(), change_reporter=None):
1203
1402
    """Revert a working tree's contents to those of a target tree."""
1204
 
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
 
1403
    target_tree.lock_read()
1205
1404
    tt = TreeTransform(working_tree, pb)
1206
1405
    try:
1207
1406
        pp = ProgressPhase("Revert phase", 3, pb)
1208
1407
        pp.next_phase()
1209
1408
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1210
1409
        try:
1211
 
            _alter_files(working_tree, target_tree, tt, child_pb, 
1212
 
                         interesting_ids, backups)
 
1410
            _alter_files(working_tree, target_tree, tt, child_pb,
 
1411
                         filenames, backups)
1213
1412
        finally:
1214
1413
            child_pb.finished()
1215
1414
        pp.next_phase()
1219
1418
        finally:
1220
1419
            child_pb.finished()
1221
1420
        conflicts = cook_conflicts(raw_conflicts, tt)
 
1421
        if change_reporter:
 
1422
            change_reporter = delta.ChangeReporter(
 
1423
                unversioned_filter=working_tree.is_ignored)
 
1424
            delta.report_changes(tt._iter_changes(), change_reporter)
1222
1425
        for conflict in conflicts:
1223
1426
            warning(conflict)
1224
1427
        pp.next_phase()
1225
1428
        tt.apply()
1226
1429
        working_tree.set_merge_modified({})
1227
1430
    finally:
 
1431
        target_tree.unlock()
1228
1432
        tt.finalize()
1229
1433
        pb.clear()
1230
1434
    return conflicts
1231
1435
 
1232
1436
 
1233
 
def _alter_files(working_tree, target_tree, tt, pb, interesting_ids, backups):
 
1437
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
 
1438
                 backups):
1234
1439
    merge_modified = working_tree.merge_modified()
1235
 
    iterator = target_tree._iter_changes(working_tree, 
1236
 
                                         specific_file_ids=interesting_ids,
1237
 
                                         pb=pb)
 
1440
    change_list = target_tree._iter_changes(working_tree,
 
1441
        specific_files=specific_files, pb=pb)
1238
1442
    if target_tree.inventory.root is None:
1239
1443
        skip_root = True
1240
1444
    else:
1241
1445
        skip_root = False
1242
1446
    basis_tree = None
1243
 
    for id_num, (file_id, path, changed_content, versioned, parent, name, kind,
1244
 
                 executable) in enumerate(iterator):
1245
 
        if skip_root and file_id[0] is not None and parent[0] is None:
1246
 
            continue
1247
 
        trans_id = tt.trans_id_file_id(file_id)
1248
 
        mode_id = None
1249
 
        if changed_content:
1250
 
            keep_content = False
1251
 
            if kind[0] == 'file' and (backups or kind[1] is None):
1252
 
                wt_sha1 = working_tree.get_file_sha1(file_id)
1253
 
                if merge_modified.get(file_id) != wt_sha1:
1254
 
                    if basis_tree is None:
1255
 
                        basis_tree = working_tree.basis_tree()
1256
 
                    if file_id in basis_tree:
1257
 
                        if wt_sha1 != basis_tree.get_file_sha1(file_id):
 
1447
    try:
 
1448
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
1449
                kind, executable) in enumerate(change_list):
 
1450
            if skip_root and file_id[0] is not None and parent[0] is None:
 
1451
                continue
 
1452
            trans_id = tt.trans_id_file_id(file_id)
 
1453
            mode_id = None
 
1454
            if changed_content:
 
1455
                keep_content = False
 
1456
                if kind[0] == 'file' and (backups or kind[1] is None):
 
1457
                    wt_sha1 = working_tree.get_file_sha1(file_id)
 
1458
                    if merge_modified.get(file_id) != wt_sha1:
 
1459
                        # acquire the basis tree lazyily to prevent the expense
 
1460
                        # of accessing it when its not needed ? (Guessing, RBC,
 
1461
                        # 200702)
 
1462
                        if basis_tree is None:
 
1463
                            basis_tree = working_tree.basis_tree()
 
1464
                            basis_tree.lock_read()
 
1465
                        if file_id in basis_tree:
 
1466
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
 
1467
                                keep_content = True
 
1468
                        elif kind[1] is None and not versioned[1]:
1258
1469
                            keep_content = True
1259
 
                    elif kind[1] is None and not versioned[1]:
1260
 
                        keep_content = True
1261
 
            if kind[0] is not None:
1262
 
                if not keep_content:
1263
 
                    tt.delete_contents(trans_id)
1264
 
                elif kind[1] is not None:
1265
 
                    parent_trans_id = tt.trans_id_file_id(parent[0])
1266
 
                    by_parent = tt.by_parent()
1267
 
                    backup_name = _get_backup_name(name[0], by_parent,
1268
 
                                                   parent_trans_id, tt)
1269
 
                    tt.adjust_path(backup_name, parent_trans_id, trans_id)
1270
 
                    new_trans_id = tt.create_path(name[0], parent_trans_id)
1271
 
                    if versioned == (True, True):
1272
 
                        tt.unversion_file(trans_id)
1273
 
                        tt.version_file(file_id, new_trans_id)
1274
 
                    # New contents should have the same unix perms as old
1275
 
                    # contents
1276
 
                    mode_id = trans_id
1277
 
                    trans_id = new_trans_id
1278
 
            if kind[1] == 'directory':
1279
 
                tt.create_directory(trans_id)
1280
 
            elif kind[1] == 'symlink':
1281
 
                tt.create_symlink(target_tree.get_symlink_target(file_id),
1282
 
                                  trans_id)
1283
 
            elif kind[1] == 'file':
1284
 
                tt.create_file(target_tree.get_file_lines(file_id),
1285
 
                               trans_id, mode_id)
1286
 
                # preserve the execute bit when backing up
1287
 
                if keep_content and executable[0] == executable[1]:
1288
 
                    tt.set_executability(executable[1], trans_id)
1289
 
            else:
1290
 
                assert kind[1] is None
1291
 
        if versioned == (False, True):
1292
 
            tt.version_file(file_id, trans_id)
1293
 
        if versioned == (True, False):
1294
 
            tt.unversion_file(trans_id)
1295
 
        if (name[1] is not None and 
1296
 
            (name[0] != name[1] or parent[0] != parent[1])):
1297
 
            tt.adjust_path(name[1], tt.trans_id_file_id(parent[1]), trans_id)
1298
 
        if executable[0] != executable[1] and kind[1] == "file":
1299
 
            tt.set_executability(executable[1], trans_id)
 
1470
                if kind[0] is not None:
 
1471
                    if not keep_content:
 
1472
                        tt.delete_contents(trans_id)
 
1473
                    elif kind[1] is not None:
 
1474
                        parent_trans_id = tt.trans_id_file_id(parent[0])
 
1475
                        by_parent = tt.by_parent()
 
1476
                        backup_name = _get_backup_name(name[0], by_parent,
 
1477
                                                       parent_trans_id, tt)
 
1478
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
 
1479
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
 
1480
                        if versioned == (True, True):
 
1481
                            tt.unversion_file(trans_id)
 
1482
                            tt.version_file(file_id, new_trans_id)
 
1483
                        # New contents should have the same unix perms as old
 
1484
                        # contents
 
1485
                        mode_id = trans_id
 
1486
                        trans_id = new_trans_id
 
1487
                if kind[1] == 'directory':
 
1488
                    tt.create_directory(trans_id)
 
1489
                elif kind[1] == 'symlink':
 
1490
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
 
1491
                                      trans_id)
 
1492
                elif kind[1] == 'file':
 
1493
                    tt.create_file(target_tree.get_file_lines(file_id),
 
1494
                                   trans_id, mode_id)
 
1495
                    # preserve the execute bit when backing up
 
1496
                    if keep_content and executable[0] == executable[1]:
 
1497
                        tt.set_executability(executable[1], trans_id)
 
1498
                else:
 
1499
                    assert kind[1] is None
 
1500
            if versioned == (False, True):
 
1501
                tt.version_file(file_id, trans_id)
 
1502
            if versioned == (True, False):
 
1503
                tt.unversion_file(trans_id)
 
1504
            if (name[1] is not None and 
 
1505
                (name[0] != name[1] or parent[0] != parent[1])):
 
1506
                tt.adjust_path(
 
1507
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
 
1508
            if executable[0] != executable[1] and kind[1] == "file":
 
1509
                tt.set_executability(executable[1], trans_id)
 
1510
    finally:
 
1511
        if basis_tree is not None:
 
1512
            basis_tree.unlock()
1300
1513
 
1301
1514
 
1302
1515
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):