18
18
from copy import deepcopy
19
19
from cStringIO import StringIO
20
24
from unittest import TestSuite
21
25
from warnings import warn
28
import bzrlib.bzrdir as bzrdir
35
29
from bzrlib.config import TreeConfig
36
30
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
from bzrlib.delta import compare_trees
37
32
import bzrlib.errors as errors
38
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
39
HistoryMissing, InvalidRevisionId,
40
InvalidRevisionNumber, LockError, NoSuchFile,
41
NoSuchRevision, NoWorkingTree, NotVersionedError,
42
NotBranchError, UninitializableFormat,
43
UnlistableStore, UnlistableBranch,
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
45
42
from bzrlib.lockable_files import LockableFiles, TransportLock
46
from bzrlib.symbol_versioning import (deprecated_function,
50
zero_eight, zero_nine,
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
52
51
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
55
68
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
106
119
def open(base, _unsupported=False):
107
"""Open the branch rooted at base.
120
"""Open the repository rooted at base.
109
For instance, if the branch is at URL/.bzr/branch,
110
Branch.open(URL) -> a Branch instance.
122
For instance, if the repository is at URL/.bzr/repository,
123
Repository.open(URL) -> a Repository instance.
112
125
control = bzrdir.BzrDir.open(base, _unsupported)
113
126
return control.open_branch(_unsupported)
145
158
warn('%s is deprecated' % self.setup_caching)
146
159
self.cache_root = cache_root
148
def get_config(self):
149
return bzrlib.config.BranchConfig(self)
151
161
def _get_nick(self):
152
return self.get_config().get_nickname()
162
cfg = self.tree_config()
163
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
154
165
def _set_nick(self, nick):
155
self.get_config().set_user_option('nickname', nick)
166
cfg = self.tree_config()
167
cfg.set_option(nick, "nickname")
168
assert cfg.get_option("nickname") == nick
157
170
nick = property(_get_nick, _set_nick)
240
def get_commit_builder(self, parents, config=None, timestamp=None,
241
timezone=None, committer=None, revprops=None,
243
"""Obtain a CommitBuilder for this branch.
245
:param parents: Revision ids of the parents of the new revision.
246
:param config: Optional configuration to use.
247
:param timestamp: Optional timestamp recorded for commit.
248
:param timezone: Optional timezone for timestamp.
249
:param committer: Optional committer to set for commit.
250
:param revprops: Optional dictionary of revision properties.
251
:param revision_id: Optional revision id.
255
config = self.get_config()
257
return self.repository.get_commit_builder(self, parents, config,
258
timestamp, timezone, committer, revprops, revision_id)
260
253
def get_master_branch(self):
261
254
"""Return the branch we are bound to.
267
def get_revision_delta(self, revno):
268
"""Return the delta for one revision.
270
The delta is relative to its mainline predecessor, or the
271
empty tree for revision 1.
273
assert isinstance(revno, int)
274
rh = self.revision_history()
275
if not (1 <= revno <= len(rh)):
276
raise InvalidRevisionNumber(revno)
277
return self.repository.get_revision_delta(rh[revno-1])
279
260
def get_root_id(self):
280
261
"""Return the id of this branches root"""
281
262
raise NotImplementedError('get_root_id is abstract')
320
301
If self and other have not diverged, return a list of the revisions
321
302
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.
323
330
self_history = self.revision_history()
324
331
self_len = len(self_history)
335
342
assert isinstance(stop_revision, int)
336
343
if stop_revision > other_len:
337
raise errors.NoSuchRevision(self, stop_revision)
344
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
338
345
return other_history[self_len:stop_revision]
340
347
def update_revisions(self, other, stop_revision=None):
406
416
raise NotImplementedError('get_parent is abstract')
408
def get_submit_branch(self):
409
"""Return the submit location of the branch.
411
This is the default location for bundle. The usual
412
pattern is that the user can override it by specifying a
415
return self.get_config().get_user_option('submit_branch')
417
def set_submit_branch(self, location):
418
"""Return the submit location of the branch.
420
This is the default location for bundle. The usual
421
pattern is that the user can override it by specifying a
424
self.get_config().set_user_option('submit_branch', location)
426
418
def get_push_location(self):
427
419
"""Return the None or the location to push this branch to."""
428
420
raise NotImplementedError('get_push_location is abstract')
465
457
revision_id: if not None, the revision history in the new branch will
466
458
be truncated to end with revision_id.
468
# for API compatibility, until 0.8 releases we provide the old api:
460
# for API compatability, until 0.8 releases we provide the old api:
469
461
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
470
462
# after 0.8 releases, the *args and **kwargs should be changed:
471
463
# def clone(self, to_bzrdir, revision_id=None):
473
465
kwargs.get('revision', None) or
474
466
kwargs.get('basis_branch', None) or
475
467
(len(args) and isinstance(args[0], basestring))):
476
# backwards compatibility api:
468
# backwards compatability api:
477
469
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
478
470
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
479
471
# get basis_branch
541
533
rev = self.repository.get_revision(revision_id)
542
534
new_history = rev.get_history(self.repository)[1:]
543
535
destination.set_revision_history(new_history)
545
parent = self.get_parent()
546
except errors.InaccessibleParent, e:
547
mutter('parent was not accessible to copy: %s', e)
550
destination.set_parent(parent)
554
"""Check consistency of the branch.
556
In particular this checks that revisions given in the revision-history
557
do actually match up in the revision graph, and that they're all
558
present in the repository.
560
Callers will typically also want to check the repository.
562
:return: A BranchCheckResult.
564
mainline_parent_id = None
565
for revision_id in self.revision_history():
567
revision = self.repository.get_revision(revision_id)
568
except errors.NoSuchRevision, e:
569
raise errors.BzrCheckError("mainline revision {%s} not in repository"
571
# In general the first entry on the revision history has no parents.
572
# But it's not illegal for it to have parents listed; this can happen
573
# in imports from Arch when the parents weren't reachable.
574
if mainline_parent_id is not None:
575
if mainline_parent_id not in revision.parent_ids:
576
raise errors.BzrCheckError("previous revision {%s} not listed among "
578
% (mainline_parent_id, revision_id))
579
mainline_parent_id = revision_id
580
return BranchCheckResult(self)
582
def create_checkout(self, to_location, revision_id=None,
584
"""Create a checkout of a branch.
586
:param to_location: The url to produce the checkout at
587
:param revision_id: The revision to check out
588
:param lightweight: If True, produce a lightweight checkout, otherwise,
589
produce a bound branch (heavyweight checkout)
590
:return: The tree of the created checkout
593
t = transport.get_transport(to_location)
596
except errors.FileExists:
598
checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
599
BranchReferenceFormat().initialize(checkout, self)
601
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
602
to_location, force_new_tree=False)
603
checkout = checkout_branch.bzrdir
604
checkout_branch.bind(self)
605
if revision_id is not None:
606
rh = checkout_branch.revision_history()
607
new_rh = rh[:rh.index(revision_id) + 1]
608
checkout_branch.set_revision_history(new_rh)
609
return checkout.create_workingtree(revision_id)
536
parent = self.get_parent()
538
destination.set_parent(parent)
612
541
class BranchFormat(object):
776
705
utf8_files = [('revision-history', ''),
777
706
('branch-name', ''),
779
control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
708
control_files = LockableFiles(branch_transport, 'lock', LockDir)
780
709
control_files.create_lock()
781
710
control_files.lock_write()
782
711
control_files.put_utf8('format', self.get_format_string())
801
730
format = BranchFormat.find_format(a_bzrdir)
802
731
assert format.__class__ == self.__class__
803
732
transport = a_bzrdir.get_branch_transport(None)
804
control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
733
control_files = LockableFiles(transport, 'lock', LockDir)
805
734
return BzrBranch5(_format=self,
806
735
_control_files=control_files,
807
736
a_bzrdir=a_bzrdir,
944
873
if (not relax_version_check
945
874
and not self._format.is_supported()):
946
raise errors.UnsupportedFormatError(format=fmt)
875
raise errors.UnsupportedFormatError(
876
'sorry, branch format %r not supported' % fmt,
877
['use a different bzr version',
878
'or remove the .bzr directory'
879
' and "bzr init" again'])
947
880
if deprecated_passed(transport):
948
881
warn("BzrBranch.__init__(transport=XXX...): The transport "
949
882
"parameter is deprecated as of bzr 0.8. "
1030
963
return self.control_files.is_locked()
1032
965
def lock_write(self):
966
# TODO: test for failed two phase locks. This is known broken.
967
self.control_files.lock_write()
1033
968
self.repository.lock_write()
1035
self.control_files.lock_write()
1037
self.repository.unlock()
1040
970
def lock_read(self):
971
# TODO: test for failed two phase locks. This is known broken.
972
self.control_files.lock_read()
1041
973
self.repository.lock_read()
1043
self.control_files.lock_read()
1045
self.repository.unlock()
1048
975
def unlock(self):
1049
976
# TODO: test for failed two phase locks. This is known broken.
978
self.repository.unlock()
1051
980
self.control_files.unlock()
1053
self.repository.unlock()
1055
982
def peek_lock_mode(self):
1056
983
if self.control_files._lock_count == 0:
1094
1021
# not really an object yet, and the transaction is for objects.
1095
1022
# 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)
1097
1044
@needs_read_lock
1098
1045
def revision_history(self):
1099
1046
"""See Branch.revision_history."""
1111
1058
return list(history)
1113
1060
@needs_write_lock
1114
def generate_revision_history(self, revision_id, last_rev=None,
1116
"""Create a new revision history that will finish with revision_id.
1118
:param revision_id: the new tip to use.
1119
:param last_rev: The previous last_revision. If not None, then this
1120
must be a ancestory of revision_id, or DivergedBranches is raised.
1121
:param other_branch: The other branch that DivergedBranches should
1122
raise with respect to.
1124
# stop_revision must be a descendant of last_revision
1125
stop_graph = self.repository.get_revision_graph(revision_id)
1126
if last_rev is not None and last_rev not in stop_graph:
1127
# our previous tip is not merged into stop_revision
1128
raise errors.DivergedBranches(self, other_branch)
1129
# make a new revision history from the graph
1130
current_rev_id = revision_id
1132
while current_rev_id not in (None, revision.NULL_REVISION):
1133
new_history.append(current_rev_id)
1134
current_rev_id_parents = stop_graph[current_rev_id]
1136
current_rev_id = current_rev_id_parents[0]
1138
current_rev_id = None
1139
new_history.reverse()
1140
self.set_revision_history(new_history)
1143
1061
def update_revisions(self, other, stop_revision=None):
1144
1062
"""See Branch.update_revisions."""
1145
1063
other.lock_read()
1159
1077
if stop_revision in my_ancestry:
1160
1078
# last_revision is a descendant of stop_revision
1162
self.generate_revision_history(stop_revision, last_rev=last_rev,
1080
# stop_revision must be a descendant of last_revision
1081
stop_graph = self.repository.get_revision_graph(stop_revision)
1082
if last_rev is not None and last_rev not in stop_graph:
1083
# our previous tip is not merged into stop_revision
1084
raise errors.DivergedBranches(self, other)
1085
# make a new revision history from the graph
1086
current_rev_id = stop_revision
1088
while current_rev_id not in (None, NULL_REVISION):
1089
new_history.append(current_rev_id)
1090
current_rev_id_parents = stop_graph[current_rev_id]
1092
current_rev_id = current_rev_id_parents[0]
1094
current_rev_id = None
1095
new_history.reverse()
1096
self.set_revision_history(new_history)
1199
1132
def get_parent(self):
1200
1133
"""See Branch.get_parent."""
1202
1135
_locs = ['parent', 'pull', 'x-pull']
1203
assert self.base[-1] == '/'
1204
1136
for l in _locs:
1206
parent = self.control_files.get(l).read().strip('\n')
1138
return self.control_files.get_utf8(l).read().strip('\n')
1207
1139
except NoSuchFile:
1209
# This is an old-format absolute path to a local branch
1210
# turn it into a url
1211
if parent.startswith('/'):
1212
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1214
return urlutils.join(self.base[:-1], parent)
1215
except errors.InvalidURLJoin, e:
1216
raise errors.InaccessibleParent(parent, self.base)
1219
1143
def get_push_location(self):
1220
1144
"""See Branch.get_push_location."""
1221
push_loc = self.get_config().get_user_option('push_location')
1145
config = bzrlib.config.BranchConfig(self)
1146
push_loc = config.get_user_option('push_location')
1222
1147
return push_loc
1224
1149
def set_push_location(self, location):
1225
1150
"""See Branch.set_push_location."""
1226
self.get_config().set_user_option('push_location', location,
1151
config = bzrlib.config.LocationConfig(self.base)
1152
config.set_user_option('push_location', location)
1229
1154
@needs_write_lock
1230
1155
def set_parent(self, url):
1237
1162
if url is None:
1238
1163
self.control_files._transport.delete('parent')
1240
if isinstance(url, unicode):
1242
url = url.encode('ascii')
1243
except UnicodeEncodeError:
1244
raise bzrlib.errors.InvalidURL(url,
1245
"Urls must be 7-bit ascii, "
1246
"use bzrlib.urlutils.escape")
1248
url = urlutils.relative_url(self.base, url)
1249
self.control_files.put('parent', url + '\n')
1165
self.control_files.put_utf8('parent', url + '\n')
1251
@deprecated_function(zero_nine)
1252
1167
def tree_config(self):
1253
"""DEPRECATED; call get_config instead.
1254
TreeConfig has become part of BranchConfig."""
1255
1168
return TreeConfig(self)
1424
class BranchCheckResult(object):
1425
"""Results of checking branch consistency.
1430
def __init__(self, branch):
1431
self.branch = branch
1433
def report_results(self, verbose):
1434
"""Report the check results via trace.note.
1436
:param verbose: Requests more detailed display of what was checked,
1439
note('checked branch %s format %s',
1441
self.branch._format)
1444
1337
######################################################################
1448
1341
@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)
1449
1349
def is_control_file(*args, **kwargs):
1450
1350
"""See bzrlib.workingtree.is_control_file."""
1451
1351
return bzrlib.workingtree.is_control_file(*args, **kwargs)