~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: Vincent Ladeuil
  • Date: 2008-01-03 08:49:38 UTC
  • mfrom: (3111.1.31 175524)
  • mto: This revision was merged to the branch mainline in revision 3158.
  • Revision ID: v.ladeuil+lp@free.fr-20080103084938-7kvurk5uvde2ui54
Fix bug #175524, http test servers are 1.1 compliant

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
    revision as _mod_revision,
55
56
    revisiontree,
56
57
    textui,
 
58
    trace,
57
59
    transform,
58
60
    urlutils,
59
61
    xml5,
129
131
        """
130
132
        self._format = _format
131
133
        self.bzrdir = _bzrdir
132
 
        from bzrlib.trace import note, mutter
133
134
        assert isinstance(basedir, basestring), \
134
135
            "base directory %r is not a string" % basedir
135
136
        basedir = safe_unicode(basedir)
150
151
        self._dirstate = None
151
152
        self._inventory = None
152
153
        #-------------
 
154
        self._setup_directory_is_tree_reference()
 
155
        self._detect_case_handling()
153
156
 
154
157
    @needs_tree_write_lock
155
158
    def _add(self, files, ids, kinds):
157
160
        state = self.current_dirstate()
158
161
        for f, file_id, kind in zip(files, ids, kinds):
159
162
            f = f.strip('/')
160
 
            assert '//' not in f
161
 
            assert '..' not in f
162
163
            if self.path2id(f):
163
164
                # special case tree root handling.
164
165
                if f == '' and self.path2id(f) == ROOT_ID:
269
270
        self._dirstate = dirstate.DirState.on_file(local_path)
270
271
        return self._dirstate
271
272
 
272
 
    def _directory_is_tree_reference(self, relpath):
273
 
        # as a special case, if a directory contains control files then 
274
 
        # it's a tree reference, except that the root of the tree is not
275
 
        return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
276
 
        # TODO: We could ask all the control formats whether they
277
 
        # recognize this directory, but at the moment there's no cheap api
278
 
        # to do that.  Since we probably can only nest bzr checkouts and
279
 
        # they always use this name it's ok for now.  -- mbp 20060306
280
 
        #
281
 
        # FIXME: There is an unhandled case here of a subdirectory
282
 
        # containing .bzr but not a branch; that will probably blow up
283
 
        # when you try to commit it.  It might happen if there is a
284
 
        # checkout in a subdirectory.  This can be avoided by not adding
285
 
        # it.  mbp 20070306
286
 
 
287
273
    def filter_unversioned_files(self, paths):
288
274
        """Filter out paths that are versioned.
289
275
 
407
393
    def get_file_sha1(self, file_id, path=None, stat_value=None):
408
394
        # check file id is valid unconditionally.
409
395
        entry = self._get_entry(file_id=file_id, path=path)
410
 
        assert entry[0] is not None, 'what error should this raise'
 
396
        if entry[0] is None:
 
397
            raise errors.NoSuchId(self, file_id)
411
398
        if path is None:
412
399
            path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
413
400
 
429
416
 
430
417
    def _get_inventory(self):
431
418
        """Get the inventory for the tree. This is only valid within a lock."""
 
419
        if 'evil' in debug.debug_flags:
 
420
            trace.mutter_callsite(2,
 
421
                "accessing .inventory forces a size of tree translation.")
432
422
        if self._inventory is not None:
433
423
            return self._inventory
434
424
        self._must_be_locked()
463
453
 
464
454
    def has_id(self, file_id):
465
455
        state = self.current_dirstate()
466
 
        file_id = osutils.safe_file_id(file_id)
467
456
        row, parents = self._get_entry(file_id=file_id)
468
457
        if row is None:
469
458
            return False
473
462
    @needs_read_lock
474
463
    def id2path(self, file_id):
475
464
        "Convert a file-id to a path."
476
 
        file_id = osutils.safe_file_id(file_id)
477
465
        state = self.current_dirstate()
478
466
        entry = self._get_entry(file_id=file_id)
479
467
        if entry == (None, None):
481
469
        path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
482
470
        return path_utf8.decode('utf8')
483
471
 
 
472
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
 
473
        entry = self._get_entry(path=path)
 
474
        if entry == (None, None):
 
475
            return False # Missing entries are not executable
 
476
        return entry[1][0][3] # Executable?
 
477
 
484
478
    if not osutils.supports_executable():
485
479
        def is_executable(self, file_id, path=None):
486
480
            """Test if a file is executable or not.
487
481
 
488
482
            Note: The caller is expected to take a read-lock before calling this.
489
483
            """
490
 
            file_id = osutils.safe_file_id(file_id)
491
484
            entry = self._get_entry(file_id=file_id, path=path)
492
485
            if entry == (None, None):
493
486
                return False
494
487
            return entry[1][0][3]
 
488
 
 
489
        _is_executable_from_path_and_stat = \
 
490
            _is_executable_from_path_and_stat_from_basis
495
491
    else:
496
492
        def is_executable(self, file_id, path=None):
497
493
            """Test if a file is executable or not.
499
495
            Note: The caller is expected to take a read-lock before calling this.
500
496
            """
501
497
            if not path:
502
 
                file_id = osutils.safe_file_id(file_id)
503
498
                path = self.id2path(file_id)
504
499
            mode = os.lstat(self.abspath(path)).st_mode
505
500
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
566
561
        if parent_ids:
567
562
            return parent_ids[0]
568
563
        else:
569
 
            return None
 
564
            return _mod_revision.NULL_REVISION
570
565
 
571
566
    def lock_read(self):
572
567
        """See Branch.lock_read, and WorkingTree.unlock."""
727
722
                if from_missing: # implicitly just update our path mapping
728
723
                    move_file = False
729
724
                elif not after:
730
 
                    raise errors.RenameFailedFilesExist(from_rel, to_rel,
731
 
                        extra="(Use --after to update the Bazaar id)")
 
725
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
732
726
 
733
727
            rollbacks = []
734
728
            def rollback_rename():
940
934
            if not all_versioned:
941
935
                raise errors.PathsNotVersionedError(paths)
942
936
        # -- remove redundancy in supplied paths to prevent over-scanning --
943
 
        search_paths = set()
944
 
        for path in paths:
945
 
            other_paths = paths.difference(set([path]))
946
 
            if not osutils.is_inside_any(other_paths, path):
947
 
                # this is a top level path, we must check it.
948
 
                search_paths.add(path)
 
937
        search_paths = osutils.minimum_path_selection(paths)
949
938
        # sketch: 
950
939
        # for all search_indexs in each path at or under each element of
951
940
        # search_paths, if the detail is relocated: add the id, and add the
1019
1008
 
1020
1009
        WorkingTree4 supplies revision_trees for any basis tree.
1021
1010
        """
1022
 
        revision_id = osutils.safe_revision_id(revision_id)
1023
1011
        dirstate = self.current_dirstate()
1024
1012
        parent_ids = dirstate.get_parent_ids()
1025
1013
        if revision_id not in parent_ids:
1032
1020
    @needs_tree_write_lock
1033
1021
    def set_last_revision(self, new_revision):
1034
1022
        """Change the last revision in the working tree."""
1035
 
        new_revision = osutils.safe_revision_id(new_revision)
1036
1023
        parents = self.get_parent_ids()
1037
1024
        if new_revision in (NULL_REVISION, None):
1038
1025
            assert len(parents) < 2, (
1056
1043
        :param revision_ids: The revision_ids to set as the parent ids of this
1057
1044
            working tree. Any of these may be ghosts.
1058
1045
        """
1059
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1060
1046
        trees = []
1061
1047
        for revision_id in revision_ids:
1062
1048
            try:
1088
1074
        # convert absent trees to the null tree, which we convert back to
1089
1075
        # missing on access.
1090
1076
        for rev_id, tree in parents_list:
1091
 
            rev_id = osutils.safe_revision_id(rev_id)
 
1077
            _mod_revision.check_not_reserved_id(rev_id)
1092
1078
            if tree is not None:
1093
1079
                real_trees.append((rev_id, tree))
1094
1080
            else:
1105
1091
        if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1106
1092
            self._make_dirty(reset_inventory=True)
1107
1093
 
 
1094
    def _sha_from_stat(self, path, stat_result):
 
1095
        """Get a sha digest from the tree's stat cache.
 
1096
 
 
1097
        The default implementation assumes no stat cache is present.
 
1098
 
 
1099
        :param path: The path.
 
1100
        :param stat_result: The stat result being looked up.
 
1101
        """
 
1102
        return self.current_dirstate().sha1_from_stat(path, stat_result)
 
1103
 
1108
1104
    @needs_read_lock
1109
1105
    def supports_tree_reference(self):
1110
1106
        return self._repo_supports_tree_reference
1111
1107
 
1112
1108
    def unlock(self):
1113
1109
        """Unlock in format 4 trees needs to write the entire dirstate."""
 
1110
        # do non-implementation specific cleanup
 
1111
        self._cleanup()
 
1112
 
1114
1113
        if self._control_files._lock_count == 1:
1115
1114
            # eventually we should do signature checking during read locks for
1116
1115
            # dirstate updates.
1147
1146
            return
1148
1147
        state = self.current_dirstate()
1149
1148
        state._read_dirblocks_if_needed()
1150
 
        ids_to_unversion = set()
1151
 
        for file_id in file_ids:
1152
 
            ids_to_unversion.add(osutils.safe_file_id(file_id))
 
1149
        ids_to_unversion = set(file_ids)
1153
1150
        paths_to_unversion = set()
1154
1151
        # sketch:
1155
1152
        # check if the root is to be unversioned, if so, assert for now.
1185
1182
                    # Mark this file id as having been removed
1186
1183
                    entry = block[1][entry_index]
1187
1184
                    ids_to_unversion.discard(entry[0][2])
1188
 
                    if (entry[1][0][0] == 'a'
 
1185
                    if (entry[1][0][0] in 'ar' # don't remove absent or renamed
 
1186
                                               # entries
1189
1187
                        or not state._make_absent(entry)):
1190
1188
                        entry_index += 1
1191
1189
                # go to the next block. (At the moment we dont delete empty
1216
1214
            for file_id in file_ids:
1217
1215
                self._inventory.remove_recursive_id(file_id)
1218
1216
 
 
1217
    def update_basis_by_delta(self, new_revid, delta):
 
1218
        """See MutableTree.update_basis_by_delta."""
 
1219
        assert self.last_revision() != new_revid
 
1220
        self.current_dirstate().update_basis_by_delta(delta, new_revid)
 
1221
 
1219
1222
    @needs_read_lock
1220
1223
    def _validate(self):
1221
1224
        self._dirstate._validate()
1223
1226
    @needs_tree_write_lock
1224
1227
    def _write_inventory(self, inv):
1225
1228
        """Write inventory as the current inventory."""
1226
 
        assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
 
1229
        assert not self._dirty, ("attempting to write an inventory when the "
 
1230
            "dirstate is dirty will cause data loss")
1227
1231
        self.current_dirstate().set_state_from_inventory(inv)
1228
1232
        self._make_dirty(reset_inventory=False)
1229
1233
        if self._inventory is not None:
1253
1257
        """See WorkingTreeFormat.get_format_description()."""
1254
1258
        return "Working tree format 4"
1255
1259
 
1256
 
    def initialize(self, a_bzrdir, revision_id=None):
 
1260
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
 
1261
                   accelerator_tree=None):
1257
1262
        """See WorkingTreeFormat.initialize().
1258
1263
 
1259
1264
        :param revision_id: allows creating a working tree at a different
1260
1265
        revision than the branch is at.
 
1266
        :param accelerator_tree: A tree which can be used for retrieving file
 
1267
            contents more quickly than the revision tree, i.e. a workingtree.
 
1268
            The revision tree will be used for cases where accelerator_tree's
 
1269
            content is different.
1261
1270
 
1262
1271
        These trees get an initial random root id, if their repository supports
1263
1272
        rich root data, TREE_ROOT otherwise.
1264
1273
        """
1265
 
        revision_id = osutils.safe_revision_id(revision_id)
1266
1274
        if not isinstance(a_bzrdir.transport, LocalTransport):
1267
1275
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1268
1276
        transport = a_bzrdir.get_workingtree_transport(self)
1270
1278
        control_files.create_lock()
1271
1279
        control_files.lock_write()
1272
1280
        control_files.put_utf8('format', self.get_format_string())
1273
 
        branch = a_bzrdir.open_branch()
 
1281
        if from_branch is not None:
 
1282
            branch = from_branch
 
1283
        else:
 
1284
            branch = a_bzrdir.open_branch()
1274
1285
        if revision_id is None:
1275
1286
            revision_id = branch.last_revision()
1276
1287
        local_path = transport.local_abspath('dirstate')
1292
1303
                else:
1293
1304
                    wt._set_root_id(ROOT_ID)
1294
1305
                wt.flush()
1295
 
            wt.set_last_revision(revision_id)
 
1306
            basis = None
 
1307
            # frequently, we will get here due to branching.  The accelerator
 
1308
            # tree will be the tree from the branch, so the desired basis
 
1309
            # tree will often be a parent of the accelerator tree.
 
1310
            if accelerator_tree is not None:
 
1311
                try:
 
1312
                    basis = accelerator_tree.revision_tree(revision_id)
 
1313
                except errors.NoSuchRevision:
 
1314
                    pass
 
1315
            if basis is None:
 
1316
                basis = branch.repository.revision_tree(revision_id)
 
1317
            if revision_id == NULL_REVISION:
 
1318
                parents_list = []
 
1319
            else:
 
1320
                parents_list = [(revision_id, basis)]
 
1321
            basis.lock_read()
 
1322
            wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
1296
1323
            wt.flush()
1297
 
            basis = wt.basis_tree()
1298
 
            basis.lock_read()
1299
1324
            # if the basis has a root id we have to use that; otherwise we use
1300
1325
            # a new random one
1301
1326
            basis_root_id = basis.get_root_id()
1302
1327
            if basis_root_id is not None:
1303
1328
                wt._set_root_id(basis_root_id)
1304
1329
                wt.flush()
1305
 
            transform.build_tree(basis, wt)
 
1330
            transform.build_tree(basis, wt, accelerator_tree)
1306
1331
            basis.unlock()
1307
1332
        finally:
1308
1333
            control_files.unlock()
1334
1359
 
1335
1360
    def __init__(self, dirstate, revision_id, repository):
1336
1361
        self._dirstate = dirstate
1337
 
        self._revision_id = osutils.safe_revision_id(revision_id)
 
1362
        self._revision_id = revision_id
1338
1363
        self._repository = repository
1339
1364
        self._inventory = None
1340
1365
        self._locked = 0
1344
1369
        return "<%s of %s in %s>" % \
1345
1370
            (self.__class__.__name__, self._revision_id, self._dirstate)
1346
1371
 
1347
 
    def annotate_iter(self, file_id):
 
1372
    def annotate_iter(self, file_id,
 
1373
                      default_revision=_mod_revision.CURRENT_REVISION):
1348
1374
        """See Tree.annotate_iter"""
1349
 
        w = self._repository.weave_store.get_weave(file_id,
1350
 
                           self._repository.get_transaction())
 
1375
        w = self._get_weave(file_id)
1351
1376
        return w.annotate_iter(self.inventory[file_id].revision)
1352
1377
 
 
1378
    def _get_ancestors(self, default_revision):
 
1379
        return set(self._repository.get_ancestry(self._revision_id,
 
1380
                                                 topo_sorted=False))
1353
1381
    def _comparison_data(self, entry, path):
1354
1382
        """See Tree._comparison_data."""
1355
1383
        if entry is None:
1372
1400
    def get_root_id(self):
1373
1401
        return self.path2id('')
1374
1402
 
 
1403
    def id2path(self, file_id):
 
1404
        "Convert a file-id to a path."
 
1405
        entry = self._get_entry(file_id=file_id)
 
1406
        if entry == (None, None):
 
1407
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1408
        path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
 
1409
        return path_utf8.decode('utf8')
 
1410
 
1375
1411
    def _get_parent_index(self):
1376
1412
        """Return the index in the dirstate referenced by this tree."""
1377
1413
        return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1389
1425
        """
1390
1426
        if file_id is None and path is None:
1391
1427
            raise errors.BzrError('must supply file_id or path')
1392
 
        file_id = osutils.safe_file_id(file_id)
1393
1428
        if path is not None:
1394
1429
            path = path.encode('utf8')
1395
1430
        parent_index = self._get_parent_index()
1488
1523
            return parent_details[1]
1489
1524
        return None
1490
1525
 
1491
 
    def get_file(self, file_id):
 
1526
    @symbol_versioning.deprecated_method(symbol_versioning.zero_ninety)
 
1527
    def get_weave(self, file_id):
 
1528
        return self._get_weave(file_id)
 
1529
 
 
1530
    def _get_weave(self, file_id):
 
1531
        return self._repository.weave_store.get_weave(file_id,
 
1532
                self._repository.get_transaction())
 
1533
 
 
1534
    def get_file(self, file_id, path=None):
1492
1535
        return StringIO(self.get_file_text(file_id))
1493
1536
 
1494
1537
    def get_file_lines(self, file_id):
1495
 
        ie = self.inventory[file_id]
1496
 
        return self._repository.weave_store.get_weave(file_id,
1497
 
                self._repository.get_transaction()).get_lines(ie.revision)
 
1538
        entry = self._get_entry(file_id=file_id)[1]
 
1539
        if entry == None:
 
1540
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1541
        return self._get_weave(file_id).get_lines(entry[1][4])
1498
1542
 
1499
1543
    def get_file_size(self, file_id):
1500
1544
        return self.inventory[file_id].text_size
1505
1549
    def get_reference_revision(self, file_id, path=None):
1506
1550
        return self.inventory[file_id].reference_revision
1507
1551
 
 
1552
    def iter_files_bytes(self, desired_files):
 
1553
        """See Tree.iter_files_bytes.
 
1554
 
 
1555
        This version is implemented on top of Repository.iter_files_bytes"""
 
1556
        parent_index = self._get_parent_index()
 
1557
        repo_desired_files = []
 
1558
        for file_id, identifier in desired_files:
 
1559
            entry = self._get_entry(file_id)
 
1560
            if entry == (None, None):
 
1561
                raise errors.NoSuchId(self, file_id)
 
1562
            repo_desired_files.append((file_id, entry[1][parent_index][4],
 
1563
                                       identifier))
 
1564
        return self._repository.iter_files_bytes(repo_desired_files)
 
1565
 
1508
1566
    def get_symlink_target(self, file_id):
1509
1567
        entry = self._get_entry(file_id=file_id)
1510
1568
        parent_index = self._get_parent_index()
1538
1596
        return bool(self.path2id(filename))
1539
1597
 
1540
1598
    def kind(self, file_id):
1541
 
        return self.inventory[file_id].kind
 
1599
        entry = self._get_entry(file_id=file_id)[1]
 
1600
        if entry == None:
 
1601
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1602
        return dirstate.DirState._minikind_to_kind[entry[1][0]]
 
1603
 
 
1604
    def path_content_summary(self, path):
 
1605
        """See Tree.path_content_summary."""
 
1606
        id = self.inventory.path2id(path)
 
1607
        if id is None:
 
1608
            return ('missing', None, None, None)
 
1609
        entry = self._inventory[id]
 
1610
        kind = entry.kind
 
1611
        if kind == 'file':
 
1612
            return (kind, entry.text_size, entry.executable, entry.text_sha1)
 
1613
        elif kind == 'symlink':
 
1614
            return (kind, None, None, entry.symlink_target)
 
1615
        else:
 
1616
            return (kind, None, None, None)
1542
1617
 
1543
1618
    def is_executable(self, file_id, path=None):
1544
1619
        ie = self.inventory[file_id]
1676
1751
        """
1677
1752
        utf8_decode = cache_utf8._utf8_decode
1678
1753
        _minikind_to_kind = dirstate.DirState._minikind_to_kind
 
1754
        cmp_by_dirs = dirstate.cmp_by_dirs
1679
1755
        # NB: show_status depends on being able to pass in non-versioned files
1680
1756
        # and report them as unknown
1681
1757
        # TODO: handle extra trees in the dirstate.
1682
 
        # TODO: handle comparisons as an empty tree as a different special
1683
 
        # case? mbp 20070226
1684
 
        if extra_trees or (self.source._revision_id == NULL_REVISION):
 
1758
        if (extra_trees or specific_files == []):
1685
1759
            # we can't fast-path these cases (yet)
1686
1760
            for f in super(InterDirStateTree, self)._iter_changes(
1687
1761
                include_unchanged, specific_files, pb, extra_trees,
1689
1763
                yield f
1690
1764
            return
1691
1765
        parent_ids = self.target.get_parent_ids()
1692
 
        assert (self.source._revision_id in parent_ids), \
 
1766
        assert (self.source._revision_id in parent_ids
 
1767
                or self.source._revision_id == NULL_REVISION), \
1693
1768
                "revision {%s} is not stored in {%s}, but %s " \
1694
1769
                "can only be used for trees stored in the dirstate" \
1695
1770
                % (self.source._revision_id, self.target, self._iter_changes)
1702
1777
                "Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1703
1778
                self.source._revision_id, parent_ids)
1704
1779
            source_index = 1 + parent_ids.index(self.source._revision_id)
1705
 
            indices = (source_index,target_index)
 
1780
            indices = (source_index, target_index)
1706
1781
        # -- make all specific_files utf8 --
1707
1782
        if specific_files:
1708
1783
            specific_files_utf8 = set()
2162
2237
                   current_block is not None):
2163
2238
                if (current_dir_info and current_block
2164
2239
                    and current_dir_info[0][0] != current_block[0]):
2165
 
                    if current_dir_info[0][0].split('/') < current_block[0].split('/'):
 
2240
                    if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
2166
2241
                        # filesystem data refers to paths not covered by the dirblock.
2167
2242
                        # this has two possibilities:
2168
2243
                        # A) it is versioned but empty, so there is no block for it
2265
2340
                        # the file on disk is not present at all in the
2266
2341
                        # dirblock. Either way, report about the dirblock
2267
2342
                        # entry, and let other code handle the filesystem one.
2268
 
                        if current_path_info[1].split('/') < current_entry[0][1].split('/'):
 
2343
 
 
2344
                        # Compare the basename for these files to determine
 
2345
                        # which comes first
 
2346
                        if current_path_info[1] < current_entry[0][1]:
2269
2347
                            # extra file on disk: pass for now, but only
2270
2348
                            # increment the path, not the entry
2271
2349
                            advance_entry = False