/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/bzrbranch.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-10 00:21:41 UTC
  • mto: This revision was merged to the branch mainline in revision 6675.
  • Revision ID: jelmer@jelmer.uk-20170610002141-m1z5k7fs8laesa65
Fix import.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2012 Canonical Ltd
 
2
# Copyright (C) 2017 Breezy Developers
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
 
 
18
from __future__ import absolute_import
 
19
 
 
20
from .lazy_import import lazy_import
 
21
lazy_import(globals(), """
 
22
from breezy import (
 
23
    cache_utf8,
 
24
    config as _mod_config,
 
25
    lockable_files,
 
26
    lockdir,
 
27
    rio,
 
28
    shelf,
 
29
    tag as _mod_tag,
 
30
    )
 
31
""")
 
32
 
 
33
from . import (
 
34
    bzrdir,
 
35
    controldir,
 
36
    errors,
 
37
    revision as _mod_revision,
 
38
    urlutils,
 
39
    )
 
40
from .branch import (
 
41
    Branch,
 
42
    BranchFormat,
 
43
    BranchWriteLockResult,
 
44
    format_registry,
 
45
    )
 
46
from .decorators import (
 
47
    needs_read_lock,
 
48
    needs_write_lock,
 
49
    only_raises,
 
50
    )
 
51
from .lock import _RelockDebugMixin, LogicalLockResult
 
52
from .sixish import (
 
53
    BytesIO,
 
54
    viewitems,
 
55
    )
 
56
from .trace import (
 
57
    mutter,
 
58
    )
 
59
 
 
60
 
 
61
class BzrBranch(Branch, _RelockDebugMixin):
 
62
    """A branch stored in the actual filesystem.
 
63
 
 
64
    Note that it's "local" in the context of the filesystem; it doesn't
 
65
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
66
    it's writable, and can be accessed via the normal filesystem API.
 
67
 
 
68
    :ivar _transport: Transport for file operations on this branch's
 
69
        control files, typically pointing to the .bzr/branch directory.
 
70
    :ivar repository: Repository for this branch.
 
71
    :ivar base: The url of the base directory for this branch; the one
 
72
        containing the .bzr directory.
 
73
    :ivar name: Optional colocated branch name as it exists in the control
 
74
        directory.
 
75
    """
 
76
 
 
77
    def __init__(self, _format=None,
 
78
                 _control_files=None, a_bzrdir=None, name=None,
 
79
                 _repository=None, ignore_fallbacks=False,
 
80
                 possible_transports=None):
 
81
        """Create new branch object at a particular location."""
 
82
        if a_bzrdir is None:
 
83
            raise ValueError('a_bzrdir must be supplied')
 
84
        if name is None:
 
85
            raise ValueError('name must be supplied')
 
86
        self.bzrdir = a_bzrdir
 
87
        self._user_transport = self.bzrdir.transport.clone('..')
 
88
        if name != "":
 
89
            self._user_transport.set_segment_parameter(
 
90
                "branch", urlutils.escape(name))
 
91
        self._base = self._user_transport.base
 
92
        self.name = name
 
93
        self._format = _format
 
94
        if _control_files is None:
 
95
            raise ValueError('BzrBranch _control_files is None')
 
96
        self.control_files = _control_files
 
97
        self._transport = _control_files._transport
 
98
        self.repository = _repository
 
99
        self.conf_store = None
 
100
        Branch.__init__(self, possible_transports)
 
101
 
 
102
    def __str__(self):
 
103
        return '%s(%s)' % (self.__class__.__name__, self.user_url)
 
104
 
 
105
    __repr__ = __str__
 
106
 
 
107
    def _get_base(self):
 
108
        """Returns the directory containing the control directory."""
 
109
        return self._base
 
110
 
 
111
    base = property(_get_base, doc="The URL for the root of this branch.")
 
112
 
 
113
    @property
 
114
    def user_transport(self):
 
115
        return self._user_transport
 
116
 
 
117
    def _get_config(self):
 
118
        return _mod_config.TransportConfig(self._transport, 'branch.conf')
 
119
 
 
120
    def _get_config_store(self):
 
121
        if self.conf_store is None:
 
122
            self.conf_store =  _mod_config.BranchStore(self)
 
123
        return self.conf_store
 
124
 
 
125
    def _uncommitted_branch(self):
 
126
        """Return the branch that may contain uncommitted changes."""
 
127
        master = self.get_master_branch()
 
128
        if master is not None:
 
129
            return master
 
130
        else:
 
131
            return self
 
132
 
 
133
    def store_uncommitted(self, creator):
 
134
        """Store uncommitted changes from a ShelfCreator.
 
135
 
 
136
        :param creator: The ShelfCreator containing uncommitted changes, or
 
137
            None to delete any stored changes.
 
138
        :raises: ChangesAlreadyStored if the branch already has changes.
 
139
        """
 
140
        branch = self._uncommitted_branch()
 
141
        if creator is None:
 
142
            branch._transport.delete('stored-transform')
 
143
            return
 
144
        if branch._transport.has('stored-transform'):
 
145
            raise errors.ChangesAlreadyStored
 
146
        transform = BytesIO()
 
147
        creator.write_shelf(transform)
 
148
        transform.seek(0)
 
149
        branch._transport.put_file('stored-transform', transform)
 
150
 
 
151
    def get_unshelver(self, tree):
 
152
        """Return a shelf.Unshelver for this branch and tree.
 
153
 
 
154
        :param tree: The tree to use to construct the Unshelver.
 
155
        :return: an Unshelver or None if no changes are stored.
 
156
        """
 
157
        branch = self._uncommitted_branch()
 
158
        try:
 
159
            transform = branch._transport.get('stored-transform')
 
160
        except errors.NoSuchFile:
 
161
            return None
 
162
        return shelf.Unshelver.from_tree_and_shelf(tree, transform)
 
163
 
 
164
    def is_locked(self):
 
165
        return self.control_files.is_locked()
 
166
 
 
167
    def lock_write(self, token=None):
 
168
        """Lock the branch for write operations.
 
169
 
 
170
        :param token: A token to permit reacquiring a previously held and
 
171
            preserved lock.
 
172
        :return: A BranchWriteLockResult.
 
173
        """
 
174
        if not self.is_locked():
 
175
            self._note_lock('w')
 
176
            self.repository._warn_if_deprecated(self)
 
177
            self.repository.lock_write()
 
178
            took_lock = True
 
179
        else:
 
180
            took_lock = False
 
181
        try:
 
182
            return BranchWriteLockResult(self.unlock,
 
183
                self.control_files.lock_write(token=token))
 
184
        except:
 
185
            if took_lock:
 
186
                self.repository.unlock()
 
187
            raise
 
188
 
 
189
    def lock_read(self):
 
190
        """Lock the branch for read operations.
 
191
 
 
192
        :return: A breezy.lock.LogicalLockResult.
 
193
        """
 
194
        if not self.is_locked():
 
195
            self._note_lock('r')
 
196
            self.repository._warn_if_deprecated(self)
 
197
            self.repository.lock_read()
 
198
            took_lock = True
 
199
        else:
 
200
            took_lock = False
 
201
        try:
 
202
            self.control_files.lock_read()
 
203
            return LogicalLockResult(self.unlock)
 
204
        except:
 
205
            if took_lock:
 
206
                self.repository.unlock()
 
207
            raise
 
208
 
 
209
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
210
    def unlock(self):
 
211
        if self.control_files._lock_count == 1 and self.conf_store is not None:
 
212
            self.conf_store.save_changes()
 
213
        try:
 
214
            self.control_files.unlock()
 
215
        finally:
 
216
            if not self.control_files.is_locked():
 
217
                self.repository.unlock()
 
218
                # we just released the lock
 
219
                self._clear_cached_state()
 
220
 
 
221
    def peek_lock_mode(self):
 
222
        if self.control_files._lock_count == 0:
 
223
            return None
 
224
        else:
 
225
            return self.control_files._lock_mode
 
226
 
 
227
    def get_physical_lock_status(self):
 
228
        return self.control_files.get_physical_lock_status()
 
229
 
 
230
    @needs_read_lock
 
231
    def print_file(self, file, revision_id):
 
232
        """See Branch.print_file."""
 
233
        return self.repository.print_file(file, revision_id)
 
234
 
 
235
    @needs_write_lock
 
236
    def set_last_revision_info(self, revno, revision_id):
 
237
        if not revision_id or not isinstance(revision_id, basestring):
 
238
            raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
 
239
        revision_id = _mod_revision.ensure_null(revision_id)
 
240
        old_revno, old_revid = self.last_revision_info()
 
241
        if self.get_append_revisions_only():
 
242
            self._check_history_violation(revision_id)
 
243
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
244
        self._write_last_revision_info(revno, revision_id)
 
245
        self._clear_cached_state()
 
246
        self._last_revision_info_cache = revno, revision_id
 
247
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
248
 
 
249
    def basis_tree(self):
 
250
        """See Branch.basis_tree."""
 
251
        return self.repository.revision_tree(self.last_revision())
 
252
 
 
253
    def _get_parent_location(self):
 
254
        _locs = ['parent', 'pull', 'x-pull']
 
255
        for l in _locs:
 
256
            try:
 
257
                return self._transport.get_bytes(l).strip('\n')
 
258
            except errors.NoSuchFile:
 
259
                pass
 
260
        return None
 
261
 
 
262
    def get_stacked_on_url(self):
 
263
        raise errors.UnstackableBranchFormat(self._format, self.user_url)
 
264
 
 
265
    def set_push_location(self, location):
 
266
        """See Branch.set_push_location."""
 
267
        self.get_config().set_user_option(
 
268
            'push_location', location,
 
269
            store=_mod_config.STORE_LOCATION_NORECURSE)
 
270
 
 
271
    def _set_parent_location(self, url):
 
272
        if url is None:
 
273
            self._transport.delete('parent')
 
274
        else:
 
275
            self._transport.put_bytes('parent', url + '\n',
 
276
                mode=self.bzrdir._get_file_mode())
 
277
 
 
278
    @needs_write_lock
 
279
    def unbind(self):
 
280
        """If bound, unbind"""
 
281
        return self.set_bound_location(None)
 
282
 
 
283
    @needs_write_lock
 
284
    def bind(self, other):
 
285
        """Bind this branch to the branch other.
 
286
 
 
287
        This does not push or pull data between the branches, though it does
 
288
        check for divergence to raise an error when the branches are not
 
289
        either the same, or one a prefix of the other. That behaviour may not
 
290
        be useful, so that check may be removed in future.
 
291
 
 
292
        :param other: The branch to bind to
 
293
        :type other: Branch
 
294
        """
 
295
        # TODO: jam 20051230 Consider checking if the target is bound
 
296
        #       It is debatable whether you should be able to bind to
 
297
        #       a branch which is itself bound.
 
298
        #       Committing is obviously forbidden,
 
299
        #       but binding itself may not be.
 
300
        #       Since we *have* to check at commit time, we don't
 
301
        #       *need* to check here
 
302
 
 
303
        # we want to raise diverged if:
 
304
        # last_rev is not in the other_last_rev history, AND
 
305
        # other_last_rev is not in our history, and do it without pulling
 
306
        # history around
 
307
        self.set_bound_location(other.base)
 
308
 
 
309
    def get_bound_location(self):
 
310
        try:
 
311
            return self._transport.get_bytes('bound')[:-1]
 
312
        except errors.NoSuchFile:
 
313
            return None
 
314
 
 
315
    @needs_read_lock
 
316
    def get_master_branch(self, possible_transports=None):
 
317
        """Return the branch we are bound to.
 
318
 
 
319
        :return: Either a Branch, or None
 
320
        """
 
321
        if self._master_branch_cache is None:
 
322
            self._master_branch_cache = self._get_master_branch(
 
323
                possible_transports)
 
324
        return self._master_branch_cache
 
325
 
 
326
    def _get_master_branch(self, possible_transports):
 
327
        bound_loc = self.get_bound_location()
 
328
        if not bound_loc:
 
329
            return None
 
330
        try:
 
331
            return Branch.open(bound_loc,
 
332
                               possible_transports=possible_transports)
 
333
        except (errors.NotBranchError, errors.ConnectionError) as e:
 
334
            raise errors.BoundBranchConnectionFailure(
 
335
                    self, bound_loc, e)
 
336
 
 
337
    @needs_write_lock
 
338
    def set_bound_location(self, location):
 
339
        """Set the target where this branch is bound to.
 
340
 
 
341
        :param location: URL to the target branch
 
342
        """
 
343
        self._master_branch_cache = None
 
344
        if location:
 
345
            self._transport.put_bytes('bound', location+'\n',
 
346
                mode=self.bzrdir._get_file_mode())
 
347
        else:
 
348
            try:
 
349
                self._transport.delete('bound')
 
350
            except errors.NoSuchFile:
 
351
                return False
 
352
            return True
 
353
 
 
354
    @needs_write_lock
 
355
    def update(self, possible_transports=None):
 
356
        """Synchronise this branch with the master branch if any.
 
357
 
 
358
        :return: None or the last_revision that was pivoted out during the
 
359
                 update.
 
360
        """
 
361
        master = self.get_master_branch(possible_transports)
 
362
        if master is not None:
 
363
            old_tip = _mod_revision.ensure_null(self.last_revision())
 
364
            self.pull(master, overwrite=True)
 
365
            if self.repository.get_graph().is_ancestor(old_tip,
 
366
                _mod_revision.ensure_null(self.last_revision())):
 
367
                return None
 
368
            return old_tip
 
369
        return None
 
370
 
 
371
    def _read_last_revision_info(self):
 
372
        revision_string = self._transport.get_bytes('last-revision')
 
373
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
 
374
        revision_id = cache_utf8.get_cached_utf8(revision_id)
 
375
        revno = int(revno)
 
376
        return revno, revision_id
 
377
 
 
378
    def _write_last_revision_info(self, revno, revision_id):
 
379
        """Simply write out the revision id, with no checks.
 
380
 
 
381
        Use set_last_revision_info to perform this safely.
 
382
 
 
383
        Does not update the revision_history cache.
 
384
        """
 
385
        revision_id = _mod_revision.ensure_null(revision_id)
 
386
        out_string = '%d %s\n' % (revno, revision_id)
 
387
        self._transport.put_bytes('last-revision', out_string,
 
388
            mode=self.bzrdir._get_file_mode())
 
389
 
 
390
    @needs_write_lock
 
391
    def update_feature_flags(self, updated_flags):
 
392
        """Update the feature flags for this branch.
 
393
 
 
394
        :param updated_flags: Dictionary mapping feature names to necessities
 
395
            A necessity can be None to indicate the feature should be removed
 
396
        """
 
397
        self._format._update_feature_flags(updated_flags)
 
398
        self.control_transport.put_bytes('format', self._format.as_string())
 
399
 
 
400
 
 
401
class BzrBranch8(BzrBranch):
 
402
    """A branch that stores tree-reference locations."""
 
403
 
 
404
    def _open_hook(self, possible_transports=None):
 
405
        if self._ignore_fallbacks:
 
406
            return
 
407
        if possible_transports is None:
 
408
            possible_transports = [self.bzrdir.root_transport]
 
409
        try:
 
410
            url = self.get_stacked_on_url()
 
411
        except (errors.UnstackableRepositoryFormat, errors.NotStacked,
 
412
            errors.UnstackableBranchFormat):
 
413
            pass
 
414
        else:
 
415
            for hook in Branch.hooks['transform_fallback_location']:
 
416
                url = hook(self, url)
 
417
                if url is None:
 
418
                    hook_name = Branch.hooks.get_hook_name(hook)
 
419
                    raise AssertionError(
 
420
                        "'transform_fallback_location' hook %s returned "
 
421
                        "None, not a URL." % hook_name)
 
422
            self._activate_fallback_location(url,
 
423
                possible_transports=possible_transports)
 
424
 
 
425
    def __init__(self, *args, **kwargs):
 
426
        self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
 
427
        super(BzrBranch8, self).__init__(*args, **kwargs)
 
428
        self._last_revision_info_cache = None
 
429
        self._reference_info = None
 
430
 
 
431
    def _clear_cached_state(self):
 
432
        super(BzrBranch8, self)._clear_cached_state()
 
433
        self._last_revision_info_cache = None
 
434
        self._reference_info = None
 
435
 
 
436
    def _check_history_violation(self, revision_id):
 
437
        current_revid = self.last_revision()
 
438
        last_revision = _mod_revision.ensure_null(current_revid)
 
439
        if _mod_revision.is_null(last_revision):
 
440
            return
 
441
        graph = self.repository.get_graph()
 
442
        for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
 
443
            if lh_ancestor == current_revid:
 
444
                return
 
445
        raise errors.AppendRevisionsOnlyViolation(self.user_url)
 
446
 
 
447
    def _gen_revision_history(self):
 
448
        """Generate the revision history from last revision
 
449
        """
 
450
        last_revno, last_revision = self.last_revision_info()
 
451
        self._extend_partial_history(stop_index=last_revno-1)
 
452
        return list(reversed(self._partial_revision_history_cache))
 
453
 
 
454
    @needs_write_lock
 
455
    def _set_parent_location(self, url):
 
456
        """Set the parent branch"""
 
457
        self._set_config_location('parent_location', url, make_relative=True)
 
458
 
 
459
    @needs_read_lock
 
460
    def _get_parent_location(self):
 
461
        """Set the parent branch"""
 
462
        return self._get_config_location('parent_location')
 
463
 
 
464
    @needs_write_lock
 
465
    def _set_all_reference_info(self, info_dict):
 
466
        """Replace all reference info stored in a branch.
 
467
 
 
468
        :param info_dict: A dict of {file_id: (tree_path, branch_location)}
 
469
        """
 
470
        s = BytesIO()
 
471
        writer = rio.RioWriter(s)
 
472
        for key, (tree_path, branch_location) in viewitems(info_dict):
 
473
            stanza = rio.Stanza(file_id=key, tree_path=tree_path,
 
474
                                branch_location=branch_location)
 
475
            writer.write_stanza(stanza)
 
476
        self._transport.put_bytes('references', s.getvalue())
 
477
        self._reference_info = info_dict
 
478
 
 
479
    @needs_read_lock
 
480
    def _get_all_reference_info(self):
 
481
        """Return all the reference info stored in a branch.
 
482
 
 
483
        :return: A dict of {file_id: (tree_path, branch_location)}
 
484
        """
 
485
        if self._reference_info is not None:
 
486
            return self._reference_info
 
487
        rio_file = self._transport.get('references')
 
488
        try:
 
489
            stanzas = rio.read_stanzas(rio_file)
 
490
            info_dict = dict((s['file_id'], (s['tree_path'],
 
491
                             s['branch_location'])) for s in stanzas)
 
492
        finally:
 
493
            rio_file.close()
 
494
        self._reference_info = info_dict
 
495
        return info_dict
 
496
 
 
497
    def set_reference_info(self, file_id, tree_path, branch_location):
 
498
        """Set the branch location to use for a tree reference.
 
499
 
 
500
        :param file_id: The file-id of the tree reference.
 
501
        :param tree_path: The path of the tree reference in the tree.
 
502
        :param branch_location: The location of the branch to retrieve tree
 
503
            references from.
 
504
        """
 
505
        info_dict = self._get_all_reference_info()
 
506
        info_dict[file_id] = (tree_path, branch_location)
 
507
        if None in (tree_path, branch_location):
 
508
            if tree_path is not None:
 
509
                raise ValueError('tree_path must be None when branch_location'
 
510
                                 ' is None.')
 
511
            if branch_location is not None:
 
512
                raise ValueError('branch_location must be None when tree_path'
 
513
                                 ' is None.')
 
514
            del info_dict[file_id]
 
515
        self._set_all_reference_info(info_dict)
 
516
 
 
517
    def get_reference_info(self, file_id):
 
518
        """Get the tree_path and branch_location for a tree reference.
 
519
 
 
520
        :return: a tuple of (tree_path, branch_location)
 
521
        """
 
522
        return self._get_all_reference_info().get(file_id, (None, None))
 
523
 
 
524
    def reference_parent(self, file_id, path, possible_transports=None):
 
525
        """Return the parent branch for a tree-reference file_id.
 
526
 
 
527
        :param file_id: The file_id of the tree reference
 
528
        :param path: The path of the file_id in the tree
 
529
        :return: A branch associated with the file_id
 
530
        """
 
531
        branch_location = self.get_reference_info(file_id)[1]
 
532
        if branch_location is None:
 
533
            return Branch.reference_parent(self, file_id, path,
 
534
                                           possible_transports)
 
535
        branch_location = urlutils.join(self.user_url, branch_location)
 
536
        return Branch.open(branch_location,
 
537
                           possible_transports=possible_transports)
 
538
 
 
539
    def set_push_location(self, location):
 
540
        """See Branch.set_push_location."""
 
541
        self._set_config_location('push_location', location)
 
542
 
 
543
    def set_bound_location(self, location):
 
544
        """See Branch.set_push_location."""
 
545
        self._master_branch_cache = None
 
546
        result = None
 
547
        conf = self.get_config_stack()
 
548
        if location is None:
 
549
            if not conf.get('bound'):
 
550
                return False
 
551
            else:
 
552
                conf.set('bound', 'False')
 
553
                return True
 
554
        else:
 
555
            self._set_config_location('bound_location', location,
 
556
                                      config=conf)
 
557
            conf.set('bound', 'True')
 
558
        return True
 
559
 
 
560
    def _get_bound_location(self, bound):
 
561
        """Return the bound location in the config file.
 
562
 
 
563
        Return None if the bound parameter does not match"""
 
564
        conf = self.get_config_stack()
 
565
        if conf.get('bound') != bound:
 
566
            return None
 
567
        return self._get_config_location('bound_location', config=conf)
 
568
 
 
569
    def get_bound_location(self):
 
570
        """See Branch.get_bound_location."""
 
571
        return self._get_bound_location(True)
 
572
 
 
573
    def get_old_bound_location(self):
 
574
        """See Branch.get_old_bound_location"""
 
575
        return self._get_bound_location(False)
 
576
 
 
577
    def get_stacked_on_url(self):
 
578
        # you can always ask for the URL; but you might not be able to use it
 
579
        # if the repo can't support stacking.
 
580
        ## self._check_stackable_repo()
 
581
        # stacked_on_location is only ever defined in branch.conf, so don't
 
582
        # waste effort reading the whole stack of config files.
 
583
        conf = _mod_config.BranchOnlyStack(self)
 
584
        stacked_url = self._get_config_location('stacked_on_location',
 
585
                                                config=conf)
 
586
        if stacked_url is None:
 
587
            raise errors.NotStacked(self)
 
588
        return stacked_url.encode('utf-8')
 
589
 
 
590
    @needs_read_lock
 
591
    def get_rev_id(self, revno, history=None):
 
592
        """Find the revision id of the specified revno."""
 
593
        if revno == 0:
 
594
            return _mod_revision.NULL_REVISION
 
595
 
 
596
        last_revno, last_revision_id = self.last_revision_info()
 
597
        if revno <= 0 or revno > last_revno:
 
598
            raise errors.NoSuchRevision(self, revno)
 
599
 
 
600
        if history is not None:
 
601
            return history[revno - 1]
 
602
 
 
603
        index = last_revno - revno
 
604
        if len(self._partial_revision_history_cache) <= index:
 
605
            self._extend_partial_history(stop_index=index)
 
606
        if len(self._partial_revision_history_cache) > index:
 
607
            return self._partial_revision_history_cache[index]
 
608
        else:
 
609
            raise errors.NoSuchRevision(self, revno)
 
610
 
 
611
    @needs_read_lock
 
612
    def revision_id_to_revno(self, revision_id):
 
613
        """Given a revision id, return its revno"""
 
614
        if _mod_revision.is_null(revision_id):
 
615
            return 0
 
616
        try:
 
617
            index = self._partial_revision_history_cache.index(revision_id)
 
618
        except ValueError:
 
619
            try:
 
620
                self._extend_partial_history(stop_revision=revision_id)
 
621
            except errors.RevisionNotPresent as e:
 
622
                raise errors.GhostRevisionsHaveNoRevno(revision_id, e.revision_id)
 
623
            index = len(self._partial_revision_history_cache) - 1
 
624
            if index < 0:
 
625
                raise errors.NoSuchRevision(self, revision_id)
 
626
            if self._partial_revision_history_cache[index] != revision_id:
 
627
                raise errors.NoSuchRevision(self, revision_id)
 
628
        return self.revno() - index
 
629
 
 
630
 
 
631
class BzrBranch7(BzrBranch8):
 
632
    """A branch with support for a fallback repository."""
 
633
 
 
634
    def set_reference_info(self, file_id, tree_path, branch_location):
 
635
        Branch.set_reference_info(self, file_id, tree_path, branch_location)
 
636
 
 
637
    def get_reference_info(self, file_id):
 
638
        Branch.get_reference_info(self, file_id)
 
639
 
 
640
    def reference_parent(self, file_id, path, possible_transports=None):
 
641
        return Branch.reference_parent(self, file_id, path,
 
642
                                       possible_transports)
 
643
 
 
644
 
 
645
class BzrBranch6(BzrBranch7):
 
646
    """See BzrBranchFormat6 for the capabilities of this branch.
 
647
 
 
648
    This subclass of BzrBranch7 disables the new features BzrBranch7 added,
 
649
    i.e. stacking.
 
650
    """
 
651
 
 
652
    def get_stacked_on_url(self):
 
653
        raise errors.UnstackableBranchFormat(self._format, self.user_url)
 
654
 
 
655
 
 
656
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
 
657
    """Base class for branch formats that live in meta directories.
 
658
    """
 
659
 
 
660
    def __init__(self):
 
661
        BranchFormat.__init__(self)
 
662
        bzrdir.BzrFormat.__init__(self)
 
663
 
 
664
    @classmethod
 
665
    def find_format(klass, controldir, name=None):
 
666
        """Return the format for the branch object in controldir."""
 
667
        try:
 
668
            transport = controldir.get_branch_transport(None, name=name)
 
669
        except errors.NoSuchFile:
 
670
            raise errors.NotBranchError(path=name, bzrdir=controldir)
 
671
        try:
 
672
            format_string = transport.get_bytes("format")
 
673
        except errors.NoSuchFile:
 
674
            raise errors.NotBranchError(path=transport.base, bzrdir=controldir)
 
675
        return klass._find_format(format_registry, 'branch', format_string)
 
676
 
 
677
    def _branch_class(self):
 
678
        """What class to instantiate on open calls."""
 
679
        raise NotImplementedError(self._branch_class)
 
680
 
 
681
    def _get_initial_config(self, append_revisions_only=None):
 
682
        if append_revisions_only:
 
683
            return "append_revisions_only = True\n"
 
684
        else:
 
685
            # Avoid writing anything if append_revisions_only is disabled,
 
686
            # as that is the default.
 
687
            return ""
 
688
 
 
689
    def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
 
690
                           repository=None):
 
691
        """Initialize a branch in a control dir, with specified files
 
692
 
 
693
        :param a_bzrdir: The bzrdir to initialize the branch in
 
694
        :param utf8_files: The files to create as a list of
 
695
            (filename, content) tuples
 
696
        :param name: Name of colocated branch to create, if any
 
697
        :return: a branch in this format
 
698
        """
 
699
        if name is None:
 
700
            name = a_bzrdir._get_selected_branch()
 
701
        mutter('creating branch %r in %s', self, a_bzrdir.user_url)
 
702
        branch_transport = a_bzrdir.get_branch_transport(self, name=name)
 
703
        control_files = lockable_files.LockableFiles(branch_transport,
 
704
            'lock', lockdir.LockDir)
 
705
        control_files.create_lock()
 
706
        control_files.lock_write()
 
707
        try:
 
708
            utf8_files += [('format', self.as_string())]
 
709
            for (filename, content) in utf8_files:
 
710
                branch_transport.put_bytes(
 
711
                    filename, content,
 
712
                    mode=a_bzrdir._get_file_mode())
 
713
        finally:
 
714
            control_files.unlock()
 
715
        branch = self.open(a_bzrdir, name, _found=True,
 
716
                found_repository=repository)
 
717
        self._run_post_branch_init_hooks(a_bzrdir, name, branch)
 
718
        return branch
 
719
 
 
720
    def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
 
721
            found_repository=None, possible_transports=None):
 
722
        """See BranchFormat.open()."""
 
723
        if name is None:
 
724
            name = a_bzrdir._get_selected_branch()
 
725
        if not _found:
 
726
            format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
 
727
            if format.__class__ != self.__class__:
 
728
                raise AssertionError("wrong format %r found for %r" %
 
729
                    (format, self))
 
730
        transport = a_bzrdir.get_branch_transport(None, name=name)
 
731
        try:
 
732
            control_files = lockable_files.LockableFiles(transport, 'lock',
 
733
                                                         lockdir.LockDir)
 
734
            if found_repository is None:
 
735
                found_repository = a_bzrdir.find_repository()
 
736
            return self._branch_class()(_format=self,
 
737
                              _control_files=control_files,
 
738
                              name=name,
 
739
                              a_bzrdir=a_bzrdir,
 
740
                              _repository=found_repository,
 
741
                              ignore_fallbacks=ignore_fallbacks,
 
742
                              possible_transports=possible_transports)
 
743
        except errors.NoSuchFile:
 
744
            raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
 
745
 
 
746
    @property
 
747
    def _matchingbzrdir(self):
 
748
        ret = bzrdir.BzrDirMetaFormat1()
 
749
        ret.set_branch_format(self)
 
750
        return ret
 
751
 
 
752
    def supports_tags(self):
 
753
        return True
 
754
 
 
755
    def supports_leaving_lock(self):
 
756
        return True
 
757
 
 
758
    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
 
759
            basedir=None):
 
760
        BranchFormat.check_support_status(self,
 
761
            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
 
762
            basedir=basedir)
 
763
        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
 
764
            recommend_upgrade=recommend_upgrade, basedir=basedir)
 
765
 
 
766
 
 
767
class BzrBranchFormat6(BranchFormatMetadir):
 
768
    """Branch format with last-revision and tags.
 
769
 
 
770
    Unlike previous formats, this has no explicit revision history. Instead,
 
771
    this just stores the last-revision, and the left-hand history leading
 
772
    up to there is the history.
 
773
 
 
774
    This format was introduced in bzr 0.15
 
775
    and became the default in 0.91.
 
776
    """
 
777
 
 
778
    def _branch_class(self):
 
779
        return BzrBranch6
 
780
 
 
781
    @classmethod
 
782
    def get_format_string(cls):
 
783
        """See BranchFormat.get_format_string()."""
 
784
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
 
785
 
 
786
    def get_format_description(self):
 
787
        """See BranchFormat.get_format_description()."""
 
788
        return "Branch format 6"
 
789
 
 
790
    def initialize(self, a_bzrdir, name=None, repository=None,
 
791
                   append_revisions_only=None):
 
792
        """Create a branch of this format in a_bzrdir."""
 
793
        utf8_files = [('last-revision', '0 null:\n'),
 
794
                      ('branch.conf',
 
795
                          self._get_initial_config(append_revisions_only)),
 
796
                      ('tags', ''),
 
797
                      ]
 
798
        return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
 
799
 
 
800
    def make_tags(self, branch):
 
801
        """See breezy.branch.BranchFormat.make_tags()."""
 
802
        return _mod_tag.BasicTags(branch)
 
803
 
 
804
    def supports_set_append_revisions_only(self):
 
805
        return True
 
806
 
 
807
 
 
808
class BzrBranchFormat8(BranchFormatMetadir):
 
809
    """Metadir format supporting storing locations of subtree branches."""
 
810
 
 
811
    def _branch_class(self):
 
812
        return BzrBranch8
 
813
 
 
814
    @classmethod
 
815
    def get_format_string(cls):
 
816
        """See BranchFormat.get_format_string()."""
 
817
        return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
 
818
 
 
819
    def get_format_description(self):
 
820
        """See BranchFormat.get_format_description()."""
 
821
        return "Branch format 8"
 
822
 
 
823
    def initialize(self, a_bzrdir, name=None, repository=None,
 
824
                   append_revisions_only=None):
 
825
        """Create a branch of this format in a_bzrdir."""
 
826
        utf8_files = [('last-revision', '0 null:\n'),
 
827
                      ('branch.conf',
 
828
                          self._get_initial_config(append_revisions_only)),
 
829
                      ('tags', ''),
 
830
                      ('references', '')
 
831
                      ]
 
832
        return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
 
833
 
 
834
    def make_tags(self, branch):
 
835
        """See breezy.branch.BranchFormat.make_tags()."""
 
836
        return _mod_tag.BasicTags(branch)
 
837
 
 
838
    def supports_set_append_revisions_only(self):
 
839
        return True
 
840
 
 
841
    def supports_stacking(self):
 
842
        return True
 
843
 
 
844
    supports_reference_locations = True
 
845
 
 
846
 
 
847
class BzrBranchFormat7(BranchFormatMetadir):
 
848
    """Branch format with last-revision, tags, and a stacked location pointer.
 
849
 
 
850
    The stacked location pointer is passed down to the repository and requires
 
851
    a repository format with supports_external_lookups = True.
 
852
 
 
853
    This format was introduced in bzr 1.6.
 
854
    """
 
855
 
 
856
    def initialize(self, a_bzrdir, name=None, repository=None,
 
857
                   append_revisions_only=None):
 
858
        """Create a branch of this format in a_bzrdir."""
 
859
        utf8_files = [('last-revision', '0 null:\n'),
 
860
                      ('branch.conf',
 
861
                          self._get_initial_config(append_revisions_only)),
 
862
                      ('tags', ''),
 
863
                      ]
 
864
        return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
 
865
 
 
866
    def _branch_class(self):
 
867
        return BzrBranch7
 
868
 
 
869
    @classmethod
 
870
    def get_format_string(cls):
 
871
        """See BranchFormat.get_format_string()."""
 
872
        return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
 
873
 
 
874
    def get_format_description(self):
 
875
        """See BranchFormat.get_format_description()."""
 
876
        return "Branch format 7"
 
877
 
 
878
    def supports_set_append_revisions_only(self):
 
879
        return True
 
880
 
 
881
    def supports_stacking(self):
 
882
        return True
 
883
 
 
884
    def make_tags(self, branch):
 
885
        """See breezy.branch.BranchFormat.make_tags()."""
 
886
        return _mod_tag.BasicTags(branch)
 
887
 
 
888
    supports_reference_locations = False
 
889
 
 
890
 
 
891
class BranchReferenceFormat(BranchFormatMetadir):
 
892
    """Bzr branch reference format.
 
893
 
 
894
    Branch references are used in implementing checkouts, they
 
895
    act as an alias to the real branch which is at some other url.
 
896
 
 
897
    This format has:
 
898
     - A location file
 
899
     - a format string
 
900
    """
 
901
 
 
902
    @classmethod
 
903
    def get_format_string(cls):
 
904
        """See BranchFormat.get_format_string()."""
 
905
        return "Bazaar-NG Branch Reference Format 1\n"
 
906
 
 
907
    def get_format_description(self):
 
908
        """See BranchFormat.get_format_description()."""
 
909
        return "Checkout reference format 1"
 
910
 
 
911
    def get_reference(self, a_bzrdir, name=None):
 
912
        """See BranchFormat.get_reference()."""
 
913
        transport = a_bzrdir.get_branch_transport(None, name=name)
 
914
        return transport.get_bytes('location')
 
915
 
 
916
    def set_reference(self, a_bzrdir, name, to_branch):
 
917
        """See BranchFormat.set_reference()."""
 
918
        transport = a_bzrdir.get_branch_transport(None, name=name)
 
919
        location = transport.put_bytes('location', to_branch.base)
 
920
 
 
921
    def initialize(self, a_bzrdir, name=None, target_branch=None,
 
922
            repository=None, append_revisions_only=None):
 
923
        """Create a branch of this format in a_bzrdir."""
 
924
        if target_branch is None:
 
925
            # this format does not implement branch itself, thus the implicit
 
926
            # creation contract must see it as uninitializable
 
927
            raise errors.UninitializableFormat(self)
 
928
        mutter('creating branch reference in %s', a_bzrdir.user_url)
 
929
        if a_bzrdir._format.fixed_components:
 
930
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
931
        if name is None:
 
932
            name = a_bzrdir._get_selected_branch()
 
933
        branch_transport = a_bzrdir.get_branch_transport(self, name=name)
 
934
        branch_transport.put_bytes('location',
 
935
            target_branch.user_url)
 
936
        branch_transport.put_bytes('format', self.as_string())
 
937
        branch = self.open(a_bzrdir, name, _found=True,
 
938
            possible_transports=[target_branch.bzrdir.root_transport])
 
939
        self._run_post_branch_init_hooks(a_bzrdir, name, branch)
 
940
        return branch
 
941
 
 
942
    def _make_reference_clone_function(format, a_branch):
 
943
        """Create a clone() routine for a branch dynamically."""
 
944
        def clone(to_bzrdir, revision_id=None,
 
945
            repository_policy=None):
 
946
            """See Branch.clone()."""
 
947
            return format.initialize(to_bzrdir, target_branch=a_branch)
 
948
            # cannot obey revision_id limits when cloning a reference ...
 
949
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
950
            # emit some sort of warning/error to the caller ?!
 
951
        return clone
 
952
 
 
953
    def open(self, a_bzrdir, name=None, _found=False, location=None,
 
954
             possible_transports=None, ignore_fallbacks=False,
 
955
             found_repository=None):
 
956
        """Return the branch that the branch reference in a_bzrdir points at.
 
957
 
 
958
        :param a_bzrdir: A BzrDir that contains a branch.
 
959
        :param name: Name of colocated branch to open, if any
 
960
        :param _found: a private parameter, do not use it. It is used to
 
961
            indicate if format probing has already be done.
 
962
        :param ignore_fallbacks: when set, no fallback branches will be opened
 
963
            (if there are any).  Default is to open fallbacks.
 
964
        :param location: The location of the referenced branch.  If
 
965
            unspecified, this will be determined from the branch reference in
 
966
            a_bzrdir.
 
967
        :param possible_transports: An optional reusable transports list.
 
968
        """
 
969
        if name is None:
 
970
            name = a_bzrdir._get_selected_branch()
 
971
        if not _found:
 
972
            format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
 
973
            if format.__class__ != self.__class__:
 
974
                raise AssertionError("wrong format %r found for %r" %
 
975
                    (format, self))
 
976
        if location is None:
 
977
            location = self.get_reference(a_bzrdir, name)
 
978
        real_bzrdir = controldir.ControlDir.open(
 
979
            location, possible_transports=possible_transports)
 
980
        result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
 
981
            possible_transports=possible_transports)
 
982
        # this changes the behaviour of result.clone to create a new reference
 
983
        # rather than a copy of the content of the branch.
 
984
        # I did not use a proxy object because that needs much more extensive
 
985
        # testing, and we are only changing one behaviour at the moment.
 
986
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
987
        # then this should be refactored to introduce a tested proxy branch
 
988
        # and a subclass of that for use in overriding clone() and ....
 
989
        # - RBC 20060210
 
990
        result.clone = self._make_reference_clone_function(result)
 
991
        return result
 
992
 
 
993
 
 
994
class Converter5to6(object):
 
995
    """Perform an in-place upgrade of format 5 to format 6"""
 
996
 
 
997
    def convert(self, branch):
 
998
        # Data for 5 and 6 can peacefully coexist.
 
999
        format = BzrBranchFormat6()
 
1000
        new_branch = format.open(branch.bzrdir, _found=True)
 
1001
 
 
1002
        # Copy source data into target
 
1003
        new_branch._write_last_revision_info(*branch.last_revision_info())
 
1004
        new_branch.lock_write()
 
1005
        try:
 
1006
            new_branch.set_parent(branch.get_parent())
 
1007
            new_branch.set_bound_location(branch.get_bound_location())
 
1008
            new_branch.set_push_location(branch.get_push_location())
 
1009
        finally:
 
1010
            new_branch.unlock()
 
1011
 
 
1012
        # New branch has no tags by default
 
1013
        new_branch.tags._set_tag_dict({})
 
1014
 
 
1015
        # Copying done; now update target format
 
1016
        new_branch._transport.put_bytes('format',
 
1017
            format.as_string(),
 
1018
            mode=new_branch.bzrdir._get_file_mode())
 
1019
 
 
1020
        # Clean up old files
 
1021
        new_branch._transport.delete('revision-history')
 
1022
        branch.lock_write()
 
1023
        try:
 
1024
            try:
 
1025
                branch.set_parent(None)
 
1026
            except errors.NoSuchFile:
 
1027
                pass
 
1028
            branch.set_bound_location(None)
 
1029
        finally:
 
1030
            branch.unlock()
 
1031
 
 
1032
 
 
1033
class Converter6to7(object):
 
1034
    """Perform an in-place upgrade of format 6 to format 7"""
 
1035
 
 
1036
    def convert(self, branch):
 
1037
        format = BzrBranchFormat7()
 
1038
        branch._set_config_location('stacked_on_location', '')
 
1039
        # update target format
 
1040
        branch._transport.put_bytes('format', format.as_string())
 
1041
 
 
1042
 
 
1043
class Converter7to8(object):
 
1044
    """Perform an in-place upgrade of format 7 to format 8"""
 
1045
 
 
1046
    def convert(self, branch):
 
1047
        format = BzrBranchFormat8()
 
1048
        branch._transport.put_bytes('references', '')
 
1049
        # update target format
 
1050
        branch._transport.put_bytes('format', format.as_string())
 
1051
 
 
1052
 
 
1053