~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: John Arbash Meinel
  • Date: 2008-09-26 22:14:42 UTC
  • mto: This revision was merged to the branch mainline in revision 3747.
  • Revision ID: john@arbash-meinel.com-20080926221442-3r67j99sr9rwe9w0
Make message optional, don't check the memory flag directly.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 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
43
43
    bzrdir,
44
44
    cache_utf8,
45
45
    conflicts as _mod_conflicts,
 
46
    debug,
46
47
    delta,
47
48
    dirstate,
48
49
    errors,
49
50
    generate_ids,
50
51
    globbing,
51
 
    hashcache,
52
52
    ignores,
53
53
    merge,
54
54
    osutils,
55
55
    revision as _mod_revision,
56
56
    revisiontree,
57
57
    textui,
 
58
    trace,
58
59
    transform,
59
60
    urlutils,
60
61
    xml5,
130
131
        """
131
132
        self._format = _format
132
133
        self.bzrdir = _bzrdir
133
 
        from bzrlib.trace import note, mutter
134
 
        assert isinstance(basedir, basestring), \
135
 
            "base directory %r is not a string" % basedir
136
134
        basedir = safe_unicode(basedir)
137
135
        mutter("opening working tree %r", basedir)
138
136
        self._branch = branch
139
 
        assert isinstance(self.branch, bzrlib.branch.Branch), \
140
 
            "branch %r is not a Branch" % self.branch
141
137
        self.basedir = realpath(basedir)
142
138
        # if branch is at our basedir and is a format 6 or less
143
139
        # assume all other formats have their own control files.
144
 
        assert isinstance(_control_files, LockableFiles), \
145
 
            "_control_files must be a LockableFiles, not %r" % _control_files
146
140
        self._control_files = _control_files
 
141
        self._transport = self._control_files._transport
147
142
        self._dirty = None
148
143
        #-------------
149
144
        # during a read or write lock these objects are set, and are
151
146
        self._dirstate = None
152
147
        self._inventory = None
153
148
        #-------------
 
149
        self._setup_directory_is_tree_reference()
 
150
        self._detect_case_handling()
 
151
        self._rules_searcher = None
154
152
 
155
153
    @needs_tree_write_lock
156
154
    def _add(self, files, ids, kinds):
158
156
        state = self.current_dirstate()
159
157
        for f, file_id, kind in zip(files, ids, kinds):
160
158
            f = f.strip('/')
161
 
            assert '//' not in f
162
 
            assert '..' not in f
163
159
            if self.path2id(f):
164
160
                # special case tree root handling.
165
161
                if f == '' and self.path2id(f) == ROOT_ID:
270
266
        self._dirstate = dirstate.DirState.on_file(local_path)
271
267
        return self._dirstate
272
268
 
273
 
    def _directory_is_tree_reference(self, relpath):
274
 
        # as a special case, if a directory contains control files then 
275
 
        # it's a tree reference, except that the root of the tree is not
276
 
        return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
277
 
        # TODO: We could ask all the control formats whether they
278
 
        # recognize this directory, but at the moment there's no cheap api
279
 
        # to do that.  Since we probably can only nest bzr checkouts and
280
 
        # they always use this name it's ok for now.  -- mbp 20060306
281
 
        #
282
 
        # FIXME: There is an unhandled case here of a subdirectory
283
 
        # containing .bzr but not a branch; that will probably blow up
284
 
        # when you try to commit it.  It might happen if there is a
285
 
        # checkout in a subdirectory.  This can be avoided by not adding
286
 
        # it.  mbp 20070306
287
 
 
288
269
    def filter_unversioned_files(self, paths):
289
270
        """Filter out paths that are versioned.
290
271
 
332
313
        state._read_dirblocks_if_needed()
333
314
        root_key, current_entry = self._get_entry(path='')
334
315
        current_id = root_key[2]
335
 
        assert current_entry[0][0] == 'd' # directory
 
316
        if not (current_entry[0][0] == 'd'): # directory
 
317
            raise AssertionError(current_entry)
336
318
        inv = Inventory(root_id=current_id)
337
319
        # Turn some things into local variables
338
320
        minikind_to_kind = dirstate.DirState._minikind_to_kind
371
353
                    # add this entry to the parent map.
372
354
                    parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
373
355
                elif kind == 'tree-reference':
374
 
                    assert self._repo_supports_tree_reference, \
375
 
                        "repository of %r " \
376
 
                        "doesn't support tree references " \
377
 
                        "required by entry %r" \
378
 
                        % (self, name)
 
356
                    if not self._repo_supports_tree_reference:
 
357
                        raise AssertionError(
 
358
                            "repository of %r "
 
359
                            "doesn't support tree references "
 
360
                            "required by entry %r"
 
361
                            % (self, name))
379
362
                    inv_entry.reference_revision = link_or_sha1 or None
380
363
                elif kind != 'symlink':
381
364
                    raise AssertionError("unknown kind %r" % kind)
382
365
                # These checks cost us around 40ms on a 55k entry tree
383
 
                assert file_id not in inv_byid, ('file_id %s already in'
384
 
                    ' inventory as %s' % (file_id, inv_byid[file_id]))
385
 
                assert name_unicode not in parent_ie.children
 
366
                if file_id in inv_byid:
 
367
                    raise AssertionError('file_id %s already in'
 
368
                        ' inventory as %s' % (file_id, inv_byid[file_id]))
 
369
                if name_unicode in parent_ie.children:
 
370
                    raise AssertionError('name %r already in parent'
 
371
                        % (name_unicode,))
386
372
                inv_byid[file_id] = inv_entry
387
373
                parent_ie.children[name_unicode] = inv_entry
388
374
        self._inventory = inv
408
394
    def get_file_sha1(self, file_id, path=None, stat_value=None):
409
395
        # check file id is valid unconditionally.
410
396
        entry = self._get_entry(file_id=file_id, path=path)
411
 
        assert entry[0] is not None, 'what error should this raise'
 
397
        if entry[0] is None:
 
398
            raise errors.NoSuchId(self, file_id)
412
399
        if path is None:
413
400
            path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
414
401
 
430
417
 
431
418
    def _get_inventory(self):
432
419
        """Get the inventory for the tree. This is only valid within a lock."""
 
420
        if 'evil' in debug.debug_flags:
 
421
            trace.mutter_callsite(2,
 
422
                "accessing .inventory forces a size of tree translation.")
433
423
        if self._inventory is not None:
434
424
            return self._inventory
435
425
        self._must_be_locked()
464
454
 
465
455
    def has_id(self, file_id):
466
456
        state = self.current_dirstate()
467
 
        file_id = osutils.safe_file_id(file_id)
468
457
        row, parents = self._get_entry(file_id=file_id)
469
458
        if row is None:
470
459
            return False
474
463
    @needs_read_lock
475
464
    def id2path(self, file_id):
476
465
        "Convert a file-id to a path."
477
 
        file_id = osutils.safe_file_id(file_id)
478
466
        state = self.current_dirstate()
479
467
        entry = self._get_entry(file_id=file_id)
480
468
        if entry == (None, None):
482
470
        path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
483
471
        return path_utf8.decode('utf8')
484
472
 
 
473
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
 
474
        entry = self._get_entry(path=path)
 
475
        if entry == (None, None):
 
476
            return False # Missing entries are not executable
 
477
        return entry[1][0][3] # Executable?
 
478
 
485
479
    if not osutils.supports_executable():
486
480
        def is_executable(self, file_id, path=None):
487
481
            """Test if a file is executable or not.
488
482
 
489
483
            Note: The caller is expected to take a read-lock before calling this.
490
484
            """
491
 
            file_id = osutils.safe_file_id(file_id)
492
485
            entry = self._get_entry(file_id=file_id, path=path)
493
486
            if entry == (None, None):
494
487
                return False
495
488
            return entry[1][0][3]
 
489
 
 
490
        _is_executable_from_path_and_stat = \
 
491
            _is_executable_from_path_and_stat_from_basis
496
492
    else:
497
493
        def is_executable(self, file_id, path=None):
498
494
            """Test if a file is executable or not.
499
495
 
500
496
            Note: The caller is expected to take a read-lock before calling this.
501
497
            """
 
498
            self._must_be_locked()
502
499
            if not path:
503
 
                file_id = osutils.safe_file_id(file_id)
504
500
                path = self.id2path(file_id)
505
501
            mode = os.lstat(self.abspath(path)).st_mode
506
502
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
507
503
 
 
504
    def all_file_ids(self):
 
505
        """See Tree.iter_all_file_ids"""
 
506
        self._must_be_locked()
 
507
        result = set()
 
508
        for key, tree_details in self.current_dirstate()._iter_entries():
 
509
            if tree_details[0][0] in ('a', 'r'): # relocated
 
510
                continue
 
511
            result.add(key[2])
 
512
        return result
 
513
 
508
514
    @needs_read_lock
509
515
    def __iter__(self):
510
516
        """Iterate through file_ids for this tree.
523
529
        return iter(result)
524
530
 
525
531
    def iter_references(self):
 
532
        if not self._repo_supports_tree_reference:
 
533
            # When the repo doesn't support references, we will have nothing to
 
534
            # return
 
535
            return
526
536
        for key, tree_details in self.current_dirstate()._iter_entries():
527
537
            if tree_details[0][0] in ('a', 'r'): # absent, relocated
528
538
                # not relevant to the working tree
530
540
            if not key[1]:
531
541
                # the root is not a reference.
532
542
                continue
533
 
            path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
 
543
            relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
534
544
            try:
535
 
                if self._kind(path) == 'tree-reference':
536
 
                    yield path, key[2]
 
545
                if self._kind(relpath) == 'tree-reference':
 
546
                    yield relpath, key[2]
537
547
            except errors.NoSuchFile:
538
548
                # path is missing on disk.
539
549
                continue
547
557
        Note: The caller is expected to take a read-lock before calling this.
548
558
        """
549
559
        relpath = self.id2path(file_id)
550
 
        assert relpath != None, \
551
 
            "path for id {%s} is None!" % file_id
 
560
        if relpath is None:
 
561
            raise AssertionError(
 
562
                "path for id {%s} is None!" % file_id)
552
563
        return self._kind(relpath)
553
564
 
554
565
    def _kind(self, relpath):
567
578
        if parent_ids:
568
579
            return parent_ids[0]
569
580
        else:
570
 
            return None
 
581
            return _mod_revision.NULL_REVISION
571
582
 
572
583
    def lock_read(self):
573
584
        """See Branch.lock_read, and WorkingTree.unlock."""
626
637
        result = []
627
638
        if not from_paths:
628
639
            return result
629
 
 
630
640
        state = self.current_dirstate()
631
 
 
632
 
        assert not isinstance(from_paths, basestring)
 
641
        if isinstance(from_paths, basestring):
 
642
            raise ValueError()
633
643
        to_dir_utf8 = to_dir.encode('utf8')
634
644
        to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
635
645
        id_index = state._get_id_index()
657
667
        if self._inventory is not None:
658
668
            update_inventory = True
659
669
            inv = self.inventory
 
670
            to_dir_id = to_entry[0][2]
660
671
            to_dir_ie = inv[to_dir_id]
661
 
            to_dir_id = to_entry[0][2]
662
672
        else:
663
673
            update_inventory = False
664
674
 
728
738
                if from_missing: # implicitly just update our path mapping
729
739
                    move_file = False
730
740
                elif not after:
731
 
                    raise errors.RenameFailedFilesExist(from_rel, to_rel,
732
 
                        extra="(Use --after to update the Bazaar id)")
 
741
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
733
742
 
734
743
            rollbacks = []
735
744
            def rollback_rename():
791
800
                if minikind == 'd':
792
801
                    def update_dirblock(from_dir, to_key, to_dir_utf8):
793
802
                        """Recursively update all entries in this dirblock."""
794
 
                        assert from_dir != '', "renaming root not supported"
 
803
                        if from_dir == '':
 
804
                            raise AssertionError("renaming root not supported")
795
805
                        from_key = (from_dir, '')
796
806
                        from_block_idx, present = \
797
807
                            state._find_block_index_from_key(from_key)
810
820
 
811
821
                        # Grab a copy since move_one may update the list.
812
822
                        for entry in from_block[1][:]:
813
 
                            assert entry[0][0] == from_dir
 
823
                            if not (entry[0][0] == from_dir):
 
824
                                raise AssertionError()
814
825
                            cur_details = entry[1][0]
815
826
                            to_key = (to_dir_utf8, entry[0][1], entry[0][2])
816
827
                            from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
941
952
            if not all_versioned:
942
953
                raise errors.PathsNotVersionedError(paths)
943
954
        # -- remove redundancy in supplied paths to prevent over-scanning --
944
 
        search_paths = set()
945
 
        for path in paths:
946
 
            other_paths = paths.difference(set([path]))
947
 
            if not osutils.is_inside_any(other_paths, path):
948
 
                # this is a top level path, we must check it.
949
 
                search_paths.add(path)
 
955
        search_paths = osutils.minimum_path_selection(paths)
950
956
        # sketch: 
951
957
        # for all search_indexs in each path at or under each element of
952
958
        # search_paths, if the detail is relocated: add the id, and add the
1020
1026
 
1021
1027
        WorkingTree4 supplies revision_trees for any basis tree.
1022
1028
        """
1023
 
        revision_id = osutils.safe_revision_id(revision_id)
1024
1029
        dirstate = self.current_dirstate()
1025
1030
        parent_ids = dirstate.get_parent_ids()
1026
1031
        if revision_id not in parent_ids:
1033
1038
    @needs_tree_write_lock
1034
1039
    def set_last_revision(self, new_revision):
1035
1040
        """Change the last revision in the working tree."""
1036
 
        new_revision = osutils.safe_revision_id(new_revision)
1037
1041
        parents = self.get_parent_ids()
1038
1042
        if new_revision in (NULL_REVISION, None):
1039
 
            assert len(parents) < 2, (
1040
 
                "setting the last parent to none with a pending merge is "
1041
 
                "unsupported.")
 
1043
            if len(parents) >= 2:
 
1044
                raise AssertionError(
 
1045
                    "setting the last parent to none with a pending merge is "
 
1046
                    "unsupported.")
1042
1047
            self.set_parent_ids([])
1043
1048
        else:
1044
1049
            self.set_parent_ids([new_revision] + parents[1:],
1057
1062
        :param revision_ids: The revision_ids to set as the parent ids of this
1058
1063
            working tree. Any of these may be ghosts.
1059
1064
        """
1060
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1061
1065
        trees = []
1062
1066
        for revision_id in revision_ids:
1063
1067
            try:
1086
1090
                raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1087
1091
        real_trees = []
1088
1092
        ghosts = []
 
1093
 
 
1094
        parent_ids = [rev_id for rev_id, tree in parents_list]
 
1095
        graph = self.branch.repository.get_graph()
 
1096
        heads = graph.heads(parent_ids)
 
1097
        accepted_revisions = set()
 
1098
 
1089
1099
        # convert absent trees to the null tree, which we convert back to
1090
1100
        # missing on access.
1091
1101
        for rev_id, tree in parents_list:
1092
 
            rev_id = osutils.safe_revision_id(rev_id)
 
1102
            if len(accepted_revisions) > 0:
 
1103
                # we always accept the first tree
 
1104
                if rev_id in accepted_revisions or rev_id not in heads:
 
1105
                    # We have already included either this tree, or its
 
1106
                    # descendent, so we skip it.
 
1107
                    continue
1093
1108
            _mod_revision.check_not_reserved_id(rev_id)
1094
1109
            if tree is not None:
1095
1110
                real_trees.append((rev_id, tree))
1096
1111
            else:
1097
1112
                real_trees.append((rev_id,
1098
 
                    self.branch.repository.revision_tree(None)))
 
1113
                    self.branch.repository.revision_tree(
 
1114
                        _mod_revision.NULL_REVISION)))
1099
1115
                ghosts.append(rev_id)
 
1116
            accepted_revisions.add(rev_id)
1100
1117
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1101
1118
        self._make_dirty(reset_inventory=False)
1102
1119
 
1107
1124
        if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1108
1125
            self._make_dirty(reset_inventory=True)
1109
1126
 
 
1127
    def _sha_from_stat(self, path, stat_result):
 
1128
        """Get a sha digest from the tree's stat cache.
 
1129
 
 
1130
        The default implementation assumes no stat cache is present.
 
1131
 
 
1132
        :param path: The path.
 
1133
        :param stat_result: The stat result being looked up.
 
1134
        """
 
1135
        return self.current_dirstate().sha1_from_stat(path, stat_result)
 
1136
 
1110
1137
    @needs_read_lock
1111
1138
    def supports_tree_reference(self):
1112
1139
        return self._repo_supports_tree_reference
1113
1140
 
1114
1141
    def unlock(self):
1115
1142
        """Unlock in format 4 trees needs to write the entire dirstate."""
 
1143
        # do non-implementation specific cleanup
 
1144
        self._cleanup()
 
1145
 
1116
1146
        if self._control_files._lock_count == 1:
1117
1147
            # eventually we should do signature checking during read locks for
1118
1148
            # dirstate updates.
1149
1179
            return
1150
1180
        state = self.current_dirstate()
1151
1181
        state._read_dirblocks_if_needed()
1152
 
        ids_to_unversion = set()
1153
 
        for file_id in file_ids:
1154
 
            ids_to_unversion.add(osutils.safe_file_id(file_id))
 
1182
        ids_to_unversion = set(file_ids)
1155
1183
        paths_to_unversion = set()
1156
1184
        # sketch:
1157
1185
        # check if the root is to be unversioned, if so, assert for now.
1187
1215
                    # Mark this file id as having been removed
1188
1216
                    entry = block[1][entry_index]
1189
1217
                    ids_to_unversion.discard(entry[0][2])
1190
 
                    if (entry[1][0][0] == 'a'
 
1218
                    if (entry[1][0][0] in 'ar' # don't remove absent or renamed
 
1219
                                               # entries
1191
1220
                        or not state._make_absent(entry)):
1192
1221
                        entry_index += 1
1193
1222
                # go to the next block. (At the moment we dont delete empty
1218
1247
            for file_id in file_ids:
1219
1248
                self._inventory.remove_recursive_id(file_id)
1220
1249
 
 
1250
    @needs_tree_write_lock
 
1251
    def rename_one(self, from_rel, to_rel, after=False):
 
1252
        """See WorkingTree.rename_one"""
 
1253
        self.flush()
 
1254
        WorkingTree.rename_one(self, from_rel, to_rel, after)
 
1255
 
 
1256
    @needs_tree_write_lock
 
1257
    def apply_inventory_delta(self, changes):
 
1258
        """See MutableTree.apply_inventory_delta"""
 
1259
        state = self.current_dirstate()
 
1260
        state.update_by_delta(changes)
 
1261
        self._make_dirty(reset_inventory=True)
 
1262
 
 
1263
    def update_basis_by_delta(self, new_revid, delta):
 
1264
        """See MutableTree.update_basis_by_delta."""
 
1265
        if self.last_revision() == new_revid:
 
1266
            raise AssertionError()
 
1267
        self.current_dirstate().update_basis_by_delta(delta, new_revid)
 
1268
 
1221
1269
    @needs_read_lock
1222
1270
    def _validate(self):
1223
1271
        self._dirstate._validate()
1225
1273
    @needs_tree_write_lock
1226
1274
    def _write_inventory(self, inv):
1227
1275
        """Write inventory as the current inventory."""
1228
 
        assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
 
1276
        if self._dirty:
 
1277
            raise AssertionError("attempting to write an inventory when the "
 
1278
                "dirstate is dirty will lose pending changes")
1229
1279
        self.current_dirstate().set_state_from_inventory(inv)
1230
1280
        self._make_dirty(reset_inventory=False)
1231
1281
        if self._inventory is not None:
1247
1297
 
1248
1298
    upgrade_recommended = False
1249
1299
 
 
1300
    _tree_class = WorkingTree4
 
1301
 
1250
1302
    def get_format_string(self):
1251
1303
        """See WorkingTreeFormat.get_format_string()."""
1252
1304
        return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1255
1307
        """See WorkingTreeFormat.get_format_description()."""
1256
1308
        return "Working tree format 4"
1257
1309
 
1258
 
    def initialize(self, a_bzrdir, revision_id=None):
 
1310
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
 
1311
                   accelerator_tree=None, hardlink=False):
1259
1312
        """See WorkingTreeFormat.initialize().
1260
1313
 
1261
1314
        :param revision_id: allows creating a working tree at a different
1262
1315
        revision than the branch is at.
 
1316
        :param accelerator_tree: A tree which can be used for retrieving file
 
1317
            contents more quickly than the revision tree, i.e. a workingtree.
 
1318
            The revision tree will be used for cases where accelerator_tree's
 
1319
            content is different.
 
1320
        :param hardlink: If true, hard-link files from accelerator_tree,
 
1321
            where possible.
1263
1322
 
1264
1323
        These trees get an initial random root id, if their repository supports
1265
1324
        rich root data, TREE_ROOT otherwise.
1266
1325
        """
1267
 
        revision_id = osutils.safe_revision_id(revision_id)
1268
1326
        if not isinstance(a_bzrdir.transport, LocalTransport):
1269
1327
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1270
1328
        transport = a_bzrdir.get_workingtree_transport(self)
1271
1329
        control_files = self._open_control_files(a_bzrdir)
1272
1330
        control_files.create_lock()
1273
1331
        control_files.lock_write()
1274
 
        control_files.put_utf8('format', self.get_format_string())
1275
 
        branch = a_bzrdir.open_branch()
 
1332
        transport.put_bytes('format', self.get_format_string(),
 
1333
            mode=a_bzrdir._get_file_mode())
 
1334
        if from_branch is not None:
 
1335
            branch = from_branch
 
1336
        else:
 
1337
            branch = a_bzrdir.open_branch()
1276
1338
        if revision_id is None:
1277
1339
            revision_id = branch.last_revision()
1278
1340
        local_path = transport.local_abspath('dirstate')
1280
1342
        state = dirstate.DirState.initialize(local_path)
1281
1343
        state.unlock()
1282
1344
        del state
1283
 
        wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
 
1345
        wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1284
1346
                         branch,
1285
1347
                         _format=self,
1286
1348
                         _bzrdir=a_bzrdir,
1288
1350
        wt._new_tree()
1289
1351
        wt.lock_tree_write()
1290
1352
        try:
 
1353
            self._init_custom_control_files(wt)
1291
1354
            if revision_id in (None, NULL_REVISION):
1292
1355
                if branch.repository.supports_rich_root():
1293
1356
                    wt._set_root_id(generate_ids.gen_root_id())
1294
1357
                else:
1295
1358
                    wt._set_root_id(ROOT_ID)
1296
1359
                wt.flush()
1297
 
            wt.set_last_revision(revision_id)
1298
 
            wt.flush()
1299
 
            basis = wt.basis_tree()
 
1360
            basis = None
 
1361
            # frequently, we will get here due to branching.  The accelerator
 
1362
            # tree will be the tree from the branch, so the desired basis
 
1363
            # tree will often be a parent of the accelerator tree.
 
1364
            if accelerator_tree is not None:
 
1365
                try:
 
1366
                    basis = accelerator_tree.revision_tree(revision_id)
 
1367
                except errors.NoSuchRevision:
 
1368
                    pass
 
1369
            if basis is None:
 
1370
                basis = branch.repository.revision_tree(revision_id)
 
1371
            if revision_id == NULL_REVISION:
 
1372
                parents_list = []
 
1373
            else:
 
1374
                parents_list = [(revision_id, basis)]
1300
1375
            basis.lock_read()
1301
 
            # if the basis has a root id we have to use that; otherwise we use
1302
 
            # a new random one
1303
 
            basis_root_id = basis.get_root_id()
1304
 
            if basis_root_id is not None:
1305
 
                wt._set_root_id(basis_root_id)
 
1376
            try:
 
1377
                wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
1306
1378
                wt.flush()
1307
 
            transform.build_tree(basis, wt)
1308
 
            basis.unlock()
 
1379
                # if the basis has a root id we have to use that; otherwise we
 
1380
                # use a new random one
 
1381
                basis_root_id = basis.get_root_id()
 
1382
                if basis_root_id is not None:
 
1383
                    wt._set_root_id(basis_root_id)
 
1384
                    wt.flush()
 
1385
                # delta_from_tree is safe even for DirStateRevisionTrees,
 
1386
                # because wt4.apply_inventory_delta does not mutate the input
 
1387
                # inventory entries.
 
1388
                transform.build_tree(basis, wt, accelerator_tree,
 
1389
                                     hardlink=hardlink, delta_from_tree=True)
 
1390
            finally:
 
1391
                basis.unlock()
1309
1392
        finally:
1310
1393
            control_files.unlock()
1311
1394
            wt.unlock()
1312
1395
        return wt
1313
1396
 
 
1397
    def _init_custom_control_files(self, wt):
 
1398
        """Subclasses with custom control files should override this method.
 
1399
        
 
1400
        The working tree and control files are locked for writing when this
 
1401
        method is called.
 
1402
        
 
1403
        :param wt: the WorkingTree object
 
1404
        """
 
1405
 
1314
1406
    def _open(self, a_bzrdir, control_files):
1315
1407
        """Open the tree itself.
1316
1408
 
1317
1409
        :param a_bzrdir: the dir for the tree.
1318
1410
        :param control_files: the control files for the tree.
1319
1411
        """
1320
 
        return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
 
1412
        return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1321
1413
                           branch=a_bzrdir.open_branch(),
1322
1414
                           _format=self,
1323
1415
                           _bzrdir=a_bzrdir,
1336
1428
 
1337
1429
    def __init__(self, dirstate, revision_id, repository):
1338
1430
        self._dirstate = dirstate
1339
 
        self._revision_id = osutils.safe_revision_id(revision_id)
 
1431
        self._revision_id = revision_id
1340
1432
        self._repository = repository
1341
1433
        self._inventory = None
1342
1434
        self._locked = 0
1343
1435
        self._dirstate_locked = False
 
1436
        self._repo_supports_tree_reference = getattr(
 
1437
            repository._format, "supports_tree_reference",
 
1438
            False)
1344
1439
 
1345
1440
    def __repr__(self):
1346
1441
        return "<%s of %s in %s>" % \
1347
1442
            (self.__class__.__name__, self._revision_id, self._dirstate)
1348
1443
 
1349
 
    def annotate_iter(self, file_id):
 
1444
    def annotate_iter(self, file_id,
 
1445
                      default_revision=_mod_revision.CURRENT_REVISION):
1350
1446
        """See Tree.annotate_iter"""
1351
 
        w = self._repository.weave_store.get_weave(file_id,
1352
 
                           self._repository.get_transaction())
1353
 
        return w.annotate_iter(self.inventory[file_id].revision)
 
1447
        text_key = (file_id, self.inventory[file_id].revision)
 
1448
        annotations = self._repository.texts.annotate(text_key)
 
1449
        return [(key[-1], line) for (key, line) in annotations]
1354
1450
 
 
1451
    def _get_ancestors(self, default_revision):
 
1452
        return set(self._repository.get_ancestry(self._revision_id,
 
1453
                                                 topo_sorted=False))
1355
1454
    def _comparison_data(self, entry, path):
1356
1455
        """See Tree._comparison_data."""
1357
1456
        if entry is None:
1374
1473
    def get_root_id(self):
1375
1474
        return self.path2id('')
1376
1475
 
 
1476
    def id2path(self, file_id):
 
1477
        "Convert a file-id to a path."
 
1478
        entry = self._get_entry(file_id=file_id)
 
1479
        if entry == (None, None):
 
1480
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1481
        path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
 
1482
        return path_utf8.decode('utf8')
 
1483
 
 
1484
    def iter_references(self):
 
1485
        if not self._repo_supports_tree_reference:
 
1486
            # When the repo doesn't support references, we will have nothing to
 
1487
            # return
 
1488
            return iter([])
 
1489
        # Otherwise, fall back to the default implementation
 
1490
        return super(DirStateRevisionTree, self).iter_references()
 
1491
 
1377
1492
    def _get_parent_index(self):
1378
1493
        """Return the index in the dirstate referenced by this tree."""
1379
1494
        return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1391
1506
        """
1392
1507
        if file_id is None and path is None:
1393
1508
            raise errors.BzrError('must supply file_id or path')
1394
 
        file_id = osutils.safe_file_id(file_id)
1395
1509
        if path is not None:
1396
1510
            path = path.encode('utf8')
1397
1511
        parent_index = self._get_parent_index()
1405
1519
 
1406
1520
        This is relatively expensive: we have to walk the entire dirstate.
1407
1521
        """
1408
 
        assert self._locked, 'cannot generate inventory of an unlocked '\
1409
 
            'dirstate revision tree'
 
1522
        if not self._locked:
 
1523
            raise AssertionError(
 
1524
                'cannot generate inventory of an unlocked '
 
1525
                'dirstate revision tree')
1410
1526
        # separate call for profiling - makes it clear where the costs are.
1411
1527
        self._dirstate._read_dirblocks_if_needed()
1412
 
        assert self._revision_id in self._dirstate.get_parent_ids(), \
1413
 
            'parent %s has disappeared from %s' % (
1414
 
            self._revision_id, self._dirstate.get_parent_ids())
 
1528
        if self._revision_id not in self._dirstate.get_parent_ids():
 
1529
            raise AssertionError(
 
1530
                'parent %s has disappeared from %s' % (
 
1531
                self._revision_id, self._dirstate.get_parent_ids()))
1415
1532
        parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1416
1533
        # This is identical now to the WorkingTree _generate_inventory except
1417
1534
        # for the tree index use.
1418
1535
        root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1419
1536
        current_id = root_key[2]
1420
 
        assert current_entry[parent_index][0] == 'd'
 
1537
        if current_entry[parent_index][0] != 'd':
 
1538
            raise AssertionError()
1421
1539
        inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1422
1540
        inv.root.revision = current_entry[parent_index][4]
1423
1541
        # Turn some things into local variables
1463
1581
                    raise AssertionError("cannot convert entry %r into an InventoryEntry"
1464
1582
                            % entry)
1465
1583
                # These checks cost us around 40ms on a 55k entry tree
1466
 
                assert file_id not in inv_byid
1467
 
                assert name_unicode not in parent_ie.children
 
1584
                if file_id in inv_byid:
 
1585
                    raise AssertionError('file_id %s already in'
 
1586
                        ' inventory as %s' % (file_id, inv_byid[file_id]))
 
1587
                if name_unicode in parent_ie.children:
 
1588
                    raise AssertionError('name %r already in parent'
 
1589
                        % (name_unicode,))
1468
1590
                inv_byid[file_id] = inv_entry
1469
1591
                parent_ie.children[name_unicode] = inv_entry
1470
1592
        self._inventory = inv
1490
1612
            return parent_details[1]
1491
1613
        return None
1492
1614
 
1493
 
    def get_weave(self, file_id):
1494
 
        return self._repository.weave_store.get_weave(file_id,
1495
 
                self._repository.get_transaction())
1496
 
 
1497
 
    def get_file(self, file_id):
 
1615
    def get_file(self, file_id, path=None):
1498
1616
        return StringIO(self.get_file_text(file_id))
1499
1617
 
1500
1618
    def get_file_lines(self, file_id):
1501
 
        ie = self.inventory[file_id]
1502
 
        return self._repository.weave_store.get_weave(file_id,
1503
 
                self._repository.get_transaction()).get_lines(ie.revision)
 
1619
        return osutils.split_lines(self.get_file_text(file_id))
1504
1620
 
1505
1621
    def get_file_size(self, file_id):
 
1622
        """See Tree.get_file_size"""
1506
1623
        return self.inventory[file_id].text_size
1507
1624
 
1508
1625
    def get_file_text(self, file_id):
1509
 
        return ''.join(self.get_file_lines(file_id))
 
1626
        return list(self.iter_files_bytes([(file_id, None)]))[0][1]
1510
1627
 
1511
1628
    def get_reference_revision(self, file_id, path=None):
1512
1629
        return self.inventory[file_id].reference_revision
1513
1630
 
 
1631
    def iter_files_bytes(self, desired_files):
 
1632
        """See Tree.iter_files_bytes.
 
1633
 
 
1634
        This version is implemented on top of Repository.iter_files_bytes"""
 
1635
        parent_index = self._get_parent_index()
 
1636
        repo_desired_files = []
 
1637
        for file_id, identifier in desired_files:
 
1638
            entry = self._get_entry(file_id)
 
1639
            if entry == (None, None):
 
1640
                raise errors.NoSuchId(self, file_id)
 
1641
            repo_desired_files.append((file_id, entry[1][parent_index][4],
 
1642
                                       identifier))
 
1643
        return self._repository.iter_files_bytes(repo_desired_files)
 
1644
 
1514
1645
    def get_symlink_target(self, file_id):
1515
1646
        entry = self._get_entry(file_id=file_id)
1516
1647
        parent_index = self._get_parent_index()
1544
1675
        return bool(self.path2id(filename))
1545
1676
 
1546
1677
    def kind(self, file_id):
1547
 
        return self.inventory[file_id].kind
 
1678
        entry = self._get_entry(file_id=file_id)[1]
 
1679
        if entry is None:
 
1680
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1681
        return dirstate.DirState._minikind_to_kind[entry[1][0]]
 
1682
 
 
1683
    def stored_kind(self, file_id):
 
1684
        """See Tree.stored_kind"""
 
1685
        return self.kind(file_id)
 
1686
 
 
1687
    def path_content_summary(self, path):
 
1688
        """See Tree.path_content_summary."""
 
1689
        id = self.inventory.path2id(path)
 
1690
        if id is None:
 
1691
            return ('missing', None, None, None)
 
1692
        entry = self._inventory[id]
 
1693
        kind = entry.kind
 
1694
        if kind == 'file':
 
1695
            return (kind, entry.text_size, entry.executable, entry.text_sha1)
 
1696
        elif kind == 'symlink':
 
1697
            return (kind, None, None, entry.symlink_target)
 
1698
        else:
 
1699
            return (kind, None, None, None)
1548
1700
 
1549
1701
    def is_executable(self, file_id, path=None):
1550
1702
        ie = self.inventory[file_id]
1596
1748
                self._dirstate_locked = False
1597
1749
            self._repository.unlock()
1598
1750
 
 
1751
    @needs_read_lock
 
1752
    def supports_tree_reference(self):
 
1753
        return self._repo_supports_tree_reference
 
1754
 
1599
1755
    def walkdirs(self, prefix=""):
1600
1756
        # TODO: jam 20070215 This is the lazy way by using the RevisionTree
1601
1757
        # implementation based on an inventory.  
1658
1814
    _matching_to_tree_format = WorkingTreeFormat4()
1659
1815
    _test_mutable_trees_to_test_trees = make_source_parent_tree
1660
1816
 
1661
 
    def _iter_changes(self, include_unchanged=False,
 
1817
    def iter_changes(self, include_unchanged=False,
1662
1818
                      specific_files=None, pb=None, extra_trees=[],
1663
1819
                      require_versioned=True, want_unversioned=False):
1664
1820
        """Return the changes from source to target.
1665
1821
 
1666
 
        :return: An iterator that yields tuples. See InterTree._iter_changes
 
1822
        :return: An iterator that yields tuples. See InterTree.iter_changes
1667
1823
            for details.
1668
1824
        :param specific_files: An optional list of file paths to restrict the
1669
1825
            comparison to. When mapping filenames to ids, all matches in all
1682
1838
        """
1683
1839
        utf8_decode = cache_utf8._utf8_decode
1684
1840
        _minikind_to_kind = dirstate.DirState._minikind_to_kind
 
1841
        cmp_by_dirs = dirstate.cmp_by_dirs
1685
1842
        # NB: show_status depends on being able to pass in non-versioned files
1686
1843
        # and report them as unknown
1687
1844
        # TODO: handle extra trees in the dirstate.
1688
 
        # TODO: handle comparisons as an empty tree as a different special
1689
 
        # case? mbp 20070226
1690
 
        if extra_trees or (self.source._revision_id == NULL_REVISION):
 
1845
        if (extra_trees or specific_files == []):
1691
1846
            # we can't fast-path these cases (yet)
1692
 
            for f in super(InterDirStateTree, self)._iter_changes(
 
1847
            for f in super(InterDirStateTree, self).iter_changes(
1693
1848
                include_unchanged, specific_files, pb, extra_trees,
1694
1849
                require_versioned, want_unversioned=want_unversioned):
1695
1850
                yield f
1696
1851
            return
1697
1852
        parent_ids = self.target.get_parent_ids()
1698
 
        assert (self.source._revision_id in parent_ids), \
1699
 
                "revision {%s} is not stored in {%s}, but %s " \
1700
 
                "can only be used for trees stored in the dirstate" \
1701
 
                % (self.source._revision_id, self.target, self._iter_changes)
 
1853
        if not (self.source._revision_id in parent_ids
 
1854
                or self.source._revision_id == NULL_REVISION):
 
1855
            raise AssertionError(
 
1856
                "revision {%s} is not stored in {%s}, but %s "
 
1857
                "can only be used for trees stored in the dirstate"
 
1858
                % (self.source._revision_id, self.target, self.iter_changes))
1702
1859
        target_index = 0
1703
1860
        if self.source._revision_id == NULL_REVISION:
1704
1861
            source_index = None
1705
1862
            indices = (target_index,)
1706
1863
        else:
1707
 
            assert (self.source._revision_id in parent_ids), \
1708
 
                "Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1709
 
                self.source._revision_id, parent_ids)
 
1864
            if not (self.source._revision_id in parent_ids):
 
1865
                raise AssertionError(
 
1866
                    "Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
 
1867
                    self.source._revision_id, parent_ids))
1710
1868
            source_index = 1 + parent_ids.index(self.source._revision_id)
1711
 
            indices = (source_index,target_index)
 
1869
            indices = (source_index, target_index)
1712
1870
        # -- make all specific_files utf8 --
1713
1871
        if specific_files:
1714
1872
            specific_files_utf8 = set()
1823
1981
        # record is handled, but isn't interesting to process (unchanged)
1824
1982
        uninteresting = object()
1825
1983
 
1826
 
 
1827
1984
        old_dirname_to_file_id = {}
1828
1985
        new_dirname_to_file_id = {}
1829
1986
        # TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
1850
2007
            target_details = entry[1][target_index]
1851
2008
            target_minikind = target_details[0]
1852
2009
            if path_info is not None and target_minikind in 'fdlt':
1853
 
                assert target_index == 0
 
2010
                if not (target_index == 0):
 
2011
                    raise AssertionError()
1854
2012
                link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
1855
2013
                                                  stat_value=path_info[3])
1856
2014
                # The entry may have been modified by update_entry
1881
2039
                                                 path_utf8=old_path)
1882
2040
                    # update the source details variable to be the real
1883
2041
                    # location.
 
2042
                    if old_entry == (None, None):
 
2043
                        raise errors.CorruptDirstate(state._filename,
 
2044
                            "entry '%s/%s' is considered renamed from %r"
 
2045
                            " but source does not exist\n"
 
2046
                            "entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
1884
2047
                    source_details = old_entry[1][source_index]
1885
2048
                    source_minikind = source_details[0]
1886
2049
                else:
1964
2127
                        #       parent entry will be the same as the source entry.
1965
2128
                        target_parent_entry = state._get_entry(target_index,
1966
2129
                                                               path_utf8=new_dirname)
1967
 
                        assert target_parent_entry != (None, None), (
1968
 
                            "Could not find target parent in wt: %s\nparent of: %s"
1969
 
                            % (new_dirname, entry))
 
2130
                        if target_parent_entry == (None, None):
 
2131
                            raise AssertionError(
 
2132
                                "Could not find target parent in wt: %s\nparent of: %s"
 
2133
                                % (new_dirname, entry))
1970
2134
                        target_parent_id = target_parent_entry[0][2]
1971
2135
                    if target_parent_id == entry[0][2]:
1972
2136
                        # This is the root, so the parent is None
2005
2169
                    return uninteresting
2006
2170
            elif source_minikind in 'a' and target_minikind in 'fdlt':
2007
2171
                # looks like a new file
 
2172
                path = pathjoin(entry[0][0], entry[0][1])
 
2173
                # parent id is the entry for the path in the target tree
 
2174
                # TODO: these are the same for an entire directory: cache em.
 
2175
                parent_id = state._get_entry(target_index,
 
2176
                                             path_utf8=entry[0][0])[0][2]
 
2177
                if parent_id == entry[0][2]:
 
2178
                    parent_id = None
2008
2179
                if path_info is not None:
2009
 
                    path = pathjoin(entry[0][0], entry[0][1])
2010
 
                    # parent id is the entry for the path in the target tree
2011
 
                    # TODO: these are the same for an entire directory: cache em.
2012
 
                    parent_id = state._get_entry(target_index,
2013
 
                                                 path_utf8=entry[0][0])[0][2]
2014
 
                    if parent_id == entry[0][2]:
2015
 
                        parent_id = None
 
2180
                    # Present on disk:
2016
2181
                    if use_filesystem_for_exec:
2017
2182
                        # We need S_ISREG here, because we aren't sure if this
2018
2183
                        # is a file or not.
2030
2195
                           (None, path_info[2]),
2031
2196
                           (None, target_exec))
2032
2197
                else:
2033
 
                    # but its not on disk: we deliberately treat this as just
2034
 
                    # never-present. (Why ?! - RBC 20070224)
2035
 
                    pass
 
2198
                    # Its a missing file, report it as such.
 
2199
                    return (entry[0][2],
 
2200
                           (None, utf8_decode(path)[0]),
 
2201
                           False,
 
2202
                           (False, True),
 
2203
                           (None, parent_id),
 
2204
                           (None, utf8_decode(entry[0][1])[0]),
 
2205
                           (None, None),
 
2206
                           (None, False))
2036
2207
            elif source_minikind in 'fdlt' and target_minikind in 'a':
2037
2208
                # unversioned, possibly, or possibly not deleted: we dont care.
2038
2209
                # if its still on disk, *and* theres no other entry at this
2155
2326
                    if current_dir_info[0][0] == '':
2156
2327
                        # remove .bzr from iteration
2157
2328
                        bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
2158
 
                        assert current_dir_info[1][bzr_index][0] == '.bzr'
 
2329
                        if current_dir_info[1][bzr_index][0] != '.bzr':
 
2330
                            raise AssertionError()
2159
2331
                        del current_dir_info[1][bzr_index]
2160
2332
            # walk until both the directory listing and the versioned metadata
2161
2333
            # are exhausted. 
2168
2340
                   current_block is not None):
2169
2341
                if (current_dir_info and current_block
2170
2342
                    and current_dir_info[0][0] != current_block[0]):
2171
 
                    if current_dir_info[0][0].split('/') < current_block[0].split('/'):
 
2343
                    if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
2172
2344
                        # filesystem data refers to paths not covered by the dirblock.
2173
2345
                        # this has two possibilities:
2174
2346
                        # A) it is versioned but empty, so there is no block for it
2307
2479
                                new_executable = bool(
2308
2480
                                    stat.S_ISREG(current_path_info[3].st_mode)
2309
2481
                                    and stat.S_IEXEC & current_path_info[3].st_mode)
 
2482
                                try:
 
2483
                                    relpath_unicode = utf8_decode(current_path_info[0])[0]
 
2484
                                except UnicodeDecodeError:
 
2485
                                    raise errors.BadFilenameEncoding(
 
2486
                                        current_path_info[0], osutils._fs_enc)
2310
2487
                                yield (None,
2311
 
                                    (None, utf8_decode(current_path_info[0])[0]),
 
2488
                                    (None, relpath_unicode),
2312
2489
                                    True,
2313
2490
                                    (False, False),
2314
2491
                                    (None, None),
2351
2528
                    except StopIteration:
2352
2529
                        current_dir_info = None
2353
2530
 
2354
 
 
2355
2531
    @staticmethod
2356
2532
    def is_compatible(source, target):
2357
2533
        # the target must be a dirstate working tree
2412
2588
 
2413
2589
    def update_format(self, tree):
2414
2590
        """Change the format marker."""
2415
 
        tree._control_files.put_utf8('format',
2416
 
            self.target_format.get_format_string())
 
2591
        tree._transport.put_bytes('format',
 
2592
            self.target_format.get_format_string(),
 
2593
            mode=tree.bzrdir._get_file_mode())