/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

Add WSGI smart server.

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)
26
27
                            delete_any)
27
28
from bzrlib.progress import DummyProgress, ProgressPhase
28
29
from bzrlib.trace import mutter, warning
 
30
from bzrlib import tree
29
31
import bzrlib.ui 
 
32
import bzrlib.urlutils as urlutils
30
33
 
31
34
 
32
35
ROOT_PARENT = "root-parent"
70
73
     * set_executability
71
74
    """
72
75
    def __init__(self, tree, pb=DummyProgress()):
73
 
        """Note: a write lock is taken on the tree.
 
76
        """Note: a tree_write lock is taken on the tree.
74
77
        
75
78
        Use TreeTransform.finalize() to release the lock
76
79
        """
77
80
        object.__init__(self)
78
81
        self._tree = tree
79
 
        self._tree.lock_write()
 
82
        self._tree.lock_tree_write()
80
83
        try:
81
84
            control_files = self._tree._control_files
82
 
            self._limbodir = control_files.controlfilename('limbo')
 
85
            self._limbodir = urlutils.local_path_from_url(
 
86
                control_files.controlfilename('limbo'))
83
87
            try:
84
88
                os.mkdir(self._limbodir)
85
89
            except OSError, e:
101
105
        self._removed_id = set()
102
106
        self._tree_path_ids = {}
103
107
        self._tree_id_paths = {}
 
108
        self._realpaths = {}
 
109
        # Cache of realpath results, to speed up canonical_path
 
110
        self._relpaths = {}
 
111
        # Cache of relpath results, to speed up canonical_path
104
112
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
105
113
        self.__done = False
106
114
        self._pb = pb
211
219
    def canonical_path(self, path):
212
220
        """Get the canonical tree-relative path"""
213
221
        # don't follow final symlinks
214
 
        dirname, basename = os.path.split(self._tree.abspath(path))
215
 
        dirname = os.path.realpath(dirname)
216
 
        return self._tree.relpath(pathjoin(dirname, basename))
 
222
        abs = self._tree.abspath(path)
 
223
        if abs in self._relpaths:
 
224
            return self._relpaths[abs]
 
225
        dirname, basename = os.path.split(abs)
 
226
        if dirname not in self._realpaths:
 
227
            self._realpaths[dirname] = os.path.realpath(dirname)
 
228
        dirname = self._realpaths[dirname]
 
229
        abs = pathjoin(dirname, basename)
 
230
        if dirname in self._relpaths:
 
231
            relpath = pathjoin(self._relpaths[dirname], basename)
 
232
            relpath = relpath.rstrip('/\\')
 
233
        else:
 
234
            relpath = self._tree.relpath(abs)
 
235
        self._relpaths[abs] = relpath
 
236
        return relpath
217
237
 
218
238
    def trans_id_tree_path(self, path):
219
239
        """Determine (and maybe set) the transaction ID for a tree path."""
241
261
        New file takes the permissions of any existing file with that id,
242
262
        unless mode_id is specified.
243
263
        """
244
 
        f = file(self._limbo_name(trans_id), 'wb')
245
 
        unique_add(self._new_contents, trans_id, 'file')
246
 
        for segment in contents:
247
 
            f.write(segment)
248
 
        f.close()
 
264
        name = self._limbo_name(trans_id)
 
265
        f = open(name, 'wb')
 
266
        try:
 
267
            try:
 
268
                unique_add(self._new_contents, trans_id, 'file')
 
269
            except:
 
270
                # Clean up the file, it never got registered so
 
271
                # TreeTransform.finalize() won't clean it up.
 
272
                f.close()
 
273
                os.unlink(name)
 
274
                raise
 
275
 
 
276
            for segment in contents:
 
277
                f.write(segment)
 
278
        finally:
 
279
            f.close()
249
280
        self._set_mode(trans_id, mode_id, S_ISREG)
250
281
 
251
282
    def _set_mode(self, trans_id, mode_id, typefunc):
261
292
        except KeyError:
262
293
            return
263
294
        try:
264
 
            mode = os.stat(old_path).st_mode
 
295
            mode = os.stat(self._tree.abspath(old_path)).st_mode
265
296
        except OSError, e:
266
297
            if e.errno == errno.ENOENT:
267
298
                return
445
476
 
446
477
    def path_changed(self, trans_id):
447
478
        """Return True if a trans_id's path has changed."""
448
 
        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)
449
483
 
450
484
    def find_conflicts(self):
451
485
        """Find any violations of inventory or filesystem invariants"""
477
511
                        self.tree_kind(t) == 'directory'])
478
512
        for trans_id in self._removed_id:
479
513
            file_id = self.tree_file_id(trans_id)
480
 
            if self._tree.inventory[file_id].kind in ('directory', 
481
 
                                                      'root_directory'):
 
514
            if self._tree.inventory[file_id].kind == 'directory':
482
515
                parents.append(trans_id)
483
516
 
484
517
        for parent_id in parents:
521
554
        if child_id is None:
522
555
            return lexists(self._tree.abspath(childpath))
523
556
        else:
524
 
            if tt.final_parent(child_id) != parent_id:
 
557
            if self.final_parent(child_id) != parent_id:
525
558
                return False
526
 
            if child_id in tt._removed_contents:
 
559
            if child_id in self._removed_contents:
527
560
                # XXX What about dangling file-ids?
528
561
                return False
529
562
            else:
617
650
            last_name = None
618
651
            last_trans_id = None
619
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
620
660
                if name == last_name:
621
661
                    conflicts.append(('duplicate', last_trans_id, trans_id,
622
662
                    name))
623
 
                try:
624
 
                    kind = self.final_kind(trans_id)
625
 
                except NoSuchFile:
626
 
                    kind = None
627
 
                file_id = self.final_file_id(trans_id)
628
 
                if kind is not None or file_id is not None:
629
 
                    last_name = name
630
 
                    last_trans_id = trans_id
 
663
                last_name = name
 
664
                last_trans_id = trans_id
631
665
        return conflicts
632
666
 
633
667
    def _duplicate_ids(self):
829
863
        parent_id is the transaction id of the parent directory of the file.
830
864
        contents is an iterator of bytestrings, which will be used to produce
831
865
        the file.
832
 
        file_id is the inventory ID of the file, if it is to be versioned.
 
866
        :param file_id: The inventory ID of the file, if it is to be versioned.
 
867
        :param executable: Only valid when a file_id has been supplied.
833
868
        """
834
869
        trans_id = self._new_entry(name, parent_id, file_id)
 
870
        # TODO: rather than scheduling a set_executable call,
 
871
        # have create_file create the file with the right mode.
835
872
        self.create_file(contents, trans_id)
836
873
        if executable is not None:
837
874
            self.set_executability(executable, trans_id)
902
939
    file_ids.sort(key=tree.id2path)
903
940
    return file_ids
904
941
 
 
942
 
905
943
def build_tree(tree, wt):
906
 
    """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)
907
958
    file_trans_id = {}
908
959
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
960
    pp = ProgressPhase("Build phase", 2, top_pb)
910
961
    tt = TreeTransform(wt)
 
962
    divert = set()
911
963
    try:
912
964
        pp.next_phase()
913
 
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
914
 
        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())
915
967
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
916
968
        try:
917
 
            for num, file_id in enumerate(file_ids):
918
 
                pb.update("Building tree", num, len(file_ids))
919
 
                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))
920
972
                if entry.parent_id is None:
921
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
922
995
                if entry.parent_id not in file_trans_id:
923
996
                    raise repr(entry.parent_id)
924
997
                parent_id = file_trans_id[entry.parent_id]
925
 
                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,
926
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)
927
1004
        finally:
928
1005
            pb.finished()
929
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
930
1017
        tt.apply()
931
1018
    finally:
932
1019
        tt.finalize()
933
1020
        top_pb.finished()
934
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
 
935
1071
def new_by_entry(tt, entry, parent_id, tree):
936
1072
    """Create a new file according to its inventory entry"""
937
1073
    name = entry.name
950
1086
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
951
1087
    """Create new file contents according to an inventory entry."""
952
1088
    if entry.kind == "file":
953
 
        if lines == None:
 
1089
        if lines is None:
954
1090
            lines = tree.get_file(entry.file_id).readlines()
955
1091
        tt.create_file(lines, trans_id, mode_id=mode_id)
956
1092
    elif entry.kind == "symlink":
966
1102
 
967
1103
def find_interesting(working_tree, target_tree, filenames):
968
1104
    """Find the ids corresponding to specified filenames."""
969
 
    if not filenames:
970
 
        interesting_ids = None
971
 
    else:
972
 
        interesting_ids = set()
973
 
        for tree_path in filenames:
974
 
            not_found = True
975
 
            for tree in (working_tree, target_tree):
976
 
                file_id = tree.inventory.path2id(tree_path)
977
 
                if file_id is not None:
978
 
                    interesting_ids.add(file_id)
979
 
                    not_found = False
980
 
            if not_found:
981
 
                raise NotVersionedError(path=tree_path)
982
 
    return interesting_ids
 
1105
    trees = (working_tree, target_tree)
 
1106
    return tree.find_ids_across_trees(filenames, trees)
983
1107
 
984
1108
 
985
1109
def change_entry(tt, file_id, working_tree, target_tree, 
1045
1169
    try:
1046
1170
        working_kind = working_tree.kind(file_id)
1047
1171
        has_contents = True
1048
 
    except OSError, e:
1049
 
        if e.errno != errno.ENOENT:
1050
 
            raise
 
1172
    except NoSuchFile:
1051
1173
        has_contents = False
1052
1174
        contents_mod = True
1053
1175
        meta_mod = False
1054
1176
    if has_contents is True:
1055
 
        real_e_kind = entry.kind
1056
 
        if real_e_kind == 'root_directory':
1057
 
            real_e_kind = 'directory'
1058
 
        if real_e_kind != working_kind:
 
1177
        if entry.kind != working_kind:
1059
1178
            contents_mod, meta_mod = True, False
1060
1179
        else:
1061
1180
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
1070
1189
    """Revert a working tree's contents to those of a target tree."""
1071
1190
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
1072
1191
    def interesting(file_id):
1073
 
        return interesting_ids is None or file_id in interesting_ids
 
1192
        return interesting_ids is None or (file_id in interesting_ids)
1074
1193
 
1075
1194
    tt = TreeTransform(working_tree, pb)
1076
1195
    try:
1110
1229
        pp.next_phase()
1111
1230
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1112
1231
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1232
        basis_tree = None
1113
1233
        try:
1114
1234
            for id_num, file_id in enumerate(wt_interesting):
1115
1235
                child_pb.update("New file check", id_num+1, 
1117
1237
                if file_id not in target_tree:
1118
1238
                    trans_id = tt.trans_id_tree_file_id(file_id)
1119
1239
                    tt.unversion_file(trans_id)
1120
 
                    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:
1121
1260
                        tt.delete_contents(trans_id)
 
1261
                    if delete_merge_modified:
1122
1262
                        del merge_modified[file_id]
1123
1263
        finally:
1124
1264
            child_pb.finished()
1140
1280
    return conflicts
1141
1281
 
1142
1282
 
1143
 
def resolve_conflicts(tt, pb=DummyProgress()):
 
1283
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1284
    """Make many conflict-resolution attempts, but die if they fail"""
 
1285
    if pass_func is None:
 
1286
        pass_func = conflict_pass
1145
1287
    new_conflicts = set()
1146
1288
    try:
1147
1289
        for n in range(10):
1149
1291
            conflicts = tt.find_conflicts()
1150
1292
            if len(conflicts) == 0:
1151
1293
                return new_conflicts
1152
 
            new_conflicts.update(conflict_pass(tt, conflicts))
 
1294
            new_conflicts.update(pass_func(tt, conflicts))
1153
1295
        raise MalformedTransform(conflicts=conflicts)
1154
1296
    finally:
1155
1297
        pb.clear()
1188
1330
            trans_id = conflict[1]
1189
1331
            try:
1190
1332
                tt.cancel_deletion(trans_id)
1191
 
                new_conflicts.add((c_type, 'Not deleting', trans_id))
 
1333
                new_conflicts.add(('deleting parent', 'Not deleting', 
 
1334
                                   trans_id))
1192
1335
            except KeyError:
1193
1336
                tt.create_directory(trans_id)
1194
 
                new_conflicts.add((c_type, 'Created directory.', trans_id))
 
1337
                new_conflicts.add((c_type, 'Created directory', trans_id))
1195
1338
        elif c_type == 'unversioned parent':
1196
1339
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1340
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
1341
    return new_conflicts
1199
1342
 
 
1343
 
1200
1344
def cook_conflicts(raw_conflicts, tt):
1201
1345
    """Generate a list of cooked conflicts, sorted by file path"""
1202
 
    def key(conflict):
1203
 
        if conflict.path is not None:
1204
 
            return conflict.path, conflict.typestring
1205
 
        elif getattr(conflict, "conflict_path", None) is not None:
1206
 
            return conflict.conflict_path, conflict.typestring
1207
 
        else:
1208
 
            return None, conflict.typestring
 
1346
    from bzrlib.conflicts import Conflict
 
1347
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
 
1348
    return sorted(conflict_iter, key=Conflict.sort_key)
1209
1349
 
1210
 
    return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1211
1350
 
1212
1351
def iter_cook_conflicts(raw_conflicts, tt):
1213
1352
    from bzrlib.conflicts import Conflict