~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: John Arbash Meinel
  • Date: 2006-10-11 00:23:23 UTC
  • mfrom: (2070 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2071.
  • Revision ID: john@arbash-meinel.com-20061011002323-82ba88c293d7caff
[merge] bzr.dev 2070

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2006 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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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)
72
73
     * set_executability
73
74
    """
74
75
    def __init__(self, tree, pb=DummyProgress()):
75
 
        """Note: a write lock is taken on the tree.
 
76
        """Note: a tree_write lock is taken on the tree.
76
77
        
77
78
        Use TreeTransform.finalize() to release the lock
78
79
        """
79
80
        object.__init__(self)
80
81
        self._tree = tree
81
 
        self._tree.lock_write()
 
82
        self._tree.lock_tree_write()
82
83
        try:
83
84
            control_files = self._tree._control_files
84
85
            self._limbodir = urlutils.local_path_from_url(
291
292
        except KeyError:
292
293
            return
293
294
        try:
294
 
            mode = os.stat(old_path).st_mode
 
295
            mode = os.stat(self._tree.abspath(old_path)).st_mode
295
296
        except OSError, e:
296
297
            if e.errno == errno.ENOENT:
297
298
                return
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"""
507
511
                        self.tree_kind(t) == 'directory'])
508
512
        for trans_id in self._removed_id:
509
513
            file_id = self.tree_file_id(trans_id)
510
 
            if self._tree.inventory[file_id].kind in ('directory', 
511
 
                                                      'root_directory'):
 
514
            if self._tree.inventory[file_id].kind == 'directory':
512
515
                parents.append(trans_id)
513
516
 
514
517
        for parent_id in parents:
647
650
            last_name = None
648
651
            last_trans_id = None
649
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
650
660
                if name == last_name:
651
661
                    conflicts.append(('duplicate', last_trans_id, trans_id,
652
662
                    name))
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 not None or file_id is not None:
659
 
                    last_name = name
660
 
                    last_trans_id = trans_id
 
663
                last_name = name
 
664
                last_trans_id = trans_id
661
665
        return conflicts
662
666
 
663
667
    def _duplicate_ids(self):
935
939
    file_ids.sort(key=tree.id2path)
936
940
    return file_ids
937
941
 
 
942
 
938
943
def build_tree(tree, wt):
939
 
    """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)
940
958
    file_trans_id = {}
941
959
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
942
960
    pp = ProgressPhase("Build phase", 2, top_pb)
943
961
    tt = TreeTransform(wt)
 
962
    divert = set()
944
963
    try:
945
964
        pp.next_phase()
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)
 
965
        file_trans_id[wt.get_root_id()] = \
 
966
            tt.trans_id_tree_file_id(wt.get_root_id())
948
967
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
949
968
        try:
950
 
            for num, file_id in enumerate(file_ids):
951
 
                pb.update("Building tree", num, len(file_ids))
952
 
                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))
953
972
                if entry.parent_id is None:
954
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
955
995
                if entry.parent_id not in file_trans_id:
956
996
                    raise repr(entry.parent_id)
957
997
                parent_id = file_trans_id[entry.parent_id]
958
 
                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,
959
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)
960
1004
        finally:
961
1005
            pb.finished()
962
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
963
1017
        tt.apply()
964
1018
    finally:
965
1019
        tt.finalize()
966
1020
        top_pb.finished()
967
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
 
968
1071
def new_by_entry(tt, entry, parent_id, tree):
969
1072
    """Create a new file according to its inventory entry"""
970
1073
    name = entry.name
983
1086
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
984
1087
    """Create new file contents according to an inventory entry."""
985
1088
    if entry.kind == "file":
986
 
        if lines == None:
 
1089
        if lines is None:
987
1090
            lines = tree.get_file(entry.file_id).readlines()
988
1091
        tt.create_file(lines, trans_id, mode_id=mode_id)
989
1092
    elif entry.kind == "symlink":
1071
1174
        contents_mod = True
1072
1175
        meta_mod = False
1073
1176
    if has_contents is True:
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:
 
1177
        if entry.kind != working_kind:
1078
1178
            contents_mod, meta_mod = True, False
1079
1179
        else:
1080
1180
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
1089
1189
    """Revert a working tree's contents to those of a target tree."""
1090
1190
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
1091
1191
    def interesting(file_id):
1092
 
        return interesting_ids is None or file_id in interesting_ids
 
1192
        return interesting_ids is None or (file_id in interesting_ids)
1093
1193
 
1094
1194
    tt = TreeTransform(working_tree, pb)
1095
1195
    try:
1129
1229
        pp.next_phase()
1130
1230
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1131
1231
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1232
        basis_tree = None
1132
1233
        try:
1133
1234
            for id_num, file_id in enumerate(wt_interesting):
1134
1235
                child_pb.update("New file check", id_num+1, 
1136
1237
                if file_id not in target_tree:
1137
1238
                    trans_id = tt.trans_id_tree_file_id(file_id)
1138
1239
                    tt.unversion_file(trans_id)
1139
 
                    if file_id in merge_modified:
 
1240
                    try:
 
1241
                        file_kind = working_tree.kind(file_id)
 
1242
                    except NoSuchFile:
 
1243
                        file_kind = None
 
1244
                    delete_merge_modified = (file_id in merge_modified)
 
1245
                    if file_kind != 'file' and file_kind is not None:
 
1246
                        keep_contents = False
 
1247
                    else:
 
1248
                        if basis_tree is None:
 
1249
                            basis_tree = working_tree.basis_tree()
 
1250
                        wt_sha1 = working_tree.get_file_sha1(file_id)
 
1251
                        if (file_id in merge_modified and 
 
1252
                            merge_modified[file_id] == wt_sha1):
 
1253
                            keep_contents = False
 
1254
                        elif (file_id in basis_tree and 
 
1255
                            basis_tree.get_file_sha1(file_id) == wt_sha1):
 
1256
                            keep_contents = False
 
1257
                        else:
 
1258
                            keep_contents = True
 
1259
                    if not keep_contents:
1140
1260
                        tt.delete_contents(trans_id)
 
1261
                    if delete_merge_modified:
1141
1262
                        del merge_modified[file_id]
1142
1263
        finally:
1143
1264
            child_pb.finished()
1159
1280
    return conflicts
1160
1281
 
1161
1282
 
1162
 
def resolve_conflicts(tt, pb=DummyProgress()):
 
1283
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1163
1284
    """Make many conflict-resolution attempts, but die if they fail"""
 
1285
    if pass_func is None:
 
1286
        pass_func = conflict_pass
1164
1287
    new_conflicts = set()
1165
1288
    try:
1166
1289
        for n in range(10):
1168
1291
            conflicts = tt.find_conflicts()
1169
1292
            if len(conflicts) == 0:
1170
1293
                return new_conflicts
1171
 
            new_conflicts.update(conflict_pass(tt, conflicts))
 
1294
            new_conflicts.update(pass_func(tt, conflicts))
1172
1295
        raise MalformedTransform(conflicts=conflicts)
1173
1296
    finally:
1174
1297
        pb.clear()
1207
1330
            trans_id = conflict[1]
1208
1331
            try:
1209
1332
                tt.cancel_deletion(trans_id)
1210
 
                new_conflicts.add((c_type, 'Not deleting', trans_id))
 
1333
                new_conflicts.add(('deleting parent', 'Not deleting', 
 
1334
                                   trans_id))
1211
1335
            except KeyError:
1212
1336
                tt.create_directory(trans_id)
1213
 
                new_conflicts.add((c_type, 'Created directory.', trans_id))
 
1337
                new_conflicts.add((c_type, 'Created directory', trans_id))
1214
1338
        elif c_type == 'unversioned parent':
1215
1339
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1216
1340
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))