18
18
from copy import deepcopy
19
19
from cStringIO import StringIO
24
20
from unittest import TestSuite
25
21
from warnings import warn
28
import bzrlib.bzrdir as bzrdir
29
36
from bzrlib.config import TreeConfig
30
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
from bzrlib.delta import compare_trees
32
38
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
39
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
40
HistoryMissing, InvalidRevisionId,
41
InvalidRevisionNumber, LockError, NoSuchFile,
42
NoSuchRevision, NoWorkingTree, NotVersionedError,
43
NotBranchError, UninitializableFormat,
44
UnlistableStore, UnlistableBranch,
42
46
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
47
from bzrlib.symbol_versioning import (deprecated_function,
51
zero_eight, zero_nine,
51
53
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
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
119
107
def open(base, _unsupported=False):
120
"""Open the repository rooted at base.
108
"""Open the branch rooted at base.
122
For instance, if the repository is at URL/.bzr/repository,
123
Repository.open(URL) -> a Repository instance.
110
For instance, if the branch is at URL/.bzr/branch,
111
Branch.open(URL) -> a Branch instance.
125
113
control = bzrdir.BzrDir.open(base, _unsupported)
126
114
return control.open_branch(_unsupported)
151
139
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
141
@deprecated_function(zero_eight)
153
142
def setup_caching(self, cache_root):
154
143
"""Subclasses that care about caching should override this, and set
155
144
up cached stores located under cache_root.
146
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
150
def get_config(self):
151
return bzrlib.config.BranchConfig(self)
161
153
def _get_nick(self):
162
cfg = self.tree_config()
163
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
154
return self.get_config().get_nickname()
165
156
def _set_nick(self, nick):
166
cfg = self.tree_config()
167
cfg.set_option(nick, "nickname")
168
assert cfg.get_option("nickname") == nick
157
self.get_config().set_user_option('nickname', nick)
170
159
nick = property(_get_nick, _set_nick)
172
161
def is_locked(self):
173
raise NotImplementedError('is_locked is abstract')
162
raise NotImplementedError(self.is_locked)
175
164
def lock_write(self):
176
raise NotImplementedError('lock_write is abstract')
165
raise NotImplementedError(self.lock_write)
178
167
def lock_read(self):
179
raise NotImplementedError('lock_read is abstract')
168
raise NotImplementedError(self.lock_read)
181
170
def unlock(self):
182
raise NotImplementedError('unlock is abstract')
171
raise NotImplementedError(self.unlock)
184
173
def peek_lock_mode(self):
185
174
"""Return lock mode for the Branch: 'r', 'w' or None"""
186
175
raise NotImplementedError(self.peek_lock_mode)
188
177
def get_physical_lock_status(self):
189
raise NotImplementedError('get_physical_lock_status is abstract')
178
raise NotImplementedError(self.get_physical_lock_status)
191
180
def abspath(self, name):
192
181
"""Return absolute filename for something in the branch
242
def get_commit_builder(self, parents, config=None, timestamp=None,
243
timezone=None, committer=None, revprops=None,
245
"""Obtain a CommitBuilder for this branch.
247
:param parents: Revision ids of the parents of the new revision.
248
:param config: Optional configuration to use.
249
:param timestamp: Optional timestamp recorded for commit.
250
:param timezone: Optional timezone for timestamp.
251
:param committer: Optional committer to set for commit.
252
:param revprops: Optional dictionary of revision properties.
253
:param revision_id: Optional revision id.
257
config = self.get_config()
259
return self.repository.get_commit_builder(self, parents, config,
260
timestamp, timezone, committer, revprops, revision_id)
253
262
def get_master_branch(self):
254
263
"""Return the branch we are bound to.
269
def get_revision_delta(self, revno):
270
"""Return the delta for one revision.
272
The delta is relative to its mainline predecessor, or the
273
empty tree for revision 1.
275
assert isinstance(revno, int)
276
rh = self.revision_history()
277
if not (1 <= revno <= len(rh)):
278
raise InvalidRevisionNumber(revno)
279
return self.repository.get_revision_delta(rh[revno-1])
260
281
def get_root_id(self):
261
282
"""Return the id of this branches root"""
262
raise NotImplementedError('get_root_id is abstract')
283
raise NotImplementedError(self.get_root_id)
264
285
def print_file(self, file, revision_id):
265
286
"""Print `file` to stdout."""
266
raise NotImplementedError('print_file is abstract')
287
raise NotImplementedError(self.print_file)
268
289
def append_revision(self, *revision_ids):
269
raise NotImplementedError('append_revision is abstract')
290
raise NotImplementedError(self.append_revision)
271
292
def set_revision_history(self, rev_history):
272
raise NotImplementedError('set_revision_history is abstract')
293
raise NotImplementedError(self.set_revision_history)
274
295
def revision_history(self):
275
296
"""Return sequence of revision hashes on to this branch."""
301
322
If self and other have not diverged, return a list of the revisions
302
323
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
325
self_history = self.revision_history()
331
326
self_len = len(self_history)
342
337
assert isinstance(stop_revision, int)
343
338
if stop_revision > other_len:
344
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
339
raise errors.NoSuchRevision(self, stop_revision)
345
340
return other_history[self_len:stop_revision]
347
342
def update_revisions(self, other, stop_revision=None):
370
365
if history is None:
371
366
history = self.revision_history()
372
elif revno <= 0 or revno > len(history):
367
if revno <= 0 or revno > len(history):
373
368
raise bzrlib.errors.NoSuchRevision(self, revno)
374
369
return history[revno - 1]
376
371
def pull(self, source, overwrite=False, stop_revision=None):
377
raise NotImplementedError('pull is abstract')
372
raise NotImplementedError(self.pull)
379
374
def basis_tree(self):
380
"""Return `Tree` object for last revision.
382
If there are no revisions yet, return an `EmptyTree`.
375
"""Return `Tree` object for last revision."""
384
376
return self.repository.revision_tree(self.last_revision())
386
378
def rename_one(self, from_rel, to_rel):
413
405
pattern is that the user can override it by specifying a
416
raise NotImplementedError('get_parent is abstract')
408
raise NotImplementedError(self.get_parent)
410
def get_submit_branch(self):
411
"""Return the submit location of the branch.
413
This is the default location for bundle. The usual
414
pattern is that the user can override it by specifying a
417
return self.get_config().get_user_option('submit_branch')
419
def set_submit_branch(self, location):
420
"""Return the submit location of the branch.
422
This is the default location for bundle. The usual
423
pattern is that the user can override it by specifying a
426
self.get_config().set_user_option('submit_branch', location)
418
428
def get_push_location(self):
419
429
"""Return the None or the location to push this branch to."""
420
raise NotImplementedError('get_push_location is abstract')
430
raise NotImplementedError(self.get_push_location)
422
432
def set_push_location(self, location):
423
433
"""Set a new push location for this branch."""
424
raise NotImplementedError('set_push_location is abstract')
434
raise NotImplementedError(self.set_push_location)
426
436
def set_parent(self, url):
427
raise NotImplementedError('set_parent is abstract')
437
raise NotImplementedError(self.set_parent)
429
439
@needs_write_lock
430
440
def update(self):
457
467
revision_id: if not None, the revision history in the new branch will
458
468
be truncated to end with revision_id.
460
# for API compatability, until 0.8 releases we provide the old api:
470
# for API compatibility, until 0.8 releases we provide the old api:
461
471
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
462
472
# after 0.8 releases, the *args and **kwargs should be changed:
463
473
# def clone(self, to_bzrdir, revision_id=None):
465
475
kwargs.get('revision', None) or
466
476
kwargs.get('basis_branch', None) or
467
477
(len(args) and isinstance(args[0], basestring))):
468
# backwards compatability api:
478
# backwards compatibility api:
469
479
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
470
480
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
471
481
# get basis_branch
533
543
rev = self.repository.get_revision(revision_id)
534
544
new_history = rev.get_history(self.repository)[1:]
535
545
destination.set_revision_history(new_history)
536
parent = self.get_parent()
538
destination.set_parent(parent)
547
parent = self.get_parent()
548
except errors.InaccessibleParent, e:
549
mutter('parent was not accessible to copy: %s', e)
552
destination.set_parent(parent)
556
"""Check consistency of the branch.
558
In particular this checks that revisions given in the revision-history
559
do actually match up in the revision graph, and that they're all
560
present in the repository.
562
Callers will typically also want to check the repository.
564
:return: A BranchCheckResult.
566
mainline_parent_id = None
567
for revision_id in self.revision_history():
569
revision = self.repository.get_revision(revision_id)
570
except errors.NoSuchRevision, e:
571
raise errors.BzrCheckError("mainline revision {%s} not in repository"
573
# In general the first entry on the revision history has no parents.
574
# But it's not illegal for it to have parents listed; this can happen
575
# in imports from Arch when the parents weren't reachable.
576
if mainline_parent_id is not None:
577
if mainline_parent_id not in revision.parent_ids:
578
raise errors.BzrCheckError("previous revision {%s} not listed among "
580
% (mainline_parent_id, revision_id))
581
mainline_parent_id = revision_id
582
return BranchCheckResult(self)
584
def create_checkout(self, to_location, revision_id=None,
586
"""Create a checkout of a branch.
588
:param to_location: The url to produce the checkout at
589
:param revision_id: The revision to check out
590
:param lightweight: If True, produce a lightweight checkout, otherwise,
591
produce a bound branch (heavyweight checkout)
592
:return: The tree of the created checkout
595
t = transport.get_transport(to_location)
598
except errors.FileExists:
600
checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
601
BranchReferenceFormat().initialize(checkout, self)
603
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
604
to_location, force_new_tree=False)
605
checkout = checkout_branch.bzrdir
606
checkout_branch.bind(self)
607
if revision_id is not None:
608
rh = checkout_branch.revision_history()
609
new_rh = rh[:rh.index(revision_id) + 1]
610
checkout_branch.set_revision_history(new_rh)
611
return checkout.create_workingtree(revision_id)
541
614
class BranchFormat(object):
705
778
utf8_files = [('revision-history', ''),
706
779
('branch-name', ''),
708
control_files = LockableFiles(branch_transport, 'lock', LockDir)
781
control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
709
782
control_files.create_lock()
710
783
control_files.lock_write()
711
784
control_files.put_utf8('format', self.get_format_string())
730
803
format = BranchFormat.find_format(a_bzrdir)
731
804
assert format.__class__ == self.__class__
732
805
transport = a_bzrdir.get_branch_transport(None)
733
control_files = LockableFiles(transport, 'lock', LockDir)
806
control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
734
807
return BzrBranch5(_format=self,
735
808
_control_files=control_files,
736
809
a_bzrdir=a_bzrdir,
767
840
raise errors.UninitializableFormat(self)
768
841
mutter('creating branch reference in %s', a_bzrdir.transport.base)
769
842
branch_transport = a_bzrdir.get_branch_transport(self)
770
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
771
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
772
branch_transport.put('format', StringIO(self.get_format_string()))
843
branch_transport.put_bytes('location',
844
target_branch.bzrdir.root_transport.base)
845
branch_transport.put_bytes('format', self.get_format_string())
773
846
return self.open(a_bzrdir, _found=True)
775
848
def __init__(self):
873
946
if (not relax_version_check
874
947
and not self._format.is_supported()):
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'])
948
raise errors.UnsupportedFormatError(format=fmt)
880
949
if deprecated_passed(transport):
881
950
warn("BzrBranch.__init__(transport=XXX...): The transport "
882
951
"parameter is deprecated as of bzr 0.8. "
891
960
__repr__ = __str__
894
# TODO: It might be best to do this somewhere else,
895
# but it is nice for a Branch object to automatically
896
# cache it's information.
897
# Alternatively, we could have the Transport objects cache requests
898
# See the earlier discussion about how major objects (like Branch)
899
# should never expect their __del__ function to run.
900
# XXX: cache_root seems to be unused, 2006-01-13 mbp
901
if hasattr(self, 'cache_root') and self.cache_root is not None:
903
rmtree(self.cache_root)
906
self.cache_root = None
908
962
def _get_base(self):
909
963
return self._base
963
1017
return self.control_files.is_locked()
965
1019
def lock_write(self):
966
# TODO: test for failed two phase locks. This is known broken.
967
self.control_files.lock_write()
968
1020
self.repository.lock_write()
1022
self.control_files.lock_write()
1024
self.repository.unlock()
970
1027
def lock_read(self):
971
# TODO: test for failed two phase locks. This is known broken.
972
self.control_files.lock_read()
973
1028
self.repository.lock_read()
1030
self.control_files.lock_read()
1032
self.repository.unlock()
975
1035
def unlock(self):
976
1036
# TODO: test for failed two phase locks. This is known broken.
978
self.repository.unlock()
980
1038
self.control_files.unlock()
1040
self.repository.unlock()
982
1042
def peek_lock_mode(self):
983
1043
if self.control_files._lock_count == 0:
1021
1081
# not really an object yet, and the transaction is for objects.
1022
1082
# 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)
1044
1084
@needs_read_lock
1045
1085
def revision_history(self):
1046
1086
"""See Branch.revision_history."""
1047
1087
transaction = self.get_transaction()
1048
1088
history = transaction.map.find_revision_history()
1049
1089
if history is not None:
1050
mutter("cache hit for revision-history in %s", self)
1090
# mutter("cache hit for revision-history in %s", self)
1051
1091
return list(history)
1052
history = [l.rstrip('\r\n') for l in
1053
self.control_files.get_utf8('revision-history').readlines()]
1092
decode_utf8 = cache_utf8.decode
1093
history = [decode_utf8(l.rstrip('\r\n')) for l in
1094
self.control_files.get('revision-history').readlines()]
1054
1095
transaction.map.add_revision_history(history)
1055
1096
# this call is disabled because revision_history is
1056
1097
# not really an object yet, and the transaction is for objects.
1058
1099
return list(history)
1060
1101
@needs_write_lock
1102
def generate_revision_history(self, revision_id, last_rev=None,
1104
"""Create a new revision history that will finish with revision_id.
1106
:param revision_id: the new tip to use.
1107
:param last_rev: The previous last_revision. If not None, then this
1108
must be a ancestory of revision_id, or DivergedBranches is raised.
1109
:param other_branch: The other branch that DivergedBranches should
1110
raise with respect to.
1112
# stop_revision must be a descendant of last_revision
1113
stop_graph = self.repository.get_revision_graph(revision_id)
1114
if last_rev is not None and last_rev not in stop_graph:
1115
# our previous tip is not merged into stop_revision
1116
raise errors.DivergedBranches(self, other_branch)
1117
# make a new revision history from the graph
1118
current_rev_id = revision_id
1120
while current_rev_id not in (None, revision.NULL_REVISION):
1121
new_history.append(current_rev_id)
1122
current_rev_id_parents = stop_graph[current_rev_id]
1124
current_rev_id = current_rev_id_parents[0]
1126
current_rev_id = None
1127
new_history.reverse()
1128
self.set_revision_history(new_history)
1061
1131
def update_revisions(self, other, stop_revision=None):
1062
1132
"""See Branch.update_revisions."""
1063
1133
other.lock_read()
1077
1147
if stop_revision in my_ancestry:
1078
1148
# last_revision is a descendant of stop_revision
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)
1150
self.generate_revision_history(stop_revision, last_rev=last_rev,
1132
1187
def get_parent(self):
1133
1188
"""See Branch.get_parent."""
1135
1190
_locs = ['parent', 'pull', 'x-pull']
1191
assert self.base[-1] == '/'
1136
1192
for l in _locs:
1138
return self.control_files.get_utf8(l).read().strip('\n')
1194
parent = self.control_files.get(l).read().strip('\n')
1139
1195
except NoSuchFile:
1197
# This is an old-format absolute path to a local branch
1198
# turn it into a url
1199
if parent.startswith('/'):
1200
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1202
return urlutils.join(self.base[:-1], parent)
1203
except errors.InvalidURLJoin, e:
1204
raise errors.InaccessibleParent(parent, self.base)
1143
1207
def get_push_location(self):
1144
1208
"""See Branch.get_push_location."""
1145
config = bzrlib.config.BranchConfig(self)
1146
push_loc = config.get_user_option('push_location')
1209
push_loc = self.get_config().get_user_option('push_location')
1147
1210
return push_loc
1149
1212
def set_push_location(self, location):
1150
1213
"""See Branch.set_push_location."""
1151
config = bzrlib.config.LocationConfig(self.base)
1152
config.set_user_option('push_location', location)
1214
self.get_config().set_user_option('push_location', location,
1154
1217
@needs_write_lock
1155
1218
def set_parent(self, url):
1162
1225
if url is None:
1163
1226
self.control_files._transport.delete('parent')
1165
self.control_files.put_utf8('parent', url + '\n')
1228
if isinstance(url, unicode):
1230
url = url.encode('ascii')
1231
except UnicodeEncodeError:
1232
raise bzrlib.errors.InvalidURL(url,
1233
"Urls must be 7-bit ascii, "
1234
"use bzrlib.urlutils.escape")
1236
url = urlutils.relative_url(self.base, url)
1237
self.control_files.put('parent', StringIO(url + '\n'))
1239
@deprecated_function(zero_nine)
1167
1240
def tree_config(self):
1241
"""DEPRECATED; call get_config instead.
1242
TreeConfig has become part of BranchConfig."""
1168
1243
return TreeConfig(self)
1412
class BranchCheckResult(object):
1413
"""Results of checking branch consistency.
1418
def __init__(self, branch):
1419
self.branch = branch
1421
def report_results(self, verbose):
1422
"""Report the check results via trace.note.
1424
:param verbose: Requests more detailed display of what was checked,
1427
note('checked branch %s format %s',
1429
self.branch._format)
1337
1432
######################################################################
1341
1436
@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
1437
def is_control_file(*args, **kwargs):
1350
1438
"""See bzrlib.workingtree.is_control_file."""
1351
1439
return bzrlib.workingtree.is_control_file(*args, **kwargs)