/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/branch.py

  • Committer: Jelmer Vernooij
  • Date: 2020-02-07 02:14:30 UTC
  • mto: This revision was merged to the branch mainline in revision 7492.
  • Revision ID: jelmer@jelmer.uk-20200207021430-m49iq3x4x8xlib6x
Drop python2 support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2012 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
from __future__ import absolute_import
 
18
 
 
19
from .lazy_import import lazy_import
 
20
lazy_import(globals(), """
 
21
import contextlib
 
22
import itertools
 
23
from breezy import (
 
24
    config as _mod_config,
 
25
    debug,
 
26
    memorytree,
 
27
    repository,
 
28
    revision as _mod_revision,
 
29
    tag as _mod_tag,
 
30
    transport,
 
31
    ui,
 
32
    urlutils,
 
33
    )
 
34
from breezy.bzr import (
 
35
    fetch,
 
36
    remote,
 
37
    vf_search,
 
38
    )
 
39
from breezy.i18n import gettext, ngettext
 
40
""")
 
41
 
 
42
from . import (
 
43
    controldir,
 
44
    errors,
 
45
    registry,
 
46
    )
 
47
from .hooks import Hooks
 
48
from .inter import InterObject
 
49
from .lock import LogicalLockResult
 
50
from .trace import mutter, mutter_callsite, note, is_quiet, warning
 
51
 
 
52
 
 
53
class UnstackableBranchFormat(errors.BzrError):
 
54
 
 
55
    _fmt = ("The branch '%(url)s'(%(format)s) is not a stackable format. "
 
56
            "You will need to upgrade the branch to permit branch stacking.")
 
57
 
 
58
    def __init__(self, format, url):
 
59
        errors.BzrError.__init__(self)
 
60
        self.format = format
 
61
        self.url = url
 
62
 
 
63
 
 
64
class Branch(controldir.ControlComponent):
 
65
    """Branch holding a history of revisions.
 
66
 
 
67
    :ivar base:
 
68
        Base directory/url of the branch; using control_url and
 
69
        control_transport is more standardized.
 
70
    :ivar hooks: An instance of BranchHooks.
 
71
    :ivar _master_branch_cache: cached result of get_master_branch, see
 
72
        _clear_cached_state.
 
73
    """
 
74
    # this is really an instance variable - FIXME move it there
 
75
    # - RBC 20060112
 
76
    base = None
 
77
 
 
78
    @property
 
79
    def control_transport(self):
 
80
        return self._transport
 
81
 
 
82
    @property
 
83
    def user_transport(self):
 
84
        return self.controldir.user_transport
 
85
 
 
86
    def __init__(self, possible_transports=None):
 
87
        self.tags = self._format.make_tags(self)
 
88
        self._revision_history_cache = None
 
89
        self._revision_id_to_revno_cache = None
 
90
        self._partial_revision_id_to_revno_cache = {}
 
91
        self._partial_revision_history_cache = []
 
92
        self._last_revision_info_cache = None
 
93
        self._master_branch_cache = None
 
94
        self._merge_sorted_revisions_cache = None
 
95
        self._open_hook(possible_transports)
 
96
        hooks = Branch.hooks['open']
 
97
        for hook in hooks:
 
98
            hook(self)
 
99
 
 
100
    def _open_hook(self, possible_transports):
 
101
        """Called by init to allow simpler extension of the base class."""
 
102
 
 
103
    def _activate_fallback_location(self, url, possible_transports):
 
104
        """Activate the branch/repository from url as a fallback repository."""
 
105
        for existing_fallback_repo in self.repository._fallback_repositories:
 
106
            if existing_fallback_repo.user_url == url:
 
107
                # This fallback is already configured.  This probably only
 
108
                # happens because ControlDir.sprout is a horrible mess.  To
 
109
                # avoid confusing _unstack we don't add this a second time.
 
110
                mutter('duplicate activation of fallback %r on %r', url, self)
 
111
                return
 
112
        repo = self._get_fallback_repository(url, possible_transports)
 
113
        if repo.has_same_location(self.repository):
 
114
            raise errors.UnstackableLocationError(self.user_url, url)
 
115
        self.repository.add_fallback_repository(repo)
 
116
 
 
117
    def break_lock(self):
 
118
        """Break a lock if one is present from another instance.
 
119
 
 
120
        Uses the ui factory to ask for confirmation if the lock may be from
 
121
        an active process.
 
122
 
 
123
        This will probe the repository for its lock as well.
 
124
        """
 
125
        self.control_files.break_lock()
 
126
        self.repository.break_lock()
 
127
        master = self.get_master_branch()
 
128
        if master is not None:
 
129
            master.break_lock()
 
130
 
 
131
    def _check_stackable_repo(self):
 
132
        if not self.repository._format.supports_external_lookups:
 
133
            raise errors.UnstackableRepositoryFormat(
 
134
                self.repository._format, self.repository.base)
 
135
 
 
136
    def _extend_partial_history(self, stop_index=None, stop_revision=None):
 
137
        """Extend the partial history to include a given index
 
138
 
 
139
        If a stop_index is supplied, stop when that index has been reached.
 
140
        If a stop_revision is supplied, stop when that revision is
 
141
        encountered.  Otherwise, stop when the beginning of history is
 
142
        reached.
 
143
 
 
144
        :param stop_index: The index which should be present.  When it is
 
145
            present, history extension will stop.
 
146
        :param stop_revision: The revision id which should be present.  When
 
147
            it is encountered, history extension will stop.
 
148
        """
 
149
        if len(self._partial_revision_history_cache) == 0:
 
150
            self._partial_revision_history_cache = [self.last_revision()]
 
151
        repository._iter_for_revno(
 
152
            self.repository, self._partial_revision_history_cache,
 
153
            stop_index=stop_index, stop_revision=stop_revision)
 
154
        if self._partial_revision_history_cache[-1] == \
 
155
                _mod_revision.NULL_REVISION:
 
156
            self._partial_revision_history_cache.pop()
 
157
 
 
158
    def _get_check_refs(self):
 
159
        """Get the references needed for check().
 
160
 
 
161
        See breezy.check.
 
162
        """
 
163
        revid = self.last_revision()
 
164
        return [('revision-existence', revid), ('lefthand-distance', revid)]
 
165
 
 
166
    @staticmethod
 
167
    def open(base, _unsupported=False, possible_transports=None):
 
168
        """Open the branch rooted at base.
 
169
 
 
170
        For instance, if the branch is at URL/.bzr/branch,
 
171
        Branch.open(URL) -> a Branch instance.
 
172
        """
 
173
        control = controldir.ControlDir.open(
 
174
            base, possible_transports=possible_transports,
 
175
            _unsupported=_unsupported)
 
176
        return control.open_branch(
 
177
            unsupported=_unsupported,
 
178
            possible_transports=possible_transports)
 
179
 
 
180
    @staticmethod
 
181
    def open_from_transport(transport, name=None, _unsupported=False,
 
182
                            possible_transports=None):
 
183
        """Open the branch rooted at transport"""
 
184
        control = controldir.ControlDir.open_from_transport(
 
185
            transport, _unsupported)
 
186
        return control.open_branch(
 
187
            name=name, unsupported=_unsupported,
 
188
            possible_transports=possible_transports)
 
189
 
 
190
    @staticmethod
 
191
    def open_containing(url, possible_transports=None):
 
192
        """Open an existing branch which contains url.
 
193
 
 
194
        This probes for a branch at url, and searches upwards from there.
 
195
 
 
196
        Basically we keep looking up until we find the control directory or
 
197
        run into the root.  If there isn't one, raises NotBranchError.
 
198
        If there is one and it is either an unrecognised format or an
 
199
        unsupported format, UnknownFormatError or UnsupportedFormatError are
 
200
        raised.  If there is one, it is returned, along with the unused portion
 
201
        of url.
 
202
        """
 
203
        control, relpath = controldir.ControlDir.open_containing(
 
204
            url, possible_transports)
 
205
        branch = control.open_branch(possible_transports=possible_transports)
 
206
        return (branch, relpath)
 
207
 
 
208
    def _push_should_merge_tags(self):
 
209
        """Should _basic_push merge this branch's tags into the target?
 
210
 
 
211
        The default implementation returns False if this branch has no tags,
 
212
        and True the rest of the time.  Subclasses may override this.
 
213
        """
 
214
        return self.supports_tags() and self.tags.get_tag_dict()
 
215
 
 
216
    def get_config(self):
 
217
        """Get a breezy.config.BranchConfig for this Branch.
 
218
 
 
219
        This can then be used to get and set configuration options for the
 
220
        branch.
 
221
 
 
222
        :return: A breezy.config.BranchConfig.
 
223
        """
 
224
        return _mod_config.BranchConfig(self)
 
225
 
 
226
    def get_config_stack(self):
 
227
        """Get a breezy.config.BranchStack for this Branch.
 
228
 
 
229
        This can then be used to get and set configuration options for the
 
230
        branch.
 
231
 
 
232
        :return: A breezy.config.BranchStack.
 
233
        """
 
234
        return _mod_config.BranchStack(self)
 
235
 
 
236
    def store_uncommitted(self, creator):
 
237
        """Store uncommitted changes from a ShelfCreator.
 
238
 
 
239
        :param creator: The ShelfCreator containing uncommitted changes, or
 
240
            None to delete any stored changes.
 
241
        :raises: ChangesAlreadyStored if the branch already has changes.
 
242
        """
 
243
        raise NotImplementedError(self.store_uncommitted)
 
244
 
 
245
    def get_unshelver(self, tree):
 
246
        """Return a shelf.Unshelver for this branch and tree.
 
247
 
 
248
        :param tree: The tree to use to construct the Unshelver.
 
249
        :return: an Unshelver or None if no changes are stored.
 
250
        """
 
251
        raise NotImplementedError(self.get_unshelver)
 
252
 
 
253
    def _get_fallback_repository(self, url, possible_transports):
 
254
        """Get the repository we fallback to at url."""
 
255
        url = urlutils.join(self.base, url)
 
256
        a_branch = Branch.open(url, possible_transports=possible_transports)
 
257
        return a_branch.repository
 
258
 
 
259
    def _get_nick(self, local=False, possible_transports=None):
 
260
        config = self.get_config()
 
261
        # explicit overrides master, but don't look for master if local is True
 
262
        if not local and not config.has_explicit_nickname():
 
263
            try:
 
264
                master = self.get_master_branch(possible_transports)
 
265
                if master and self.user_url == master.user_url:
 
266
                    raise errors.RecursiveBind(self.user_url)
 
267
                if master is not None:
 
268
                    # return the master branch value
 
269
                    return master.nick
 
270
            except errors.RecursiveBind as e:
 
271
                raise e
 
272
            except errors.BzrError as e:
 
273
                # Silently fall back to local implicit nick if the master is
 
274
                # unavailable
 
275
                mutter("Could not connect to bound branch, "
 
276
                       "falling back to local nick.\n " + str(e))
 
277
        return config.get_nickname()
 
278
 
 
279
    def _set_nick(self, nick):
 
280
        self.get_config().set_user_option('nickname', nick, warn_masked=True)
 
281
 
 
282
    nick = property(_get_nick, _set_nick)
 
283
 
 
284
    def is_locked(self):
 
285
        raise NotImplementedError(self.is_locked)
 
286
 
 
287
    def _lefthand_history(self, revision_id, last_rev=None,
 
288
                          other_branch=None):
 
289
        if 'evil' in debug.debug_flags:
 
290
            mutter_callsite(4, "_lefthand_history scales with history.")
 
291
        # stop_revision must be a descendant of last_revision
 
292
        graph = self.repository.get_graph()
 
293
        if last_rev is not None:
 
294
            if not graph.is_ancestor(last_rev, revision_id):
 
295
                # our previous tip is not merged into stop_revision
 
296
                raise errors.DivergedBranches(self, other_branch)
 
297
        # make a new revision history from the graph
 
298
        parents_map = graph.get_parent_map([revision_id])
 
299
        if revision_id not in parents_map:
 
300
            raise errors.NoSuchRevision(self, revision_id)
 
301
        current_rev_id = revision_id
 
302
        new_history = []
 
303
        check_not_reserved_id = _mod_revision.check_not_reserved_id
 
304
        # Do not include ghosts or graph origin in revision_history
 
305
        while (current_rev_id in parents_map
 
306
               and len(parents_map[current_rev_id]) > 0):
 
307
            check_not_reserved_id(current_rev_id)
 
308
            new_history.append(current_rev_id)
 
309
            current_rev_id = parents_map[current_rev_id][0]
 
310
            parents_map = graph.get_parent_map([current_rev_id])
 
311
        new_history.reverse()
 
312
        return new_history
 
313
 
 
314
    def lock_write(self, token=None):
 
315
        """Lock the branch for write operations.
 
316
 
 
317
        :param token: A token to permit reacquiring a previously held and
 
318
            preserved lock.
 
319
        :return: A BranchWriteLockResult.
 
320
        """
 
321
        raise NotImplementedError(self.lock_write)
 
322
 
 
323
    def lock_read(self):
 
324
        """Lock the branch for read operations.
 
325
 
 
326
        :return: A breezy.lock.LogicalLockResult.
 
327
        """
 
328
        raise NotImplementedError(self.lock_read)
 
329
 
 
330
    def unlock(self):
 
331
        raise NotImplementedError(self.unlock)
 
332
 
 
333
    def peek_lock_mode(self):
 
334
        """Return lock mode for the Branch: 'r', 'w' or None"""
 
335
        raise NotImplementedError(self.peek_lock_mode)
 
336
 
 
337
    def get_physical_lock_status(self):
 
338
        raise NotImplementedError(self.get_physical_lock_status)
 
339
 
 
340
    def dotted_revno_to_revision_id(self, revno, _cache_reverse=False):
 
341
        """Return the revision_id for a dotted revno.
 
342
 
 
343
        :param revno: a tuple like (1,) or (1,1,2)
 
344
        :param _cache_reverse: a private parameter enabling storage
 
345
           of the reverse mapping in a top level cache. (This should
 
346
           only be done in selective circumstances as we want to
 
347
           avoid having the mapping cached multiple times.)
 
348
        :return: the revision_id
 
349
        :raises errors.NoSuchRevision: if the revno doesn't exist
 
350
        """
 
351
        with self.lock_read():
 
352
            rev_id = self._do_dotted_revno_to_revision_id(revno)
 
353
            if _cache_reverse:
 
354
                self._partial_revision_id_to_revno_cache[rev_id] = revno
 
355
            return rev_id
 
356
 
 
357
    def _do_dotted_revno_to_revision_id(self, revno):
 
358
        """Worker function for dotted_revno_to_revision_id.
 
359
 
 
360
        Subclasses should override this if they wish to
 
361
        provide a more efficient implementation.
 
362
        """
 
363
        if len(revno) == 1:
 
364
            try:
 
365
                return self.get_rev_id(revno[0])
 
366
            except errors.RevisionNotPresent as e:
 
367
                raise errors.GhostRevisionsHaveNoRevno(revno[0], e.revision_id)
 
368
        revision_id_to_revno = self.get_revision_id_to_revno_map()
 
369
        revision_ids = [revision_id for revision_id, this_revno
 
370
                        in revision_id_to_revno.items()
 
371
                        if revno == this_revno]
 
372
        if len(revision_ids) == 1:
 
373
            return revision_ids[0]
 
374
        else:
 
375
            revno_str = '.'.join(map(str, revno))
 
376
            raise errors.NoSuchRevision(self, revno_str)
 
377
 
 
378
    def revision_id_to_dotted_revno(self, revision_id):
 
379
        """Given a revision id, return its dotted revno.
 
380
 
 
381
        :return: a tuple like (1,) or (400,1,3).
 
382
        """
 
383
        with self.lock_read():
 
384
            return self._do_revision_id_to_dotted_revno(revision_id)
 
385
 
 
386
    def _do_revision_id_to_dotted_revno(self, revision_id):
 
387
        """Worker function for revision_id_to_revno."""
 
388
        # Try the caches if they are loaded
 
389
        result = self._partial_revision_id_to_revno_cache.get(revision_id)
 
390
        if result is not None:
 
391
            return result
 
392
        if self._revision_id_to_revno_cache:
 
393
            result = self._revision_id_to_revno_cache.get(revision_id)
 
394
            if result is None:
 
395
                raise errors.NoSuchRevision(self, revision_id)
 
396
        # Try the mainline as it's optimised
 
397
        try:
 
398
            revno = self.revision_id_to_revno(revision_id)
 
399
            return (revno,)
 
400
        except errors.NoSuchRevision:
 
401
            # We need to load and use the full revno map after all
 
402
            result = self.get_revision_id_to_revno_map().get(revision_id)
 
403
            if result is None:
 
404
                raise errors.NoSuchRevision(self, revision_id)
 
405
        return result
 
406
 
 
407
    def get_revision_id_to_revno_map(self):
 
408
        """Return the revision_id => dotted revno map.
 
409
 
 
410
        This will be regenerated on demand, but will be cached.
 
411
 
 
412
        :return: A dictionary mapping revision_id => dotted revno.
 
413
            This dictionary should not be modified by the caller.
 
414
        """
 
415
        if 'evil' in debug.debug_flags:
 
416
            mutter_callsite(
 
417
                3, "get_revision_id_to_revno_map scales with ancestry.")
 
418
        with self.lock_read():
 
419
            if self._revision_id_to_revno_cache is not None:
 
420
                mapping = self._revision_id_to_revno_cache
 
421
            else:
 
422
                mapping = self._gen_revno_map()
 
423
                self._cache_revision_id_to_revno(mapping)
 
424
            # TODO: jam 20070417 Since this is being cached, should we be
 
425
            # returning a copy?
 
426
            # I would rather not, and instead just declare that users should
 
427
            # not modify the return value.
 
428
            return mapping
 
429
 
 
430
    def _gen_revno_map(self):
 
431
        """Create a new mapping from revision ids to dotted revnos.
 
432
 
 
433
        Dotted revnos are generated based on the current tip in the revision
 
434
        history.
 
435
        This is the worker function for get_revision_id_to_revno_map, which
 
436
        just caches the return value.
 
437
 
 
438
        :return: A dictionary mapping revision_id => dotted revno.
 
439
        """
 
440
        revision_id_to_revno = {
 
441
            rev_id: revno for rev_id, depth, revno, end_of_merge
 
442
            in self.iter_merge_sorted_revisions()}
 
443
        return revision_id_to_revno
 
444
 
 
445
    def iter_merge_sorted_revisions(self, start_revision_id=None,
 
446
                                    stop_revision_id=None,
 
447
                                    stop_rule='exclude', direction='reverse'):
 
448
        """Walk the revisions for a branch in merge sorted order.
 
449
 
 
450
        Merge sorted order is the output from a merge-aware,
 
451
        topological sort, i.e. all parents come before their
 
452
        children going forward; the opposite for reverse.
 
453
 
 
454
        :param start_revision_id: the revision_id to begin walking from.
 
455
            If None, the branch tip is used.
 
456
        :param stop_revision_id: the revision_id to terminate the walk
 
457
            after. If None, the rest of history is included.
 
458
        :param stop_rule: if stop_revision_id is not None, the precise rule
 
459
            to use for termination:
 
460
 
 
461
            * 'exclude' - leave the stop revision out of the result (default)
 
462
            * 'include' - the stop revision is the last item in the result
 
463
            * 'with-merges' - include the stop revision and all of its
 
464
              merged revisions in the result
 
465
            * 'with-merges-without-common-ancestry' - filter out revisions
 
466
              that are in both ancestries
 
467
        :param direction: either 'reverse' or 'forward':
 
468
 
 
469
            * reverse means return the start_revision_id first, i.e.
 
470
              start at the most recent revision and go backwards in history
 
471
            * forward returns tuples in the opposite order to reverse.
 
472
              Note in particular that forward does *not* do any intelligent
 
473
              ordering w.r.t. depth as some clients of this API may like.
 
474
              (If required, that ought to be done at higher layers.)
 
475
 
 
476
        :return: an iterator over (revision_id, depth, revno, end_of_merge)
 
477
            tuples where:
 
478
 
 
479
            * revision_id: the unique id of the revision
 
480
            * depth: How many levels of merging deep this node has been
 
481
              found.
 
482
            * revno_sequence: This field provides a sequence of
 
483
              revision numbers for all revisions. The format is:
 
484
              (REVNO, BRANCHNUM, BRANCHREVNO). BRANCHNUM is the number of the
 
485
              branch that the revno is on. From left to right the REVNO numbers
 
486
              are the sequence numbers within that branch of the revision.
 
487
            * end_of_merge: When True the next node (earlier in history) is
 
488
              part of a different merge.
 
489
        """
 
490
        with self.lock_read():
 
491
            # Note: depth and revno values are in the context of the branch so
 
492
            # we need the full graph to get stable numbers, regardless of the
 
493
            # start_revision_id.
 
494
            if self._merge_sorted_revisions_cache is None:
 
495
                last_revision = self.last_revision()
 
496
                known_graph = self.repository.get_known_graph_ancestry(
 
497
                    [last_revision])
 
498
                self._merge_sorted_revisions_cache = known_graph.merge_sort(
 
499
                    last_revision)
 
500
            filtered = self._filter_merge_sorted_revisions(
 
501
                self._merge_sorted_revisions_cache, start_revision_id,
 
502
                stop_revision_id, stop_rule)
 
503
            # Make sure we don't return revisions that are not part of the
 
504
            # start_revision_id ancestry.
 
505
            filtered = self._filter_start_non_ancestors(filtered)
 
506
            if direction == 'reverse':
 
507
                return filtered
 
508
            if direction == 'forward':
 
509
                return reversed(list(filtered))
 
510
            else:
 
511
                raise ValueError('invalid direction %r' % direction)
 
512
 
 
513
    def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
 
514
                                       start_revision_id, stop_revision_id,
 
515
                                       stop_rule):
 
516
        """Iterate over an inclusive range of sorted revisions."""
 
517
        rev_iter = iter(merge_sorted_revisions)
 
518
        if start_revision_id is not None:
 
519
            for node in rev_iter:
 
520
                rev_id = node.key
 
521
                if rev_id != start_revision_id:
 
522
                    continue
 
523
                else:
 
524
                    # The decision to include the start or not
 
525
                    # depends on the stop_rule if a stop is provided
 
526
                    # so pop this node back into the iterator
 
527
                    rev_iter = itertools.chain(iter([node]), rev_iter)
 
528
                    break
 
529
        if stop_revision_id is None:
 
530
            # Yield everything
 
531
            for node in rev_iter:
 
532
                rev_id = node.key
 
533
                yield (rev_id, node.merge_depth, node.revno,
 
534
                       node.end_of_merge)
 
535
        elif stop_rule == 'exclude':
 
536
            for node in rev_iter:
 
537
                rev_id = node.key
 
538
                if rev_id == stop_revision_id:
 
539
                    return
 
540
                yield (rev_id, node.merge_depth, node.revno,
 
541
                       node.end_of_merge)
 
542
        elif stop_rule == 'include':
 
543
            for node in rev_iter:
 
544
                rev_id = node.key
 
545
                yield (rev_id, node.merge_depth, node.revno,
 
546
                       node.end_of_merge)
 
547
                if rev_id == stop_revision_id:
 
548
                    return
 
549
        elif stop_rule == 'with-merges-without-common-ancestry':
 
550
            # We want to exclude all revisions that are already part of the
 
551
            # stop_revision_id ancestry.
 
552
            graph = self.repository.get_graph()
 
553
            ancestors = graph.find_unique_ancestors(start_revision_id,
 
554
                                                    [stop_revision_id])
 
555
            for node in rev_iter:
 
556
                rev_id = node.key
 
557
                if rev_id not in ancestors:
 
558
                    continue
 
559
                yield (rev_id, node.merge_depth, node.revno,
 
560
                       node.end_of_merge)
 
561
        elif stop_rule == 'with-merges':
 
562
            stop_rev = self.repository.get_revision(stop_revision_id)
 
563
            if stop_rev.parent_ids:
 
564
                left_parent = stop_rev.parent_ids[0]
 
565
            else:
 
566
                left_parent = _mod_revision.NULL_REVISION
 
567
            # left_parent is the actual revision we want to stop logging at,
 
568
            # since we want to show the merged revisions after the stop_rev too
 
569
            reached_stop_revision_id = False
 
570
            revision_id_whitelist = []
 
571
            for node in rev_iter:
 
572
                rev_id = node.key
 
573
                if rev_id == left_parent:
 
574
                    # reached the left parent after the stop_revision
 
575
                    return
 
576
                if (not reached_stop_revision_id
 
577
                        or rev_id in revision_id_whitelist):
 
578
                    yield (rev_id, node.merge_depth, node.revno,
 
579
                           node.end_of_merge)
 
580
                    if reached_stop_revision_id or rev_id == stop_revision_id:
 
581
                        # only do the merged revs of rev_id from now on
 
582
                        rev = self.repository.get_revision(rev_id)
 
583
                        if rev.parent_ids:
 
584
                            reached_stop_revision_id = True
 
585
                            revision_id_whitelist.extend(rev.parent_ids)
 
586
        else:
 
587
            raise ValueError('invalid stop_rule %r' % stop_rule)
 
588
 
 
589
    def _filter_start_non_ancestors(self, rev_iter):
 
590
        # If we started from a dotted revno, we want to consider it as a tip
 
591
        # and don't want to yield revisions that are not part of its
 
592
        # ancestry. Given the order guaranteed by the merge sort, we will see
 
593
        # uninteresting descendants of the first parent of our tip before the
 
594
        # tip itself.
 
595
        try:
 
596
            first = next(rev_iter)
 
597
        except StopIteration:
 
598
            return
 
599
        (rev_id, merge_depth, revno, end_of_merge) = first
 
600
        yield first
 
601
        if not merge_depth:
 
602
            # We start at a mainline revision so by definition, all others
 
603
            # revisions in rev_iter are ancestors
 
604
            for node in rev_iter:
 
605
                yield node
 
606
 
 
607
        clean = False
 
608
        whitelist = set()
 
609
        pmap = self.repository.get_parent_map([rev_id])
 
610
        parents = pmap.get(rev_id, [])
 
611
        if parents:
 
612
            whitelist.update(parents)
 
613
        else:
 
614
            # If there is no parents, there is nothing of interest left
 
615
 
 
616
            # FIXME: It's hard to test this scenario here as this code is never
 
617
            # called in that case. -- vila 20100322
 
618
            return
 
619
 
 
620
        for (rev_id, merge_depth, revno, end_of_merge) in rev_iter:
 
621
            if not clean:
 
622
                if rev_id in whitelist:
 
623
                    pmap = self.repository.get_parent_map([rev_id])
 
624
                    parents = pmap.get(rev_id, [])
 
625
                    whitelist.remove(rev_id)
 
626
                    whitelist.update(parents)
 
627
                    if merge_depth == 0:
 
628
                        # We've reached the mainline, there is nothing left to
 
629
                        # filter
 
630
                        clean = True
 
631
                else:
 
632
                    # A revision that is not part of the ancestry of our
 
633
                    # starting revision.
 
634
                    continue
 
635
            yield (rev_id, merge_depth, revno, end_of_merge)
 
636
 
 
637
    def leave_lock_in_place(self):
 
638
        """Tell this branch object not to release the physical lock when this
 
639
        object is unlocked.
 
640
 
 
641
        If lock_write doesn't return a token, then this method is not
 
642
        supported.
 
643
        """
 
644
        self.control_files.leave_in_place()
 
645
 
 
646
    def dont_leave_lock_in_place(self):
 
647
        """Tell this branch object to release the physical lock when this
 
648
        object is unlocked, even if it didn't originally acquire it.
 
649
 
 
650
        If lock_write doesn't return a token, then this method is not
 
651
        supported.
 
652
        """
 
653
        self.control_files.dont_leave_in_place()
 
654
 
 
655
    def bind(self, other):
 
656
        """Bind the local branch the other branch.
 
657
 
 
658
        :param other: The branch to bind to
 
659
        :type other: Branch
 
660
        """
 
661
        raise errors.UpgradeRequired(self.user_url)
 
662
 
 
663
    def get_append_revisions_only(self):
 
664
        """Whether it is only possible to append revisions to the history.
 
665
        """
 
666
        if not self._format.supports_set_append_revisions_only():
 
667
            return False
 
668
        return self.get_config_stack().get('append_revisions_only')
 
669
 
 
670
    def set_append_revisions_only(self, enabled):
 
671
        if not self._format.supports_set_append_revisions_only():
 
672
            raise errors.UpgradeRequired(self.user_url)
 
673
        self.get_config_stack().set('append_revisions_only', enabled)
 
674
 
 
675
    def fetch(self, from_branch, stop_revision=None, limit=None, lossy=False):
 
676
        """Copy revisions from from_branch into this branch.
 
677
 
 
678
        :param from_branch: Where to copy from.
 
679
        :param stop_revision: What revision to stop at (None for at the end
 
680
                              of the branch.
 
681
        :param limit: Optional rough limit of revisions to fetch
 
682
        :return: None
 
683
        """
 
684
        with self.lock_write():
 
685
            return InterBranch.get(from_branch, self).fetch(
 
686
                stop_revision, limit=limit, lossy=lossy)
 
687
 
 
688
    def get_bound_location(self):
 
689
        """Return the URL of the branch we are bound to.
 
690
 
 
691
        Older format branches cannot bind, please be sure to use a metadir
 
692
        branch.
 
693
        """
 
694
        return None
 
695
 
 
696
    def get_old_bound_location(self):
 
697
        """Return the URL of the branch we used to be bound to
 
698
        """
 
699
        raise errors.UpgradeRequired(self.user_url)
 
700
 
 
701
    def get_commit_builder(self, parents, config_stack=None, timestamp=None,
 
702
                           timezone=None, committer=None, revprops=None,
 
703
                           revision_id=None, lossy=False):
 
704
        """Obtain a CommitBuilder for this branch.
 
705
 
 
706
        :param parents: Revision ids of the parents of the new revision.
 
707
        :param config: Optional configuration to use.
 
708
        :param timestamp: Optional timestamp recorded for commit.
 
709
        :param timezone: Optional timezone for timestamp.
 
710
        :param committer: Optional committer to set for commit.
 
711
        :param revprops: Optional dictionary of revision properties.
 
712
        :param revision_id: Optional revision id.
 
713
        :param lossy: Whether to discard data that can not be natively
 
714
            represented, when pushing to a foreign VCS
 
715
        """
 
716
 
 
717
        if config_stack is None:
 
718
            config_stack = self.get_config_stack()
 
719
 
 
720
        return self.repository.get_commit_builder(
 
721
            self, parents, config_stack, timestamp, timezone, committer,
 
722
            revprops, revision_id, lossy)
 
723
 
 
724
    def get_master_branch(self, possible_transports=None):
 
725
        """Return the branch we are bound to.
 
726
 
 
727
        :return: Either a Branch, or None
 
728
        """
 
729
        return None
 
730
 
 
731
    def get_stacked_on_url(self):
 
732
        """Get the URL this branch is stacked against.
 
733
 
 
734
        :raises NotStacked: If the branch is not stacked.
 
735
        :raises UnstackableBranchFormat: If the branch does not support
 
736
            stacking.
 
737
        """
 
738
        raise NotImplementedError(self.get_stacked_on_url)
 
739
 
 
740
    def set_last_revision_info(self, revno, revision_id):
 
741
        """Set the last revision of this branch.
 
742
 
 
743
        The caller is responsible for checking that the revno is correct
 
744
        for this revision id.
 
745
 
 
746
        It may be possible to set the branch last revision to an id not
 
747
        present in the repository.  However, branches can also be
 
748
        configured to check constraints on history, in which case this may not
 
749
        be permitted.
 
750
        """
 
751
        raise NotImplementedError(self.set_last_revision_info)
 
752
 
 
753
    def generate_revision_history(self, revision_id, last_rev=None,
 
754
                                  other_branch=None):
 
755
        """See Branch.generate_revision_history"""
 
756
        with self.lock_write():
 
757
            graph = self.repository.get_graph()
 
758
            (last_revno, last_revid) = self.last_revision_info()
 
759
            known_revision_ids = [
 
760
                (last_revid, last_revno),
 
761
                (_mod_revision.NULL_REVISION, 0),
 
762
                ]
 
763
            if last_rev is not None:
 
764
                if not graph.is_ancestor(last_rev, revision_id):
 
765
                    # our previous tip is not merged into stop_revision
 
766
                    raise errors.DivergedBranches(self, other_branch)
 
767
            revno = graph.find_distance_to_null(
 
768
                revision_id, known_revision_ids)
 
769
            self.set_last_revision_info(revno, revision_id)
 
770
 
 
771
    def set_parent(self, url):
 
772
        """See Branch.set_parent."""
 
773
        # TODO: Maybe delete old location files?
 
774
        # URLs should never be unicode, even on the local fs,
 
775
        # FIXUP this and get_parent in a future branch format bump:
 
776
        # read and rewrite the file. RBC 20060125
 
777
        if url is not None:
 
778
            if isinstance(url, str):
 
779
                try:
 
780
                    url.encode('ascii')
 
781
                except UnicodeEncodeError:
 
782
                    raise urlutils.InvalidURL(
 
783
                        url, "Urls must be 7-bit ascii, "
 
784
                        "use breezy.urlutils.escape")
 
785
            url = urlutils.relative_url(self.base, url)
 
786
        with self.lock_write():
 
787
            self._set_parent_location(url)
 
788
 
 
789
    def set_stacked_on_url(self, url):
 
790
        """Set the URL this branch is stacked against.
 
791
 
 
792
        :raises UnstackableBranchFormat: If the branch does not support
 
793
            stacking.
 
794
        :raises UnstackableRepositoryFormat: If the repository does not support
 
795
            stacking.
 
796
        """
 
797
        if not self._format.supports_stacking():
 
798
            raise UnstackableBranchFormat(self._format, self.user_url)
 
799
        with self.lock_write():
 
800
            # XXX: Changing from one fallback repository to another does not
 
801
            # check that all the data you need is present in the new fallback.
 
802
            # Possibly it should.
 
803
            self._check_stackable_repo()
 
804
            if not url:
 
805
                try:
 
806
                    self.get_stacked_on_url()
 
807
                except (errors.NotStacked, UnstackableBranchFormat,
 
808
                        errors.UnstackableRepositoryFormat):
 
809
                    return
 
810
                self._unstack()
 
811
            else:
 
812
                self._activate_fallback_location(
 
813
                    url, possible_transports=[self.controldir.root_transport])
 
814
            # write this out after the repository is stacked to avoid setting a
 
815
            # stacked config that doesn't work.
 
816
            self._set_config_location('stacked_on_location', url)
 
817
 
 
818
    def _unstack(self):
 
819
        """Change a branch to be unstacked, copying data as needed.
 
820
 
 
821
        Don't call this directly, use set_stacked_on_url(None).
 
822
        """
 
823
        with ui.ui_factory.nested_progress_bar() as pb:
 
824
            pb.update(gettext("Unstacking"))
 
825
            # The basic approach here is to fetch the tip of the branch,
 
826
            # including all available ghosts, from the existing stacked
 
827
            # repository into a new repository object without the fallbacks.
 
828
            #
 
829
            # XXX: See <https://launchpad.net/bugs/397286> - this may not be
 
830
            # correct for CHKMap repostiories
 
831
            old_repository = self.repository
 
832
            if len(old_repository._fallback_repositories) != 1:
 
833
                raise AssertionError(
 
834
                    "can't cope with fallback repositories "
 
835
                    "of %r (fallbacks: %r)" % (
 
836
                        old_repository, old_repository._fallback_repositories))
 
837
            # Open the new repository object.
 
838
            # Repositories don't offer an interface to remove fallback
 
839
            # repositories today; take the conceptually simpler option and just
 
840
            # reopen it.  We reopen it starting from the URL so that we
 
841
            # get a separate connection for RemoteRepositories and can
 
842
            # stream from one of them to the other.  This does mean doing
 
843
            # separate SSH connection setup, but unstacking is not a
 
844
            # common operation so it's tolerable.
 
845
            new_bzrdir = controldir.ControlDir.open(
 
846
                self.controldir.root_transport.base)
 
847
            new_repository = new_bzrdir.find_repository()
 
848
            if new_repository._fallback_repositories:
 
849
                raise AssertionError(
 
850
                    "didn't expect %r to have fallback_repositories"
 
851
                    % (self.repository,))
 
852
            # Replace self.repository with the new repository.
 
853
            # Do our best to transfer the lock state (i.e. lock-tokens and
 
854
            # lock count) of self.repository to the new repository.
 
855
            lock_token = old_repository.lock_write().repository_token
 
856
            self.repository = new_repository
 
857
            if isinstance(self, remote.RemoteBranch):
 
858
                # Remote branches can have a second reference to the old
 
859
                # repository that need to be replaced.
 
860
                if self._real_branch is not None:
 
861
                    self._real_branch.repository = new_repository
 
862
            self.repository.lock_write(token=lock_token)
 
863
            if lock_token is not None:
 
864
                old_repository.leave_lock_in_place()
 
865
            old_repository.unlock()
 
866
            if lock_token is not None:
 
867
                # XXX: self.repository.leave_lock_in_place() before this
 
868
                # function will not be preserved.  Fortunately that doesn't
 
869
                # affect the current default format (2a), and would be a
 
870
                # corner-case anyway.
 
871
                #  - Andrew Bennetts, 2010/06/30
 
872
                self.repository.dont_leave_lock_in_place()
 
873
            old_lock_count = 0
 
874
            while True:
 
875
                try:
 
876
                    old_repository.unlock()
 
877
                except errors.LockNotHeld:
 
878
                    break
 
879
                old_lock_count += 1
 
880
            if old_lock_count == 0:
 
881
                raise AssertionError(
 
882
                    'old_repository should have been locked at least once.')
 
883
            for i in range(old_lock_count - 1):
 
884
                self.repository.lock_write()
 
885
            # Fetch from the old repository into the new.
 
886
            with old_repository.lock_read():
 
887
                # XXX: If you unstack a branch while it has a working tree
 
888
                # with a pending merge, the pending-merged revisions will no
 
889
                # longer be present.  You can (probably) revert and remerge.
 
890
                try:
 
891
                    tags_to_fetch = set(self.tags.get_reverse_tag_dict())
 
892
                except errors.TagsNotSupported:
 
893
                    tags_to_fetch = set()
 
894
                fetch_spec = vf_search.NotInOtherForRevs(
 
895
                    self.repository, old_repository,
 
896
                    required_ids=[self.last_revision()],
 
897
                    if_present_ids=tags_to_fetch, find_ghosts=True).execute()
 
898
                self.repository.fetch(old_repository, fetch_spec=fetch_spec)
 
899
 
 
900
    def _cache_revision_history(self, rev_history):
 
901
        """Set the cached revision history to rev_history.
 
902
 
 
903
        The revision_history method will use this cache to avoid regenerating
 
904
        the revision history.
 
905
 
 
906
        This API is semi-public; it only for use by subclasses, all other code
 
907
        should consider it to be private.
 
908
        """
 
909
        self._revision_history_cache = rev_history
 
910
 
 
911
    def _cache_revision_id_to_revno(self, revision_id_to_revno):
 
912
        """Set the cached revision_id => revno map to revision_id_to_revno.
 
913
 
 
914
        This API is semi-public; it only for use by subclasses, all other code
 
915
        should consider it to be private.
 
916
        """
 
917
        self._revision_id_to_revno_cache = revision_id_to_revno
 
918
 
 
919
    def _clear_cached_state(self):
 
920
        """Clear any cached data on this branch, e.g. cached revision history.
 
921
 
 
922
        This means the next call to revision_history will need to call
 
923
        _gen_revision_history.
 
924
 
 
925
        This API is semi-public; it is only for use by subclasses, all other
 
926
        code should consider it to be private.
 
927
        """
 
928
        self._revision_history_cache = None
 
929
        self._revision_id_to_revno_cache = None
 
930
        self._last_revision_info_cache = None
 
931
        self._master_branch_cache = None
 
932
        self._merge_sorted_revisions_cache = None
 
933
        self._partial_revision_history_cache = []
 
934
        self._partial_revision_id_to_revno_cache = {}
 
935
 
 
936
    def _gen_revision_history(self):
 
937
        """Return sequence of revision hashes on to this branch.
 
938
 
 
939
        Unlike revision_history, this method always regenerates or rereads the
 
940
        revision history, i.e. it does not cache the result, so repeated calls
 
941
        may be expensive.
 
942
 
 
943
        Concrete subclasses should override this instead of revision_history so
 
944
        that subclasses do not need to deal with caching logic.
 
945
 
 
946
        This API is semi-public; it only for use by subclasses, all other code
 
947
        should consider it to be private.
 
948
        """
 
949
        raise NotImplementedError(self._gen_revision_history)
 
950
 
 
951
    def _revision_history(self):
 
952
        if 'evil' in debug.debug_flags:
 
953
            mutter_callsite(3, "revision_history scales with history.")
 
954
        if self._revision_history_cache is not None:
 
955
            history = self._revision_history_cache
 
956
        else:
 
957
            history = self._gen_revision_history()
 
958
            self._cache_revision_history(history)
 
959
        return list(history)
 
960
 
 
961
    def revno(self):
 
962
        """Return current revision number for this branch.
 
963
 
 
964
        That is equivalent to the number of revisions committed to
 
965
        this branch.
 
966
        """
 
967
        return self.last_revision_info()[0]
 
968
 
 
969
    def unbind(self):
 
970
        """Older format branches cannot bind or unbind."""
 
971
        raise errors.UpgradeRequired(self.user_url)
 
972
 
 
973
    def last_revision(self):
 
974
        """Return last revision id, or NULL_REVISION."""
 
975
        return self.last_revision_info()[1]
 
976
 
 
977
    def last_revision_info(self):
 
978
        """Return information about the last revision.
 
979
 
 
980
        :return: A tuple (revno, revision_id).
 
981
        """
 
982
        with self.lock_read():
 
983
            if self._last_revision_info_cache is None:
 
984
                self._last_revision_info_cache = (
 
985
                    self._read_last_revision_info())
 
986
            return self._last_revision_info_cache
 
987
 
 
988
    def _read_last_revision_info(self):
 
989
        raise NotImplementedError(self._read_last_revision_info)
 
990
 
 
991
    def import_last_revision_info_and_tags(self, source, revno, revid,
 
992
                                           lossy=False):
 
993
        """Set the last revision info, importing from another repo if necessary.
 
994
 
 
995
        This is used by the bound branch code to upload a revision to
 
996
        the master branch first before updating the tip of the local branch.
 
997
        Revisions referenced by source's tags are also transferred.
 
998
 
 
999
        :param source: Source branch to optionally fetch from
 
1000
        :param revno: Revision number of the new tip
 
1001
        :param revid: Revision id of the new tip
 
1002
        :param lossy: Whether to discard metadata that can not be
 
1003
            natively represented
 
1004
        :return: Tuple with the new revision number and revision id
 
1005
            (should only be different from the arguments when lossy=True)
 
1006
        """
 
1007
        if not self.repository.has_same_location(source.repository):
 
1008
            self.fetch(source, revid)
 
1009
        self.set_last_revision_info(revno, revid)
 
1010
        return (revno, revid)
 
1011
 
 
1012
    def revision_id_to_revno(self, revision_id):
 
1013
        """Given a revision id, return its revno"""
 
1014
        if _mod_revision.is_null(revision_id):
 
1015
            return 0
 
1016
        history = self._revision_history()
 
1017
        try:
 
1018
            return history.index(revision_id) + 1
 
1019
        except ValueError:
 
1020
            raise errors.NoSuchRevision(self, revision_id)
 
1021
 
 
1022
    def get_rev_id(self, revno, history=None):
 
1023
        """Find the revision id of the specified revno."""
 
1024
        with self.lock_read():
 
1025
            if revno == 0:
 
1026
                return _mod_revision.NULL_REVISION
 
1027
            last_revno, last_revid = self.last_revision_info()
 
1028
            if revno == last_revno:
 
1029
                return last_revid
 
1030
            if revno <= 0 or revno > last_revno:
 
1031
                raise errors.NoSuchRevision(self, revno)
 
1032
            distance_from_last = last_revno - revno
 
1033
            if len(self._partial_revision_history_cache) <= distance_from_last:
 
1034
                self._extend_partial_history(distance_from_last)
 
1035
            return self._partial_revision_history_cache[distance_from_last]
 
1036
 
 
1037
    def pull(self, source, overwrite=False, stop_revision=None,
 
1038
             possible_transports=None, *args, **kwargs):
 
1039
        """Mirror source into this branch.
 
1040
 
 
1041
        This branch is considered to be 'local', having low latency.
 
1042
 
 
1043
        :returns: PullResult instance
 
1044
        """
 
1045
        return InterBranch.get(source, self).pull(
 
1046
            overwrite=overwrite, stop_revision=stop_revision,
 
1047
            possible_transports=possible_transports, *args, **kwargs)
 
1048
 
 
1049
    def push(self, target, overwrite=False, stop_revision=None, lossy=False,
 
1050
             *args, **kwargs):
 
1051
        """Mirror this branch into target.
 
1052
 
 
1053
        This branch is considered to be 'local', having low latency.
 
1054
        """
 
1055
        return InterBranch.get(self, target).push(
 
1056
            overwrite, stop_revision, lossy, *args, **kwargs)
 
1057
 
 
1058
    def basis_tree(self):
 
1059
        """Return `Tree` object for last revision."""
 
1060
        return self.repository.revision_tree(self.last_revision())
 
1061
 
 
1062
    def get_parent(self):
 
1063
        """Return the parent location of the branch.
 
1064
 
 
1065
        This is the default location for pull/missing.  The usual
 
1066
        pattern is that the user can override it by specifying a
 
1067
        location.
 
1068
        """
 
1069
        parent = self._get_parent_location()
 
1070
        if parent is None:
 
1071
            return parent
 
1072
        # This is an old-format absolute path to a local branch
 
1073
        # turn it into a url
 
1074
        if parent.startswith('/'):
 
1075
            parent = urlutils.local_path_to_url(parent)
 
1076
        try:
 
1077
            return urlutils.join(self.base[:-1], parent)
 
1078
        except urlutils.InvalidURLJoin:
 
1079
            raise errors.InaccessibleParent(parent, self.user_url)
 
1080
 
 
1081
    def _get_parent_location(self):
 
1082
        raise NotImplementedError(self._get_parent_location)
 
1083
 
 
1084
    def _set_config_location(self, name, url, config=None,
 
1085
                             make_relative=False):
 
1086
        if config is None:
 
1087
            config = self.get_config_stack()
 
1088
        if url is None:
 
1089
            url = ''
 
1090
        elif make_relative:
 
1091
            url = urlutils.relative_url(self.base, url)
 
1092
        config.set(name, url)
 
1093
 
 
1094
    def _get_config_location(self, name, config=None):
 
1095
        if config is None:
 
1096
            config = self.get_config_stack()
 
1097
        location = config.get(name)
 
1098
        if location == '':
 
1099
            location = None
 
1100
        return location
 
1101
 
 
1102
    def get_child_submit_format(self):
 
1103
        """Return the preferred format of submissions to this branch."""
 
1104
        return self.get_config_stack().get('child_submit_format')
 
1105
 
 
1106
    def get_submit_branch(self):
 
1107
        """Return the submit location of the branch.
 
1108
 
 
1109
        This is the default location for bundle.  The usual
 
1110
        pattern is that the user can override it by specifying a
 
1111
        location.
 
1112
        """
 
1113
        return self.get_config_stack().get('submit_branch')
 
1114
 
 
1115
    def set_submit_branch(self, location):
 
1116
        """Return the submit location of the branch.
 
1117
 
 
1118
        This is the default location for bundle.  The usual
 
1119
        pattern is that the user can override it by specifying a
 
1120
        location.
 
1121
        """
 
1122
        self.get_config_stack().set('submit_branch', location)
 
1123
 
 
1124
    def get_public_branch(self):
 
1125
        """Return the public location of the branch.
 
1126
 
 
1127
        This is used by merge directives.
 
1128
        """
 
1129
        return self._get_config_location('public_branch')
 
1130
 
 
1131
    def set_public_branch(self, location):
 
1132
        """Return the submit location of the branch.
 
1133
 
 
1134
        This is the default location for bundle.  The usual
 
1135
        pattern is that the user can override it by specifying a
 
1136
        location.
 
1137
        """
 
1138
        self._set_config_location('public_branch', location)
 
1139
 
 
1140
    def get_push_location(self):
 
1141
        """Return None or the location to push this branch to."""
 
1142
        return self.get_config_stack().get('push_location')
 
1143
 
 
1144
    def set_push_location(self, location):
 
1145
        """Set a new push location for this branch."""
 
1146
        raise NotImplementedError(self.set_push_location)
 
1147
 
 
1148
    def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
 
1149
        """Run the post_change_branch_tip hooks."""
 
1150
        hooks = Branch.hooks['post_change_branch_tip']
 
1151
        if not hooks:
 
1152
            return
 
1153
        new_revno, new_revid = self.last_revision_info()
 
1154
        params = ChangeBranchTipParams(
 
1155
            self, old_revno, new_revno, old_revid, new_revid)
 
1156
        for hook in hooks:
 
1157
            hook(params)
 
1158
 
 
1159
    def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
 
1160
        """Run the pre_change_branch_tip hooks."""
 
1161
        hooks = Branch.hooks['pre_change_branch_tip']
 
1162
        if not hooks:
 
1163
            return
 
1164
        old_revno, old_revid = self.last_revision_info()
 
1165
        params = ChangeBranchTipParams(
 
1166
            self, old_revno, new_revno, old_revid, new_revid)
 
1167
        for hook in hooks:
 
1168
            hook(params)
 
1169
 
 
1170
    def update(self):
 
1171
        """Synchronise this branch with the master branch if any.
 
1172
 
 
1173
        :return: None or the last_revision pivoted out during the update.
 
1174
        """
 
1175
        return None
 
1176
 
 
1177
    def check_revno(self, revno):
 
1178
        """\
 
1179
        Check whether a revno corresponds to any revision.
 
1180
        Zero (the NULL revision) is considered valid.
 
1181
        """
 
1182
        if revno != 0:
 
1183
            self.check_real_revno(revno)
 
1184
 
 
1185
    def check_real_revno(self, revno):
 
1186
        """\
 
1187
        Check whether a revno corresponds to a real revision.
 
1188
        Zero (the NULL revision) is considered invalid
 
1189
        """
 
1190
        if revno < 1 or revno > self.revno():
 
1191
            raise errors.InvalidRevisionNumber(revno)
 
1192
 
 
1193
    def clone(self, to_controldir, revision_id=None, repository_policy=None):
 
1194
        """Clone this branch into to_controldir preserving all semantic values.
 
1195
 
 
1196
        Most API users will want 'create_clone_on_transport', which creates a
 
1197
        new bzrdir and branch on the fly.
 
1198
 
 
1199
        revision_id: if not None, the revision history in the new branch will
 
1200
                     be truncated to end with revision_id.
 
1201
        """
 
1202
        result = to_controldir.create_branch()
 
1203
        with self.lock_read(), result.lock_write():
 
1204
            if repository_policy is not None:
 
1205
                repository_policy.configure_branch(result)
 
1206
            self.copy_content_into(result, revision_id=revision_id)
 
1207
        return result
 
1208
 
 
1209
    def sprout(self, to_controldir, revision_id=None, repository_policy=None,
 
1210
               repository=None, lossy=False):
 
1211
        """Create a new line of development from the branch, into to_controldir.
 
1212
 
 
1213
        to_controldir controls the branch format.
 
1214
 
 
1215
        revision_id: if not None, the revision history in the new branch will
 
1216
                     be truncated to end with revision_id.
 
1217
        """
 
1218
        if (repository_policy is not None
 
1219
                and repository_policy.requires_stacking()):
 
1220
            to_controldir._format.require_stacking(_skip_repo=True)
 
1221
        result = to_controldir.create_branch(repository=repository)
 
1222
        if lossy:
 
1223
            raise errors.LossyPushToSameVCS(self, result)
 
1224
        with self.lock_read(), result.lock_write():
 
1225
            if repository_policy is not None:
 
1226
                repository_policy.configure_branch(result)
 
1227
            self.copy_content_into(result, revision_id=revision_id)
 
1228
            master_url = self.get_bound_location()
 
1229
            if master_url is None:
 
1230
                result.set_parent(self.user_url)
 
1231
            else:
 
1232
                result.set_parent(master_url)
 
1233
        return result
 
1234
 
 
1235
    def _synchronize_history(self, destination, revision_id):
 
1236
        """Synchronize last revision and revision history between branches.
 
1237
 
 
1238
        This version is most efficient when the destination is also a
 
1239
        BzrBranch6, but works for BzrBranch5, as long as the destination's
 
1240
        repository contains all the lefthand ancestors of the intended
 
1241
        last_revision.  If not, set_last_revision_info will fail.
 
1242
 
 
1243
        :param destination: The branch to copy the history into
 
1244
        :param revision_id: The revision-id to truncate history at.  May
 
1245
          be None to copy complete history.
 
1246
        """
 
1247
        source_revno, source_revision_id = self.last_revision_info()
 
1248
        if revision_id is None:
 
1249
            revno, revision_id = source_revno, source_revision_id
 
1250
        else:
 
1251
            graph = self.repository.get_graph()
 
1252
            try:
 
1253
                revno = graph.find_distance_to_null(
 
1254
                    revision_id, [(source_revision_id, source_revno)])
 
1255
            except errors.GhostRevisionsHaveNoRevno:
 
1256
                # Default to 1, if we can't find anything else
 
1257
                revno = 1
 
1258
        destination.set_last_revision_info(revno, revision_id)
 
1259
 
 
1260
    def copy_content_into(self, destination, revision_id=None):
 
1261
        """Copy the content of self into destination.
 
1262
 
 
1263
        revision_id: if not None, the revision history in the new branch will
 
1264
                     be truncated to end with revision_id.
 
1265
        """
 
1266
        return InterBranch.get(self, destination).copy_content_into(
 
1267
            revision_id=revision_id)
 
1268
 
 
1269
    def update_references(self, target):
 
1270
        if not self._format.supports_reference_locations:
 
1271
            return
 
1272
        return InterBranch.get(self, target).update_references()
 
1273
 
 
1274
    def check(self, refs):
 
1275
        """Check consistency of the branch.
 
1276
 
 
1277
        In particular this checks that revisions given in the revision-history
 
1278
        do actually match up in the revision graph, and that they're all
 
1279
        present in the repository.
 
1280
 
 
1281
        Callers will typically also want to check the repository.
 
1282
 
 
1283
        :param refs: Calculated refs for this branch as specified by
 
1284
            branch._get_check_refs()
 
1285
        :return: A BranchCheckResult.
 
1286
        """
 
1287
        with self.lock_read():
 
1288
            result = BranchCheckResult(self)
 
1289
            last_revno, last_revision_id = self.last_revision_info()
 
1290
            actual_revno = refs[('lefthand-distance', last_revision_id)]
 
1291
            if actual_revno != last_revno:
 
1292
                result.errors.append(errors.BzrCheckError(
 
1293
                    'revno does not match len(mainline) %s != %s' % (
 
1294
                        last_revno, actual_revno)))
 
1295
            # TODO: We should probably also check that self.revision_history
 
1296
            # matches the repository for older branch formats.
 
1297
            # If looking for the code that cross-checks repository parents
 
1298
            # against the Graph.iter_lefthand_ancestry output, that is now a
 
1299
            # repository specific check.
 
1300
            return result
 
1301
 
 
1302
    def _get_checkout_format(self, lightweight=False):
 
1303
        """Return the most suitable metadir for a checkout of this branch.
 
1304
        Weaves are used if this branch's repository uses weaves.
 
1305
        """
 
1306
        format = self.repository.controldir.checkout_metadir()
 
1307
        format.set_branch_format(self._format)
 
1308
        return format
 
1309
 
 
1310
    def create_clone_on_transport(self, to_transport, revision_id=None,
 
1311
                                  stacked_on=None, create_prefix=False,
 
1312
                                  use_existing_dir=False, no_tree=None):
 
1313
        """Create a clone of this branch and its bzrdir.
 
1314
 
 
1315
        :param to_transport: The transport to clone onto.
 
1316
        :param revision_id: The revision id to use as tip in the new branch.
 
1317
            If None the tip is obtained from this branch.
 
1318
        :param stacked_on: An optional URL to stack the clone on.
 
1319
        :param create_prefix: Create any missing directories leading up to
 
1320
            to_transport.
 
1321
        :param use_existing_dir: Use an existing directory if one exists.
 
1322
        """
 
1323
        # XXX: Fix the bzrdir API to allow getting the branch back from the
 
1324
        # clone call. Or something. 20090224 RBC/spiv.
 
1325
        # XXX: Should this perhaps clone colocated branches as well,
 
1326
        # rather than just the default branch? 20100319 JRV
 
1327
        if revision_id is None:
 
1328
            revision_id = self.last_revision()
 
1329
        dir_to = self.controldir.clone_on_transport(
 
1330
            to_transport, revision_id=revision_id, stacked_on=stacked_on,
 
1331
            create_prefix=create_prefix, use_existing_dir=use_existing_dir,
 
1332
            no_tree=no_tree)
 
1333
        return dir_to.open_branch()
 
1334
 
 
1335
    def create_checkout(self, to_location, revision_id=None,
 
1336
                        lightweight=False, accelerator_tree=None,
 
1337
                        hardlink=False, recurse_nested=True):
 
1338
        """Create a checkout of a branch.
 
1339
 
 
1340
        :param to_location: The url to produce the checkout at
 
1341
        :param revision_id: The revision to check out
 
1342
        :param lightweight: If True, produce a lightweight checkout, otherwise,
 
1343
            produce a bound branch (heavyweight checkout)
 
1344
        :param accelerator_tree: A tree which can be used for retrieving file
 
1345
            contents more quickly than the revision tree, i.e. a workingtree.
 
1346
            The revision tree will be used for cases where accelerator_tree's
 
1347
            content is different.
 
1348
        :param hardlink: If true, hard-link files from accelerator_tree,
 
1349
            where possible.
 
1350
        :param recurse_nested: Whether to recurse into nested trees
 
1351
        :return: The tree of the created checkout
 
1352
        """
 
1353
        t = transport.get_transport(to_location)
 
1354
        t.ensure_base()
 
1355
        format = self._get_checkout_format(lightweight=lightweight)
 
1356
        try:
 
1357
            checkout = format.initialize_on_transport(t)
 
1358
        except errors.AlreadyControlDirError:
 
1359
            # It's fine if the control directory already exists,
 
1360
            # as long as there is no existing branch and working tree.
 
1361
            checkout = controldir.ControlDir.open_from_transport(t)
 
1362
            try:
 
1363
                checkout.open_branch()
 
1364
            except errors.NotBranchError:
 
1365
                pass
 
1366
            else:
 
1367
                raise errors.AlreadyControlDirError(t.base)
 
1368
            if (checkout.control_transport.base
 
1369
                    == self.controldir.control_transport.base):
 
1370
                # When checking out to the same control directory,
 
1371
                # always create a lightweight checkout
 
1372
                lightweight = True
 
1373
 
 
1374
        if lightweight:
 
1375
            from_branch = checkout.set_branch_reference(target_branch=self)
 
1376
        else:
 
1377
            policy = checkout.determine_repository_policy()
 
1378
            policy.acquire_repository()
 
1379
            checkout_branch = checkout.create_branch()
 
1380
            checkout_branch.bind(self)
 
1381
            # pull up to the specified revision_id to set the initial
 
1382
            # branch tip correctly, and seed it with history.
 
1383
            checkout_branch.pull(self, stop_revision=revision_id)
 
1384
            from_branch = None
 
1385
        tree = checkout.create_workingtree(revision_id,
 
1386
                                           from_branch=from_branch,
 
1387
                                           accelerator_tree=accelerator_tree,
 
1388
                                           hardlink=hardlink)
 
1389
        basis_tree = tree.basis_tree()
 
1390
        with basis_tree.lock_read():
 
1391
            for path in basis_tree.iter_references():
 
1392
                reference_parent = tree.reference_parent(path)
 
1393
                if reference_parent is None:
 
1394
                    warning('Branch location for %s unknown.', path)
 
1395
                    continue
 
1396
                reference_parent.create_checkout(
 
1397
                    tree.abspath(path),
 
1398
                    basis_tree.get_reference_revision(path), lightweight)
 
1399
        return tree
 
1400
 
 
1401
    def reconcile(self, thorough=True):
 
1402
        """Make sure the data stored in this branch is consistent.
 
1403
 
 
1404
        :return: A `ReconcileResult` object.
 
1405
        """
 
1406
        raise NotImplementedError(self.reconcile)
 
1407
 
 
1408
    def supports_tags(self):
 
1409
        return self._format.supports_tags()
 
1410
 
 
1411
    def automatic_tag_name(self, revision_id):
 
1412
        """Try to automatically find the tag name for a revision.
 
1413
 
 
1414
        :param revision_id: Revision id of the revision.
 
1415
        :return: A tag name or None if no tag name could be determined.
 
1416
        """
 
1417
        for hook in Branch.hooks['automatic_tag_name']:
 
1418
            ret = hook(self, revision_id)
 
1419
            if ret is not None:
 
1420
                return ret
 
1421
        return None
 
1422
 
 
1423
    def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
 
1424
                                         other_branch):
 
1425
        """Ensure that revision_b is a descendant of revision_a.
 
1426
 
 
1427
        This is a helper function for update_revisions.
 
1428
 
 
1429
        :raises: DivergedBranches if revision_b has diverged from revision_a.
 
1430
        :returns: True if revision_b is a descendant of revision_a.
 
1431
        """
 
1432
        relation = self._revision_relations(revision_a, revision_b, graph)
 
1433
        if relation == 'b_descends_from_a':
 
1434
            return True
 
1435
        elif relation == 'diverged':
 
1436
            raise errors.DivergedBranches(self, other_branch)
 
1437
        elif relation == 'a_descends_from_b':
 
1438
            return False
 
1439
        else:
 
1440
            raise AssertionError("invalid relation: %r" % (relation,))
 
1441
 
 
1442
    def _revision_relations(self, revision_a, revision_b, graph):
 
1443
        """Determine the relationship between two revisions.
 
1444
 
 
1445
        :returns: One of: 'a_descends_from_b', 'b_descends_from_a', 'diverged'
 
1446
        """
 
1447
        heads = graph.heads([revision_a, revision_b])
 
1448
        if heads == {revision_b}:
 
1449
            return 'b_descends_from_a'
 
1450
        elif heads == {revision_a, revision_b}:
 
1451
            # These branches have diverged
 
1452
            return 'diverged'
 
1453
        elif heads == {revision_a}:
 
1454
            return 'a_descends_from_b'
 
1455
        else:
 
1456
            raise AssertionError("invalid heads: %r" % (heads,))
 
1457
 
 
1458
    def heads_to_fetch(self):
 
1459
        """Return the heads that must and that should be fetched to copy this
 
1460
        branch into another repo.
 
1461
 
 
1462
        :returns: a 2-tuple of (must_fetch, if_present_fetch).  must_fetch is a
 
1463
            set of heads that must be fetched.  if_present_fetch is a set of
 
1464
            heads that must be fetched if present, but no error is necessary if
 
1465
            they are not present.
 
1466
        """
 
1467
        # For bzr native formats must_fetch is just the tip, and
 
1468
        # if_present_fetch are the tags.
 
1469
        must_fetch = {self.last_revision()}
 
1470
        if_present_fetch = set()
 
1471
        if self.get_config_stack().get('branch.fetch_tags'):
 
1472
            try:
 
1473
                if_present_fetch = set(self.tags.get_reverse_tag_dict())
 
1474
            except errors.TagsNotSupported:
 
1475
                pass
 
1476
        must_fetch.discard(_mod_revision.NULL_REVISION)
 
1477
        if_present_fetch.discard(_mod_revision.NULL_REVISION)
 
1478
        return must_fetch, if_present_fetch
 
1479
 
 
1480
    def create_memorytree(self):
 
1481
        """Create a memory tree for this branch.
 
1482
 
 
1483
        :return: An in-memory MutableTree instance
 
1484
        """
 
1485
        return memorytree.MemoryTree.create_on_branch(self)
 
1486
 
 
1487
 
 
1488
class BranchFormat(controldir.ControlComponentFormat):
 
1489
    """An encapsulation of the initialization and open routines for a format.
 
1490
 
 
1491
    Formats provide three things:
 
1492
     * An initialization routine,
 
1493
     * a format description
 
1494
     * an open routine.
 
1495
 
 
1496
    Formats are placed in an dict by their format string for reference
 
1497
    during branch opening. It's not required that these be instances, they
 
1498
    can be classes themselves with class methods - it simply depends on
 
1499
    whether state is needed for a given format or not.
 
1500
 
 
1501
    Once a format is deprecated, just deprecate the initialize and open
 
1502
    methods on the format class. Do not deprecate the object, as the
 
1503
    object will be created every time regardless.
 
1504
    """
 
1505
 
 
1506
    def __eq__(self, other):
 
1507
        return self.__class__ is other.__class__
 
1508
 
 
1509
    def __ne__(self, other):
 
1510
        return not (self == other)
 
1511
 
 
1512
    def get_reference(self, controldir, name=None):
 
1513
        """Get the target reference of the branch in controldir.
 
1514
 
 
1515
        format probing must have been completed before calling
 
1516
        this method - it is assumed that the format of the branch
 
1517
        in controldir is correct.
 
1518
 
 
1519
        :param controldir: The controldir to get the branch data from.
 
1520
        :param name: Name of the colocated branch to fetch
 
1521
        :return: None if the branch is not a reference branch.
 
1522
        """
 
1523
        return None
 
1524
 
 
1525
    @classmethod
 
1526
    def set_reference(self, controldir, name, to_branch):
 
1527
        """Set the target reference of the branch in controldir.
 
1528
 
 
1529
        format probing must have been completed before calling
 
1530
        this method - it is assumed that the format of the branch
 
1531
        in controldir is correct.
 
1532
 
 
1533
        :param controldir: The controldir to set the branch reference for.
 
1534
        :param name: Name of colocated branch to set, None for default
 
1535
        :param to_branch: branch that the checkout is to reference
 
1536
        """
 
1537
        raise NotImplementedError(self.set_reference)
 
1538
 
 
1539
    def get_format_description(self):
 
1540
        """Return the short format description for this format."""
 
1541
        raise NotImplementedError(self.get_format_description)
 
1542
 
 
1543
    def _run_post_branch_init_hooks(self, controldir, name, branch):
 
1544
        hooks = Branch.hooks['post_branch_init']
 
1545
        if not hooks:
 
1546
            return
 
1547
        params = BranchInitHookParams(self, controldir, name, branch)
 
1548
        for hook in hooks:
 
1549
            hook(params)
 
1550
 
 
1551
    def initialize(self, controldir, name=None, repository=None,
 
1552
                   append_revisions_only=None):
 
1553
        """Create a branch of this format in controldir.
 
1554
 
 
1555
        :param name: Name of the colocated branch to create.
 
1556
        """
 
1557
        raise NotImplementedError(self.initialize)
 
1558
 
 
1559
    def is_supported(self):
 
1560
        """Is this format supported?
 
1561
 
 
1562
        Supported formats can be initialized and opened.
 
1563
        Unsupported formats may not support initialization or committing or
 
1564
        some other features depending on the reason for not being supported.
 
1565
        """
 
1566
        return True
 
1567
 
 
1568
    def make_tags(self, branch):
 
1569
        """Create a tags object for branch.
 
1570
 
 
1571
        This method is on BranchFormat, because BranchFormats are reflected
 
1572
        over the wire via network_name(), whereas full Branch instances require
 
1573
        multiple VFS method calls to operate at all.
 
1574
 
 
1575
        The default implementation returns a disabled-tags instance.
 
1576
 
 
1577
        Note that it is normal for branch to be a RemoteBranch when using tags
 
1578
        on a RemoteBranch.
 
1579
        """
 
1580
        return _mod_tag.DisabledTags(branch)
 
1581
 
 
1582
    def network_name(self):
 
1583
        """A simple byte string uniquely identifying this format for RPC calls.
 
1584
 
 
1585
        MetaDir branch formats use their disk format string to identify the
 
1586
        repository over the wire. All in one formats such as bzr < 0.8, and
 
1587
        foreign formats like svn/git and hg should use some marker which is
 
1588
        unique and immutable.
 
1589
        """
 
1590
        raise NotImplementedError(self.network_name)
 
1591
 
 
1592
    def open(self, controldir, name=None, _found=False, ignore_fallbacks=False,
 
1593
             found_repository=None, possible_transports=None):
 
1594
        """Return the branch object for controldir.
 
1595
 
 
1596
        :param controldir: A ControlDir that contains a branch.
 
1597
        :param name: Name of colocated branch to open
 
1598
        :param _found: a private parameter, do not use it. It is used to
 
1599
            indicate if format probing has already be done.
 
1600
        :param ignore_fallbacks: when set, no fallback branches will be opened
 
1601
            (if there are any).  Default is to open fallbacks.
 
1602
        """
 
1603
        raise NotImplementedError(self.open)
 
1604
 
 
1605
    def supports_set_append_revisions_only(self):
 
1606
        """True if this format supports set_append_revisions_only."""
 
1607
        return False
 
1608
 
 
1609
    def supports_stacking(self):
 
1610
        """True if this format records a stacked-on branch."""
 
1611
        return False
 
1612
 
 
1613
    def supports_leaving_lock(self):
 
1614
        """True if this format supports leaving locks in place."""
 
1615
        return False  # by default
 
1616
 
 
1617
    def __str__(self):
 
1618
        return self.get_format_description().rstrip()
 
1619
 
 
1620
    def supports_tags(self):
 
1621
        """True if this format supports tags stored in the branch"""
 
1622
        return False  # by default
 
1623
 
 
1624
    def tags_are_versioned(self):
 
1625
        """Whether the tag container for this branch versions tags."""
 
1626
        return False
 
1627
 
 
1628
    def supports_tags_referencing_ghosts(self):
 
1629
        """True if tags can reference ghost revisions."""
 
1630
        return True
 
1631
 
 
1632
    def supports_store_uncommitted(self):
 
1633
        """True if uncommitted changes can be stored in this branch."""
 
1634
        return True
 
1635
 
 
1636
    def stores_revno(self):
 
1637
        """True if this branch format store revision numbers."""
 
1638
        return True
 
1639
 
 
1640
 
 
1641
class BranchHooks(Hooks):
 
1642
    """A dictionary mapping hook name to a list of callables for branch hooks.
 
1643
 
 
1644
    e.g. ['post_push'] Is the list of items to be called when the
 
1645
    push function is invoked.
 
1646
    """
 
1647
 
 
1648
    def __init__(self):
 
1649
        """Create the default hooks.
 
1650
 
 
1651
        These are all empty initially, because by default nothing should get
 
1652
        notified.
 
1653
        """
 
1654
        Hooks.__init__(self, "breezy.branch", "Branch.hooks")
 
1655
        self.add_hook(
 
1656
            'open',
 
1657
            "Called with the Branch object that has been opened after a "
 
1658
            "branch is opened.", (1, 8))
 
1659
        self.add_hook(
 
1660
            'post_push',
 
1661
            "Called after a push operation completes. post_push is called "
 
1662
            "with a breezy.branch.BranchPushResult object and only runs in "
 
1663
            "the bzr client.", (0, 15))
 
1664
        self.add_hook(
 
1665
            'post_pull',
 
1666
            "Called after a pull operation completes. post_pull is called "
 
1667
            "with a breezy.branch.PullResult object and only runs in the "
 
1668
            "bzr client.", (0, 15))
 
1669
        self.add_hook(
 
1670
            'pre_commit',
 
1671
            "Called after a commit is calculated but before it is "
 
1672
            "completed. pre_commit is called with (local, master, old_revno, "
 
1673
            "old_revid, future_revno, future_revid, tree_delta, future_tree"
 
1674
            "). old_revid is NULL_REVISION for the first commit to a branch, "
 
1675
            "tree_delta is a TreeDelta object describing changes from the "
 
1676
            "basis revision. hooks MUST NOT modify this delta. "
 
1677
            " future_tree is an in-memory tree obtained from "
 
1678
            "CommitBuilder.revision_tree() and hooks MUST NOT modify this "
 
1679
            "tree.", (0, 91))
 
1680
        self.add_hook(
 
1681
            'post_commit',
 
1682
            "Called in the bzr client after a commit has completed. "
 
1683
            "post_commit is called with (local, master, old_revno, old_revid, "
 
1684
            "new_revno, new_revid). old_revid is NULL_REVISION for the first "
 
1685
            "commit to a branch.", (0, 15))
 
1686
        self.add_hook(
 
1687
            'post_uncommit',
 
1688
            "Called in the bzr client after an uncommit completes. "
 
1689
            "post_uncommit is called with (local, master, old_revno, "
 
1690
            "old_revid, new_revno, new_revid) where local is the local branch "
 
1691
            "or None, master is the target branch, and an empty branch "
 
1692
            "receives new_revno of 0, new_revid of None.", (0, 15))
 
1693
        self.add_hook(
 
1694
            'pre_change_branch_tip',
 
1695
            "Called in bzr client and server before a change to the tip of a "
 
1696
            "branch is made. pre_change_branch_tip is called with a "
 
1697
            "breezy.branch.ChangeBranchTipParams. Note that push, pull, "
 
1698
            "commit, uncommit will all trigger this hook.", (1, 6))
 
1699
        self.add_hook(
 
1700
            'post_change_branch_tip',
 
1701
            "Called in bzr client and server after a change to the tip of a "
 
1702
            "branch is made. post_change_branch_tip is called with a "
 
1703
            "breezy.branch.ChangeBranchTipParams. Note that push, pull, "
 
1704
            "commit, uncommit will all trigger this hook.", (1, 4))
 
1705
        self.add_hook(
 
1706
            'transform_fallback_location',
 
1707
            "Called when a stacked branch is activating its fallback "
 
1708
            "locations. transform_fallback_location is called with (branch, "
 
1709
            "url), and should return a new url. Returning the same url "
 
1710
            "allows it to be used as-is, returning a different one can be "
 
1711
            "used to cause the branch to stack on a closer copy of that "
 
1712
            "fallback_location. Note that the branch cannot have history "
 
1713
            "accessing methods called on it during this hook because the "
 
1714
            "fallback locations have not been activated. When there are "
 
1715
            "multiple hooks installed for transform_fallback_location, "
 
1716
            "all are called with the url returned from the previous hook."
 
1717
            "The order is however undefined.", (1, 9))
 
1718
        self.add_hook(
 
1719
            'automatic_tag_name',
 
1720
            "Called to determine an automatic tag name for a revision. "
 
1721
            "automatic_tag_name is called with (branch, revision_id) and "
 
1722
            "should return a tag name or None if no tag name could be "
 
1723
            "determined. The first non-None tag name returned will be used.",
 
1724
            (2, 2))
 
1725
        self.add_hook(
 
1726
            'post_branch_init',
 
1727
            "Called after new branch initialization completes. "
 
1728
            "post_branch_init is called with a "
 
1729
            "breezy.branch.BranchInitHookParams. "
 
1730
            "Note that init, branch and checkout (both heavyweight and "
 
1731
            "lightweight) will all trigger this hook.", (2, 2))
 
1732
        self.add_hook(
 
1733
            'post_switch',
 
1734
            "Called after a checkout switches branch. "
 
1735
            "post_switch is called with a "
 
1736
            "breezy.branch.SwitchHookParams.", (2, 2))
 
1737
 
 
1738
 
 
1739
# install the default hooks into the Branch class.
 
1740
Branch.hooks = BranchHooks()
 
1741
 
 
1742
 
 
1743
class ChangeBranchTipParams(object):
 
1744
    """Object holding parameters passed to `*_change_branch_tip` hooks.
 
1745
 
 
1746
    There are 5 fields that hooks may wish to access:
 
1747
 
 
1748
    :ivar branch: the branch being changed
 
1749
    :ivar old_revno: revision number before the change
 
1750
    :ivar new_revno: revision number after the change
 
1751
    :ivar old_revid: revision id before the change
 
1752
    :ivar new_revid: revision id after the change
 
1753
 
 
1754
    The revid fields are strings. The revno fields are integers.
 
1755
    """
 
1756
 
 
1757
    def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
 
1758
        """Create a group of ChangeBranchTip parameters.
 
1759
 
 
1760
        :param branch: The branch being changed.
 
1761
        :param old_revno: Revision number before the change.
 
1762
        :param new_revno: Revision number after the change.
 
1763
        :param old_revid: Tip revision id before the change.
 
1764
        :param new_revid: Tip revision id after the change.
 
1765
        """
 
1766
        self.branch = branch
 
1767
        self.old_revno = old_revno
 
1768
        self.new_revno = new_revno
 
1769
        self.old_revid = old_revid
 
1770
        self.new_revid = new_revid
 
1771
 
 
1772
    def __eq__(self, other):
 
1773
        return self.__dict__ == other.__dict__
 
1774
 
 
1775
    def __repr__(self):
 
1776
        return "<%s of %s from (%s, %s) to (%s, %s)>" % (
 
1777
            self.__class__.__name__, self.branch,
 
1778
            self.old_revno, self.old_revid, self.new_revno, self.new_revid)
 
1779
 
 
1780
 
 
1781
class BranchInitHookParams(object):
 
1782
    """Object holding parameters passed to `*_branch_init` hooks.
 
1783
 
 
1784
    There are 4 fields that hooks may wish to access:
 
1785
 
 
1786
    :ivar format: the branch format
 
1787
    :ivar bzrdir: the ControlDir where the branch will be/has been initialized
 
1788
    :ivar name: name of colocated branch, if any (or None)
 
1789
    :ivar branch: the branch created
 
1790
 
 
1791
    Note that for lightweight checkouts, the bzrdir and format fields refer to
 
1792
    the checkout, hence they are different from the corresponding fields in
 
1793
    branch, which refer to the original branch.
 
1794
    """
 
1795
 
 
1796
    def __init__(self, format, controldir, name, branch):
 
1797
        """Create a group of BranchInitHook parameters.
 
1798
 
 
1799
        :param format: the branch format
 
1800
        :param controldir: the ControlDir where the branch will be/has been
 
1801
            initialized
 
1802
        :param name: name of colocated branch, if any (or None)
 
1803
        :param branch: the branch created
 
1804
 
 
1805
        Note that for lightweight checkouts, the bzrdir and format fields refer
 
1806
        to the checkout, hence they are different from the corresponding fields
 
1807
        in branch, which refer to the original branch.
 
1808
        """
 
1809
        self.format = format
 
1810
        self.controldir = controldir
 
1811
        self.name = name
 
1812
        self.branch = branch
 
1813
 
 
1814
    def __eq__(self, other):
 
1815
        return self.__dict__ == other.__dict__
 
1816
 
 
1817
    def __repr__(self):
 
1818
        return "<%s of %s>" % (self.__class__.__name__, self.branch)
 
1819
 
 
1820
 
 
1821
class SwitchHookParams(object):
 
1822
    """Object holding parameters passed to `*_switch` hooks.
 
1823
 
 
1824
    There are 4 fields that hooks may wish to access:
 
1825
 
 
1826
    :ivar control_dir: ControlDir of the checkout to change
 
1827
    :ivar to_branch: branch that the checkout is to reference
 
1828
    :ivar force: skip the check for local commits in a heavy checkout
 
1829
    :ivar revision_id: revision ID to switch to (or None)
 
1830
    """
 
1831
 
 
1832
    def __init__(self, control_dir, to_branch, force, revision_id):
 
1833
        """Create a group of SwitchHook parameters.
 
1834
 
 
1835
        :param control_dir: ControlDir of the checkout to change
 
1836
        :param to_branch: branch that the checkout is to reference
 
1837
        :param force: skip the check for local commits in a heavy checkout
 
1838
        :param revision_id: revision ID to switch to (or None)
 
1839
        """
 
1840
        self.control_dir = control_dir
 
1841
        self.to_branch = to_branch
 
1842
        self.force = force
 
1843
        self.revision_id = revision_id
 
1844
 
 
1845
    def __eq__(self, other):
 
1846
        return self.__dict__ == other.__dict__
 
1847
 
 
1848
    def __repr__(self):
 
1849
        return "<%s for %s to (%s, %s)>" % (
 
1850
            self.__class__.__name__, self.control_dir, self.to_branch,
 
1851
            self.revision_id)
 
1852
 
 
1853
 
 
1854
class BranchFormatRegistry(controldir.ControlComponentFormatRegistry):
 
1855
    """Branch format registry."""
 
1856
 
 
1857
    def __init__(self, other_registry=None):
 
1858
        super(BranchFormatRegistry, self).__init__(other_registry)
 
1859
        self._default_format = None
 
1860
        self._default_format_key = None
 
1861
 
 
1862
    def get_default(self):
 
1863
        """Return the current default format."""
 
1864
        if (self._default_format_key is not None
 
1865
                and self._default_format is None):
 
1866
            self._default_format = self.get(self._default_format_key)
 
1867
        return self._default_format
 
1868
 
 
1869
    def set_default(self, format):
 
1870
        """Set the default format."""
 
1871
        self._default_format = format
 
1872
        self._default_format_key = None
 
1873
 
 
1874
    def set_default_key(self, format_string):
 
1875
        """Set the default format by its format string."""
 
1876
        self._default_format_key = format_string
 
1877
        self._default_format = None
 
1878
 
 
1879
 
 
1880
network_format_registry = registry.FormatRegistry()
 
1881
"""Registry of formats indexed by their network name.
 
1882
 
 
1883
The network name for a branch format is an identifier that can be used when
 
1884
referring to formats with smart server operations. See
 
1885
BranchFormat.network_name() for more detail.
 
1886
"""
 
1887
 
 
1888
format_registry = BranchFormatRegistry(network_format_registry)
 
1889
 
 
1890
 
 
1891
# formats which have no format string are not discoverable
 
1892
# and not independently creatable, so are not registered.
 
1893
format_registry.register_lazy(
 
1894
    b"Bazaar-NG branch format 5\n", "breezy.bzr.fullhistory",
 
1895
    "BzrBranchFormat5")
 
1896
format_registry.register_lazy(
 
1897
    b"Bazaar Branch Format 6 (bzr 0.15)\n",
 
1898
    "breezy.bzr.branch", "BzrBranchFormat6")
 
1899
format_registry.register_lazy(
 
1900
    b"Bazaar Branch Format 7 (needs bzr 1.6)\n",
 
1901
    "breezy.bzr.branch", "BzrBranchFormat7")
 
1902
format_registry.register_lazy(
 
1903
    b"Bazaar Branch Format 8 (needs bzr 1.15)\n",
 
1904
    "breezy.bzr.branch", "BzrBranchFormat8")
 
1905
format_registry.register_lazy(
 
1906
    b"Bazaar-NG Branch Reference Format 1\n",
 
1907
    "breezy.bzr.branch", "BranchReferenceFormat")
 
1908
 
 
1909
format_registry.set_default_key(b"Bazaar Branch Format 7 (needs bzr 1.6)\n")
 
1910
 
 
1911
 
 
1912
class BranchWriteLockResult(LogicalLockResult):
 
1913
    """The result of write locking a branch.
 
1914
 
 
1915
    :ivar token: The token obtained from the underlying branch lock, or
 
1916
        None.
 
1917
    :ivar unlock: A callable which will unlock the lock.
 
1918
    """
 
1919
 
 
1920
    def __repr__(self):
 
1921
        return "BranchWriteLockResult(%r, %r)" % (self.unlock, self.token)
 
1922
 
 
1923
 
 
1924
######################################################################
 
1925
# results of operations
 
1926
 
 
1927
 
 
1928
class _Result(object):
 
1929
 
 
1930
    def _show_tag_conficts(self, to_file):
 
1931
        if not getattr(self, 'tag_conflicts', None):
 
1932
            return
 
1933
        to_file.write('Conflicting tags:\n')
 
1934
        for name, value1, value2 in self.tag_conflicts:
 
1935
            to_file.write('    %s\n' % (name, ))
 
1936
 
 
1937
 
 
1938
class PullResult(_Result):
 
1939
    """Result of a Branch.pull operation.
 
1940
 
 
1941
    :ivar old_revno: Revision number before pull.
 
1942
    :ivar new_revno: Revision number after pull.
 
1943
    :ivar old_revid: Tip revision id before pull.
 
1944
    :ivar new_revid: Tip revision id after pull.
 
1945
    :ivar source_branch: Source (local) branch object. (read locked)
 
1946
    :ivar master_branch: Master branch of the target, or the target if no
 
1947
        Master
 
1948
    :ivar local_branch: target branch if there is a Master, else None
 
1949
    :ivar target_branch: Target/destination branch object. (write locked)
 
1950
    :ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
 
1951
    :ivar tag_updates: A dict with new tags, see BasicTags.merge_to
 
1952
    """
 
1953
 
 
1954
    def report(self, to_file):
 
1955
        tag_conflicts = getattr(self, "tag_conflicts", None)
 
1956
        tag_updates = getattr(self, "tag_updates", None)
 
1957
        if not is_quiet():
 
1958
            if self.old_revid != self.new_revid:
 
1959
                to_file.write('Now on revision %d.\n' % self.new_revno)
 
1960
            if tag_updates:
 
1961
                to_file.write('%d tag(s) updated.\n' % len(tag_updates))
 
1962
            if self.old_revid == self.new_revid and not tag_updates:
 
1963
                if not tag_conflicts:
 
1964
                    to_file.write('No revisions or tags to pull.\n')
 
1965
                else:
 
1966
                    to_file.write('No revisions to pull.\n')
 
1967
        self._show_tag_conficts(to_file)
 
1968
 
 
1969
 
 
1970
class BranchPushResult(_Result):
 
1971
    """Result of a Branch.push operation.
 
1972
 
 
1973
    :ivar old_revno: Revision number (eg 10) of the target before push.
 
1974
    :ivar new_revno: Revision number (eg 12) of the target after push.
 
1975
    :ivar old_revid: Tip revision id (eg joe@foo.com-1234234-aoeua34) of target
 
1976
        before the push.
 
1977
    :ivar new_revid: Tip revision id (eg joe@foo.com-5676566-boa234a) of target
 
1978
        after the push.
 
1979
    :ivar source_branch: Source branch object that the push was from. This is
 
1980
        read locked, and generally is a local (and thus low latency) branch.
 
1981
    :ivar master_branch: If target is a bound branch, the master branch of
 
1982
        target, or target itself. Always write locked.
 
1983
    :ivar target_branch: The direct Branch where data is being sent (write
 
1984
        locked).
 
1985
    :ivar local_branch: If the target is a bound branch this will be the
 
1986
        target, otherwise it will be None.
 
1987
    """
 
1988
 
 
1989
    def report(self, to_file):
 
1990
        # TODO: This function gets passed a to_file, but then
 
1991
        # ignores it and calls note() instead. This is also
 
1992
        # inconsistent with PullResult(), which writes to stdout.
 
1993
        # -- JRV20110901, bug #838853
 
1994
        tag_conflicts = getattr(self, "tag_conflicts", None)
 
1995
        tag_updates = getattr(self, "tag_updates", None)
 
1996
        if not is_quiet():
 
1997
            if self.old_revid != self.new_revid:
 
1998
                if self.new_revno is not None:
 
1999
                    note(gettext('Pushed up to revision %d.'),
 
2000
                         self.new_revno)
 
2001
                else:
 
2002
                    note(gettext('Pushed up to revision id %s.'),
 
2003
                         self.new_revid.decode('utf-8'))
 
2004
            if tag_updates:
 
2005
                note(ngettext('%d tag updated.', '%d tags updated.',
 
2006
                              len(tag_updates)) % len(tag_updates))
 
2007
            if self.old_revid == self.new_revid and not tag_updates:
 
2008
                if not tag_conflicts:
 
2009
                    note(gettext('No new revisions or tags to push.'))
 
2010
                else:
 
2011
                    note(gettext('No new revisions to push.'))
 
2012
        self._show_tag_conficts(to_file)
 
2013
 
 
2014
 
 
2015
class BranchCheckResult(object):
 
2016
    """Results of checking branch consistency.
 
2017
 
 
2018
    :see: Branch.check
 
2019
    """
 
2020
 
 
2021
    def __init__(self, branch):
 
2022
        self.branch = branch
 
2023
        self.errors = []
 
2024
 
 
2025
    def report_results(self, verbose):
 
2026
        """Report the check results via trace.note.
 
2027
 
 
2028
        :param verbose: Requests more detailed display of what was checked,
 
2029
            if any.
 
2030
        """
 
2031
        note(gettext('checked branch {0} format {1}').format(
 
2032
            self.branch.user_url, self.branch._format))
 
2033
        for error in self.errors:
 
2034
            note(gettext('found error:%s'), error)
 
2035
 
 
2036
 
 
2037
class InterBranch(InterObject):
 
2038
    """This class represents operations taking place between two branches.
 
2039
 
 
2040
    Its instances have methods like pull() and push() and contain
 
2041
    references to the source and target repositories these operations
 
2042
    can be carried out on.
 
2043
    """
 
2044
 
 
2045
    _optimisers = []
 
2046
    """The available optimised InterBranch types."""
 
2047
 
 
2048
    @classmethod
 
2049
    def _get_branch_formats_to_test(klass):
 
2050
        """Return an iterable of format tuples for testing.
 
2051
 
 
2052
        :return: An iterable of (from_format, to_format) to use when testing
 
2053
            this InterBranch class. Each InterBranch class should define this
 
2054
            method itself.
 
2055
        """
 
2056
        raise NotImplementedError(klass._get_branch_formats_to_test)
 
2057
 
 
2058
    def pull(self, overwrite=False, stop_revision=None,
 
2059
             possible_transports=None, local=False):
 
2060
        """Mirror source into target branch.
 
2061
 
 
2062
        The target branch is considered to be 'local', having low latency.
 
2063
 
 
2064
        :returns: PullResult instance
 
2065
        """
 
2066
        raise NotImplementedError(self.pull)
 
2067
 
 
2068
    def push(self, overwrite=False, stop_revision=None, lossy=False,
 
2069
             _override_hook_source_branch=None):
 
2070
        """Mirror the source branch into the target branch.
 
2071
 
 
2072
        The source branch is considered to be 'local', having low latency.
 
2073
        """
 
2074
        raise NotImplementedError(self.push)
 
2075
 
 
2076
    def copy_content_into(self, revision_id=None):
 
2077
        """Copy the content of source into target
 
2078
 
 
2079
        revision_id: if not None, the revision history in the new branch will
 
2080
                     be truncated to end with revision_id.
 
2081
        """
 
2082
        raise NotImplementedError(self.copy_content_into)
 
2083
 
 
2084
    def fetch(self, stop_revision=None, limit=None, lossy=False):
 
2085
        """Fetch revisions.
 
2086
 
 
2087
        :param stop_revision: Last revision to fetch
 
2088
        :param limit: Optional rough limit of revisions to fetch
 
2089
        :return: FetchResult object
 
2090
        """
 
2091
        raise NotImplementedError(self.fetch)
 
2092
 
 
2093
    def update_references(self):
 
2094
        """Import reference information from source to target.
 
2095
        """
 
2096
        raise NotImplementedError(self.update_references)
 
2097
 
 
2098
 
 
2099
def _fix_overwrite_type(overwrite):
 
2100
    if isinstance(overwrite, bool):
 
2101
        if overwrite:
 
2102
            return ["history", "tags"]
 
2103
        else:
 
2104
            return []
 
2105
    return overwrite
 
2106
 
 
2107
 
 
2108
class GenericInterBranch(InterBranch):
 
2109
    """InterBranch implementation that uses public Branch functions."""
 
2110
 
 
2111
    @classmethod
 
2112
    def is_compatible(klass, source, target):
 
2113
        # GenericBranch uses the public API, so always compatible
 
2114
        return True
 
2115
 
 
2116
    @classmethod
 
2117
    def _get_branch_formats_to_test(klass):
 
2118
        return [(format_registry.get_default(), format_registry.get_default())]
 
2119
 
 
2120
    @classmethod
 
2121
    def unwrap_format(klass, format):
 
2122
        if isinstance(format, remote.RemoteBranchFormat):
 
2123
            format._ensure_real()
 
2124
            return format._custom_format
 
2125
        return format
 
2126
 
 
2127
    def copy_content_into(self, revision_id=None):
 
2128
        """Copy the content of source into target
 
2129
 
 
2130
        revision_id: if not None, the revision history in the new branch will
 
2131
                     be truncated to end with revision_id.
 
2132
        """
 
2133
        with self.source.lock_read(), self.target.lock_write():
 
2134
            self.source._synchronize_history(self.target, revision_id)
 
2135
            self.update_references()
 
2136
            try:
 
2137
                parent = self.source.get_parent()
 
2138
            except errors.InaccessibleParent as e:
 
2139
                mutter('parent was not accessible to copy: %s', str(e))
 
2140
            else:
 
2141
                if parent:
 
2142
                    self.target.set_parent(parent)
 
2143
            if self.source._push_should_merge_tags():
 
2144
                self.source.tags.merge_to(self.target.tags)
 
2145
 
 
2146
    def fetch(self, stop_revision=None, limit=None, lossy=False):
 
2147
        if self.target.base == self.source.base:
 
2148
            return (0, [])
 
2149
        with self.source.lock_read(), self.target.lock_write():
 
2150
            fetch_spec_factory = fetch.FetchSpecFactory()
 
2151
            fetch_spec_factory.source_branch = self.source
 
2152
            fetch_spec_factory.source_branch_stop_revision_id = stop_revision
 
2153
            fetch_spec_factory.source_repo = self.source.repository
 
2154
            fetch_spec_factory.target_repo = self.target.repository
 
2155
            fetch_spec_factory.target_repo_kind = (
 
2156
                fetch.TargetRepoKinds.PREEXISTING)
 
2157
            fetch_spec_factory.limit = limit
 
2158
            fetch_spec = fetch_spec_factory.make_fetch_spec()
 
2159
            return self.target.repository.fetch(
 
2160
                self.source.repository,
 
2161
                lossy=lossy,
 
2162
                fetch_spec=fetch_spec)
 
2163
 
 
2164
    def _update_revisions(self, stop_revision=None, overwrite=False,
 
2165
                          graph=None):
 
2166
        with self.source.lock_read(), self.target.lock_write():
 
2167
            other_revno, other_last_revision = self.source.last_revision_info()
 
2168
            stop_revno = None  # unknown
 
2169
            if stop_revision is None:
 
2170
                stop_revision = other_last_revision
 
2171
                if _mod_revision.is_null(stop_revision):
 
2172
                    # if there are no commits, we're done.
 
2173
                    return
 
2174
                stop_revno = other_revno
 
2175
 
 
2176
            # what's the current last revision, before we fetch [and change it
 
2177
            # possibly]
 
2178
            last_rev = _mod_revision.ensure_null(self.target.last_revision())
 
2179
            # we fetch here so that we don't process data twice in the common
 
2180
            # case of having something to pull, and so that the check for
 
2181
            # already merged can operate on the just fetched graph, which will
 
2182
            # be cached in memory.
 
2183
            self.fetch(stop_revision=stop_revision)
 
2184
            # Check to see if one is an ancestor of the other
 
2185
            if not overwrite:
 
2186
                if graph is None:
 
2187
                    graph = self.target.repository.get_graph()
 
2188
                if self.target._check_if_descendant_or_diverged(
 
2189
                        stop_revision, last_rev, graph, self.source):
 
2190
                    # stop_revision is a descendant of last_rev, but we aren't
 
2191
                    # overwriting, so we're done.
 
2192
                    return
 
2193
            if stop_revno is None:
 
2194
                if graph is None:
 
2195
                    graph = self.target.repository.get_graph()
 
2196
                this_revno, this_last_revision = \
 
2197
                    self.target.last_revision_info()
 
2198
                stop_revno = graph.find_distance_to_null(
 
2199
                    stop_revision, [(other_last_revision, other_revno),
 
2200
                                    (this_last_revision, this_revno)])
 
2201
            self.target.set_last_revision_info(stop_revno, stop_revision)
 
2202
 
 
2203
    def pull(self, overwrite=False, stop_revision=None,
 
2204
             possible_transports=None, run_hooks=True,
 
2205
             _override_hook_target=None, local=False):
 
2206
        """Pull from source into self, updating my master if any.
 
2207
 
 
2208
        :param run_hooks: Private parameter - if false, this branch
 
2209
            is being called because it's the master of the primary branch,
 
2210
            so it should not run its hooks.
 
2211
        """
 
2212
        with contextlib.ExitStack() as exit_stack:
 
2213
            exit_stack.enter_context(self.target.lock_write())
 
2214
            bound_location = self.target.get_bound_location()
 
2215
            if local and not bound_location:
 
2216
                raise errors.LocalRequiresBoundBranch()
 
2217
            master_branch = None
 
2218
            source_is_master = False
 
2219
            if bound_location:
 
2220
                # bound_location comes from a config file, some care has to be
 
2221
                # taken to relate it to source.user_url
 
2222
                normalized = urlutils.normalize_url(bound_location)
 
2223
                try:
 
2224
                    relpath = self.source.user_transport.relpath(normalized)
 
2225
                    source_is_master = (relpath == '')
 
2226
                except (errors.PathNotChild, urlutils.InvalidURL):
 
2227
                    source_is_master = False
 
2228
            if not local and bound_location and not source_is_master:
 
2229
                # not pulling from master, so we need to update master.
 
2230
                master_branch = self.target.get_master_branch(
 
2231
                    possible_transports)
 
2232
                exit_stack.enter_context(master_branch.lock_write())
 
2233
            if master_branch:
 
2234
                # pull from source into master.
 
2235
                master_branch.pull(
 
2236
                    self.source, overwrite, stop_revision, run_hooks=False)
 
2237
            return self._pull(
 
2238
                overwrite, stop_revision, _hook_master=master_branch,
 
2239
                run_hooks=run_hooks,
 
2240
                _override_hook_target=_override_hook_target,
 
2241
                merge_tags_to_master=not source_is_master)
 
2242
 
 
2243
    def push(self, overwrite=False, stop_revision=None, lossy=False,
 
2244
             _override_hook_source_branch=None):
 
2245
        """See InterBranch.push.
 
2246
 
 
2247
        This is the basic concrete implementation of push()
 
2248
 
 
2249
        :param _override_hook_source_branch: If specified, run the hooks
 
2250
            passing this Branch as the source, rather than self.  This is for
 
2251
            use of RemoteBranch, where push is delegated to the underlying
 
2252
            vfs-based Branch.
 
2253
        """
 
2254
        if lossy:
 
2255
            raise errors.LossyPushToSameVCS(self.source, self.target)
 
2256
        # TODO: Public option to disable running hooks - should be trivial but
 
2257
        # needs tests.
 
2258
 
 
2259
        def _run_hooks():
 
2260
            if _override_hook_source_branch:
 
2261
                result.source_branch = _override_hook_source_branch
 
2262
            for hook in Branch.hooks['post_push']:
 
2263
                hook(result)
 
2264
 
 
2265
        with self.source.lock_read(), self.target.lock_write():
 
2266
            bound_location = self.target.get_bound_location()
 
2267
            if bound_location and self.target.base != bound_location:
 
2268
                # there is a master branch.
 
2269
                #
 
2270
                # XXX: Why the second check?  Is it even supported for a branch
 
2271
                # to be bound to itself? -- mbp 20070507
 
2272
                master_branch = self.target.get_master_branch()
 
2273
                with master_branch.lock_write():
 
2274
                    # push into the master from the source branch.
 
2275
                    master_inter = InterBranch.get(self.source, master_branch)
 
2276
                    master_inter._basic_push(overwrite, stop_revision)
 
2277
                    # and push into the target branch from the source. Note
 
2278
                    # that we push from the source branch again, because it's
 
2279
                    # considered the highest bandwidth repository.
 
2280
                    result = self._basic_push(overwrite, stop_revision)
 
2281
                    result.master_branch = master_branch
 
2282
                    result.local_branch = self.target
 
2283
                    _run_hooks()
 
2284
            else:
 
2285
                master_branch = None
 
2286
                # no master branch
 
2287
                result = self._basic_push(overwrite, stop_revision)
 
2288
                # TODO: Why set master_branch and local_branch if there's no
 
2289
                # binding?  Maybe cleaner to just leave them unset? -- mbp
 
2290
                # 20070504
 
2291
                result.master_branch = self.target
 
2292
                result.local_branch = None
 
2293
                _run_hooks()
 
2294
            return result
 
2295
 
 
2296
    def _basic_push(self, overwrite, stop_revision):
 
2297
        """Basic implementation of push without bound branches or hooks.
 
2298
 
 
2299
        Must be called with source read locked and target write locked.
 
2300
        """
 
2301
        result = BranchPushResult()
 
2302
        result.source_branch = self.source
 
2303
        result.target_branch = self.target
 
2304
        result.old_revno, result.old_revid = self.target.last_revision_info()
 
2305
        overwrite = _fix_overwrite_type(overwrite)
 
2306
        if result.old_revid != stop_revision:
 
2307
            # We assume that during 'push' this repository is closer than
 
2308
            # the target.
 
2309
            graph = self.source.repository.get_graph(self.target.repository)
 
2310
            self._update_revisions(
 
2311
                stop_revision, overwrite=("history" in overwrite), graph=graph)
 
2312
        if self.source._push_should_merge_tags():
 
2313
            result.tag_updates, result.tag_conflicts = (
 
2314
                self.source.tags.merge_to(
 
2315
                    self.target.tags, "tags" in overwrite))
 
2316
        self.update_references()
 
2317
        result.new_revno, result.new_revid = self.target.last_revision_info()
 
2318
        return result
 
2319
 
 
2320
    def _pull(self, overwrite=False, stop_revision=None,
 
2321
              possible_transports=None, _hook_master=None, run_hooks=True,
 
2322
              _override_hook_target=None, local=False,
 
2323
              merge_tags_to_master=True):
 
2324
        """See Branch.pull.
 
2325
 
 
2326
        This function is the core worker, used by GenericInterBranch.pull to
 
2327
        avoid duplication when pulling source->master and source->local.
 
2328
 
 
2329
        :param _hook_master: Private parameter - set the branch to
 
2330
            be supplied as the master to pull hooks.
 
2331
        :param run_hooks: Private parameter - if false, this branch
 
2332
            is being called because it's the master of the primary branch,
 
2333
            so it should not run its hooks.
 
2334
            is being called because it's the master of the primary branch,
 
2335
            so it should not run its hooks.
 
2336
        :param _override_hook_target: Private parameter - set the branch to be
 
2337
            supplied as the target_branch to pull hooks.
 
2338
        :param local: Only update the local branch, and not the bound branch.
 
2339
        """
 
2340
        # This type of branch can't be bound.
 
2341
        if local:
 
2342
            raise errors.LocalRequiresBoundBranch()
 
2343
        result = PullResult()
 
2344
        result.source_branch = self.source
 
2345
        if _override_hook_target is None:
 
2346
            result.target_branch = self.target
 
2347
        else:
 
2348
            result.target_branch = _override_hook_target
 
2349
        with self.source.lock_read():
 
2350
            # We assume that during 'pull' the target repository is closer than
 
2351
            # the source one.
 
2352
            graph = self.target.repository.get_graph(self.source.repository)
 
2353
            # TODO: Branch formats should have a flag that indicates
 
2354
            # that revno's are expensive, and pull() should honor that flag.
 
2355
            # -- JRV20090506
 
2356
            result.old_revno, result.old_revid = \
 
2357
                self.target.last_revision_info()
 
2358
            overwrite = _fix_overwrite_type(overwrite)
 
2359
            self._update_revisions(
 
2360
                stop_revision, overwrite=("history" in overwrite), graph=graph)
 
2361
            # TODO: The old revid should be specified when merging tags,
 
2362
            # so a tags implementation that versions tags can only
 
2363
            # pull in the most recent changes. -- JRV20090506
 
2364
            result.tag_updates, result.tag_conflicts = (
 
2365
                self.source.tags.merge_to(
 
2366
                    self.target.tags, "tags" in overwrite,
 
2367
                    ignore_master=not merge_tags_to_master))
 
2368
            self.update_references()
 
2369
            result.new_revno, result.new_revid = (
 
2370
                self.target.last_revision_info())
 
2371
            if _hook_master:
 
2372
                result.master_branch = _hook_master
 
2373
                result.local_branch = result.target_branch
 
2374
            else:
 
2375
                result.master_branch = result.target_branch
 
2376
                result.local_branch = None
 
2377
            if run_hooks:
 
2378
                for hook in Branch.hooks['post_pull']:
 
2379
                    hook(result)
 
2380
            return result
 
2381
 
 
2382
    def update_references(self):
 
2383
        if not getattr(self.source._format, 'supports_reference_locations', False):
 
2384
            return
 
2385
        reference_dict = self.source._get_all_reference_info()
 
2386
        if len(reference_dict) == 0:
 
2387
            return
 
2388
        old_base = self.source.base
 
2389
        new_base = self.target.base
 
2390
        target_reference_dict = self.target._get_all_reference_info()
 
2391
        for tree_path, (branch_location, file_id) in reference_dict.items():
 
2392
            try:
 
2393
                branch_location = urlutils.rebase_url(branch_location,
 
2394
                                                      old_base, new_base)
 
2395
            except urlutils.InvalidRebaseURLs:
 
2396
                # Fall back to absolute URL
 
2397
                branch_location = urlutils.join(old_base, branch_location)
 
2398
            target_reference_dict.setdefault(
 
2399
                tree_path, (branch_location, file_id))
 
2400
        self.target._set_all_reference_info(target_reference_dict)
 
2401
 
 
2402
 
 
2403
InterBranch.register_optimiser(GenericInterBranch)