328
275
The delta is relative to its mainline predecessor, or the
329
276
empty tree for revision 1.
278
assert isinstance(revno, int)
331
279
rh = self.revision_history()
332
280
if not (1 <= revno <= len(rh)):
333
raise errors.InvalidRevisionNumber(revno)
281
raise InvalidRevisionNumber(revno)
334
282
return self.repository.get_revision_delta(rh[revno-1])
336
def get_stacked_on_url(self):
337
"""Get the URL this branch is stacked against.
339
:raises NotStacked: If the branch is not stacked.
340
:raises UnstackableBranchFormat: If the branch does not support
343
raise NotImplementedError(self.get_stacked_on_url)
284
def get_root_id(self):
285
"""Return the id of this branches root"""
286
raise NotImplementedError(self.get_root_id)
345
288
def print_file(self, file, revision_id):
346
289
"""Print `file` to stdout."""
347
290
raise NotImplementedError(self.print_file)
292
def append_revision(self, *revision_ids):
293
raise NotImplementedError(self.append_revision)
349
295
def set_revision_history(self, rev_history):
350
296
raise NotImplementedError(self.set_revision_history)
352
def set_stacked_on_url(self, url):
353
"""Set the URL this branch is stacked against.
355
:raises UnstackableBranchFormat: If the branch does not support
357
:raises UnstackableRepositoryFormat: If the repository does not support
360
raise NotImplementedError(self.set_stacked_on_url)
362
def _cache_revision_history(self, rev_history):
363
"""Set the cached revision history to rev_history.
365
The revision_history method will use this cache to avoid regenerating
366
the revision history.
368
This API is semi-public; it only for use by subclasses, all other code
369
should consider it to be private.
371
self._revision_history_cache = rev_history
373
def _cache_revision_id_to_revno(self, revision_id_to_revno):
374
"""Set the cached revision_id => revno map to revision_id_to_revno.
376
This API is semi-public; it only for use by subclasses, all other code
377
should consider it to be private.
379
self._revision_id_to_revno_cache = revision_id_to_revno
381
def _clear_cached_state(self):
382
"""Clear any cached data on this branch, e.g. cached revision history.
384
This means the next call to revision_history will need to call
385
_gen_revision_history.
387
This API is semi-public; it only for use by subclasses, all other code
388
should consider it to be private.
390
self._revision_history_cache = None
391
self._revision_id_to_revno_cache = None
392
self._last_revision_info_cache = None
394
def _gen_revision_history(self):
395
"""Return sequence of revision hashes on to this branch.
397
Unlike revision_history, this method always regenerates or rereads the
398
revision history, i.e. it does not cache the result, so repeated calls
401
Concrete subclasses should override this instead of revision_history so
402
that subclasses do not need to deal with caching logic.
404
This API is semi-public; it only for use by subclasses, all other code
405
should consider it to be private.
407
raise NotImplementedError(self._gen_revision_history)
410
298
def revision_history(self):
411
"""Return sequence of revision ids on this branch.
413
This method will cache the revision history for as long as it is safe to
416
if 'evil' in debug.debug_flags:
417
mutter_callsite(3, "revision_history scales with history.")
418
if self._revision_history_cache is not None:
419
history = self._revision_history_cache
421
history = self._gen_revision_history()
422
self._cache_revision_history(history)
299
"""Return sequence of revision hashes on to this branch."""
300
raise NotImplementedError(self.revision_history)
426
303
"""Return current revision number for this branch.
474
344
common_index = min(self_len, other_len) -1
475
345
if common_index >= 0 and \
476
346
self_history[common_index] != other_history[common_index]:
477
raise errors.DivergedBranches(self, other)
347
raise DivergedBranches(self, other)
479
349
if stop_revision is None:
480
350
stop_revision = other_len
352
assert isinstance(stop_revision, int)
482
353
if stop_revision > other_len:
483
354
raise errors.NoSuchRevision(self, stop_revision)
484
355
return other_history[self_len:stop_revision]
487
def update_revisions(self, other, stop_revision=None, overwrite=False,
357
def update_revisions(self, other, stop_revision=None):
489
358
"""Pull in new perfect-fit revisions.
491
360
:param other: Another Branch to pull from
492
361
:param stop_revision: Updated until the given revision
493
:param overwrite: Always set the branch pointer, rather than checking
494
to see if it is a proper descendant.
495
:param graph: A Graph object that can be used to query history
496
information. This can be None.
501
other_revno, other_last_revision = other.last_revision_info()
502
stop_revno = None # unknown
503
if stop_revision is None:
504
stop_revision = other_last_revision
505
if _mod_revision.is_null(stop_revision):
506
# if there are no commits, we're done.
508
stop_revno = other_revno
510
# what's the current last revision, before we fetch [and change it
512
last_rev = _mod_revision.ensure_null(self.last_revision())
513
# we fetch here so that we don't process data twice in the common
514
# case of having something to pull, and so that the check for
515
# already merged can operate on the just fetched graph, which will
516
# be cached in memory.
517
self.fetch(other, stop_revision)
518
# Check to see if one is an ancestor of the other
521
graph = self.repository.get_graph()
522
if self._check_if_descendant_or_diverged(
523
stop_revision, last_rev, graph, other):
524
# stop_revision is a descendant of last_rev, but we aren't
525
# overwriting, so we're done.
527
if stop_revno is None:
529
graph = self.repository.get_graph()
530
this_revno, this_last_revision = self.last_revision_info()
531
stop_revno = graph.find_distance_to_null(stop_revision,
532
[(other_last_revision, other_revno),
533
(this_last_revision, this_revno)])
534
self.set_last_revision_info(stop_revno, stop_revision)
364
raise NotImplementedError(self.update_revisions)
538
366
def revision_id_to_revno(self, revision_id):
539
367
"""Given a revision id, return its revno"""
540
if _mod_revision.is_null(revision_id):
368
if revision_id is None:
542
370
history = self.revision_history()
544
372
return history.index(revision_id) + 1
545
373
except ValueError:
546
raise errors.NoSuchRevision(self, revision_id)
374
raise bzrlib.errors.NoSuchRevision(self, revision_id)
548
376
def get_rev_id(self, revno, history=None):
549
377
"""Find the revision id of the specified revno."""
551
return _mod_revision.NULL_REVISION
552
380
if history is None:
553
381
history = self.revision_history()
554
382
if revno <= 0 or revno > len(history):
555
raise errors.NoSuchRevision(self, revno)
383
raise bzrlib.errors.NoSuchRevision(self, revno)
556
384
return history[revno - 1]
558
def pull(self, source, overwrite=False, stop_revision=None,
559
possible_transports=None, _override_hook_target=None):
386
def pull(self, source, overwrite=False, stop_revision=None):
560
387
"""Mirror source into this branch.
562
389
This branch is considered to be 'local', having low latency.
564
:returns: PullResult instance
566
391
raise NotImplementedError(self.pull)
1090
749
These are all empty initially, because by default nothing should get
1093
Hooks.__init__(self)
1094
# Introduced in 0.15:
1095
753
# invoked whenever the revision history has been set
1096
754
# with set_revision_history. The api signature is
1097
755
# (branch, revision_history), and the branch will
756
# be write-locked. Introduced in 0.15.
1099
757
self['set_rh'] = []
1100
# invoked after a push operation completes.
1101
# the api signature is
1103
# containing the members
1104
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
1105
# where local is the local target branch or None, master is the target
1106
# master branch, and the rest should be self explanatory. The source
1107
# is read locked and the target branches write locked. Source will
1108
# be the local low-latency branch.
1109
self['post_push'] = []
1110
# invoked after a pull operation completes.
1111
# the api signature is
1113
# containing the members
1114
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
1115
# where local is the local branch or None, master is the target
1116
# master branch, and the rest should be self explanatory. The source
1117
# is read locked and the target branches write locked. The local
1118
# branch is the low-latency branch.
1119
self['post_pull'] = []
1120
# invoked before a commit operation takes place.
1121
# the api signature is
1122
# (local, master, old_revno, old_revid, future_revno, future_revid,
1123
# tree_delta, future_tree).
1124
# old_revid is NULL_REVISION for the first commit to a branch
1125
# tree_delta is a TreeDelta object describing changes from the basis
1126
# revision, hooks MUST NOT modify this delta
1127
# future_tree is an in-memory tree obtained from
1128
# CommitBuilder.revision_tree() and hooks MUST NOT modify this tree
1129
self['pre_commit'] = []
1130
# invoked after a commit operation completes.
1131
# the api signature is
1132
# (local, master, old_revno, old_revid, new_revno, new_revid)
1133
# old_revid is NULL_REVISION for the first commit to a branch.
1134
self['post_commit'] = []
1135
# invoked after a uncommit operation completes.
1136
# the api signature is
1137
# (local, master, old_revno, old_revid, new_revno, new_revid) where
1138
# local is the local branch or None, master is the target branch,
1139
# and an empty branch recieves new_revno of 0, new_revid of None.
1140
self['post_uncommit'] = []
1142
# Invoked before the tip of a branch changes.
1143
# the api signature is
1144
# (params) where params is a ChangeBranchTipParams with the members
1145
# (branch, old_revno, new_revno, old_revid, new_revid)
1146
self['pre_change_branch_tip'] = []
1148
# Invoked after the tip of a branch changes.
1149
# the api signature is
1150
# (params) where params is a ChangeBranchTipParams with the members
1151
# (branch, old_revno, new_revno, old_revid, new_revid)
1152
self['post_change_branch_tip'] = []
759
def install_hook(self, hook_name, a_callable):
760
"""Install a_callable in to the hook hook_name.
762
:param hook_name: A hook name. See the __init__ method of BranchHooks
763
for the complete list of hooks.
764
:param a_callable: The callable to be invoked when the hook triggers.
765
The exact signature will depend on the hook - see the __init__
766
method of BranchHooks for details on each hook.
769
self[hook_name].append(a_callable)
771
raise errors.UnknownHook('branch', hook_name)
1155
774
# install the default hooks into the Branch class.
1156
775
Branch.hooks = BranchHooks()
1159
class ChangeBranchTipParams(object):
1160
"""Object holding parameters passed to *_change_branch_tip hooks.
1162
There are 5 fields that hooks may wish to access:
1164
:ivar branch: the branch being changed
1165
:ivar old_revno: revision number before the change
1166
:ivar new_revno: revision number after the change
1167
:ivar old_revid: revision id before the change
1168
:ivar new_revid: revision id after the change
1170
The revid fields are strings. The revno fields are integers.
1173
def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
1174
"""Create a group of ChangeBranchTip parameters.
1176
:param branch: The branch being changed.
1177
:param old_revno: Revision number before the change.
1178
:param new_revno: Revision number after the change.
1179
:param old_revid: Tip revision id before the change.
1180
:param new_revid: Tip revision id after the change.
1182
self.branch = branch
1183
self.old_revno = old_revno
1184
self.new_revno = new_revno
1185
self.old_revid = old_revid
1186
self.new_revid = new_revid
1188
def __eq__(self, other):
1189
return self.__dict__ == other.__dict__
1192
return "<%s of %s from (%s, %s) to (%s, %s)>" % (
1193
self.__class__.__name__, self.branch,
1194
self.old_revno, self.old_revid, self.new_revno, self.new_revid)
1197
778
class BzrBranchFormat4(BranchFormat):
1198
779
"""Bzr branch format 4.
1300
851
def initialize(self, a_bzrdir):
1301
852
"""Create a branch of this format in a_bzrdir."""
853
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
854
branch_transport = a_bzrdir.get_branch_transport(self)
1302
855
utf8_files = [('revision-history', ''),
1303
856
('branch-name', ''),
1305
return self._initialize_helper(a_bzrdir, utf8_files)
1307
def supports_tags(self):
1311
class BzrBranchFormat6(BranchFormatMetadir):
1312
"""Branch format with last-revision and tags.
1314
Unlike previous formats, this has no explicit revision history. Instead,
1315
this just stores the last-revision, and the left-hand history leading
1316
up to there is the history.
1318
This format was introduced in bzr 0.15
1319
and became the default in 0.91.
1322
def _branch_class(self):
1325
def get_format_string(self):
1326
"""See BranchFormat.get_format_string()."""
1327
return "Bazaar Branch Format 6 (bzr 0.15)\n"
1329
def get_format_description(self):
1330
"""See BranchFormat.get_format_description()."""
1331
return "Branch format 6"
1333
def initialize(self, a_bzrdir):
1334
"""Create a branch of this format in a_bzrdir."""
1335
utf8_files = [('last-revision', '0 null:\n'),
1336
('branch.conf', ''),
1339
return self._initialize_helper(a_bzrdir, utf8_files)
1342
class BzrBranchFormat7(BranchFormatMetadir):
1343
"""Branch format with last-revision, tags, and a stacked location pointer.
1345
The stacked location pointer is passed down to the repository and requires
1346
a repository format with supports_external_lookups = True.
1348
This format was introduced in bzr 1.6.
1351
def _branch_class(self):
1354
def get_format_string(self):
1355
"""See BranchFormat.get_format_string()."""
1356
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
1358
def get_format_description(self):
1359
"""See BranchFormat.get_format_description()."""
1360
return "Branch format 7"
1362
def initialize(self, a_bzrdir):
1363
"""Create a branch of this format in a_bzrdir."""
1364
utf8_files = [('last-revision', '0 null:\n'),
1365
('branch.conf', ''),
1368
return self._initialize_helper(a_bzrdir, utf8_files)
858
control_files = lockable_files.LockableFiles(branch_transport, 'lock',
860
control_files.create_lock()
861
control_files.lock_write()
862
control_files.put_utf8('format', self.get_format_string())
864
for file, content in utf8_files:
865
control_files.put_utf8(file, content)
867
control_files.unlock()
868
return self.open(a_bzrdir, _found=True, )
1370
870
def __init__(self):
1371
super(BzrBranchFormat7, self).__init__()
1372
self._matchingbzrdir.repository_format = \
1373
RepositoryFormatPackDevelopment1Subtree()
1375
def supports_stacking(self):
871
super(BzrBranchFormat5, self).__init__()
872
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
874
def open(self, a_bzrdir, _found=False):
875
"""Return the branch object for a_bzrdir
877
_found is a private parameter, do not use it. It is used to indicate
878
if format probing has already be done.
881
format = BranchFormat.find_format(a_bzrdir)
882
assert format.__class__ == self.__class__
883
transport = a_bzrdir.get_branch_transport(None)
884
control_files = lockable_files.LockableFiles(transport, 'lock',
886
return BzrBranch5(_format=self,
887
_control_files=control_files,
889
_repository=a_bzrdir.find_repository())
892
return "Bazaar-NG Metadir branch format 5"
1379
895
class BranchReferenceFormat(BranchFormat):
1482
977
Note that it's "local" in the context of the filesystem; it doesn't
1483
978
really matter if it's on an nfs/smb/afs/coda/... share, as long as
1484
979
it's writable, and can be accessed via the normal filesystem API.
1486
:ivar _transport: Transport for file operations on this branch's
1487
control files, typically pointing to the .bzr/branch directory.
1488
:ivar repository: Repository for this branch.
1489
:ivar base: The url of the base directory for this branch; the one
1490
containing the .bzr directory.
1493
def __init__(self, _format=None,
982
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
983
relax_version_check=DEPRECATED_PARAMETER, _format=None,
1494
984
_control_files=None, a_bzrdir=None, _repository=None):
1495
"""Create new branch object at a particular location."""
985
"""Create new branch object at a particular location.
987
transport -- A Transport object, defining how to access files.
989
init -- If True, create new control files in a previously
990
unversioned directory. If False, the branch must already
993
relax_version_check -- If true, the usual check for the branch
994
version is not applied. This is intended only for
995
upgrade/recovery type use; it's not guaranteed that
996
all operations will work on old format branches.
1496
998
if a_bzrdir is None:
1497
raise ValueError('a_bzrdir must be supplied')
999
self.bzrdir = bzrdir.BzrDir.open(transport.base)
1499
1001
self.bzrdir = a_bzrdir
1500
self._base = self.bzrdir.transport.clone('..').base
1501
# XXX: We should be able to just do
1502
# self.base = self.bzrdir.root_transport.base
1503
# but this does not quite work yet -- mbp 20080522
1002
self._transport = self.bzrdir.transport.clone('..')
1003
self._base = self._transport.base
1504
1004
self._format = _format
1505
1005
if _control_files is None:
1506
1006
raise ValueError('BzrBranch _control_files is None')
1507
1007
self.control_files = _control_files
1508
self._transport = _control_files._transport
1008
if deprecated_passed(init):
1009
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
1010
"deprecated as of bzr 0.8. Please use Branch.create().",
1014
# this is slower than before deprecation, oh well never mind.
1015
# -> its deprecated.
1016
self._initialize(transport.base)
1017
self._check_format(_format)
1018
if deprecated_passed(relax_version_check):
1019
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
1020
"relax_version_check parameter is deprecated as of bzr 0.8. "
1021
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
1025
if (not relax_version_check
1026
and not self._format.is_supported()):
1027
raise errors.UnsupportedFormatError(format=fmt)
1028
if deprecated_passed(transport):
1029
warn("BzrBranch.__init__(transport=XXX...): The transport "
1030
"parameter is deprecated as of bzr 0.8. "
1031
"Please use Branch.open, or bzrdir.open_branch().",
1509
1034
self.repository = _repository
1510
Branch.__init__(self)
1512
1036
def __str__(self):
1513
1037
return '%s(%r)' % (self.__class__.__name__, self.base)
1569
1132
"""See Branch.print_file."""
1570
1133
return self.repository.print_file(file, revision_id)
1572
def _write_revision_history(self, history):
1573
"""Factored out of set_revision_history.
1575
This performs the actual writing to disk.
1576
It is intended to be called by BzrBranch5.set_revision_history."""
1577
self._transport.put_bytes(
1578
'revision-history', '\n'.join(history),
1579
mode=self.bzrdir._get_file_mode())
1136
def append_revision(self, *revision_ids):
1137
"""See Branch.append_revision."""
1138
for revision_id in revision_ids:
1139
_mod_revision.check_not_reserved_id(revision_id)
1140
mutter("add {%s} to revision-history" % revision_id)
1141
rev_history = self.revision_history()
1142
rev_history.extend(revision_ids)
1143
self.set_revision_history(rev_history)
1581
1145
@needs_write_lock
1582
1146
def set_revision_history(self, rev_history):
1583
1147
"""See Branch.set_revision_history."""
1584
if 'evil' in debug.debug_flags:
1585
mutter_callsite(3, "set_revision_history scales with history.")
1586
check_not_reserved_id = _mod_revision.check_not_reserved_id
1587
for rev_id in rev_history:
1588
check_not_reserved_id(rev_id)
1589
if Branch.hooks['post_change_branch_tip']:
1590
# Don't calculate the last_revision_info() if there are no hooks
1592
old_revno, old_revid = self.last_revision_info()
1593
if len(rev_history) == 0:
1594
revid = _mod_revision.NULL_REVISION
1148
self.control_files.put_utf8(
1149
'revision-history', '\n'.join(rev_history))
1150
transaction = self.get_transaction()
1151
history = transaction.map.find_revision_history()
1152
if history is not None:
1153
# update the revision history in the identity map.
1154
history[:] = list(rev_history)
1155
# this call is disabled because revision_history is
1156
# not really an object yet, and the transaction is for objects.
1157
# transaction.register_dirty(history)
1596
revid = rev_history[-1]
1597
self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
1598
self._write_revision_history(rev_history)
1599
self._clear_cached_state()
1600
self._cache_revision_history(rev_history)
1159
transaction.map.add_revision_history(rev_history)
1160
# this call is disabled because revision_history is
1161
# not really an object yet, and the transaction is for objects.
1162
# transaction.register_clean(history)
1601
1163
for hook in Branch.hooks['set_rh']:
1602
1164
hook(self, rev_history)
1603
if Branch.hooks['post_change_branch_tip']:
1604
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1606
def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
1607
"""Run the pre_change_branch_tip hooks."""
1608
hooks = Branch.hooks['pre_change_branch_tip']
1611
old_revno, old_revid = self.last_revision_info()
1612
params = ChangeBranchTipParams(
1613
self, old_revno, new_revno, old_revid, new_revid)
1617
except errors.TipChangeRejected:
1620
exc_info = sys.exc_info()
1621
hook_name = Branch.hooks.get_hook_name(hook)
1622
raise errors.HookFailed(
1623
'pre_change_branch_tip', hook_name, exc_info)
1625
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1626
"""Run the post_change_branch_tip hooks."""
1627
hooks = Branch.hooks['post_change_branch_tip']
1630
new_revno, new_revid = self.last_revision_info()
1631
params = ChangeBranchTipParams(
1632
self, old_revno, new_revno, old_revid, new_revid)
1637
def set_last_revision_info(self, revno, revision_id):
1638
"""Set the last revision of this branch.
1640
The caller is responsible for checking that the revno is correct
1641
for this revision id.
1643
It may be possible to set the branch last revision to an id not
1644
present in the repository. However, branches can also be
1645
configured to check constraints on history, in which case this may not
1648
revision_id = _mod_revision.ensure_null(revision_id)
1649
# this old format stores the full history, but this api doesn't
1650
# provide it, so we must generate, and might as well check it's
1652
history = self._lefthand_history(revision_id)
1653
if len(history) != revno:
1654
raise AssertionError('%d != %d' % (len(history), revno))
1655
self.set_revision_history(history)
1657
def _gen_revision_history(self):
1658
history = self._transport.get_bytes('revision-history').split('\n')
1659
if history[-1:] == ['']:
1660
# There shouldn't be a trailing newline, but just in case.
1664
def _lefthand_history(self, revision_id, last_rev=None,
1666
if 'evil' in debug.debug_flags:
1667
mutter_callsite(4, "_lefthand_history scales with history.")
1668
# stop_revision must be a descendant of last_revision
1669
graph = self.repository.get_graph()
1670
if last_rev is not None:
1671
if not graph.is_ancestor(last_rev, revision_id):
1672
# our previous tip is not merged into stop_revision
1673
raise errors.DivergedBranches(self, other_branch)
1674
# make a new revision history from the graph
1675
parents_map = graph.get_parent_map([revision_id])
1676
if revision_id not in parents_map:
1677
raise errors.NoSuchRevision(self, revision_id)
1678
current_rev_id = revision_id
1680
check_not_reserved_id = _mod_revision.check_not_reserved_id
1681
# Do not include ghosts or graph origin in revision_history
1682
while (current_rev_id in parents_map and
1683
len(parents_map[current_rev_id]) > 0):
1684
check_not_reserved_id(current_rev_id)
1685
new_history.append(current_rev_id)
1686
current_rev_id = parents_map[current_rev_id][0]
1687
parents_map = graph.get_parent_map([current_rev_id])
1688
new_history.reverse()
1692
def generate_revision_history(self, revision_id, last_rev=None,
1167
def revision_history(self):
1168
"""See Branch.revision_history."""
1169
transaction = self.get_transaction()
1170
history = transaction.map.find_revision_history()
1171
if history is not None:
1172
# mutter("cache hit for revision-history in %s", self)
1173
return list(history)
1174
decode_utf8 = cache_utf8.decode
1175
history = [decode_utf8(l.rstrip('\r\n')) for l in
1176
self.control_files.get('revision-history').readlines()]
1177
transaction.map.add_revision_history(history)
1178
# this call is disabled because revision_history is
1179
# not really an object yet, and the transaction is for objects.
1180
# transaction.register_clean(history, precious=True)
1181
return list(history)
1184
def generate_revision_history(self, revision_id, last_rev=None,
1693
1185
other_branch=None):
1694
1186
"""Create a new revision history that will finish with revision_id.
1696
1188
:param revision_id: the new tip to use.
1697
1189
:param last_rev: The previous last_revision. If not None, then this
1698
1190
must be a ancestory of revision_id, or DivergedBranches is raised.
1699
1191
:param other_branch: The other branch that DivergedBranches should
1700
1192
raise with respect to.
1702
self.set_revision_history(self._lefthand_history(revision_id,
1703
last_rev, other_branch))
1194
# stop_revision must be a descendant of last_revision
1195
stop_graph = self.repository.get_revision_graph(revision_id)
1196
if last_rev is not None and last_rev not in stop_graph:
1197
# our previous tip is not merged into stop_revision
1198
raise errors.DivergedBranches(self, other_branch)
1199
# make a new revision history from the graph
1200
current_rev_id = revision_id
1202
while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1203
new_history.append(current_rev_id)
1204
current_rev_id_parents = stop_graph[current_rev_id]
1206
current_rev_id = current_rev_id_parents[0]
1208
current_rev_id = None
1209
new_history.reverse()
1210
self.set_revision_history(new_history)
1213
def update_revisions(self, other, stop_revision=None):
1214
"""See Branch.update_revisions."""
1217
if stop_revision is None:
1218
stop_revision = other.last_revision()
1219
if stop_revision is None:
1220
# if there are no commits, we're done.
1222
# whats the current last revision, before we fetch [and change it
1224
last_rev = self.last_revision()
1225
# we fetch here regardless of whether we need to so that we pickup
1227
self.fetch(other, stop_revision)
1228
my_ancestry = self.repository.get_ancestry(last_rev)
1229
if stop_revision in my_ancestry:
1230
# last_revision is a descendant of stop_revision
1232
self.generate_revision_history(stop_revision, last_rev=last_rev,
1705
1237
def basis_tree(self):
1706
1238
"""See Branch.basis_tree."""
1707
1239
return self.repository.revision_tree(self.last_revision())
1241
@deprecated_method(zero_eight)
1242
def working_tree(self):
1243
"""Create a Working tree object for this branch."""
1245
from bzrlib.transport.local import LocalTransport
1246
if (self.base.find('://') != -1 or
1247
not isinstance(self._transport, LocalTransport)):
1248
raise NoWorkingTree(self.base)
1249
return self.bzrdir.open_workingtree()
1709
1251
@needs_write_lock
1710
def pull(self, source, overwrite=False, stop_revision=None,
1711
_hook_master=None, run_hooks=True, possible_transports=None,
1712
_override_hook_target=None):
1715
:param _hook_master: Private parameter - set the branch to
1716
be supplied as the master to pull hooks.
1717
:param run_hooks: Private parameter - if false, this branch
1718
is being called because it's the master of the primary branch,
1719
so it should not run its hooks.
1720
:param _override_hook_target: Private parameter - set the branch to be
1721
supplied as the target_branch to pull hooks.
1723
result = PullResult()
1724
result.source_branch = source
1725
if _override_hook_target is None:
1726
result.target_branch = self
1728
result.target_branch = _override_hook_target
1252
def pull(self, source, overwrite=False, stop_revision=None):
1253
"""See Branch.pull."""
1729
1254
source.lock_read()
1731
# We assume that during 'pull' the local repository is closer than
1733
graph = self.repository.get_graph(source.repository)
1734
result.old_revno, result.old_revid = self.last_revision_info()
1735
self.update_revisions(source, stop_revision, overwrite=overwrite,
1737
result.tag_conflicts = source.tags.merge_to(self.tags, overwrite)
1738
result.new_revno, result.new_revid = self.last_revision_info()
1740
result.master_branch = _hook_master
1741
result.local_branch = result.target_branch
1743
result.master_branch = result.target_branch
1744
result.local_branch = None
1746
for hook in Branch.hooks['post_pull']:
1256
old_count = self.last_revision_info()[0]
1258
self.update_revisions(source, stop_revision)
1259
except DivergedBranches:
1263
self.set_revision_history(source.revision_history())
1264
new_count = self.last_revision_info()[0]
1265
return new_count - old_count
1749
1267
source.unlock()
1752
def _get_parent_location(self):
1753
_locs = ['parent', 'pull', 'x-pull']
1756
return self._transport.get_bytes(l).strip('\n')
1757
except errors.NoSuchFile:
1761
1269
@needs_read_lock
1762
def push(self, target, overwrite=False, stop_revision=None,
1763
_override_hook_source_branch=None):
1766
This is the basic concrete implementation of push()
1768
:param _override_hook_source_branch: If specified, run
1769
the hooks passing this Branch as the source, rather than self.
1770
This is for use of RemoteBranch, where push is delegated to the
1771
underlying vfs-based Branch.
1773
# TODO: Public option to disable running hooks - should be trivial but
1270
def push(self, target, overwrite=False, stop_revision=None):
1271
"""See Branch.push."""
1775
1272
target.lock_write()
1777
result = self._push_with_bound_branches(target, overwrite,
1779
_override_hook_source_branch=_override_hook_source_branch)
1274
old_count = len(target.revision_history())
1276
target.update_revisions(self, stop_revision)
1277
except DivergedBranches:
1281
target.set_revision_history(self.revision_history())
1282
new_count = len(target.revision_history())
1283
return new_count - old_count
1782
1285
target.unlock()
1784
def _push_with_bound_branches(self, target, overwrite,
1786
_override_hook_source_branch=None):
1787
"""Push from self into target, and into target's master if any.
1789
This is on the base BzrBranch class even though it doesn't support
1790
bound branches because the *target* might be bound.
1793
if _override_hook_source_branch:
1794
result.source_branch = _override_hook_source_branch
1795
for hook in Branch.hooks['post_push']:
1798
bound_location = target.get_bound_location()
1799
if bound_location and target.base != bound_location:
1800
# there is a master branch.
1802
# XXX: Why the second check? Is it even supported for a branch to
1803
# be bound to itself? -- mbp 20070507
1804
master_branch = target.get_master_branch()
1805
master_branch.lock_write()
1807
# push into the master from this branch.
1808
self._basic_push(master_branch, overwrite, stop_revision)
1809
# and push into the target branch from this. Note that we push from
1810
# this branch again, because its considered the highest bandwidth
1812
result = self._basic_push(target, overwrite, stop_revision)
1813
result.master_branch = master_branch
1814
result.local_branch = target
1818
master_branch.unlock()
1821
result = self._basic_push(target, overwrite, stop_revision)
1822
# TODO: Why set master_branch and local_branch if there's no
1823
# binding? Maybe cleaner to just leave them unset? -- mbp
1825
result.master_branch = target
1826
result.local_branch = None
1830
def _basic_push(self, target, overwrite, stop_revision):
1831
"""Basic implementation of push without bound branches or hooks.
1833
Must be called with self read locked and target write locked.
1835
result = PushResult()
1836
result.source_branch = self
1837
result.target_branch = target
1838
result.old_revno, result.old_revid = target.last_revision_info()
1840
# We assume that during 'push' this repository is closer than
1842
graph = self.repository.get_graph(target.repository)
1843
target.update_revisions(self, stop_revision, overwrite=overwrite,
1845
result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1846
result.new_revno, result.new_revid = target.last_revision_info()
1849
1287
def get_parent(self):
1850
1288
"""See Branch.get_parent."""
1851
parent = self._get_parent_location()
1854
# This is an old-format absolute path to a local branch
1855
# turn it into a url
1856
if parent.startswith('/'):
1857
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1859
return urlutils.join(self.base[:-1], parent)
1860
except errors.InvalidURLJoin, e:
1861
raise errors.InaccessibleParent(parent, self.base)
1863
def get_stacked_on_url(self):
1864
raise errors.UnstackableBranchFormat(self._format, self.base)
1290
_locs = ['parent', 'pull', 'x-pull']
1291
assert self.base[-1] == '/'
1294
parent = self.control_files.get(l).read().strip('\n')
1297
# This is an old-format absolute path to a local branch
1298
# turn it into a url
1299
if parent.startswith('/'):
1300
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1302
return urlutils.join(self.base[:-1], parent)
1303
except errors.InvalidURLJoin, e:
1304
raise errors.InaccessibleParent(parent, self.base)
1307
def get_push_location(self):
1308
"""See Branch.get_push_location."""
1309
push_loc = self.get_config().get_user_option('push_location')
1866
1312
def set_push_location(self, location):
1867
1313
"""See Branch.set_push_location."""
1875
1321
# TODO: Maybe delete old location files?
1876
1322
# URLs should never be unicode, even on the local fs,
1877
1323
# FIXUP this and get_parent in a future branch format bump:
1878
# read and rewrite the file. RBC 20060125
1324
# read and rewrite the file, and have the new format code read
1325
# using .get not .get_utf8. RBC 20060125
1327
self.control_files._transport.delete('parent')
1880
1329
if isinstance(url, unicode):
1882
1331
url = url.encode('ascii')
1883
1332
except UnicodeEncodeError:
1884
raise errors.InvalidURL(url,
1333
raise bzrlib.errors.InvalidURL(url,
1885
1334
"Urls must be 7-bit ascii, "
1886
1335
"use bzrlib.urlutils.escape")
1887
1337
url = urlutils.relative_url(self.base, url)
1888
self._set_parent_location(url)
1890
def _set_parent_location(self, url):
1892
self._transport.delete('parent')
1894
self._transport.put_bytes('parent', url + '\n',
1895
mode=self.bzrdir._get_file_mode())
1897
def set_stacked_on_url(self, url):
1898
raise errors.UnstackableBranchFormat(self._format, self.base)
1338
self.control_files.put('parent', StringIO(url + '\n'))
1340
@deprecated_function(zero_nine)
1341
def tree_config(self):
1342
"""DEPRECATED; call get_config instead.
1343
TreeConfig has become part of BranchConfig."""
1344
return TreeConfig(self)
1901
1347
class BzrBranch5(BzrBranch):
1902
"""A format 5 branch. This supports new features over plain branches.
1348
"""A format 5 branch. This supports new features over plan branches.
1904
1350
It has support for a master_branch which is the data for bound branches.
1358
super(BzrBranch5, self).__init__(_format=_format,
1359
_control_files=_control_files,
1361
_repository=_repository)
1907
1363
@needs_write_lock
1908
def pull(self, source, overwrite=False, stop_revision=None,
1909
run_hooks=True, possible_transports=None,
1910
_override_hook_target=None):
1911
"""Pull from source into self, updating my master if any.
1913
:param run_hooks: Private parameter - if false, this branch
1914
is being called because it's the master of the primary branch,
1915
so it should not run its hooks.
1364
def pull(self, source, overwrite=False, stop_revision=None):
1365
"""Extends branch.pull to be bound branch aware."""
1917
1366
bound_location = self.get_bound_location()
1918
master_branch = None
1919
if bound_location and source.base != bound_location:
1367
if source.base != bound_location:
1920
1368
# not pulling from master, so we need to update master.
1921
master_branch = self.get_master_branch(possible_transports)
1922
master_branch.lock_write()
1925
# pull from source into master.
1926
master_branch.pull(source, overwrite, stop_revision,
1928
return super(BzrBranch5, self).pull(source, overwrite,
1929
stop_revision, _hook_master=master_branch,
1930
run_hooks=run_hooks,
1931
_override_hook_target=_override_hook_target)
1934
master_branch.unlock()
1369
master_branch = self.get_master_branch()
1371
master_branch.pull(source)
1372
source = master_branch
1373
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1376
def push(self, target, overwrite=False, stop_revision=None):
1377
"""Updates branch.push to be bound branch aware."""
1378
bound_location = target.get_bound_location()
1379
if target.base != bound_location:
1380
# not pushing to master, so we need to update master.
1381
master_branch = target.get_master_branch()
1383
# push into the master from this branch.
1384
super(BzrBranch5, self).push(master_branch, overwrite,
1386
# and push into the target branch from this. Note that we push from
1387
# this branch again, because its considered the highest bandwidth
1389
return super(BzrBranch5, self).push(target, overwrite, stop_revision)
1936
1391
def get_bound_location(self):
1938
return self._transport.get_bytes('bound')[:-1]
1393
return self.control_files.get_utf8('bound').read()[:-1]
1939
1394
except errors.NoSuchFile:
1942
1397
@needs_read_lock
1943
def get_master_branch(self, possible_transports=None):
1398
def get_master_branch(self):
1944
1399
"""Return the branch we are bound to.
1946
1401
:return: Either a Branch, or None
2008
1477
return self.set_bound_location(None)
2010
1479
@needs_write_lock
2011
def update(self, possible_transports=None):
2012
1481
"""Synchronise this branch with the master branch if any.
2014
1483
:return: None or the last_revision that was pivoted out during the
2017
master = self.get_master_branch(possible_transports)
1486
master = self.get_master_branch()
2018
1487
if master is not None:
2019
old_tip = _mod_revision.ensure_null(self.last_revision())
1488
old_tip = self.last_revision()
2020
1489
self.pull(master, overwrite=True)
2021
if self.repository.get_graph().is_ancestor(old_tip,
2022
_mod_revision.ensure_null(self.last_revision())):
1490
if old_tip in self.repository.get_ancestry(self.last_revision()):
2028
class BzrBranch7(BzrBranch5):
2029
"""A branch with support for a fallback repository."""
2031
def _get_fallback_repository(self, url):
2032
"""Get the repository we fallback to at url."""
2033
url = urlutils.join(self.base, url)
2034
return bzrdir.BzrDir.open(url).open_branch().repository
2036
def _activate_fallback_location(self, url):
2037
"""Activate the branch/repository from url as a fallback repository."""
2038
self.repository.add_fallback_repository(
2039
self._get_fallback_repository(url))
2041
def _open_hook(self):
2043
url = self.get_stacked_on_url()
2044
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
2045
errors.UnstackableBranchFormat):
2048
self._activate_fallback_location(url)
2050
def _check_stackable_repo(self):
2051
if not self.repository._format.supports_external_lookups:
2052
raise errors.UnstackableRepositoryFormat(self.repository._format,
2053
self.repository.base)
2055
def __init__(self, *args, **kwargs):
2056
super(BzrBranch7, self).__init__(*args, **kwargs)
2057
self._last_revision_info_cache = None
2058
self._partial_revision_history_cache = []
2060
def _clear_cached_state(self):
2061
super(BzrBranch7, self)._clear_cached_state()
2062
self._last_revision_info_cache = None
2063
self._partial_revision_history_cache = []
2065
def _last_revision_info(self):
2066
revision_string = self._transport.get_bytes('last-revision')
2067
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
2068
revision_id = cache_utf8.get_cached_utf8(revision_id)
2070
return revno, revision_id
2072
def _write_last_revision_info(self, revno, revision_id):
2073
"""Simply write out the revision id, with no checks.
2075
Use set_last_revision_info to perform this safely.
2077
Does not update the revision_history cache.
2078
Intended to be called by set_last_revision_info and
2079
_write_revision_history.
2081
revision_id = _mod_revision.ensure_null(revision_id)
2082
out_string = '%d %s\n' % (revno, revision_id)
2083
self._transport.put_bytes('last-revision', out_string,
2084
mode=self.bzrdir._get_file_mode())
2087
def set_last_revision_info(self, revno, revision_id):
2088
revision_id = _mod_revision.ensure_null(revision_id)
2089
old_revno, old_revid = self.last_revision_info()
2090
if self._get_append_revisions_only():
2091
self._check_history_violation(revision_id)
2092
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2093
self._write_last_revision_info(revno, revision_id)
2094
self._clear_cached_state()
2095
self._last_revision_info_cache = revno, revision_id
2096
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2098
def _check_history_violation(self, revision_id):
2099
last_revision = _mod_revision.ensure_null(self.last_revision())
2100
if _mod_revision.is_null(last_revision):
2102
if last_revision not in self._lefthand_history(revision_id):
2103
raise errors.AppendRevisionsOnlyViolation(self.base)
2105
def _gen_revision_history(self):
2106
"""Generate the revision history from last revision
2108
last_revno, last_revision = self.last_revision_info()
2109
self._extend_partial_history(stop_index=last_revno-1)
2110
return list(reversed(self._partial_revision_history_cache))
2112
def _extend_partial_history(self, stop_index=None, stop_revision=None):
2113
"""Extend the partial history to include a given index
2115
If a stop_index is supplied, stop when that index has been reached.
2116
If a stop_revision is supplied, stop when that revision is
2117
encountered. Otherwise, stop when the beginning of history is
2120
:param stop_index: The index which should be present. When it is
2121
present, history extension will stop.
2122
:param revision_id: The revision id which should be present. When
2123
it is encountered, history extension will stop.
2125
repo = self.repository
2126
if len(self._partial_revision_history_cache) == 0:
2127
iterator = repo.iter_reverse_revision_history(self.last_revision())
2129
start_revision = self._partial_revision_history_cache[-1]
2130
iterator = repo.iter_reverse_revision_history(start_revision)
2131
#skip the last revision in the list
2132
next_revision = iterator.next()
2133
for revision_id in iterator:
2134
self._partial_revision_history_cache.append(revision_id)
2135
if (stop_index is not None and
2136
len(self._partial_revision_history_cache) > stop_index):
2138
if revision_id == stop_revision:
2141
def _write_revision_history(self, history):
2142
"""Factored out of set_revision_history.
2144
This performs the actual writing to disk, with format-specific checks.
2145
It is intended to be called by BzrBranch5.set_revision_history.
2147
if len(history) == 0:
2148
last_revision = 'null:'
2150
if history != self._lefthand_history(history[-1]):
2151
raise errors.NotLefthandHistory(history)
2152
last_revision = history[-1]
2153
if self._get_append_revisions_only():
2154
self._check_history_violation(last_revision)
2155
self._write_last_revision_info(len(history), last_revision)
2158
def _set_parent_location(self, url):
2159
"""Set the parent branch"""
2160
self._set_config_location('parent_location', url, make_relative=True)
2163
def _get_parent_location(self):
2164
"""Set the parent branch"""
2165
return self._get_config_location('parent_location')
2167
def set_push_location(self, location):
2168
"""See Branch.set_push_location."""
2169
self._set_config_location('push_location', location)
2171
def set_bound_location(self, location):
2172
"""See Branch.set_push_location."""
2174
config = self.get_config()
2175
if location is None:
2176
if config.get_user_option('bound') != 'True':
2179
config.set_user_option('bound', 'False', warn_masked=True)
2182
self._set_config_location('bound_location', location,
2184
config.set_user_option('bound', 'True', warn_masked=True)
2187
def _get_bound_location(self, bound):
2188
"""Return the bound location in the config file.
2190
Return None if the bound parameter does not match"""
2191
config = self.get_config()
2192
config_bound = (config.get_user_option('bound') == 'True')
2193
if config_bound != bound:
2195
return self._get_config_location('bound_location', config=config)
2197
def get_bound_location(self):
2198
"""See Branch.set_push_location."""
2199
return self._get_bound_location(True)
2201
def get_old_bound_location(self):
2202
"""See Branch.get_old_bound_location"""
2203
return self._get_bound_location(False)
2205
def get_stacked_on_url(self):
2206
self._check_stackable_repo()
2207
stacked_url = self._get_config_location('stacked_on_location')
2208
if stacked_url is None:
2209
raise errors.NotStacked(self)
2212
def set_append_revisions_only(self, enabled):
2217
self.get_config().set_user_option('append_revisions_only', value,
2220
def set_stacked_on_url(self, url):
2221
self._check_stackable_repo()
2224
old_url = self.get_stacked_on_url()
2225
except (errors.NotStacked, errors.UnstackableBranchFormat,
2226
errors.UnstackableRepositoryFormat):
2229
# repositories don't offer an interface to remove fallback
2230
# repositories today; take the conceptually simpler option and just
2232
self.repository = self.bzrdir.find_repository()
2233
# for every revision reference the branch has, ensure it is pulled
2235
source_repository = self._get_fallback_repository(old_url)
2236
for revision_id in chain([self.last_revision()],
2237
self.tags.get_reverse_tag_dict()):
2238
self.repository.fetch(source_repository, revision_id,
2241
self._activate_fallback_location(url)
2242
# write this out after the repository is stacked to avoid setting a
2243
# stacked config that doesn't work.
2244
self._set_config_location('stacked_on_location', url)
2246
def _get_append_revisions_only(self):
2247
value = self.get_config().get_user_option('append_revisions_only')
2248
return value == 'True'
2250
def _synchronize_history(self, destination, revision_id):
2251
"""Synchronize last revision and revision history between branches.
2253
This version is most efficient when the destination is also a
2254
BzrBranch6, but works for BzrBranch5, as long as the destination's
2255
repository contains all the lefthand ancestors of the intended
2256
last_revision. If not, set_last_revision_info will fail.
2258
:param destination: The branch to copy the history into
2259
:param revision_id: The revision-id to truncate history at. May
2260
be None to copy complete history.
2262
source_revno, source_revision_id = self.last_revision_info()
2263
if revision_id is None:
2264
revno, revision_id = source_revno, source_revision_id
2265
elif source_revision_id == revision_id:
2266
# we know the revno without needing to walk all of history
2267
revno = source_revno
2269
# To figure out the revno for a random revision, we need to build
2270
# the revision history, and count its length.
2271
# We don't care about the order, just how long it is.
2272
# Alternatively, we could start at the current location, and count
2273
# backwards. But there is no guarantee that we will find it since
2274
# it may be a merged revision.
2275
revno = len(list(self.repository.iter_reverse_revision_history(
2277
destination.set_last_revision_info(revno, revision_id)
2279
def _make_tags(self):
2280
return BasicTags(self)
2283
def generate_revision_history(self, revision_id, last_rev=None,
2285
"""See BzrBranch5.generate_revision_history"""
2286
history = self._lefthand_history(revision_id, last_rev, other_branch)
2287
revno = len(history)
2288
self.set_last_revision_info(revno, revision_id)
2291
def get_rev_id(self, revno, history=None):
2292
"""Find the revision id of the specified revno."""
2294
return _mod_revision.NULL_REVISION
2296
last_revno, last_revision_id = self.last_revision_info()
2297
if revno <= 0 or revno > last_revno:
2298
raise errors.NoSuchRevision(self, revno)
2300
if history is not None:
2301
return history[revno - 1]
2303
index = last_revno - revno
2304
if len(self._partial_revision_history_cache) <= index:
2305
self._extend_partial_history(stop_index=index)
2306
if len(self._partial_revision_history_cache) > index:
2307
return self._partial_revision_history_cache[index]
2309
raise errors.NoSuchRevision(self, revno)
2312
def revision_id_to_revno(self, revision_id):
2313
"""Given a revision id, return its revno"""
2314
if _mod_revision.is_null(revision_id):
2317
index = self._partial_revision_history_cache.index(revision_id)
2319
self._extend_partial_history(stop_revision=revision_id)
2320
index = len(self._partial_revision_history_cache) - 1
2321
if self._partial_revision_history_cache[index] != revision_id:
2322
raise errors.NoSuchRevision(self, revision_id)
2323
return self.revno() - index
2326
class BzrBranch6(BzrBranch7):
2327
"""See BzrBranchFormat6 for the capabilities of this branch.
2329
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
2333
def get_stacked_on_url(self):
2334
raise errors.UnstackableBranchFormat(self._format, self.base)
2336
def set_stacked_on_url(self, url):
2337
raise errors.UnstackableBranchFormat(self._format, self.base)
2340
######################################################################
2341
# results of operations
2344
class _Result(object):
2346
def _show_tag_conficts(self, to_file):
2347
if not getattr(self, 'tag_conflicts', None):
2349
to_file.write('Conflicting tags:\n')
2350
for name, value1, value2 in self.tag_conflicts:
2351
to_file.write(' %s\n' % (name, ))
2354
class PullResult(_Result):
2355
"""Result of a Branch.pull operation.
2357
:ivar old_revno: Revision number before pull.
2358
:ivar new_revno: Revision number after pull.
2359
:ivar old_revid: Tip revision id before pull.
2360
:ivar new_revid: Tip revision id after pull.
2361
:ivar source_branch: Source (local) branch object.
2362
:ivar master_branch: Master branch of the target, or the target if no
2364
:ivar local_branch: target branch if there is a Master, else None
2365
:ivar target_branch: Target/destination branch object.
2366
:ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
2370
# DEPRECATED: pull used to return the change in revno
2371
return self.new_revno - self.old_revno
2373
def report(self, to_file):
2375
if self.old_revid == self.new_revid:
2376
to_file.write('No revisions to pull.\n')
2378
to_file.write('Now on revision %d.\n' % self.new_revno)
2379
self._show_tag_conficts(to_file)
2382
class PushResult(_Result):
2383
"""Result of a Branch.push operation.
2385
:ivar old_revno: Revision number before push.
2386
:ivar new_revno: Revision number after push.
2387
:ivar old_revid: Tip revision id before push.
2388
:ivar new_revid: Tip revision id after push.
2389
:ivar source_branch: Source branch object.
2390
:ivar master_branch: Master branch of the target, or None.
2391
:ivar target_branch: Target/destination branch object.
2395
# DEPRECATED: push used to return the change in revno
2396
return self.new_revno - self.old_revno
2398
def report(self, to_file):
2399
"""Write a human-readable description of the result."""
2400
if self.old_revid == self.new_revid:
2401
to_file.write('No new revisions to push.\n')
2403
to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2404
self._show_tag_conficts(to_file)
1496
class BranchTestProviderAdapter(object):
1497
"""A tool to generate a suite testing multiple branch formats at once.
1499
This is done by copying the test once for each transport and injecting
1500
the transport_server, transport_readonly_server, and branch_format
1501
classes into each copy. Each copy is also given a new id() to make it
1505
def __init__(self, transport_server, transport_readonly_server, formats):
1506
self._transport_server = transport_server
1507
self._transport_readonly_server = transport_readonly_server
1508
self._formats = formats
1510
def adapt(self, test):
1511
result = TestSuite()
1512
for branch_format, bzrdir_format in self._formats:
1513
new_test = deepcopy(test)
1514
new_test.transport_server = self._transport_server
1515
new_test.transport_readonly_server = self._transport_readonly_server
1516
new_test.bzrdir_format = bzrdir_format
1517
new_test.branch_format = branch_format
1518
def make_new_test_id():
1519
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1520
return lambda: new_id
1521
new_test.id = make_new_test_id()
1522
result.addTest(new_test)
2407
1526
class BranchCheckResult(object):