15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from cStringIO import StringIO
20
from bzrlib.lazy_import import lazy_import
21
lazy_import(globals(), """
18
22
from copy import deepcopy
19
from cStringIO import StringIO
24
23
from unittest import TestSuite
25
24
from warnings import warn
28
import bzrlib.bzrdir as bzrdir
29
from bzrlib.config import TreeConfig
30
config as _mod_config,
35
revision as _mod_revision,
41
from bzrlib.config import BranchConfig, TreeConfig
42
from bzrlib.lockable_files import LockableFiles, TransportLock
30
45
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
from bzrlib.delta import compare_trees
32
import bzrlib.errors as errors
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError,
36
UninitializableFormat,
38
UnlistableBranch, NoSuchFile, NotVersionedError,
40
import bzrlib.inventory as inventory
41
from bzrlib.inventory import Inventory
42
from bzrlib.lockable_files import LockableFiles, TransportLock
43
from bzrlib.lockdir import LockDir
44
from bzrlib.osutils import (isdir, quotefn,
45
rename, splitpath, sha_file,
46
file_kind, abspath, normpath, pathjoin,
50
from bzrlib.textui import show_status
46
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
47
HistoryMissing, InvalidRevisionId,
48
InvalidRevisionNumber, LockError, NoSuchFile,
49
NoSuchRevision, NoWorkingTree, NotVersionedError,
50
NotBranchError, UninitializableFormat,
51
UnlistableStore, UnlistableBranch,
53
from bzrlib.symbol_versioning import (deprecated_function,
57
zero_eight, zero_nine,
51
59
from bzrlib.trace import mutter, note
52
from bzrlib.tree import EmptyTree, RevisionTree
53
from bzrlib.repository import Repository
54
from bzrlib.revision import (
59
from bzrlib.store import copy_all
60
from bzrlib.symbol_versioning import *
61
import bzrlib.transactions as transactions
62
from bzrlib.transport import Transport, get_transport
63
from bzrlib.tree import EmptyTree, RevisionTree
68
62
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
151
147
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
149
@deprecated_function(zero_eight)
153
150
def setup_caching(self, cache_root):
154
151
"""Subclasses that care about caching should override this, and set
155
152
up cached stores located under cache_root.
154
NOTE: This is unused.
157
# seems to be unused, 2006-01-13 mbp
158
warn('%s is deprecated' % self.setup_caching)
159
self.cache_root = cache_root
158
def get_config(self):
159
return BranchConfig(self)
161
161
def _get_nick(self):
162
cfg = self.tree_config()
163
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
162
return self.get_config().get_nickname()
165
164
def _set_nick(self, nick):
166
cfg = self.tree_config()
167
cfg.set_option(nick, "nickname")
168
assert cfg.get_option("nickname") == nick
165
self.get_config().set_user_option('nickname', nick)
170
167
nick = property(_get_nick, _set_nick)
172
169
def is_locked(self):
173
raise NotImplementedError('is_locked is abstract')
170
raise NotImplementedError(self.is_locked)
175
172
def lock_write(self):
176
raise NotImplementedError('lock_write is abstract')
173
raise NotImplementedError(self.lock_write)
178
175
def lock_read(self):
179
raise NotImplementedError('lock_read is abstract')
176
raise NotImplementedError(self.lock_read)
181
178
def unlock(self):
182
raise NotImplementedError('unlock is abstract')
179
raise NotImplementedError(self.unlock)
184
181
def peek_lock_mode(self):
185
182
"""Return lock mode for the Branch: 'r', 'w' or None"""
186
183
raise NotImplementedError(self.peek_lock_mode)
188
185
def get_physical_lock_status(self):
189
raise NotImplementedError('get_physical_lock_status is abstract')
186
raise NotImplementedError(self.get_physical_lock_status)
191
188
def abspath(self, name):
192
189
"""Return absolute filename for something in the branch
247
def get_old_bound_location(self):
248
"""Return the URL of the branch we used to be bound to
250
raise errors.UpgradeRequired(self.base)
252
def get_commit_builder(self, parents, config=None, timestamp=None,
253
timezone=None, committer=None, revprops=None,
255
"""Obtain a CommitBuilder for this branch.
257
:param parents: Revision ids of the parents of the new revision.
258
:param config: Optional configuration to use.
259
:param timestamp: Optional timestamp recorded for commit.
260
:param timezone: Optional timezone for timestamp.
261
:param committer: Optional committer to set for commit.
262
:param revprops: Optional dictionary of revision properties.
263
:param revision_id: Optional revision id.
267
config = self.get_config()
269
return self.repository.get_commit_builder(self, parents, config,
270
timestamp, timezone, committer, revprops, revision_id)
253
272
def get_master_branch(self):
254
273
"""Return the branch we are bound to.
279
def get_revision_delta(self, revno):
280
"""Return the delta for one revision.
282
The delta is relative to its mainline predecessor, or the
283
empty tree for revision 1.
285
assert isinstance(revno, int)
286
rh = self.revision_history()
287
if not (1 <= revno <= len(rh)):
288
raise InvalidRevisionNumber(revno)
289
return self.repository.get_revision_delta(rh[revno-1])
260
291
def get_root_id(self):
261
292
"""Return the id of this branches root"""
262
raise NotImplementedError('get_root_id is abstract')
293
raise NotImplementedError(self.get_root_id)
264
295
def print_file(self, file, revision_id):
265
296
"""Print `file` to stdout."""
266
raise NotImplementedError('print_file is abstract')
297
raise NotImplementedError(self.print_file)
268
299
def append_revision(self, *revision_ids):
269
raise NotImplementedError('append_revision is abstract')
300
raise NotImplementedError(self.append_revision)
271
302
def set_revision_history(self, rev_history):
272
raise NotImplementedError('set_revision_history is abstract')
303
raise NotImplementedError(self.set_revision_history)
274
305
def revision_history(self):
275
306
"""Return sequence of revision hashes on to this branch."""
276
raise NotImplementedError('revision_history is abstract')
307
raise NotImplementedError(self.revision_history)
279
310
"""Return current revision number for this branch.
287
318
"""Older format branches cannot bind or unbind."""
288
319
raise errors.UpgradeRequired(self.base)
321
def set_append_revisions_only(self, enabled):
322
"""Older format branches are never restricted to append-only"""
323
raise errors.UpgradeRequired(self.base)
290
325
def last_revision(self):
291
"""Return last patch hash, or None if no history."""
326
"""Return last revision id, or None"""
292
327
ph = self.revision_history()
333
def last_revision_info(self):
334
"""Return information about the last revision.
336
:return: A tuple (revno, last_revision_id).
338
rh = self.revision_history()
341
return (revno, rh[-1])
343
return (0, _mod_revision.NULL_REVISION)
298
345
def missing_revisions(self, other, stop_revision=None):
299
346
"""Return a list of new revisions that would perfectly fit.
301
348
If self and other have not diverged, return a list of the revisions
302
349
present in other, but missing from self.
304
>>> from bzrlib.workingtree import WorkingTree
305
>>> bzrlib.trace.silent = True
306
>>> d1 = bzrdir.ScratchDir()
307
>>> br1 = d1.open_branch()
308
>>> wt1 = d1.open_workingtree()
309
>>> d2 = bzrdir.ScratchDir()
310
>>> br2 = d2.open_branch()
311
>>> wt2 = d2.open_workingtree()
312
>>> br1.missing_revisions(br2)
314
>>> wt2.commit("lala!", rev_id="REVISION-ID-1")
315
>>> br1.missing_revisions(br2)
317
>>> br2.missing_revisions(br1)
319
>>> wt1.commit("lala!", rev_id="REVISION-ID-1")
320
>>> br1.missing_revisions(br2)
322
>>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
323
>>> br1.missing_revisions(br2)
325
>>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
326
>>> br1.missing_revisions(br2)
327
Traceback (most recent call last):
328
DivergedBranches: These branches have diverged. Try merge.
330
351
self_history = self.revision_history()
331
352
self_len = len(self_history)
370
392
if history is None:
371
393
history = self.revision_history()
372
elif revno <= 0 or revno > len(history):
394
if revno <= 0 or revno > len(history):
373
395
raise bzrlib.errors.NoSuchRevision(self, revno)
374
396
return history[revno - 1]
376
398
def pull(self, source, overwrite=False, stop_revision=None):
377
raise NotImplementedError('pull is abstract')
399
"""Mirror source into this branch.
401
This branch is considered to be 'local', having low latency.
403
raise NotImplementedError(self.pull)
405
def push(self, target, overwrite=False, stop_revision=None):
406
"""Mirror this branch into target.
408
This branch is considered to be 'local', having low latency.
410
raise NotImplementedError(self.push)
379
412
def basis_tree(self):
380
"""Return `Tree` object for last revision.
382
If there are no revisions yet, return an `EmptyTree`.
413
"""Return `Tree` object for last revision."""
384
414
return self.repository.revision_tree(self.last_revision())
386
416
def rename_one(self, from_rel, to_rel):
413
443
pattern is that the user can override it by specifying a
416
raise NotImplementedError('get_parent is abstract')
446
raise NotImplementedError(self.get_parent)
448
def get_submit_branch(self):
449
"""Return the submit location of the branch.
451
This is the default location for bundle. The usual
452
pattern is that the user can override it by specifying a
455
return self.get_config().get_user_option('submit_branch')
457
def set_submit_branch(self, location):
458
"""Return the submit location of the branch.
460
This is the default location for bundle. The usual
461
pattern is that the user can override it by specifying a
464
self.get_config().set_user_option('submit_branch', location)
418
466
def get_push_location(self):
419
467
"""Return the None or the location to push this branch to."""
420
raise NotImplementedError('get_push_location is abstract')
468
raise NotImplementedError(self.get_push_location)
422
470
def set_push_location(self, location):
423
471
"""Set a new push location for this branch."""
424
raise NotImplementedError('set_push_location is abstract')
472
raise NotImplementedError(self.set_push_location)
426
474
def set_parent(self, url):
427
raise NotImplementedError('set_parent is abstract')
475
raise NotImplementedError(self.set_parent)
429
477
@needs_write_lock
430
478
def update(self):
518
566
result.set_parent(self.bzrdir.root_transport.base)
522
def copy_content_into(self, destination, revision_id=None):
523
"""Copy the content of self into destination.
525
revision_id: if not None, the revision history in the new branch will
526
be truncated to end with revision_id.
569
def _synchronize_history(self, destination, revision_id):
570
"""Synchronize last revision and revision history between branches.
572
This version is most efficient when the destination is also a
573
BzrBranch5, but works for BzrBranch6 as long as the revision
574
history is the true lefthand parent history, and all of the revisions
575
are in the destination's repository. If not, set_revision_history
578
:param destination: The branch to copy the history into
579
:param revision_id: The revision-id to truncate history at. May
580
be None to copy complete history.
528
582
new_history = self.revision_history()
529
583
if revision_id is not None:
584
revision_id = osutils.safe_revision_id(revision_id)
531
586
new_history = new_history[:new_history.index(revision_id) + 1]
532
587
except ValueError:
533
588
rev = self.repository.get_revision(revision_id)
534
589
new_history = rev.get_history(self.repository)[1:]
535
590
destination.set_revision_history(new_history)
536
parent = self.get_parent()
538
destination.set_parent(parent)
593
def copy_content_into(self, destination, revision_id=None):
594
"""Copy the content of self into destination.
596
revision_id: if not None, the revision history in the new branch will
597
be truncated to end with revision_id.
599
self._synchronize_history(destination, revision_id)
601
parent = self.get_parent()
602
except errors.InaccessibleParent, e:
603
mutter('parent was not accessible to copy: %s', e)
606
destination.set_parent(parent)
610
"""Check consistency of the branch.
612
In particular this checks that revisions given in the revision-history
613
do actually match up in the revision graph, and that they're all
614
present in the repository.
616
Callers will typically also want to check the repository.
618
:return: A BranchCheckResult.
620
mainline_parent_id = None
621
for revision_id in self.revision_history():
623
revision = self.repository.get_revision(revision_id)
624
except errors.NoSuchRevision, e:
625
raise errors.BzrCheckError("mainline revision {%s} not in repository"
627
# In general the first entry on the revision history has no parents.
628
# But it's not illegal for it to have parents listed; this can happen
629
# in imports from Arch when the parents weren't reachable.
630
if mainline_parent_id is not None:
631
if mainline_parent_id not in revision.parent_ids:
632
raise errors.BzrCheckError("previous revision {%s} not listed among "
634
% (mainline_parent_id, revision_id))
635
mainline_parent_id = revision_id
636
return BranchCheckResult(self)
638
def _get_checkout_format(self):
639
"""Return the most suitable metadir for a checkout of this branch.
640
Weaves are used if this branch's repostory uses weaves.
642
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
643
from bzrlib.repofmt import weaverepo
644
format = bzrdir.BzrDirMetaFormat1()
645
format.repository_format = weaverepo.RepositoryFormat7()
647
format = self.repository.bzrdir.cloning_metadir()
648
format.branch_format = self._format
651
def create_checkout(self, to_location, revision_id=None,
653
"""Create a checkout of a branch.
655
:param to_location: The url to produce the checkout at
656
:param revision_id: The revision to check out
657
:param lightweight: If True, produce a lightweight checkout, otherwise,
658
produce a bound branch (heavyweight checkout)
659
:return: The tree of the created checkout
661
t = transport.get_transport(to_location)
664
except errors.FileExists:
667
checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
668
BranchReferenceFormat().initialize(checkout, self)
670
format = self._get_checkout_format()
671
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
672
to_location, force_new_tree=False, format=format)
673
checkout = checkout_branch.bzrdir
674
checkout_branch.bind(self)
675
# pull up to the specified revision_id to set the initial
676
# branch tip correctly, and seed it with history.
677
checkout_branch.pull(self, stop_revision=revision_id)
678
return checkout.create_workingtree(revision_id)
541
681
class BranchFormat(object):
586
726
def get_format_description(self):
587
727
"""Return the short format description for this format."""
588
raise NotImplementedError(self.get_format_string)
728
raise NotImplementedError(self.get_format_description)
730
def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
732
"""Initialize a branch in a bzrdir, with specified files
734
:param a_bzrdir: The bzrdir to initialize the branch in
735
:param utf8_files: The files to create as a list of
736
(filename, content) tuples
737
:param set_format: If True, set the format with
738
self.get_format_string. (BzrBranch4 has its format set
740
:return: a branch in this format
742
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
743
branch_transport = a_bzrdir.get_branch_transport(self)
745
'metadir': ('lock', lockdir.LockDir),
746
'branch4': ('branch-lock', lockable_files.TransportLock),
748
lock_name, lock_class = lock_map[lock_type]
749
control_files = lockable_files.LockableFiles(branch_transport,
750
lock_name, lock_class)
751
control_files.create_lock()
752
control_files.lock_write()
754
control_files.put_utf8('format', self.get_format_string())
756
for file, content in utf8_files:
757
control_files.put_utf8(file, content)
759
control_files.unlock()
760
return self.open(a_bzrdir, _found=True)
590
762
def initialize(self, a_bzrdir):
591
763
"""Create a branch of this format in a_bzrdir."""
592
raise NotImplementedError(self.initialized)
764
raise NotImplementedError(self.initialize)
594
766
def is_supported(self):
595
767
"""Is this format supported?
625
797
return self.get_format_string().rstrip()
800
class BranchHooks(dict):
801
"""A dictionary mapping hook name to a list of callables for branch hooks.
803
e.g. ['set_rh'] Is the list of items to be called when the
804
set_revision_history function is invoked.
808
"""Create the default hooks.
810
These are all empty initially, because by default nothing should get
814
# Introduced in 0.15:
815
# invoked whenever the revision history has been set
816
# with set_revision_history. The api signature is
817
# (branch, revision_history), and the branch will
820
# invoked after a push operation completes.
821
# the api signature is
822
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
823
# where local is the local branch or None, master is the target
824
# master branch, and the rest should be self explanatory. The source
825
# is read locked and the target branches write locked. Source will
826
# be the local low-latency branch.
827
self['post_push'] = []
828
# invoked after a pull operation completes.
829
# the api signature is
830
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
831
# where local is the local branch or None, master is the target
832
# master branch, and the rest should be self explanatory. The source
833
# is read locked and the target branches write locked. The local
834
# branch is the low-latency branch.
835
self['post_pull'] = []
836
# invoked after a commit operation completes.
837
# the api signature is
838
# (local, master, old_revno, old_revid, new_revno, new_revid)
839
# old_revid is NULL_REVISION for the first commit to a branch.
840
self['post_commit'] = []
841
# invoked after a uncommit operation completes.
842
# the api signature is
843
# (local, master, old_revno, old_revid, new_revno, new_revid) where
844
# local is the local branch or None, master is the target branch,
845
# and an empty branch recieves new_revno of 0, new_revid of None.
846
self['post_uncommit'] = []
848
def install_hook(self, hook_name, a_callable):
849
"""Install a_callable in to the hook hook_name.
851
:param hook_name: A hook name. See the __init__ method of BranchHooks
852
for the complete list of hooks.
853
:param a_callable: The callable to be invoked when the hook triggers.
854
The exact signature will depend on the hook - see the __init__
855
method of BranchHooks for details on each hook.
858
self[hook_name].append(a_callable)
860
raise errors.UnknownHook('branch', hook_name)
863
# install the default hooks into the Branch class.
864
Branch.hooks = BranchHooks()
628
867
class BzrBranchFormat4(BranchFormat):
629
868
"""Bzr branch format 4.
740
959
return "Bazaar-NG Metadir branch format 5"
962
class BzrBranchFormat6(BzrBranchFormat5):
963
"""Branch format with last-revision
965
Unlike previous formats, this has no explicit revision history. Instead,
966
this just stores the last-revision, and the left-hand history leading
967
up to there is the history.
969
This format was introduced in bzr 0.15
972
def get_format_string(self):
973
"""See BranchFormat.get_format_string()."""
974
return "Bazaar-NG branch format 6\n"
976
def get_format_description(self):
977
"""See BranchFormat.get_format_description()."""
978
return "Branch format 6"
980
def initialize(self, a_bzrdir):
981
"""Create a branch of this format in a_bzrdir."""
982
utf8_files = [('last-revision', '0 null:\n'),
986
return self._initialize_helper(a_bzrdir, utf8_files)
988
def open(self, a_bzrdir, _found=False):
989
"""Return the branch object for a_bzrdir
991
_found is a private parameter, do not use it. It is used to indicate
992
if format probing has already be done.
995
format = BranchFormat.find_format(a_bzrdir)
996
assert format.__class__ == self.__class__
997
transport = a_bzrdir.get_branch_transport(None)
998
control_files = lockable_files.LockableFiles(transport, 'lock',
1000
return BzrBranch6(_format=self,
1001
_control_files=control_files,
1003
_repository=a_bzrdir.find_repository())
743
1006
class BranchReferenceFormat(BranchFormat):
744
1007
"""Bzr branch reference format.
996
1247
@needs_write_lock
997
1248
def append_revision(self, *revision_ids):
998
1249
"""See Branch.append_revision."""
1250
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
999
1251
for revision_id in revision_ids:
1252
_mod_revision.check_not_reserved_id(revision_id)
1000
1253
mutter("add {%s} to revision-history" % revision_id)
1001
1254
rev_history = self.revision_history()
1002
1255
rev_history.extend(revision_ids)
1003
1256
self.set_revision_history(rev_history)
1258
def _write_revision_history(self, history):
1259
"""Factored out of set_revision_history.
1261
This performs the actual writing to disk.
1262
It is intended to be called by BzrBranch5.set_revision_history."""
1263
self.control_files.put_bytes(
1264
'revision-history', '\n'.join(history))
1005
1266
@needs_write_lock
1006
1267
def set_revision_history(self, rev_history):
1007
1268
"""See Branch.set_revision_history."""
1008
self.control_files.put_utf8(
1009
'revision-history', '\n'.join(rev_history))
1269
rev_history = [osutils.safe_revision_id(r) for r in rev_history]
1270
self._write_revision_history(rev_history)
1010
1271
transaction = self.get_transaction()
1011
1272
history = transaction.map.find_revision_history()
1012
1273
if history is not None:
1020
1281
# this call is disabled because revision_history is
1021
1282
# not really an object yet, and the transaction is for objects.
1022
1283
# transaction.register_clean(history)
1024
def get_revision_delta(self, revno):
1025
"""Return the delta for one revision.
1027
The delta is relative to its mainline predecessor, or the
1028
empty tree for revision 1.
1030
assert isinstance(revno, int)
1031
rh = self.revision_history()
1032
if not (1 <= revno <= len(rh)):
1033
raise InvalidRevisionNumber(revno)
1035
# revno is 1-based; list is 0-based
1037
new_tree = self.repository.revision_tree(rh[revno-1])
1039
old_tree = EmptyTree()
1041
old_tree = self.repository.revision_tree(rh[revno-2])
1042
return compare_trees(old_tree, new_tree)
1284
for hook in Branch.hooks['set_rh']:
1285
hook(self, rev_history)
1288
def set_last_revision_info(self, revno, revision_id):
1289
revision_id = osutils.safe_revision_id(revision_id)
1290
history = self._lefthand_history(revision_id)
1291
assert len(history) == revno, '%d != %d' % (len(history), revno)
1292
self.set_revision_history(history)
1294
def _gen_revision_history(self):
1295
get_cached_utf8 = cache_utf8.get_cached_utf8
1296
history = [get_cached_utf8(l.rstrip('\r\n')) for l in
1297
self.control_files.get('revision-history').readlines()]
1044
1300
@needs_read_lock
1045
1301
def revision_history(self):
1047
1303
transaction = self.get_transaction()
1048
1304
history = transaction.map.find_revision_history()
1049
1305
if history is not None:
1050
mutter("cache hit for revision-history in %s", self)
1306
# mutter("cache hit for revision-history in %s", self)
1051
1307
return list(history)
1052
history = [l.rstrip('\r\n') for l in
1053
self.control_files.get_utf8('revision-history').readlines()]
1308
history = self._gen_revision_history()
1054
1309
transaction.map.add_revision_history(history)
1055
1310
# this call is disabled because revision_history is
1056
1311
# not really an object yet, and the transaction is for objects.
1057
1312
# transaction.register_clean(history, precious=True)
1058
1313
return list(history)
1315
def _lefthand_history(self, revision_id, last_rev=None,
1317
# stop_revision must be a descendant of last_revision
1318
stop_graph = self.repository.get_revision_graph(revision_id)
1319
if last_rev is not None and last_rev not in stop_graph:
1320
# our previous tip is not merged into stop_revision
1321
raise errors.DivergedBranches(self, other_branch)
1322
# make a new revision history from the graph
1323
current_rev_id = revision_id
1325
while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1326
new_history.append(current_rev_id)
1327
current_rev_id_parents = stop_graph[current_rev_id]
1329
current_rev_id = current_rev_id_parents[0]
1331
current_rev_id = None
1332
new_history.reverse()
1336
def generate_revision_history(self, revision_id, last_rev=None,
1338
"""Create a new revision history that will finish with revision_id.
1340
:param revision_id: the new tip to use.
1341
:param last_rev: The previous last_revision. If not None, then this
1342
must be a ancestory of revision_id, or DivergedBranches is raised.
1343
:param other_branch: The other branch that DivergedBranches should
1344
raise with respect to.
1346
revision_id = osutils.safe_revision_id(revision_id)
1347
self.set_revision_history(self._lefthand_history(revision_id,
1348
last_rev, other_branch))
1060
1350
@needs_write_lock
1061
1351
def update_revisions(self, other, stop_revision=None):
1062
1352
"""See Branch.update_revisions."""
1112
1389
return self.bzrdir.open_workingtree()
1114
1391
@needs_write_lock
1115
def pull(self, source, overwrite=False, stop_revision=None):
1116
"""See Branch.pull."""
1392
def pull(self, source, overwrite=False, stop_revision=None,
1393
_hook_master=None, _run_hooks=True):
1396
:param _hook_master: Private parameter - set the branch to
1397
be supplied as the master to push hooks.
1398
:param _run_hooks: Private parameter - allow disabling of
1399
hooks, used when pushing to a master branch.
1117
1401
source.lock_read()
1119
old_count = len(self.revision_history())
1403
old_count, old_tip = self.last_revision_info()
1121
self.update_revisions(source,stop_revision)
1405
self.update_revisions(source, stop_revision)
1122
1406
except DivergedBranches:
1123
1407
if not overwrite:
1126
1410
self.set_revision_history(source.revision_history())
1127
new_count = len(self.revision_history())
1411
new_count, new_tip = self.last_revision_info()
1418
for hook in Branch.hooks['post_pull']:
1419
hook(source, _hook_local, _hook_master, old_count, old_tip,
1128
1421
return new_count - old_count
1130
1423
source.unlock()
1132
def get_parent(self):
1133
"""See Branch.get_parent."""
1425
def _get_parent_location(self):
1135
1426
_locs = ['parent', 'pull', 'x-pull']
1136
1427
for l in _locs:
1138
return self.control_files.get_utf8(l).read().strip('\n')
1429
return self.control_files.get(l).read().strip('\n')
1139
1430
except NoSuchFile:
1435
def push(self, target, overwrite=False, stop_revision=None,
1436
_hook_master=None, _run_hooks=True):
1439
:param _hook_master: Private parameter - set the branch to
1440
be supplied as the master to push hooks.
1441
:param _run_hooks: Private parameter - allow disabling of
1442
hooks, used when pushing to a master branch.
1446
old_count, old_tip = target.last_revision_info()
1448
target.update_revisions(self, stop_revision)
1449
except DivergedBranches:
1453
target.set_revision_history(self.revision_history())
1454
new_count, new_tip = target.last_revision_info()
1457
_hook_local = target
1459
_hook_master = target
1461
for hook in Branch.hooks['post_push']:
1462
hook(self, _hook_local, _hook_master, old_count, old_tip,
1464
return new_count - old_count
1468
def get_parent(self):
1469
"""See Branch.get_parent."""
1471
assert self.base[-1] == '/'
1472
parent = self._get_parent_location()
1475
# This is an old-format absolute path to a local branch
1476
# turn it into a url
1477
if parent.startswith('/'):
1478
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1480
return urlutils.join(self.base[:-1], parent)
1481
except errors.InvalidURLJoin, e:
1482
raise errors.InaccessibleParent(parent, self.base)
1143
1484
def get_push_location(self):
1144
1485
"""See Branch.get_push_location."""
1145
config = bzrlib.config.BranchConfig(self)
1146
push_loc = config.get_user_option('push_location')
1486
push_loc = self.get_config().get_user_option('push_location')
1147
1487
return push_loc
1149
1489
def set_push_location(self, location):
1150
1490
"""See Branch.set_push_location."""
1151
config = bzrlib.config.LocationConfig(self.base)
1152
config.set_user_option('push_location', location)
1491
self.get_config().set_user_option(
1492
'push_location', location,
1493
store=_mod_config.STORE_LOCATION_NORECURSE)
1154
1495
@needs_write_lock
1155
1496
def set_parent(self, url):
1185
1542
_repository=_repository)
1187
1544
@needs_write_lock
1188
def pull(self, source, overwrite=False, stop_revision=None):
1189
"""Updates branch.pull to be bound branch aware."""
1545
def pull(self, source, overwrite=False, stop_revision=None,
1547
"""Extends branch.pull to be bound branch aware.
1549
:param _run_hooks: Private parameter used to force hook running
1550
off during bound branch double-pushing.
1190
1552
bound_location = self.get_bound_location()
1191
if source.base != bound_location:
1553
master_branch = None
1554
if bound_location and source.base != bound_location:
1192
1555
# not pulling from master, so we need to update master.
1193
1556
master_branch = self.get_master_branch()
1195
master_branch.pull(source)
1196
source = master_branch
1197
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1557
master_branch.lock_write()
1560
# pull from source into master.
1561
master_branch.pull(source, overwrite, stop_revision,
1563
return super(BzrBranch5, self).pull(source, overwrite,
1564
stop_revision, _hook_master=master_branch,
1565
_run_hooks=_run_hooks)
1568
master_branch.unlock()
1571
def push(self, target, overwrite=False, stop_revision=None):
1572
"""Updates branch.push to be bound branch aware."""
1573
bound_location = target.get_bound_location()
1574
master_branch = None
1575
if bound_location and target.base != bound_location:
1576
# not pushing to master, so we need to update master.
1577
master_branch = target.get_master_branch()
1578
master_branch.lock_write()
1581
# push into the master from this branch.
1582
super(BzrBranch5, self).push(master_branch, overwrite,
1583
stop_revision, _run_hooks=False)
1584
# and push into the target branch from this. Note that we push from
1585
# this branch again, because its considered the highest bandwidth
1587
return super(BzrBranch5, self).push(target, overwrite,
1588
stop_revision, _hook_master=master_branch)
1591
master_branch.unlock()
1199
1593
def get_bound_location(self):
1251
1650
# but binding itself may not be.
1252
1651
# Since we *have* to check at commit time, we don't
1253
1652
# *need* to check here
1256
# we are now equal to or a suffix of other.
1258
# Since we have 'pulled' from the remote location,
1259
# now we should try to pull in the opposite direction
1260
# in case the local tree has more revisions than the
1262
# There may be a different check you could do here
1263
# rather than actually trying to install revisions remotely.
1264
# TODO: capture an exception which indicates the remote branch
1266
# If it is up-to-date, this probably should not be a failure
1268
# lock other for write so the revision-history syncing cannot race
1272
# if this does not error, other now has the same last rev we do
1273
# it can only error if the pull from other was concurrent with
1274
# a commit to other from someone else.
1276
# until we ditch revision-history, we need to sync them up:
1277
self.set_revision_history(other.revision_history())
1278
# now other and self are up to date with each other and have the
1279
# same revision-history.
1654
# we want to raise diverged if:
1655
# last_rev is not in the other_last_rev history, AND
1656
# other_last_rev is not in our history, and do it without pulling
1658
last_rev = self.last_revision()
1659
if last_rev is not None:
1662
other_last_rev = other.last_revision()
1663
if other_last_rev is not None:
1664
# neither branch is new, we have to do some work to
1665
# ascertain diversion.
1666
remote_graph = other.repository.get_revision_graph(
1668
local_graph = self.repository.get_revision_graph(last_rev)
1669
if (last_rev not in remote_graph and
1670
other_last_rev not in local_graph):
1671
raise errors.DivergedBranches(self, other)
1283
1674
self.set_bound_location(other.base)
1285
1676
@needs_write_lock
1698
class BzrBranch6(BzrBranch5):
1701
def last_revision_info(self):
1702
revision_string = self.control_files.get('last-revision').read()
1703
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1704
revision_id = cache_utf8.get_cached_utf8(revision_id)
1706
return revno, revision_id
1708
def last_revision(self):
1709
"""Return last revision id, or None"""
1710
revision_id = self.last_revision_info()[1]
1711
if revision_id == _mod_revision.NULL_REVISION:
1715
def _write_last_revision_info(self, revno, revision_id):
1716
"""Simply write out the revision id, with no checks.
1718
Use set_last_revision_info to perform this safely.
1720
Does not update the revision_history cache.
1721
Intended to be called by set_last_revision_info and
1722
_write_revision_history.
1724
if revision_id is None:
1725
revision_id = 'null:'
1726
out_string = '%d %s\n' % (revno, revision_id)
1727
self.control_files.put_bytes('last-revision', out_string)
1730
def set_last_revision_info(self, revno, revision_id):
1731
revision_id = osutils.safe_revision_id(revision_id)
1732
if self._get_append_revisions_only():
1733
self._check_history_violation(revision_id)
1734
self._write_last_revision_info(revno, revision_id)
1735
transaction = self.get_transaction()
1736
cached_history = transaction.map.find_revision_history()
1737
if cached_history is not None:
1738
transaction.map.remove_object(cached_history)
1740
def _check_history_violation(self, revision_id):
1741
last_revision = self.last_revision()
1742
if last_revision is None:
1744
if last_revision not in self._lefthand_history(revision_id):
1745
raise errors.AppendRevisionsOnlyViolation(self.base)
1747
def _gen_revision_history(self):
1748
"""Generate the revision history from last revision
1750
history = list(self.repository.iter_reverse_revision_history(
1751
self.last_revision()))
1755
def _write_revision_history(self, history):
1756
"""Factored out of set_revision_history.
1758
This performs the actual writing to disk, with format-specific checks.
1759
It is intended to be called by BzrBranch5.set_revision_history.
1761
if len(history) == 0:
1762
last_revision = 'null:'
1764
if history != self._lefthand_history(history[-1]):
1765
raise errors.NotLefthandHistory(history)
1766
last_revision = history[-1]
1767
if self._get_append_revisions_only():
1768
self._check_history_violation(last_revision)
1769
self._write_last_revision_info(len(history), last_revision)
1772
def append_revision(self, *revision_ids):
1773
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1774
if len(revision_ids) == 0:
1776
prev_revno, prev_revision = self.last_revision_info()
1777
for revision in self.repository.get_revisions(revision_ids):
1778
if prev_revision == _mod_revision.NULL_REVISION:
1779
if revision.parent_ids != []:
1780
raise errors.NotLeftParentDescendant(self, prev_revision,
1781
revision.revision_id)
1783
if revision.parent_ids[0] != prev_revision:
1784
raise errors.NotLeftParentDescendant(self, prev_revision,
1785
revision.revision_id)
1786
prev_revision = revision.revision_id
1787
self.set_last_revision_info(prev_revno + len(revision_ids),
1790
def _set_config_location(self, name, url, config=None,
1791
make_relative=False):
1793
config = self.get_config()
1797
url = urlutils.relative_url(self.base, url)
1798
config.set_user_option(name, url)
1801
def _get_config_location(self, name, config=None):
1803
config = self.get_config()
1804
location = config.get_user_option(name)
1810
def _set_parent_location(self, url):
1811
"""Set the parent branch"""
1812
self._set_config_location('parent_location', url, make_relative=True)
1815
def _get_parent_location(self):
1816
"""Set the parent branch"""
1817
return self._get_config_location('parent_location')
1819
def set_push_location(self, location):
1820
"""See Branch.set_push_location."""
1821
self._set_config_location('push_location', location)
1823
def set_bound_location(self, location):
1824
"""See Branch.set_push_location."""
1826
config = self.get_config()
1827
if location is None:
1828
if config.get_user_option('bound') != 'True':
1831
config.set_user_option('bound', 'False')
1834
self._set_config_location('bound_location', location,
1836
config.set_user_option('bound', 'True')
1839
def _get_bound_location(self, bound):
1840
"""Return the bound location in the config file.
1842
Return None if the bound parameter does not match"""
1843
config = self.get_config()
1844
config_bound = (config.get_user_option('bound') == 'True')
1845
if config_bound != bound:
1847
return self._get_config_location('bound_location', config=config)
1849
def get_bound_location(self):
1850
"""See Branch.set_push_location."""
1851
return self._get_bound_location(True)
1853
def get_old_bound_location(self):
1854
"""See Branch.get_old_bound_location"""
1855
return self._get_bound_location(False)
1857
def set_append_revisions_only(self, enabled):
1862
self.get_config().set_user_option('append_revisions_only', value)
1864
def _get_append_revisions_only(self):
1865
value = self.get_config().get_user_option('append_revisions_only')
1866
return value == 'True'
1868
def _synchronize_history(self, destination, revision_id):
1869
"""Synchronize last revision and revision history between branches.
1871
This version is most efficient when the destination is also a
1872
BzrBranch6, but works for BzrBranch5, as long as the destination's
1873
repository contains all the lefthand ancestors of the intended
1874
last_revision. If not, set_last_revision_info will fail.
1876
:param destination: The branch to copy the history into
1877
:param revision_id: The revision-id to truncate history at. May
1878
be None to copy complete history.
1880
if revision_id is None:
1881
revno, revision_id = self.last_revision_info()
1883
revno = self.revision_id_to_revno(revision_id)
1884
destination.set_last_revision_info(revno, revision_id)
1307
1887
class BranchTestProviderAdapter(object):
1308
1888
"""A tool to generate a suite testing multiple branch formats at once.
1917
class BranchCheckResult(object):
1918
"""Results of checking branch consistency.
1923
def __init__(self, branch):
1924
self.branch = branch
1926
def report_results(self, verbose):
1927
"""Report the check results via trace.note.
1929
:param verbose: Requests more detailed display of what was checked,
1932
note('checked branch %s format %s',
1934
self.branch._format)
1337
1937
######################################################################
1341
1941
@deprecated_function(zero_eight)
1342
def ScratchBranch(*args, **kwargs):
1343
"""See bzrlib.bzrdir.ScratchDir."""
1344
d = ScratchDir(*args, **kwargs)
1345
return d.open_branch()
1348
@deprecated_function(zero_eight)
1349
1942
def is_control_file(*args, **kwargs):
1350
1943
"""See bzrlib.workingtree.is_control_file."""
1351
return bzrlib.workingtree.is_control_file(*args, **kwargs)
1944
from bzrlib import workingtree
1945
return workingtree.is_control_file(*args, **kwargs)
1948
class Converter5to6(object):
1949
"""Perform an in-place upgrade of format 5 to format 6"""
1951
def convert(self, branch):
1952
# Data for 5 and 6 can peacefully coexist.
1953
format = BzrBranchFormat6()
1954
new_branch = format.open(branch.bzrdir, _found=True)
1956
# Copy source data into target
1957
new_branch.set_last_revision_info(*branch.last_revision_info())
1958
new_branch.set_parent(branch.get_parent())
1959
new_branch.set_bound_location(branch.get_bound_location())
1960
new_branch.set_push_location(branch.get_push_location())
1962
# Copying done; now update target format
1963
new_branch.control_files.put_utf8('format',
1964
format.get_format_string())
1966
# Clean up old files
1967
new_branch.control_files._transport.delete('revision-history')
1969
branch.set_parent(None)
1972
branch.set_bound_location(None)