~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Martin Pool
  • Date: 2010-08-13 07:56:06 UTC
  • mfrom: (5050.17.4 2.2)
  • mto: (5050.17.6 2.2)
  • mto: This revision was merged to the branch mainline in revision 5379.
  • Revision ID: mbp@sourcefrog.net-20100813075606-8zgmov3ezwans2zo
merge bzr 2.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
24
24
    bzrdir,
25
25
    check,
26
26
    chk_map,
 
27
    config,
27
28
    debug,
28
 
    errors,
 
29
    fetch as _mod_fetch,
29
30
    fifo_cache,
30
31
    generate_ids,
31
32
    gpg,
38
39
    lru_cache,
39
40
    osutils,
40
41
    revision as _mod_revision,
 
42
    static_tuple,
41
43
    symbol_versioning,
 
44
    trace,
42
45
    tsort,
43
 
    ui,
44
46
    versionedfile,
45
47
    )
46
48
from bzrlib.bundle import serializer
49
51
from bzrlib.testament import Testament
50
52
""")
51
53
 
52
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
54
from bzrlib import (
 
55
    errors,
 
56
    registry,
 
57
    ui,
 
58
    )
 
59
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
53
60
from bzrlib.inter import InterObject
54
61
from bzrlib.inventory import (
55
62
    Inventory,
57
64
    ROOT_ID,
58
65
    entry_factory,
59
66
    )
60
 
from bzrlib import registry
 
67
from bzrlib.recordcounter import RecordCounter
 
68
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
61
69
from bzrlib.trace import (
62
70
    log_exception_quietly, note, mutter, mutter_callsite, warning)
63
71
 
66
74
_deprecation_warning_done = False
67
75
 
68
76
 
 
77
class IsInWriteGroupError(errors.InternalBzrError):
 
78
 
 
79
    _fmt = "May not refresh_data of repo %(repo)s while in a write group."
 
80
 
 
81
    def __init__(self, repo):
 
82
        errors.InternalBzrError.__init__(self, repo=repo)
 
83
 
 
84
 
69
85
class CommitBuilder(object):
70
86
    """Provides an interface to build up a commit.
71
87
 
205
221
            # an inventory delta was accumulated without creating a new
206
222
            # inventory.
207
223
            basis_id = self.basis_delta_revision
208
 
            self.inv_sha1 = self.repository.add_inventory_by_delta(
 
224
            # We ignore the 'inventory' returned by add_inventory_by_delta
 
225
            # because self.new_inventory is used to hint to the rest of the
 
226
            # system what code path was taken
 
227
            self.inv_sha1, _ = self.repository.add_inventory_by_delta(
209
228
                basis_id, self._basis_delta, self._new_revision_id,
210
229
                self.parents)
211
230
        else:
223
242
 
224
243
    def _gen_revision_id(self):
225
244
        """Return new revision-id."""
226
 
        return generate_ids.gen_revision_id(self._config.username(),
227
 
                                            self._timestamp)
 
245
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
228
246
 
229
247
    def _generate_revision_if_needed(self):
230
248
        """Create a revision id if None was supplied.
270
288
 
271
289
        :param tree: The tree which is being committed.
272
290
        """
273
 
        # NB: if there are no parents then this method is not called, so no
274
 
        # need to guard on parents having length.
 
291
        if len(self.parents) == 0:
 
292
            raise errors.RootMissing()
275
293
        entry = entry_factory['directory'](tree.path2id(''), '',
276
294
            None)
277
295
        entry.revision = self._new_revision_id
465
483
            if content_summary[2] is None:
466
484
                raise ValueError("Files must not have executable = None")
467
485
            if not store:
468
 
                if (# if the file length changed we have to store:
469
 
                    parent_entry.text_size != content_summary[1] or
470
 
                    # if the exec bit has changed we have to store:
 
486
                # We can't trust a check of the file length because of content
 
487
                # filtering...
 
488
                if (# if the exec bit has changed we have to store:
471
489
                    parent_entry.executable != content_summary[2]):
472
490
                    store = True
473
491
                elif parent_entry.text_sha1 == content_summary[3]:
540
558
                ie.revision = parent_entry.revision
541
559
                return self._get_delta(ie, basis_inv, path), False, None
542
560
            ie.reference_revision = content_summary[3]
 
561
            if ie.reference_revision is None:
 
562
                raise AssertionError("invalid content_summary for nested tree: %r"
 
563
                    % (content_summary,))
543
564
            self._add_text_to_weave(ie.file_id, '', heads, None)
544
565
        else:
545
566
            raise NotImplementedError('unknown kind')
807
828
                seen_root = True
808
829
        self.new_inventory = None
809
830
        if len(inv_delta):
 
831
            # This should perhaps be guarded by a check that the basis we
 
832
            # commit against is the basis for the commit and if not do a delta
 
833
            # against the basis.
810
834
            self._any_changes = True
811
835
        if not seen_root:
812
836
            # housekeeping root entry changes do not affect no-change commits.
846
870
        # versioned roots do not change unless the tree found a change.
847
871
 
848
872
 
 
873
class RepositoryWriteLockResult(LogicalLockResult):
 
874
    """The result of write locking a repository.
 
875
 
 
876
    :ivar repository_token: The token obtained from the underlying lock, or
 
877
        None.
 
878
    :ivar unlock: A callable which will unlock the lock.
 
879
    """
 
880
 
 
881
    def __init__(self, unlock, repository_token):
 
882
        LogicalLockResult.__init__(self, unlock)
 
883
        self.repository_token = repository_token
 
884
 
 
885
    def __repr__(self):
 
886
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
 
887
            self.unlock)
 
888
 
 
889
 
849
890
######################################################################
850
891
# Repositories
851
892
 
852
893
 
853
 
class Repository(object):
 
894
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
854
895
    """Repository holding history for one or more branches.
855
896
 
856
897
    The repository holds and retrieves historical information including
1004
1045
                " id and insertion revid (%r, %r)"
1005
1046
                % (inv.revision_id, revision_id))
1006
1047
        if inv.root is None:
1007
 
            raise AssertionError()
 
1048
            raise errors.RootMissing()
1008
1049
        return self._add_inventory_checked(revision_id, inv, parents)
1009
1050
 
1010
1051
    def _add_inventory_checked(self, revision_id, inv, parents):
1014
1055
 
1015
1056
        :seealso: add_inventory, for the contract.
1016
1057
        """
1017
 
        inv_lines = self._serialise_inventory_to_lines(inv)
 
1058
        inv_lines = self._serializer.write_inventory_to_lines(inv)
1018
1059
        return self._inventory_add_lines(revision_id, parents,
1019
1060
            inv_lines, check_content=False)
1020
1061
 
1216
1257
                    for record in getattr(self, kind).check(keys=keys[kind]):
1217
1258
                        if record.storage_kind == 'absent':
1218
1259
                            checker._report_items.append(
1219
 
                                'Missing inventory {%s}' % (record.key,))
 
1260
                                'Missing %s {%s}' % (kind, record.key,))
1220
1261
                        else:
1221
1262
                            last_object = self._check_record(kind, record,
1222
1263
                                checker, last_object, current_keys[(kind,) + record.key])
1227
1268
        """Check a single text from this repository."""
1228
1269
        if kind == 'inventories':
1229
1270
            rev_id = record.key[0]
1230
 
            inv = self.deserialise_inventory(rev_id,
 
1271
            inv = self._deserialise_inventory(rev_id,
1231
1272
                record.get_bytes_as('fulltext'))
1232
1273
            if last_object is not None:
1233
1274
                delta = inv._make_delta(last_object)
1277
1318
 
1278
1319
        :param _format: The format of the repository on disk.
1279
1320
        :param a_bzrdir: The BzrDir of the repository.
1280
 
 
1281
 
        In the future we will have a single api for all stores for
1282
 
        getting file texts, inventories and revisions, then
1283
 
        this construct will accept instances of those things.
1284
1321
        """
 
1322
        # In the future we will have a single api for all stores for
 
1323
        # getting file texts, inventories and revisions, then
 
1324
        # this construct will accept instances of those things.
1285
1325
        super(Repository, self).__init__()
1286
1326
        self._format = _format
1287
1327
        # the following are part of the public API for Repository:
1293
1333
        self._reconcile_does_inventory_gc = True
1294
1334
        self._reconcile_fixes_text_parents = False
1295
1335
        self._reconcile_backsup_inventory = True
1296
 
        # not right yet - should be more semantically clear ?
1297
 
        #
1298
 
        # TODO: make sure to construct the right store classes, etc, depending
1299
 
        # on whether escaping is required.
1300
 
        self._warn_if_deprecated()
1301
1336
        self._write_group = None
1302
1337
        # Additional places to query for data.
1303
1338
        self._fallback_repositories = []
1304
1339
        # An InventoryEntry cache, used during deserialization
1305
1340
        self._inventory_entry_cache = fifo_cache.FIFOCache(10*1024)
 
1341
        # Is it safe to return inventory entries directly from the entry cache,
 
1342
        # rather copying them?
 
1343
        self._safe_to_return_from_cache = False
 
1344
 
 
1345
    @property
 
1346
    def user_transport(self):
 
1347
        return self.bzrdir.user_transport
 
1348
 
 
1349
    @property
 
1350
    def control_transport(self):
 
1351
        return self._transport
1306
1352
 
1307
1353
    def __repr__(self):
1308
1354
        if self._fallback_repositories:
1357
1403
        data during reads, and allows a 'write_group' to be obtained. Write
1358
1404
        groups must be used for actual data insertion.
1359
1405
 
 
1406
        A token should be passed in if you know that you have locked the object
 
1407
        some other way, and need to synchronise this object's state with that
 
1408
        fact.
 
1409
 
 
1410
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1411
 
1360
1412
        :param token: if this is already locked, then lock_write will fail
1361
1413
            unless the token matches the existing lock.
1362
1414
        :returns: a token if this instance supports tokens, otherwise None.
1365
1417
        :raises MismatchedToken: if the specified token doesn't match the token
1366
1418
            of the existing lock.
1367
1419
        :seealso: start_write_group.
1368
 
 
1369
 
        A token should be passed in if you know that you have locked the object
1370
 
        some other way, and need to synchronise this object's state with that
1371
 
        fact.
1372
 
 
1373
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1420
        :return: A RepositoryWriteLockResult.
1374
1421
        """
1375
1422
        locked = self.is_locked()
1376
 
        result = self.control_files.lock_write(token=token)
 
1423
        token = self.control_files.lock_write(token=token)
1377
1424
        if not locked:
 
1425
            self._warn_if_deprecated()
 
1426
            self._note_lock('w')
1378
1427
            for repo in self._fallback_repositories:
1379
1428
                # Writes don't affect fallback repos
1380
1429
                repo.lock_read()
1381
1430
            self._refresh_data()
1382
 
        return result
 
1431
        return RepositoryWriteLockResult(self.unlock, token)
1383
1432
 
1384
1433
    def lock_read(self):
 
1434
        """Lock the repository for read operations.
 
1435
 
 
1436
        :return: An object with an unlock method which will release the lock
 
1437
            obtained.
 
1438
        """
1385
1439
        locked = self.is_locked()
1386
1440
        self.control_files.lock_read()
1387
1441
        if not locked:
 
1442
            self._warn_if_deprecated()
 
1443
            self._note_lock('r')
1388
1444
            for repo in self._fallback_repositories:
1389
1445
                repo.lock_read()
1390
1446
            self._refresh_data()
 
1447
        return LogicalLockResult(self.unlock)
1391
1448
 
1392
1449
    def get_physical_lock_status(self):
1393
1450
        return self.control_files.get_physical_lock_status()
1453
1510
 
1454
1511
        # now gather global repository information
1455
1512
        # XXX: This is available for many repos regardless of listability.
1456
 
        if self.bzrdir.root_transport.listable():
 
1513
        if self.user_transport.listable():
1457
1514
            # XXX: do we want to __define len__() ?
1458
1515
            # Maybe the versionedfiles object should provide a different
1459
1516
            # method to get the number of keys.
1469
1526
        :param using: If True, list only branches using this repository.
1470
1527
        """
1471
1528
        if using and not self.is_shared():
1472
 
            try:
1473
 
                return [self.bzrdir.open_branch()]
1474
 
            except errors.NotBranchError:
1475
 
                return []
 
1529
            return self.bzrdir.list_branches()
1476
1530
        class Evaluator(object):
1477
1531
 
1478
1532
            def __init__(self):
1487
1541
                    except errors.NoRepositoryPresent:
1488
1542
                        pass
1489
1543
                    else:
1490
 
                        return False, (None, repository)
 
1544
                        return False, ([], repository)
1491
1545
                self.first_call = False
1492
 
                try:
1493
 
                    value = (bzrdir.open_branch(), None)
1494
 
                except errors.NotBranchError:
1495
 
                    value = (None, None)
 
1546
                value = (bzrdir.list_branches(), None)
1496
1547
                return True, value
1497
1548
 
1498
 
        branches = []
1499
 
        for branch, repository in bzrdir.BzrDir.find_bzrdirs(
1500
 
                self.bzrdir.root_transport, evaluate=Evaluator()):
1501
 
            if branch is not None:
1502
 
                branches.append(branch)
 
1549
        ret = []
 
1550
        for branches, repository in bzrdir.BzrDir.find_bzrdirs(
 
1551
                self.user_transport, evaluate=Evaluator()):
 
1552
            if branches is not None:
 
1553
                ret.extend(branches)
1503
1554
            if not using and repository is not None:
1504
 
                branches.extend(repository.find_branches())
1505
 
        return branches
 
1555
                ret.extend(repository.find_branches())
 
1556
        return ret
1506
1557
 
1507
1558
    @needs_read_lock
1508
1559
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1598
1649
        # but at the moment we're only checking for texts referenced by
1599
1650
        # inventories at the graph's edge.
1600
1651
        key_deps = self.revisions._index._key_dependencies
1601
 
        key_deps.add_keys(present_inventories)
 
1652
        key_deps.satisfy_refs_for_keys(present_inventories)
1602
1653
        referrers = frozenset(r[0] for r in key_deps.get_referrers())
1603
1654
        file_ids = self.fileids_altered_by_revision_ids(referrers)
1604
1655
        missing_texts = set()
1617
1668
        return missing_keys
1618
1669
 
1619
1670
    def refresh_data(self):
1620
 
        """Re-read any data needed to to synchronise with disk.
 
1671
        """Re-read any data needed to synchronise with disk.
1621
1672
 
1622
1673
        This method is intended to be called after another repository instance
1623
1674
        (such as one used by a smart server) has inserted data into the
1624
 
        repository. It may not be called during a write group, but may be
1625
 
        called at any other time.
 
1675
        repository. On all repositories this will work outside of write groups.
 
1676
        Some repository formats (pack and newer for bzrlib native formats)
 
1677
        support refresh_data inside write groups. If called inside a write
 
1678
        group on a repository that does not support refreshing in a write group
 
1679
        IsInWriteGroupError will be raised.
1626
1680
        """
1627
 
        if self.is_in_write_group():
1628
 
            raise errors.InternalBzrError(
1629
 
                "May not refresh_data while in a write group.")
1630
1681
        self._refresh_data()
1631
1682
 
1632
1683
    def resume_write_group(self, tokens):
1671
1722
                "May not fetch while in a write group.")
1672
1723
        # fast path same-url fetch operations
1673
1724
        # TODO: lift out to somewhere common with RemoteRepository
1674
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1725
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1675
1726
        if (self.has_same_location(source)
1676
1727
            and fetch_spec is None
1677
1728
            and self._has_same_fallbacks(source)):
1714
1765
        self.start_write_group()
1715
1766
        return result
1716
1767
 
 
1768
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1717
1769
    def unlock(self):
1718
1770
        if (self.control_files._lock_count == 1 and
1719
1771
            self.control_files._lock_mode == 'w'):
1885
1937
                rev = self._serializer.read_revision_from_string(text)
1886
1938
                yield (revid, rev)
1887
1939
 
1888
 
    @needs_read_lock
1889
 
    def get_revision_xml(self, revision_id):
1890
 
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
1891
 
        #       would have already do it.
1892
 
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
1893
 
        # TODO: this can't just be replaced by:
1894
 
        # return self._serializer.write_revision_to_string(
1895
 
        #     self.get_revision(revision_id))
1896
 
        # as cStringIO preservers the encoding unlike write_revision_to_string
1897
 
        # or some other call down the path.
1898
 
        rev = self.get_revision(revision_id)
1899
 
        rev_tmp = cStringIO.StringIO()
1900
 
        # the current serializer..
1901
 
        self._serializer.write_revision(rev, rev_tmp)
1902
 
        rev_tmp.seek(0)
1903
 
        return rev_tmp.getvalue()
1904
 
 
1905
1940
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1906
1941
        """Produce a generator of revision deltas.
1907
1942
 
2149
2184
        """
2150
2185
        selected_keys = set((revid,) for revid in revision_ids)
2151
2186
        w = _inv_weave or self.inventories
2152
 
        pb = ui.ui_factory.nested_progress_bar()
2153
 
        try:
2154
 
            return self._find_file_ids_from_xml_inventory_lines(
2155
 
                w.iter_lines_added_or_present_in_keys(
2156
 
                    selected_keys, pb=pb),
2157
 
                selected_keys)
2158
 
        finally:
2159
 
            pb.finished()
 
2187
        return self._find_file_ids_from_xml_inventory_lines(
 
2188
            w.iter_lines_added_or_present_in_keys(
 
2189
                selected_keys, pb=None),
 
2190
            selected_keys)
2160
2191
 
2161
2192
    def iter_files_bytes(self, desired_files):
2162
2193
        """Iterate through file versions.
2323
2354
        num_file_ids = len(file_ids)
2324
2355
        for file_id, altered_versions in file_ids.iteritems():
2325
2356
            if pb is not None:
2326
 
                pb.update("fetch texts", count, num_file_ids)
 
2357
                pb.update("Fetch texts", count, num_file_ids)
2327
2358
            count += 1
2328
2359
            yield ("file", file_id, altered_versions)
2329
2360
 
2372
2403
        """single-document based inventory iteration."""
2373
2404
        inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
2374
2405
        for text, revision_id in inv_xmls:
2375
 
            yield self.deserialise_inventory(revision_id, text)
 
2406
            yield self._deserialise_inventory(revision_id, text)
2376
2407
 
2377
2408
    def _iter_inventory_xmls(self, revision_ids, ordering):
2378
2409
        if ordering is None:
2410
2441
                        next_key = None
2411
2442
                        break
2412
2443
 
2413
 
    def deserialise_inventory(self, revision_id, xml):
 
2444
    def _deserialise_inventory(self, revision_id, xml):
2414
2445
        """Transform the xml into an inventory object.
2415
2446
 
2416
2447
        :param revision_id: The expected revision id of the inventory.
2417
2448
        :param xml: A serialised inventory.
2418
2449
        """
2419
2450
        result = self._serializer.read_inventory_from_string(xml, revision_id,
2420
 
                    entry_cache=self._inventory_entry_cache)
 
2451
                    entry_cache=self._inventory_entry_cache,
 
2452
                    return_from_cache=self._safe_to_return_from_cache)
2421
2453
        if result.revision_id != revision_id:
2422
2454
            raise AssertionError('revision id mismatch %s != %s' % (
2423
2455
                result.revision_id, revision_id))
2424
2456
        return result
2425
2457
 
2426
 
    def serialise_inventory(self, inv):
2427
 
        return self._serializer.write_inventory_to_string(inv)
2428
 
 
2429
 
    def _serialise_inventory_to_lines(self, inv):
2430
 
        return self._serializer.write_inventory_to_lines(inv)
2431
 
 
2432
2458
    def get_serializer_format(self):
2433
2459
        return self._serializer.format_num
2434
2460
 
2435
2461
    @needs_read_lock
2436
 
    def get_inventory_xml(self, revision_id):
2437
 
        """Get inventory XML as a file object."""
 
2462
    def _get_inventory_xml(self, revision_id):
 
2463
        """Get serialized inventory as a string."""
2438
2464
        texts = self._iter_inventory_xmls([revision_id], 'unordered')
2439
2465
        try:
2440
2466
            text, revision_id = texts.next()
2442
2468
            raise errors.HistoryMissing(self, 'inventory', revision_id)
2443
2469
        return text
2444
2470
 
2445
 
    @needs_read_lock
2446
 
    def get_inventory_sha1(self, revision_id):
2447
 
        """Return the sha1 hash of the inventory entry
2448
 
        """
2449
 
        return self.get_revision(revision_id).inventory_sha1
2450
 
 
2451
2471
    def get_rev_id_for_revno(self, revno, known_pair):
2452
2472
        """Return the revision id of a revno, given a later (revno, revid)
2453
2473
        pair in the same history.
2504
2524
            else:
2505
2525
                next_id = parents[0]
2506
2526
 
2507
 
    @needs_read_lock
2508
 
    def get_revision_inventory(self, revision_id):
2509
 
        """Return inventory of a past revision."""
2510
 
        # TODO: Unify this with get_inventory()
2511
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
2512
 
        # must be the same as its revision, so this is trivial.
2513
 
        if revision_id is None:
2514
 
            # This does not make sense: if there is no revision,
2515
 
            # then it is the current tree inventory surely ?!
2516
 
            # and thus get_root_id() is something that looks at the last
2517
 
            # commit on the branch, and the get_root_id is an inventory check.
2518
 
            raise NotImplementedError
2519
 
            # return Inventory(self.get_root_id())
2520
 
        else:
2521
 
            return self.get_inventory(revision_id)
2522
 
 
2523
2527
    def is_shared(self):
2524
2528
        """Return True if this repository is flagged as a shared repository."""
2525
2529
        raise NotImplementedError(self.is_shared)
2559
2563
            return RevisionTree(self, Inventory(root_id=None),
2560
2564
                                _mod_revision.NULL_REVISION)
2561
2565
        else:
2562
 
            inv = self.get_revision_inventory(revision_id)
 
2566
            inv = self.get_inventory(revision_id)
2563
2567
            return RevisionTree(self, inv, revision_id)
2564
2568
 
2565
2569
    def revision_trees(self, revision_ids):
2618
2622
            keys = tsort.topo_sort(parent_map)
2619
2623
        return [None] + list(keys)
2620
2624
 
2621
 
    def pack(self, hint=None):
 
2625
    def pack(self, hint=None, clean_obsolete_packs=False):
2622
2626
        """Compress the data within the repository.
2623
2627
 
2624
2628
        This operation only makes sense for some repository types. For other
2634
2638
            obtained from the result of commit_write_group(). Out of
2635
2639
            date hints are simply ignored, because concurrent operations
2636
2640
            can obsolete them rapidly.
 
2641
 
 
2642
        :param clean_obsolete_packs: Clean obsolete packs immediately after
 
2643
            the pack operation.
2637
2644
        """
2638
2645
 
2639
2646
    def get_transaction(self):
2655
2662
        for ((revision_id,), parent_keys) in \
2656
2663
                self.revisions.get_parent_map(query_keys).iteritems():
2657
2664
            if parent_keys:
2658
 
                result[revision_id] = tuple(parent_revid
2659
 
                    for (parent_revid,) in parent_keys)
 
2665
                result[revision_id] = tuple([parent_revid
 
2666
                    for (parent_revid,) in parent_keys])
2660
2667
            else:
2661
2668
                result[revision_id] = (_mod_revision.NULL_REVISION,)
2662
2669
        return result
2664
2671
    def _make_parents_provider(self):
2665
2672
        return self
2666
2673
 
 
2674
    @needs_read_lock
 
2675
    def get_known_graph_ancestry(self, revision_ids):
 
2676
        """Return the known graph for a set of revision ids and their ancestors.
 
2677
        """
 
2678
        st = static_tuple.StaticTuple
 
2679
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
2680
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
2681
        return graph.GraphThunkIdsToKeys(known_graph)
 
2682
 
2667
2683
    def get_graph(self, other_repository=None):
2668
2684
        """Return the graph walker for this repository format"""
2669
2685
        parents_provider = self._make_parents_provider()
2764
2780
        result.check(callback_refs)
2765
2781
        return result
2766
2782
 
2767
 
    def _warn_if_deprecated(self):
 
2783
    def _warn_if_deprecated(self, branch=None):
2768
2784
        global _deprecation_warning_done
2769
2785
        if _deprecation_warning_done:
2770
2786
            return
2771
 
        _deprecation_warning_done = True
2772
 
        warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
2773
 
                % (self._format, self.bzrdir.transport.base))
 
2787
        try:
 
2788
            if branch is None:
 
2789
                conf = config.GlobalConfig()
 
2790
            else:
 
2791
                conf = branch.get_config()
 
2792
            if conf.suppress_warning('format_deprecation'):
 
2793
                return
 
2794
            warning("Format %s for %s is deprecated -"
 
2795
                    " please use 'bzr upgrade' to get better performance"
 
2796
                    % (self._format, self.bzrdir.transport.base))
 
2797
        finally:
 
2798
            _deprecation_warning_done = True
2774
2799
 
2775
2800
    def supports_rich_root(self):
2776
2801
        return self._format.rich_root_data
3057
3082
    # help), and for fetching when data won't have come from the same
3058
3083
    # compressor.
3059
3084
    pack_compresses = False
 
3085
    # Does the repository inventory storage understand references to trees?
 
3086
    supports_tree_reference = None
 
3087
    # Is the format experimental ?
 
3088
    experimental = False
3060
3089
 
3061
 
    def __str__(self):
3062
 
        return "<%s>" % self.__class__.__name__
 
3090
    def __repr__(self):
 
3091
        return "%s()" % self.__class__.__name__
3063
3092
 
3064
3093
    def __eq__(self, other):
3065
3094
        # format objects are generally stateless
3078
3107
        """
3079
3108
        try:
3080
3109
            transport = a_bzrdir.get_repository_transport(None)
3081
 
            format_string = transport.get("format").read()
 
3110
            format_string = transport.get_bytes("format")
3082
3111
            return format_registry.get(format_string)
3083
3112
        except errors.NoSuchFile:
3084
3113
            raise errors.NoRepositoryPresent(a_bzrdir)
3166
3195
        raise NotImplementedError(self.network_name)
3167
3196
 
3168
3197
    def check_conversion_target(self, target_format):
3169
 
        raise NotImplementedError(self.check_conversion_target)
 
3198
        if self.rich_root_data and not target_format.rich_root_data:
 
3199
            raise errors.BadConversionTarget(
 
3200
                'Does not support rich root data.', target_format,
 
3201
                from_format=self)
 
3202
        if (self.supports_tree_reference and 
 
3203
            not getattr(target_format, 'supports_tree_reference', False)):
 
3204
            raise errors.BadConversionTarget(
 
3205
                'Does not support nested trees', target_format,
 
3206
                from_format=self)
3170
3207
 
3171
3208
    def open(self, a_bzrdir, _found=False):
3172
3209
        """Return an instance of this format for the bzrdir a_bzrdir.
3175
3212
        """
3176
3213
        raise NotImplementedError(self.open)
3177
3214
 
 
3215
    def _run_post_repo_init_hooks(self, repository, a_bzrdir, shared):
 
3216
        from bzrlib.bzrdir import BzrDir, RepoInitHookParams
 
3217
        hooks = BzrDir.hooks['post_repo_init']
 
3218
        if not hooks:
 
3219
            return
 
3220
        params = RepoInitHookParams(repository, self, a_bzrdir, shared)
 
3221
        for hook in hooks:
 
3222
            hook(params)
 
3223
 
3178
3224
 
3179
3225
class MetaDirRepositoryFormat(RepositoryFormat):
3180
3226
    """Common base class for the new repositories using the metadir layout."""
3385
3431
 
3386
3432
        :param revision_id: if None all content is copied, if NULL_REVISION no
3387
3433
                            content is copied.
3388
 
        :param pb: optional progress bar to use for progress reports. If not
3389
 
                   provided a default one will be created.
 
3434
        :param pb: ignored.
3390
3435
        :return: None.
3391
3436
        """
 
3437
        ui.ui_factory.warn_experimental_format_fetch(self)
3392
3438
        from bzrlib.fetch import RepoFetcher
 
3439
        # See <https://launchpad.net/bugs/456077> asking for a warning here
 
3440
        if self.source._format.network_name() != self.target._format.network_name():
 
3441
            ui.ui_factory.show_user_warning('cross_format_fetch',
 
3442
                from_format=self.source._format,
 
3443
                to_format=self.target._format)
3393
3444
        f = RepoFetcher(to_repository=self.target,
3394
3445
                               from_repository=self.source,
3395
3446
                               last_revision=revision_id,
3396
3447
                               fetch_spec=fetch_spec,
3397
 
                               pb=pb, find_ghosts=find_ghosts)
 
3448
                               find_ghosts=find_ghosts)
3398
3449
 
3399
3450
    def _walk_to_common_revisions(self, revision_ids):
3400
3451
        """Walk out from revision_ids in source to revisions target has.
3569
3620
                self.target.texts.insert_record_stream(
3570
3621
                    self.source.texts.get_record_stream(
3571
3622
                        self.source.texts.keys(), 'topological', False))
3572
 
                pb.update('copying inventory', 0, 1)
 
3623
                pb.update('Copying inventory', 0, 1)
3573
3624
                self.target.inventories.insert_record_stream(
3574
3625
                    self.source.inventories.get_record_stream(
3575
3626
                        self.source.inventories.keys(), 'topological', False))
3722
3773
            return False
3723
3774
        return True
3724
3775
 
3725
 
    def _get_delta_for_revision(self, tree, parent_ids, basis_id, cache):
 
3776
    def _get_trees(self, revision_ids, cache):
 
3777
        possible_trees = []
 
3778
        for rev_id in revision_ids:
 
3779
            if rev_id in cache:
 
3780
                possible_trees.append((rev_id, cache[rev_id]))
 
3781
            else:
 
3782
                # Not cached, but inventory might be present anyway.
 
3783
                try:
 
3784
                    tree = self.source.revision_tree(rev_id)
 
3785
                except errors.NoSuchRevision:
 
3786
                    # Nope, parent is ghost.
 
3787
                    pass
 
3788
                else:
 
3789
                    cache[rev_id] = tree
 
3790
                    possible_trees.append((rev_id, tree))
 
3791
        return possible_trees
 
3792
 
 
3793
    def _get_delta_for_revision(self, tree, parent_ids, possible_trees):
3726
3794
        """Get the best delta and base for this revision.
3727
3795
 
3728
3796
        :return: (basis_id, delta)
3729
3797
        """
3730
 
        possible_trees = [(parent_id, cache[parent_id])
3731
 
                          for parent_id in parent_ids
3732
 
                           if parent_id in cache]
3733
 
        if len(possible_trees) == 0:
3734
 
            # There either aren't any parents, or the parents aren't in the
3735
 
            # cache, so just use the last converted tree
3736
 
            possible_trees.append((basis_id, cache[basis_id]))
3737
3798
        deltas = []
 
3799
        # Generate deltas against each tree, to find the shortest.
 
3800
        texts_possibly_new_in_tree = set()
3738
3801
        for basis_id, basis_tree in possible_trees:
3739
3802
            delta = tree.inventory._make_delta(basis_tree.inventory)
 
3803
            for old_path, new_path, file_id, new_entry in delta:
 
3804
                if new_path is None:
 
3805
                    # This file_id isn't present in the new rev, so we don't
 
3806
                    # care about it.
 
3807
                    continue
 
3808
                if not new_path:
 
3809
                    # Rich roots are handled elsewhere...
 
3810
                    continue
 
3811
                kind = new_entry.kind
 
3812
                if kind != 'directory' and kind != 'file':
 
3813
                    # No text record associated with this inventory entry.
 
3814
                    continue
 
3815
                # This is a directory or file that has changed somehow.
 
3816
                texts_possibly_new_in_tree.add((file_id, new_entry.revision))
3740
3817
            deltas.append((len(delta), basis_id, delta))
3741
3818
        deltas.sort()
3742
3819
        return deltas[0][1:]
3743
3820
 
3744
 
    def _fetch_batch(self, revision_ids, basis_id, cache):
 
3821
    def _fetch_parent_invs_for_stacking(self, parent_map, cache):
 
3822
        """Find all parent revisions that are absent, but for which the
 
3823
        inventory is present, and copy those inventories.
 
3824
 
 
3825
        This is necessary to preserve correctness when the source is stacked
 
3826
        without fallbacks configured.  (Note that in cases like upgrade the
 
3827
        source may be not have _fallback_repositories even though it is
 
3828
        stacked.)
 
3829
        """
 
3830
        parent_revs = set()
 
3831
        for parents in parent_map.values():
 
3832
            parent_revs.update(parents)
 
3833
        present_parents = self.source.get_parent_map(parent_revs)
 
3834
        absent_parents = set(parent_revs).difference(present_parents)
 
3835
        parent_invs_keys_for_stacking = self.source.inventories.get_parent_map(
 
3836
            (rev_id,) for rev_id in absent_parents)
 
3837
        parent_inv_ids = [key[-1] for key in parent_invs_keys_for_stacking]
 
3838
        for parent_tree in self.source.revision_trees(parent_inv_ids):
 
3839
            current_revision_id = parent_tree.get_revision_id()
 
3840
            parents_parents_keys = parent_invs_keys_for_stacking[
 
3841
                (current_revision_id,)]
 
3842
            parents_parents = [key[-1] for key in parents_parents_keys]
 
3843
            basis_id = _mod_revision.NULL_REVISION
 
3844
            basis_tree = self.source.revision_tree(basis_id)
 
3845
            delta = parent_tree.inventory._make_delta(basis_tree.inventory)
 
3846
            self.target.add_inventory_by_delta(
 
3847
                basis_id, delta, current_revision_id, parents_parents)
 
3848
            cache[current_revision_id] = parent_tree
 
3849
 
 
3850
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
3745
3851
        """Fetch across a few revisions.
3746
3852
 
3747
3853
        :param revision_ids: The revisions to copy
3748
3854
        :param basis_id: The revision_id of a tree that must be in cache, used
3749
3855
            as a basis for delta when no other base is available
3750
3856
        :param cache: A cache of RevisionTrees that we can use.
 
3857
        :param a_graph: A Graph object to determine the heads() of the
 
3858
            rich-root data stream.
3751
3859
        :return: The revision_id of the last converted tree. The RevisionTree
3752
3860
            for it will be in cache
3753
3861
        """
3759
3867
        pending_deltas = []
3760
3868
        pending_revisions = []
3761
3869
        parent_map = self.source.get_parent_map(revision_ids)
 
3870
        self._fetch_parent_invs_for_stacking(parent_map, cache)
 
3871
        self.source._safe_to_return_from_cache = True
3762
3872
        for tree in self.source.revision_trees(revision_ids):
 
3873
            # Find a inventory delta for this revision.
 
3874
            # Find text entries that need to be copied, too.
3763
3875
            current_revision_id = tree.get_revision_id()
3764
3876
            parent_ids = parent_map.get(current_revision_id, ())
 
3877
            parent_trees = self._get_trees(parent_ids, cache)
 
3878
            possible_trees = list(parent_trees)
 
3879
            if len(possible_trees) == 0:
 
3880
                # There either aren't any parents, or the parents are ghosts,
 
3881
                # so just use the last converted tree.
 
3882
                possible_trees.append((basis_id, cache[basis_id]))
3765
3883
            basis_id, delta = self._get_delta_for_revision(tree, parent_ids,
3766
 
                                                           basis_id, cache)
 
3884
                                                           possible_trees)
 
3885
            revision = self.source.get_revision(current_revision_id)
 
3886
            pending_deltas.append((basis_id, delta,
 
3887
                current_revision_id, revision.parent_ids))
3767
3888
            if self._converting_to_rich_root:
3768
3889
                self._revision_id_to_root_id[current_revision_id] = \
3769
3890
                    tree.get_root_id()
3770
 
            # Find text entries that need to be copied
 
3891
            # Determine which texts are in present in this revision but not in
 
3892
            # any of the available parents.
 
3893
            texts_possibly_new_in_tree = set()
3771
3894
            for old_path, new_path, file_id, entry in delta:
3772
 
                if new_path is not None:
3773
 
                    if not new_path:
3774
 
                        # This is the root
3775
 
                        if not self.target.supports_rich_root():
3776
 
                            # The target doesn't support rich root, so we don't
3777
 
                            # copy
3778
 
                            continue
3779
 
                        if self._converting_to_rich_root:
3780
 
                            # This can't be copied normally, we have to insert
3781
 
                            # it specially
3782
 
                            root_keys_to_create.add((file_id, entry.revision))
3783
 
                            continue
3784
 
                    text_keys.add((file_id, entry.revision))
3785
 
            revision = self.source.get_revision(current_revision_id)
3786
 
            pending_deltas.append((basis_id, delta,
3787
 
                current_revision_id, revision.parent_ids))
 
3895
                if new_path is None:
 
3896
                    # This file_id isn't present in the new rev
 
3897
                    continue
 
3898
                if not new_path:
 
3899
                    # This is the root
 
3900
                    if not self.target.supports_rich_root():
 
3901
                        # The target doesn't support rich root, so we don't
 
3902
                        # copy
 
3903
                        continue
 
3904
                    if self._converting_to_rich_root:
 
3905
                        # This can't be copied normally, we have to insert
 
3906
                        # it specially
 
3907
                        root_keys_to_create.add((file_id, entry.revision))
 
3908
                        continue
 
3909
                kind = entry.kind
 
3910
                texts_possibly_new_in_tree.add((file_id, entry.revision))
 
3911
            for basis_id, basis_tree in possible_trees:
 
3912
                basis_inv = basis_tree.inventory
 
3913
                for file_key in list(texts_possibly_new_in_tree):
 
3914
                    file_id, file_revision = file_key
 
3915
                    try:
 
3916
                        entry = basis_inv[file_id]
 
3917
                    except errors.NoSuchId:
 
3918
                        continue
 
3919
                    if entry.revision == file_revision:
 
3920
                        texts_possibly_new_in_tree.remove(file_key)
 
3921
            text_keys.update(texts_possibly_new_in_tree)
3788
3922
            pending_revisions.append(revision)
3789
3923
            cache[current_revision_id] = tree
3790
3924
            basis_id = current_revision_id
 
3925
        self.source._safe_to_return_from_cache = False
3791
3926
        # Copy file texts
3792
3927
        from_texts = self.source.texts
3793
3928
        to_texts = self.target.texts
3794
3929
        if root_keys_to_create:
3795
 
            from bzrlib.fetch import _new_root_data_stream
3796
 
            root_stream = _new_root_data_stream(
 
3930
            root_stream = _mod_fetch._new_root_data_stream(
3797
3931
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3798
 
                self.source)
 
3932
                self.source, graph=a_graph)
3799
3933
            to_texts.insert_record_stream(root_stream)
3800
3934
        to_texts.insert_record_stream(from_texts.get_record_stream(
3801
3935
            text_keys, self.target._format._fetch_order,
3823
3957
            for parent_tree in self.source.revision_trees(parent_map):
3824
3958
                current_revision_id = parent_tree.get_revision_id()
3825
3959
                parents_parents = parent_map[current_revision_id]
 
3960
                possible_trees = self._get_trees(parents_parents, cache)
 
3961
                if len(possible_trees) == 0:
 
3962
                    # There either aren't any parents, or the parents are
 
3963
                    # ghosts, so just use the last converted tree.
 
3964
                    possible_trees.append((basis_id, cache[basis_id]))
3826
3965
                basis_id, delta = self._get_delta_for_revision(parent_tree,
3827
 
                    parents_parents, basis_id, cache)
 
3966
                    parents_parents, possible_trees)
3828
3967
                self.target.add_inventory_by_delta(
3829
3968
                    basis_id, delta, current_revision_id, parents_parents)
3830
3969
        # insert signatures and revisions
3853
3992
        cache[basis_id] = basis_tree
3854
3993
        del basis_tree # We don't want to hang on to it here
3855
3994
        hints = []
 
3995
        if self._converting_to_rich_root and len(revision_ids) > 100:
 
3996
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
 
3997
                                                            revision_ids)
 
3998
        else:
 
3999
            a_graph = None
 
4000
 
3856
4001
        for offset in range(0, len(revision_ids), batch_size):
3857
4002
            self.target.start_write_group()
3858
4003
            try:
3859
4004
                pb.update('Transferring revisions', offset,
3860
4005
                          len(revision_ids))
3861
4006
                batch = revision_ids[offset:offset+batch_size]
3862
 
                basis_id = self._fetch_batch(batch, basis_id, cache)
 
4007
                basis_id = self._fetch_batch(batch, basis_id, cache,
 
4008
                                             a_graph=a_graph)
3863
4009
            except:
 
4010
                self.source._safe_to_return_from_cache = False
3864
4011
                self.target.abort_write_group()
3865
4012
                raise
3866
4013
            else:
3878
4025
        """See InterRepository.fetch()."""
3879
4026
        if fetch_spec is not None:
3880
4027
            raise AssertionError("Not implemented yet...")
 
4028
        ui.ui_factory.warn_experimental_format_fetch(self)
3881
4029
        if (not self.source.supports_rich_root()
3882
4030
            and self.target.supports_rich_root()):
3883
4031
            self._converting_to_rich_root = True
3884
4032
            self._revision_id_to_root_id = {}
3885
4033
        else:
3886
4034
            self._converting_to_rich_root = False
 
4035
        # See <https://launchpad.net/bugs/456077> asking for a warning here
 
4036
        if self.source._format.network_name() != self.target._format.network_name():
 
4037
            ui.ui_factory.show_user_warning('cross_format_fetch',
 
4038
                from_format=self.source._format,
 
4039
                to_format=self.target._format)
3887
4040
        revision_ids = self.target.search_missing_revision_ids(self.source,
3888
4041
            revision_id, find_ghosts=find_ghosts).get_keys()
3889
4042
        if not revision_ids:
3958
4111
        :param to_convert: The disk object to convert.
3959
4112
        :param pb: a progress bar to use for progress information.
3960
4113
        """
3961
 
        self.pb = pb
 
4114
        pb = ui.ui_factory.nested_progress_bar()
3962
4115
        self.count = 0
3963
4116
        self.total = 4
3964
4117
        # this is only useful with metadir layouts - separated repo content.
3965
4118
        # trigger an assertion if not such
3966
4119
        repo._format.get_format_string()
3967
4120
        self.repo_dir = repo.bzrdir
3968
 
        self.step('Moving repository to repository.backup')
 
4121
        pb.update('Moving repository to repository.backup')
3969
4122
        self.repo_dir.transport.move('repository', 'repository.backup')
3970
4123
        backup_transport =  self.repo_dir.transport.clone('repository.backup')
3971
4124
        repo._format.check_conversion_target(self.target_format)
3972
4125
        self.source_repo = repo._format.open(self.repo_dir,
3973
4126
            _found=True,
3974
4127
            _override_transport=backup_transport)
3975
 
        self.step('Creating new repository')
 
4128
        pb.update('Creating new repository')
3976
4129
        converted = self.target_format.initialize(self.repo_dir,
3977
4130
                                                  self.source_repo.is_shared())
3978
4131
        converted.lock_write()
3979
4132
        try:
3980
 
            self.step('Copying content into repository.')
 
4133
            pb.update('Copying content')
3981
4134
            self.source_repo.copy_content_into(converted)
3982
4135
        finally:
3983
4136
            converted.unlock()
3984
 
        self.step('Deleting old repository content.')
 
4137
        pb.update('Deleting old repository content')
3985
4138
        self.repo_dir.transport.delete_tree('repository.backup')
3986
 
        self.pb.note('repository converted')
3987
 
 
3988
 
    def step(self, message):
3989
 
        """Update the pb by a step."""
3990
 
        self.count +=1
3991
 
        self.pb.update(message, self.count, self.total)
 
4139
        ui.ui_factory.note('repository converted')
 
4140
        pb.finished()
3992
4141
 
3993
4142
 
3994
4143
_unescape_map = {
4134
4283
                is_resume = False
4135
4284
            try:
4136
4285
                # locked_insert_stream performs a commit|suspend.
4137
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4286
                return self._locked_insert_stream(stream, src_format,
 
4287
                    is_resume)
4138
4288
            except:
4139
4289
                self.target_repo.abort_write_group(suppress_errors=True)
4140
4290
                raise
4187
4337
                # required if the serializers are different only in terms of
4188
4338
                # the inventory.
4189
4339
                if src_serializer == to_serializer:
4190
 
                    self.target_repo.revisions.insert_record_stream(
4191
 
                        substream)
 
4340
                    self.target_repo.revisions.insert_record_stream(substream)
4192
4341
                else:
4193
4342
                    self._extract_and_insert_revisions(substream,
4194
4343
                        src_serializer)
4214
4363
                ):
4215
4364
                if versioned_file is None:
4216
4365
                    continue
 
4366
                # TODO: key is often going to be a StaticTuple object
 
4367
                #       I don't believe we can define a method by which
 
4368
                #       (prefix,) + StaticTuple will work, though we could
 
4369
                #       define a StaticTuple.sq_concat that would allow you to
 
4370
                #       pass in either a tuple or a StaticTuple as the second
 
4371
                #       object, so instead we could have:
 
4372
                #       StaticTuple(prefix) + key here...
4217
4373
                missing_keys.update((prefix,) + key for key in
4218
4374
                    versioned_file.get_missing_compression_parent_keys())
4219
4375
        except NotImplementedError:
4295
4451
        """Create a StreamSource streaming from from_repository."""
4296
4452
        self.from_repository = from_repository
4297
4453
        self.to_format = to_format
 
4454
        self._record_counter = RecordCounter()
4298
4455
 
4299
4456
    def delta_on_metadata(self):
4300
4457
        """Return True if delta's are permitted on metadata streams.
4331
4488
        fetching the inventory weave.
4332
4489
        """
4333
4490
        if self._rich_root_upgrade():
4334
 
            import bzrlib.fetch
4335
 
            return bzrlib.fetch.Inter1and2Helper(
 
4491
            return _mod_fetch.Inter1and2Helper(
4336
4492
                self.from_repository).generate_root_texts(revs)
4337
4493
        else:
4338
4494
            return []
4341
4497
        phase = 'file'
4342
4498
        revs = search.get_keys()
4343
4499
        graph = self.from_repository.get_graph()
4344
 
        revs = list(graph.iter_topo_order(revs))
 
4500
        revs = tsort.topo_sort(graph.get_parent_map(revs))
4345
4501
        data_to_fetch = self.from_repository.item_keys_introduced_by(revs)
4346
4502
        text_keys = []
4347
4503
        for knit_kind, file_id, revisions in data_to_fetch:
4480
4636
 
4481
4637
    def _get_convertable_inventory_stream(self, revision_ids,
4482
4638
                                          delta_versus_null=False):
4483
 
        # The source is using CHKs, but the target either doesn't or it has a
4484
 
        # different serializer.  The StreamSink code expects to be able to
 
4639
        # The two formats are sufficiently different that there is no fast
 
4640
        # path, so we need to send just inventorydeltas, which any
 
4641
        # sufficiently modern client can insert into any repository.
 
4642
        # The StreamSink code expects to be able to
4485
4643
        # convert on the target, so we need to put bytes-on-the-wire that can
4486
4644
        # be converted.  That means inventory deltas (if the remote is <1.19,
4487
4645
        # RemoteStreamSink will fallback to VFS to insert the deltas).