/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-05 09:42:14 UTC
  • mto: This revision was merged to the branch mainline in revision 6666.
  • Revision ID: jelmer@jelmer.uk-20170605094214-iy8m1eay3skjasyf
Fix some more imports.

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