/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-04 21:54:56 UTC
  • mto: This revision was merged to the branch mainline in revision 6666.
  • Revision ID: jelmer@jelmer.uk-20170604215456-pbu16psy2m4v1cya
Split bzr branch code out into breezy.bzrbranch.

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