18
18
from copy import deepcopy
19
19
from cStringIO import StringIO
20
24
from unittest import TestSuite
21
25
from warnings import warn
27
import xml.sax.saxutils
29
raise ImportError("We were unable to import 'xml.sax.saxutils',"
30
" most likely you have an xml.pyc or xml.pyo file"
31
" lying around in your bzrlib directory."
24
from bzrlib import bzrdir, errors, lockdir, osutils, revision, \
36
import bzrlib.bzrdir as bzrdir
28
37
from bzrlib.config import TreeConfig
29
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
from bzrlib.delta import compare_trees
30
40
import bzrlib.errors as errors
31
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
32
HistoryMissing, InvalidRevisionId,
33
InvalidRevisionNumber, LockError, NoSuchFile,
34
NoSuchRevision, NoWorkingTree, NotVersionedError,
35
NotBranchError, UninitializableFormat,
36
UnlistableStore, UnlistableBranch,
38
from bzrlib.lockable_files import LockableFiles, TransportLock
39
from bzrlib.symbol_versioning import (deprecated_function,
43
zero_eight, zero_nine,
41
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
42
NoSuchRevision, HistoryMissing, NotBranchError,
43
DivergedBranches, LockError,
44
UninitializableFormat,
46
UnlistableBranch, NoSuchFile, NotVersionedError,
48
import bzrlib.inventory as inventory
49
from bzrlib.inventory import Inventory
50
from bzrlib.lockable_files import LockableFiles
51
from bzrlib.osutils import (isdir, quotefn,
52
rename, splitpath, sha_file,
53
file_kind, abspath, normpath, pathjoin,
56
from bzrlib.textui import show_status
45
57
from bzrlib.trace import mutter, note
58
from bzrlib.tree import EmptyTree, RevisionTree
59
from bzrlib.repository import Repository
60
from bzrlib.revision import (
61
get_intervening_revisions,
66
from bzrlib.store import copy_all
67
from bzrlib.symbol_versioning import *
68
import bzrlib.transactions as transactions
69
from bzrlib.transport import Transport, get_transport
70
from bzrlib.tree import EmptyTree, RevisionTree
48
75
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
213
210
last_revision = from_history[-1]
215
212
# no history in the source branch
216
last_revision = revision.NULL_REVISION
213
last_revision = NULL_REVISION
217
214
return self.repository.fetch(from_branch.repository,
218
215
revision_id=last_revision,
221
if nested_pb is not None:
223
218
from_branch.unlock()
225
def get_bound_location(self):
226
"""Return the URL of the branch we are bound to.
228
Older format branches cannot bind, please be sure to use a metadir
233
def get_commit_builder(self, parents, config=None, timestamp=None,
234
timezone=None, committer=None, revprops=None,
236
"""Obtain a CommitBuilder for this branch.
238
:param parents: Revision ids of the parents of the new revision.
239
:param config: Optional configuration to use.
240
:param timestamp: Optional timestamp recorded for commit.
241
:param timezone: Optional timezone for timestamp.
242
:param committer: Optional committer to set for commit.
243
:param revprops: Optional dictionary of revision properties.
244
:param revision_id: Optional revision id.
248
config = self.get_config()
250
return self.repository.get_commit_builder(self, parents, config,
251
timestamp, timezone, committer, revprops, revision_id)
253
def get_master_branch(self):
254
"""Return the branch we are bound to.
256
:return: Either a Branch, or None
260
def get_revision_delta(self, revno):
261
"""Return the delta for one revision.
263
The delta is relative to its mainline predecessor, or the
264
empty tree for revision 1.
266
assert isinstance(revno, int)
267
rh = self.revision_history()
268
if not (1 <= revno <= len(rh)):
269
raise InvalidRevisionNumber(revno)
270
return self.repository.get_revision_delta(rh[revno-1])
272
220
def get_root_id(self):
273
221
"""Return the id of this branches root"""
274
222
raise NotImplementedError('get_root_id is abstract')
310
def missing_revisions(self, other, stop_revision=None):
254
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
311
255
"""Return a list of new revisions that would perfectly fit.
313
257
If self and other have not diverged, return a list of the revisions
314
258
present in other, but missing from self.
260
>>> from bzrlib.workingtree import WorkingTree
261
>>> bzrlib.trace.silent = True
262
>>> d1 = bzrdir.ScratchDir()
263
>>> br1 = d1.open_branch()
264
>>> wt1 = d1.open_workingtree()
265
>>> d2 = bzrdir.ScratchDir()
266
>>> br2 = d2.open_branch()
267
>>> wt2 = d2.open_workingtree()
268
>>> br1.missing_revisions(br2)
270
>>> wt2.commit("lala!", rev_id="REVISION-ID-1")
271
>>> br1.missing_revisions(br2)
273
>>> br2.missing_revisions(br1)
275
>>> wt1.commit("lala!", rev_id="REVISION-ID-1")
276
>>> br1.missing_revisions(br2)
278
>>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
279
>>> br1.missing_revisions(br2)
281
>>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
282
>>> br1.missing_revisions(br2)
283
Traceback (most recent call last):
284
DivergedBranches: These branches have diverged. Try merge.
316
286
self_history = self.revision_history()
317
287
self_len = len(self_history)
542
484
destination.set_parent(parent)
546
"""Check consistency of the branch.
548
In particular this checks that revisions given in the revision-history
549
do actually match up in the revision graph, and that they're all
550
present in the repository.
552
Callers will typically also want to check the repository.
554
:return: A BranchCheckResult.
556
mainline_parent_id = None
557
for revision_id in self.revision_history():
559
revision = self.repository.get_revision(revision_id)
560
except errors.NoSuchRevision, e:
561
raise errors.BzrCheckError("mainline revision {%s} not in repository"
563
# In general the first entry on the revision history has no parents.
564
# But it's not illegal for it to have parents listed; this can happen
565
# in imports from Arch when the parents weren't reachable.
566
if mainline_parent_id is not None:
567
if mainline_parent_id not in revision.parent_ids:
568
raise errors.BzrCheckError("previous revision {%s} not listed among "
570
% (mainline_parent_id, revision_id))
571
mainline_parent_id = revision_id
572
return BranchCheckResult(self)
575
487
class BranchFormat(object):
576
488
"""An encapsulation of the initialization and open routines for a format.
718
614
- a revision-history file.
719
615
- a format string
720
- a lock dir guarding the branch itself
721
- all of this stored in a branch/ subdirectory
722
617
- works with shared repositories.
724
This format is new in bzr 0.8.
727
620
def get_format_string(self):
728
621
"""See BranchFormat.get_format_string()."""
729
622
return "Bazaar-NG branch format 5\n"
731
def get_format_description(self):
732
"""See BranchFormat.get_format_description()."""
733
return "Branch format 5"
735
624
def initialize(self, a_bzrdir):
736
625
"""Create a branch of this format in a_bzrdir."""
737
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
626
mutter('creating branch in %s', a_bzrdir.transport.base)
738
627
branch_transport = a_bzrdir.get_branch_transport(self)
739
629
utf8_files = [('revision-history', ''),
740
630
('branch-name', ''),
742
control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
743
control_files.create_lock()
633
branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
634
control_files = LockableFiles(branch_transport, 'lock')
744
635
control_files.lock_write()
745
636
control_files.put_utf8('format', self.get_format_string())
989
887
tree = self.repository.revision_tree(self.last_revision())
990
888
return tree.inventory.root.file_id
993
return self.control_files.is_locked()
995
890
def lock_write(self):
891
# TODO: test for failed two phase locks. This is known broken.
892
self.control_files.lock_write()
996
893
self.repository.lock_write()
998
self.control_files.lock_write()
1000
self.repository.unlock()
1003
895
def lock_read(self):
896
# TODO: test for failed two phase locks. This is known broken.
897
self.control_files.lock_read()
1004
898
self.repository.lock_read()
1006
self.control_files.lock_read()
1008
self.repository.unlock()
1011
900
def unlock(self):
1012
901
# TODO: test for failed two phase locks. This is known broken.
1014
self.control_files.unlock()
1016
self.repository.unlock()
902
self.repository.unlock()
903
self.control_files.unlock()
1018
905
def peek_lock_mode(self):
1019
906
if self.control_files._lock_count == 0:
1022
909
return self.control_files._lock_mode
1024
def get_physical_lock_status(self):
1025
return self.control_files.get_physical_lock_status()
1027
911
@needs_read_lock
1028
912
def print_file(self, file, revision_id):
1029
913
"""See Branch.print_file."""
1043
927
"""See Branch.set_revision_history."""
1044
928
self.control_files.put_utf8(
1045
929
'revision-history', '\n'.join(rev_history))
1046
transaction = self.get_transaction()
1047
history = transaction.map.find_revision_history()
1048
if history is not None:
1049
# update the revision history in the identity map.
1050
history[:] = list(rev_history)
1051
# this call is disabled because revision_history is
1052
# not really an object yet, and the transaction is for objects.
1053
# transaction.register_dirty(history)
931
def get_revision_delta(self, revno):
932
"""Return the delta for one revision.
934
The delta is relative to its mainline predecessor, or the
935
empty tree for revision 1.
937
assert isinstance(revno, int)
938
rh = self.revision_history()
939
if not (1 <= revno <= len(rh)):
940
raise InvalidRevisionNumber(revno)
942
# revno is 1-based; list is 0-based
944
new_tree = self.repository.revision_tree(rh[revno-1])
946
old_tree = EmptyTree()
1055
transaction.map.add_revision_history(rev_history)
1056
# this call is disabled because revision_history is
1057
# not really an object yet, and the transaction is for objects.
1058
# transaction.register_clean(history)
948
old_tree = self.repository.revision_tree(rh[revno-2])
949
return compare_trees(old_tree, new_tree)
1060
951
@needs_read_lock
1061
952
def revision_history(self):
1062
953
"""See Branch.revision_history."""
954
# FIXME are transactions bound to control files ? RBC 20051121
1063
955
transaction = self.get_transaction()
1064
956
history = transaction.map.find_revision_history()
1065
957
if history is not None:
1073
965
# transaction.register_clean(history, precious=True)
1074
966
return list(history)
1077
def generate_revision_history(self, revision_id, last_rev=None,
1079
"""Create a new revision history that will finish with revision_id.
1081
:param revision_id: the new tip to use.
1082
:param last_rev: The previous last_revision. If not None, then this
1083
must be a ancestory of revision_id, or DivergedBranches is raised.
1084
:param other_branch: The other branch that DivergedBranches should
1085
raise with respect to.
1087
# stop_revision must be a descendant of last_revision
1088
stop_graph = self.repository.get_revision_graph(revision_id)
1089
if last_rev is not None and last_rev not in stop_graph:
1090
# our previous tip is not merged into stop_revision
1091
raise errors.DivergedBranches(self, other_branch)
1092
# make a new revision history from the graph
1093
current_rev_id = revision_id
1095
while current_rev_id not in (None, revision.NULL_REVISION):
1096
new_history.append(current_rev_id)
1097
current_rev_id_parents = stop_graph[current_rev_id]
1099
current_rev_id = current_rev_id_parents[0]
1101
current_rev_id = None
1102
new_history.reverse()
1103
self.set_revision_history(new_history)
1106
968
def update_revisions(self, other, stop_revision=None):
1107
969
"""See Branch.update_revisions."""
970
if stop_revision is None:
971
stop_revision = other.last_revision()
972
### Should this be checking is_ancestor instead of revision_history?
973
if (stop_revision is not None and
974
stop_revision in self.revision_history()):
976
self.fetch(other, stop_revision)
977
pullable_revs = self.pullable_revisions(other, stop_revision)
978
if len(pullable_revs) > 0:
979
self.append_revision(*pullable_revs)
981
def pullable_revisions(self, other, stop_revision):
982
"""See Branch.pullable_revisions."""
983
other_revno = other.revision_id_to_revno(stop_revision)
1110
if stop_revision is None:
1111
stop_revision = other.last_revision()
1112
if stop_revision is None:
1113
# if there are no commits, we're done.
1115
# whats the current last revision, before we fetch [and change it
1117
last_rev = self.last_revision()
1118
# we fetch here regardless of whether we need to so that we pickup
1120
self.fetch(other, stop_revision)
1121
my_ancestry = self.repository.get_ancestry(last_rev)
1122
if stop_revision in my_ancestry:
1123
# last_revision is a descendant of stop_revision
1125
self.generate_revision_history(stop_revision, last_rev=last_rev,
985
return self.missing_revisions(other, other_revno)
986
except DivergedBranches, e:
988
pullable_revs = get_intervening_revisions(self.last_revision(),
991
assert self.last_revision() not in pullable_revs
993
except bzrlib.errors.NotAncestor:
994
if is_ancestor(self.last_revision(), stop_revision, self):
1130
999
def basis_tree(self):
1131
1000
"""See Branch.basis_tree."""
1132
1001
return self.repository.revision_tree(self.last_revision())
1162
1031
def get_parent(self):
1163
1032
"""See Branch.get_parent."""
1165
1034
_locs = ['parent', 'pull', 'x-pull']
1166
assert self.base[-1] == '/'
1167
1035
for l in _locs:
1169
parent = self.control_files.get(l).read().strip('\n')
1037
return self.control_files.get_utf8(l).read().strip('\n')
1170
1038
except NoSuchFile:
1172
# This is an old-format absolute path to a local branch
1173
# turn it into a url
1174
if parent.startswith('/'):
1175
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1176
return urlutils.join(self.base[:-1], parent)
1179
1042
def get_push_location(self):
1180
1043
"""See Branch.get_push_location."""
1181
push_loc = self.get_config().get_user_option('push_location')
1044
config = bzrlib.config.BranchConfig(self)
1045
push_loc = config.get_user_option('push_location')
1182
1046
return push_loc
1184
1048
def set_push_location(self, location):
1185
1049
"""See Branch.set_push_location."""
1186
self.get_config().set_user_option('push_location', location,
1050
config = bzrlib.config.LocationConfig(self.base)
1051
config.set_user_option('push_location', location)
1189
1053
@needs_write_lock
1190
1054
def set_parent(self, url):
1194
1058
# FIXUP this and get_parent in a future branch format bump:
1195
1059
# read and rewrite the file, and have the new format code read
1196
1060
# using .get not .get_utf8. RBC 20060125
1198
self.control_files._transport.delete('parent')
1200
if isinstance(url, unicode):
1202
url = url.encode('ascii')
1203
except UnicodeEncodeError:
1204
raise bzrlib.errors.InvalidURL(url,
1205
"Urls must be 7-bit ascii, "
1206
"use bzrlib.urlutils.escape")
1208
url = urlutils.relative_url(self.base, url)
1209
self.control_files.put('parent', url + '\n')
1061
self.control_files.put_utf8('parent', url + '\n')
1211
@deprecated_function(zero_nine)
1212
1063
def tree_config(self):
1213
"""DEPRECATED; call get_config instead.
1214
TreeConfig has become part of BranchConfig."""
1215
1064
return TreeConfig(self)
1218
class BzrBranch5(BzrBranch):
1219
"""A format 5 branch. This supports new features over plan branches.
1221
It has support for a master_branch which is the data for bound branches.
1229
super(BzrBranch5, self).__init__(_format=_format,
1230
_control_files=_control_files,
1232
_repository=_repository)
1235
def pull(self, source, overwrite=False, stop_revision=None):
1236
"""Updates branch.pull to be bound branch aware."""
1237
bound_location = self.get_bound_location()
1238
if source.base != bound_location:
1239
# not pulling from master, so we need to update master.
1240
master_branch = self.get_master_branch()
1242
master_branch.pull(source)
1243
source = master_branch
1244
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1246
def get_bound_location(self):
1066
def _get_truncated_history(self, revision_id):
1067
history = self.revision_history()
1068
if revision_id is None:
1248
return self.control_files.get_utf8('bound').read()[:-1]
1249
except errors.NoSuchFile:
1071
idx = history.index(revision_id)
1073
raise InvalidRevisionId(revision_id=revision, branch=self)
1074
return history[:idx+1]
1252
1076
@needs_read_lock
1253
def get_master_branch(self):
1254
"""Return the branch we are bound to.
1256
:return: Either a Branch, or None
1258
This could memoise the branch, but if thats done
1259
it must be revalidated on each new lock.
1260
So for now we just don't memoise it.
1261
# RBC 20060304 review this decision.
1263
bound_loc = self.get_bound_location()
1267
return Branch.open(bound_loc)
1268
except (errors.NotBranchError, errors.ConnectionError), e:
1269
raise errors.BoundBranchConnectionFailure(
1273
def set_bound_location(self, location):
1274
"""Set the target where this branch is bound to.
1276
:param location: URL to the target branch
1279
self.control_files.put_utf8('bound', location+'\n')
1282
self.control_files._transport.delete('bound')
1288
def bind(self, other):
1289
"""Bind the local branch the other branch.
1291
:param other: The branch to bind to
1294
# TODO: jam 20051230 Consider checking if the target is bound
1295
# It is debatable whether you should be able to bind to
1296
# a branch which is itself bound.
1297
# Committing is obviously forbidden,
1298
# but binding itself may not be.
1299
# Since we *have* to check at commit time, we don't
1300
# *need* to check here
1303
# we are now equal to or a suffix of other.
1305
# Since we have 'pulled' from the remote location,
1306
# now we should try to pull in the opposite direction
1307
# in case the local tree has more revisions than the
1309
# There may be a different check you could do here
1310
# rather than actually trying to install revisions remotely.
1311
# TODO: capture an exception which indicates the remote branch
1313
# If it is up-to-date, this probably should not be a failure
1315
# lock other for write so the revision-history syncing cannot race
1319
# if this does not error, other now has the same last rev we do
1320
# it can only error if the pull from other was concurrent with
1321
# a commit to other from someone else.
1323
# until we ditch revision-history, we need to sync them up:
1324
self.set_revision_history(other.revision_history())
1325
# now other and self are up to date with each other and have the
1326
# same revision-history.
1330
self.set_bound_location(other.base)
1334
"""If bound, unbind"""
1335
return self.set_bound_location(None)
1339
"""Synchronise this branch with the master branch if any.
1341
:return: None or the last_revision that was pivoted out during the
1344
master = self.get_master_branch()
1345
if master is not None:
1346
old_tip = self.last_revision()
1347
self.pull(master, overwrite=True)
1348
if old_tip in self.repository.get_ancestry(self.last_revision()):
1077
def _clone_weave(self, to_location, revision=None, basis_branch=None):
1079
from bzrlib.workingtree import WorkingTree
1080
assert isinstance(to_location, basestring)
1081
if basis_branch is not None:
1082
note("basis_branch is not supported for fast weave copy yet.")
1084
history = self._get_truncated_history(revision)
1085
if not bzrlib.osutils.lexists(to_location):
1086
os.mkdir(to_location)
1087
bzrdir_to = self.bzrdir._format.initialize(to_location)
1088
self.repository.clone(bzrdir_to)
1089
branch_to = bzrdir_to.create_branch()
1090
mutter("copy branch from %s to %s", self, branch_to)
1092
# FIXME duplicate code with base .clone().
1093
# .. would template method be useful here? RBC 20051207
1094
branch_to.set_parent(self.base)
1095
branch_to.append_revision(*history)
1096
WorkingTree.create(branch_to, branch_to.base)
1354
1101
class BranchTestProviderAdapter(object):