47
49
from bzrlib.textui import show_status
48
50
from bzrlib.trace import mutter, note
49
51
from bzrlib.tree import EmptyTree, RevisionTree
50
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
52
from bzrlib.repository import Repository
53
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
52
54
from bzrlib.store import copy_all
53
from bzrlib.store.text import TextStore
54
from bzrlib.store.weave import WeaveStore
55
55
from bzrlib.symbol_versioning import *
56
from bzrlib.testament import Testament
57
56
import bzrlib.transactions as transactions
58
57
from bzrlib.transport import Transport, get_transport
58
from bzrlib.tree import EmptyTree, RevisionTree
63
63
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
64
64
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
65
65
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
66
## TODO: Maybe include checks for common corruption of newlines, etc?
68
# TODO: Maybe include checks for common corruption of newlines, etc?
69
70
# TODO: Some operations like log might retrieve the same revisions
70
71
# repeatedly to calculate deltas. We could perhaps have a weakref
71
72
# cache in memory to make this faster. In general anything can be
72
# cached in memory between lock and unlock operations.
74
def find_branch(*ignored, **ignored_too):
75
# XXX: leave this here for about one release, then remove it
76
raise NotImplementedError('find_branch() is not supported anymore, '
77
'please use one of the new branch constructors')
80
def needs_read_lock(unbound):
81
"""Decorate unbound to take out and release a read lock."""
82
def decorated(self, *args, **kwargs):
85
return unbound(self, *args, **kwargs)
91
def needs_write_lock(unbound):
92
"""Decorate unbound to take out and release a write lock."""
93
def decorated(self, *args, **kwargs):
96
return unbound(self, *args, **kwargs)
73
# cached in memory between lock and unlock operations. .. nb thats
74
# what the transaction identity map provides
101
77
######################################################################
242
216
raise NotImplementedError('abspath is abstract')
244
def controlfilename(self, file_or_path):
245
"""Return location relative to branch."""
246
raise NotImplementedError('controlfilename is abstract')
248
def controlfile(self, file_or_path, mode='r'):
249
"""Open a control file for this branch.
251
There are two classes of file in the control directory: text
252
and binary. binary files are untranslated byte streams. Text
253
control files are stored with Unix newlines and in UTF-8, even
254
if the platform or locale defaults are different.
256
Controlfiles should almost never be opened in write mode but
257
rather should be atomically copied and replaced using atomicfile.
259
raise NotImplementedError('controlfile is abstract')
261
def put_controlfile(self, path, f, encode=True):
262
"""Write an entry as a controlfile.
264
:param path: The path to put the file, relative to the .bzr control
266
:param f: A file-like or string object whose contents should be copied.
267
:param encode: If true, encode the contents as utf-8
269
raise NotImplementedError('put_controlfile is abstract')
271
def put_controlfiles(self, files, encode=True):
272
"""Write several entries as controlfiles.
274
:param files: A list of [(path, file)] pairs, where the path is the directory
275
underneath the bzr control directory
276
:param encode: If true, encode the contents as utf-8
278
raise NotImplementedError('put_controlfiles is abstract')
280
218
def get_root_id(self):
281
219
"""Return the id of this branches root"""
282
220
raise NotImplementedError('get_root_id is abstract')
284
def set_root_id(self, file_id):
285
raise NotImplementedError('set_root_id is abstract')
287
222
def print_file(self, file, revision_id):
288
223
"""Print `file` to stdout."""
289
224
raise NotImplementedError('print_file is abstract')
294
229
def set_revision_history(self, rev_history):
295
230
raise NotImplementedError('set_revision_history is abstract')
297
def has_revision(self, revision_id):
298
"""True if this branch has a copy of the revision.
300
This does not necessarily imply the revision is merge
301
or on the mainline."""
302
raise NotImplementedError('has_revision is abstract')
304
def get_revision_xml(self, revision_id):
305
raise NotImplementedError('get_revision_xml is abstract')
307
def get_revision(self, revision_id):
308
"""Return the Revision object for a named revision"""
309
raise NotImplementedError('get_revision is abstract')
311
def get_revision_delta(self, revno):
312
"""Return the delta for one revision.
314
The delta is relative to its mainline predecessor, or the
315
empty tree for revision 1.
317
assert isinstance(revno, int)
318
rh = self.revision_history()
319
if not (1 <= revno <= len(rh)):
320
raise InvalidRevisionNumber(revno)
322
# revno is 1-based; list is 0-based
324
new_tree = self.revision_tree(rh[revno-1])
326
old_tree = EmptyTree()
328
old_tree = self.revision_tree(rh[revno-2])
330
return compare_trees(old_tree, new_tree)
332
def get_revision_sha1(self, revision_id):
333
"""Hash the stored value of a revision, and return it."""
334
raise NotImplementedError('get_revision_sha1 is abstract')
336
def get_ancestry(self, revision_id):
337
"""Return a list of revision-ids integrated by a revision.
339
This currently returns a list, but the ordering is not guaranteed:
342
raise NotImplementedError('get_ancestry is abstract')
344
def get_inventory(self, revision_id):
345
"""Get Inventory object by hash."""
346
raise NotImplementedError('get_inventory is abstract')
348
def get_inventory_xml(self, revision_id):
349
"""Get inventory XML as a file object."""
350
raise NotImplementedError('get_inventory_xml is abstract')
352
def get_inventory_sha1(self, revision_id):
353
"""Return the sha1 hash of the inventory entry."""
354
raise NotImplementedError('get_inventory_sha1 is abstract')
356
def get_revision_inventory(self, revision_id):
357
"""Return inventory of a past revision."""
358
raise NotImplementedError('get_revision_inventory is abstract')
360
232
def revision_history(self):
361
233
"""Return sequence of revision hashes on to this branch."""
362
234
raise NotImplementedError('revision_history is abstract')
534
399
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
535
400
raise NotImplementedError('store_revision_signature is abstract')
402
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
403
"""Copy this branch into the existing directory to_location.
405
Returns the newly created branch object.
408
If not None, only revisions up to this point will be copied.
409
The head of the new branch will be that revision. Must be a
412
to_location -- The destination directory; must either exist and be
413
empty, or not exist, in which case it is created.
416
A local branch to copy revisions from, related to this branch.
417
This is used when branching from a remote (slow) branch, and we have
418
a local branch that might contain some relevant revisions.
421
Branch type of destination branch
423
from bzrlib.workingtree import WorkingTree
424
assert isinstance(to_location, basestring)
425
if not bzrlib.osutils.lexists(to_location):
426
os.mkdir(to_location)
427
if to_branch_type is None:
428
to_branch_type = BzrBranch
429
print "FIXME use a branch format here"
430
br_to = to_branch_type.initialize(to_location)
431
mutter("copy branch from %s to %s", self, br_to)
432
if basis_branch is not None:
433
basis_branch.push_stores(br_to)
435
revision = self.last_revision()
436
br_to.update_revisions(self, stop_revision=revision)
437
br_to.set_parent(self.base)
438
WorkingTree.create(br_to, to_location).set_root_id(self.get_root_id())
537
442
def fileid_involved_between_revs(self, from_revid, to_revid):
538
443
""" This function returns the file_id(s) involved in the
539
444
changes between the from_revid revision and the to_revid
649
554
# Since we don't have a .bzr directory, inherit the
650
555
# mode from the root directory
651
dir_mode, file_mode = self._find_modes(t)
653
t.mkdir('.bzr', mode=dir_mode)
556
temp_control = LockableFiles(t, '')
557
temp_control._transport.mkdir('.bzr',
558
mode=temp_control._dir_mode)
559
file_mode = temp_control._file_mode
561
mutter('created control directory in ' + t.base)
654
562
control = t.clone('.bzr')
655
563
dirs = ['revision-store', 'weaves']
657
StringIO("This is a Bazaar-NG control directory.\n"
658
"Do not change any files in this directory.\n")),
659
('branch-format', StringIO(self.get_format_string())),
660
('revision-history', StringIO('')),
661
('branch-name', StringIO('')),
662
('branch-lock', StringIO('')),
663
('inventory.weave', StringIO(empty_weave)),
665
control.mkdir_multi(dirs, mode=dir_mode)
666
control.put_multi(files, mode=file_mode)
667
mutter('created control directory in ' + t.base)
668
return BzrBranch(t, format=self)
564
lock_file = 'branch-lock'
565
utf8_files = [('README',
566
"This is a Bazaar-NG control directory.\n"
567
"Do not change any files in this directory.\n"),
568
('branch-format', self.get_format_string()),
569
('revision-history', ''),
572
files = [('inventory.weave', StringIO(empty_weave)),
575
# FIXME: RBC 20060125 dont peek under the covers
576
# NB: no need to escape relative paths that are url safe.
577
control.put(lock_file, StringIO(), mode=file_mode)
578
control_files = LockableFiles(control, lock_file)
579
control_files.lock_write()
580
control_files._transport.mkdir_multi(dirs,
581
mode=control_files._dir_mode)
583
for file, content in utf8_files:
584
control_files.put_utf8(file, content)
585
for file, content in files:
586
control_files.put(file, content)
588
control_files.unlock()
589
return BzrBranch(t, _format=self, _control_files=control_files)
670
591
def is_supported(self):
671
592
"""Is this format supported?
858
768
['use a different bzr version',
859
769
'or remove the .bzr directory'
860
770
' and "bzr init" again'])
862
def get_store(name, compressed=True, prefixed=False):
863
relpath = self._rel_controlfilename(safe_unicode(name))
864
store = TextStore(self._transport.clone(relpath),
865
dir_mode=self._dir_mode,
866
file_mode=self._file_mode,
868
compressed=compressed)
871
def get_weave(name, prefixed=False):
872
relpath = self._rel_controlfilename(unicode(name))
873
ws = WeaveStore(self._transport.clone(relpath),
875
dir_mode=self._dir_mode,
876
file_mode=self._file_mode)
877
if self._transport.should_cache():
878
ws.enable_cache = True
881
if isinstance(self._branch_format, BzrBranchFormat4):
882
self.inventory_store = get_store('inventory-store')
883
self.text_store = get_store('text-store')
884
self.revision_store = get_store('revision-store')
885
elif isinstance(self._branch_format, BzrBranchFormat5):
886
self.control_weaves = get_weave(u'')
887
self.weave_store = get_weave(u'weaves')
888
self.revision_store = get_store(u'revision-store', compressed=False)
889
elif isinstance(self._branch_format, BzrBranchFormat6):
890
self.control_weaves = get_weave(u'')
891
self.weave_store = get_weave(u'weaves', prefixed=True)
892
self.revision_store = get_store(u'revision-store', compressed=False,
894
self.revision_store.register_suffix('sig')
895
self._transaction = None
771
self.repository = Repository(transport, self._branch_format)
898
775
def _initialize(base):
925
797
self.cache_root = None
927
799
def _get_base(self):
929
return self._transport.base
932
802
base = property(_get_base, doc="The URL for the root of this branch.")
934
804
def _finish_transaction(self):
935
805
"""Exit the current transaction."""
936
if self._transaction is None:
937
raise errors.LockError('Branch %s is not in a transaction' %
939
transaction = self._transaction
940
self._transaction = None
806
return self.control_files._finish_transaction()
943
808
def get_transaction(self):
944
"""See Branch.get_transaction."""
945
if self._transaction is None:
946
return transactions.PassThroughTransaction()
948
return self._transaction
950
def _set_transaction(self, new_transaction):
809
"""Return the current active transaction.
811
If no transaction is active, this returns a passthrough object
812
for which all data is immediately flushed and no caching happens.
814
# this is an explicit function so that we can do tricky stuff
815
# when the storage in rev_storage is elsewhere.
816
# we probably need to hook the two 'lock a location' and
817
# 'have a transaction' together more delicately, so that
818
# we can have two locks (branch and storage) and one transaction
819
# ... and finishing the transaction unlocks both, but unlocking
820
# does not. - RBC 20051121
821
return self.control_files.get_transaction()
823
def _set_transaction(self, transaction):
951
824
"""Set a new active transaction."""
952
if self._transaction is not None:
953
raise errors.LockError('Branch %s is in a transaction already.' %
955
self._transaction = new_transaction
957
def lock_write(self):
958
#mutter("lock write: %s (%s)", self, self._lock_count)
959
# TODO: Upgrade locking to support using a Transport,
960
# and potentially a remote locking protocol
962
if self._lock_mode != 'w':
963
raise LockError("can't upgrade to a write lock from %r" %
965
self._lock_count += 1
967
self._lock = self._transport.lock_write(
968
self._rel_controlfilename('branch-lock'))
969
self._lock_mode = 'w'
971
self._set_transaction(transactions.PassThroughTransaction())
974
#mutter("lock read: %s (%s)", self, self._lock_count)
976
assert self._lock_mode in ('r', 'w'), \
977
"invalid lock mode %r" % self._lock_mode
978
self._lock_count += 1
980
self._lock = self._transport.lock_read(
981
self._rel_controlfilename('branch-lock'))
982
self._lock_mode = 'r'
984
self._set_transaction(transactions.ReadOnlyTransaction())
985
# 5K may be excessive, but hey, its a knob.
986
self.get_transaction().set_cache_size(5000)
989
#mutter("unlock: %s (%s)", self, self._lock_count)
990
if not self._lock_mode:
991
raise LockError('branch %r is not locked' % (self))
993
if self._lock_count > 1:
994
self._lock_count -= 1
996
self._finish_transaction()
999
self._lock_mode = self._lock_count = None
825
return self.control_files._set_transaction(transaction)
1001
827
def abspath(self, name):
1002
828
"""See Branch.abspath."""
1003
return self._transport.abspath(name)
1005
def _rel_controlfilename(self, file_or_path):
1006
if not isinstance(file_or_path, basestring):
1007
file_or_path = u'/'.join(file_or_path)
1008
if file_or_path == '':
1009
return bzrlib.BZRDIR
1010
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
1012
def controlfilename(self, file_or_path):
1013
"""See Branch.controlfilename."""
1014
return self._transport.abspath(self._rel_controlfilename(file_or_path))
1016
def controlfile(self, file_or_path, mode='r'):
1017
"""See Branch.controlfile."""
1020
relpath = self._rel_controlfilename(file_or_path)
1021
#TODO: codecs.open() buffers linewise, so it was overloaded with
1022
# a much larger buffer, do we need to do the same for getreader/getwriter?
1024
return self._transport.get(relpath)
1026
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
1028
# XXX: Do we really want errors='replace'? Perhaps it should be
1029
# an error, or at least reported, if there's incorrectly-encoded
1030
# data inside a file.
1031
# <https://launchpad.net/products/bzr/+bug/3823>
1032
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
1034
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
1036
raise BzrError("invalid controlfile mode %r" % mode)
1038
def put_controlfile(self, path, f, encode=True):
1039
"""See Branch.put_controlfile."""
1040
self.put_controlfiles([(path, f)], encode=encode)
1042
def put_controlfiles(self, files, encode=True):
1043
"""See Branch.put_controlfiles."""
1046
for path, f in files:
1048
if isinstance(f, basestring):
1049
f = StringIO(f.encode('utf-8', 'replace'))
1051
f = codecs.getwriter('utf-8')(f, errors='replace')
1052
path = self._rel_controlfilename(path)
1053
ctrl_files.append((path, f))
1054
self._transport.put_multi(ctrl_files, mode=self._file_mode)
1056
def _find_modes(self, path=None):
1057
"""Determine the appropriate modes for files and directories."""
1060
path = self._rel_controlfilename('')
1061
st = self._transport.stat(path)
1062
except errors.TransportNotPossible:
1063
self._dir_mode = 0755
1064
self._file_mode = 0644
1066
self._dir_mode = st.st_mode & 07777
1067
# Remove the sticky and execute bits for files
1068
self._file_mode = self._dir_mode & ~07111
1069
if not self._set_dir_mode:
1070
self._dir_mode = None
1071
if not self._set_file_mode:
1072
self._file_mode = None
829
return self.control_files._transport.abspath(name)
1074
831
def _check_format(self, format):
1075
832
"""Identify the branch format if needed.
1088
845
@needs_read_lock
1089
846
def get_root_id(self):
1090
847
"""See Branch.get_root_id."""
1091
inv = self.get_inventory(self.last_revision())
1092
return inv.root.file_id
848
tree = self.repository.revision_tree(self.last_revision())
849
return tree.inventory.root.file_id
851
def lock_write(self):
852
# TODO: test for failed two phase locks. This is known broken.
853
self.control_files.lock_write()
854
self.repository.lock_write()
857
# TODO: test for failed two phase locks. This is known broken.
858
self.control_files.lock_read()
859
self.repository.lock_read()
862
# TODO: test for failed two phase locks. This is known broken.
863
self.repository.unlock()
864
self.control_files.unlock()
866
def peek_lock_mode(self):
867
if self.control_files._lock_count == 0:
870
return self.control_files._lock_mode
1094
872
@needs_read_lock
1095
873
def print_file(self, file, revision_id):
1096
874
"""See Branch.print_file."""
1097
tree = self.revision_tree(revision_id)
1098
# use inventory as it was in that revision
1099
file_id = tree.inventory.path2id(file)
1102
revno = self.revision_id_to_revno(revision_id)
1103
except errors.NoSuchRevision:
1104
# TODO: This should not be BzrError,
1105
# but NoSuchFile doesn't fit either
1106
raise BzrError('%r is not present in revision %s'
1107
% (file, revision_id))
1109
raise BzrError('%r is not present in revision %s'
1111
tree.print_file(file_id)
875
return self.repository.print_file(file, revision_id)
1113
877
@needs_write_lock
1114
878
def append_revision(self, *revision_ids):
1122
886
@needs_write_lock
1123
887
def set_revision_history(self, rev_history):
1124
888
"""See Branch.set_revision_history."""
1125
old_revision = self.last_revision()
1126
new_revision = rev_history[-1]
1127
self.put_controlfile('revision-history', '\n'.join(rev_history))
1129
def has_revision(self, revision_id):
1130
"""See Branch.has_revision."""
1131
return (revision_id is None
1132
or self.revision_store.has_id(revision_id))
1135
def _get_revision_xml_file(self, revision_id):
1136
if not revision_id or not isinstance(revision_id, basestring):
1137
raise InvalidRevisionId(revision_id=revision_id, branch=self)
1139
return self.revision_store.get(revision_id)
1140
except (IndexError, KeyError):
1141
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1143
def get_revision_xml(self, revision_id):
1144
"""See Branch.get_revision_xml."""
1145
return self._get_revision_xml_file(revision_id).read()
1147
def get_revision(self, revision_id):
1148
"""See Branch.get_revision."""
1149
xml_file = self._get_revision_xml_file(revision_id)
1152
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1153
except SyntaxError, e:
1154
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
1158
assert r.revision_id == revision_id
1161
def get_revision_sha1(self, revision_id):
1162
"""See Branch.get_revision_sha1."""
1163
# In the future, revision entries will be signed. At that
1164
# point, it is probably best *not* to include the signature
1165
# in the revision hash. Because that lets you re-sign
1166
# the revision, (add signatures/remove signatures) and still
1167
# have all hash pointers stay consistent.
1168
# But for now, just hash the contents.
1169
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
1171
def get_ancestry(self, revision_id):
1172
"""See Branch.get_ancestry."""
1173
if revision_id is None:
1175
w = self._get_inventory_weave()
1176
return [None] + map(w.idx_to_name,
1177
w.inclusions([w.lookup(revision_id)]))
1179
def _get_inventory_weave(self):
1180
return self.control_weaves.get_weave('inventory',
1181
self.get_transaction())
1183
def get_inventory(self, revision_id):
1184
"""See Branch.get_inventory."""
1185
xml = self.get_inventory_xml(revision_id)
1186
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1188
def get_inventory_xml(self, revision_id):
1189
"""See Branch.get_inventory_xml."""
1191
assert isinstance(revision_id, basestring), type(revision_id)
1192
iw = self._get_inventory_weave()
1193
return iw.get_text(iw.lookup(revision_id))
1195
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1197
def get_inventory_sha1(self, revision_id):
1198
"""See Branch.get_inventory_sha1."""
1199
return self.get_revision(revision_id).inventory_sha1
1201
def get_revision_inventory(self, revision_id):
1202
"""See Branch.get_revision_inventory."""
1203
# TODO: Unify this with get_inventory()
1204
# bzr 0.0.6 and later imposes the constraint that the inventory_id
1205
# must be the same as its revision, so this is trivial.
1206
if revision_id == None:
1207
# This does not make sense: if there is no revision,
1208
# then it is the current tree inventory surely ?!
1209
# and thus get_root_id() is something that looks at the last
1210
# commit on the branch, and the get_root_id is an inventory check.
1211
raise NotImplementedError
1212
# return Inventory(self.get_root_id())
889
self.control_files.put_utf8(
890
'revision-history', '\n'.join(rev_history))
892
def get_revision_delta(self, revno):
893
"""Return the delta for one revision.
895
The delta is relative to its mainline predecessor, or the
896
empty tree for revision 1.
898
assert isinstance(revno, int)
899
rh = self.revision_history()
900
if not (1 <= revno <= len(rh)):
901
raise InvalidRevisionNumber(revno)
903
# revno is 1-based; list is 0-based
905
new_tree = self.repository.revision_tree(rh[revno-1])
907
old_tree = EmptyTree()
1214
return self.get_inventory(revision_id)
909
old_tree = self.repository.revision_tree(rh[revno-2])
910
return compare_trees(old_tree, new_tree)
1216
912
@needs_read_lock
1217
913
def revision_history(self):
1218
914
"""See Branch.revision_history."""
915
# FIXME are transactions bound to control files ? RBC 20051121
1219
916
transaction = self.get_transaction()
1220
917
history = transaction.map.find_revision_history()
1221
918
if history is not None:
1222
919
mutter("cache hit for revision-history in %s", self)
1223
920
return list(history)
1224
921
history = [l.rstrip('\r\n') for l in
1225
self.controlfile('revision-history', 'r').readlines()]
922
self.control_files.get_utf8('revision-history').readlines()]
1226
923
transaction.map.add_revision_history(history)
1227
924
# this call is disabled because revision_history is
1228
925
# not really an object yet, and the transaction is for objects.
1264
def revision_tree(self, revision_id):
1265
"""See Branch.revision_tree."""
1266
# TODO: refactor this to use an existing revision object
1267
# so we don't need to read it in twice.
1268
if revision_id == None or revision_id == NULL_REVISION:
1271
inv = self.get_revision_inventory(revision_id)
1272
return RevisionTree(self, inv, revision_id)
1274
962
def basis_tree(self):
1275
963
"""See Branch.basis_tree."""
1277
965
revision_id = self.revision_history()[-1]
966
# FIXME: This is an abstraction violation, the basis tree
967
# here as defined is on the working tree, the method should
968
# be too. The basis tree for a branch can be different than
969
# that for a working tree. RBC 20051207
1278
970
xml = self.working_tree().read_basis_inventory(revision_id)
1279
971
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1280
return RevisionTree(self, inv, revision_id)
972
return RevisionTree(self.repository, inv, revision_id)
1281
973
except (IndexError, NoSuchFile, NoWorkingTree), e:
1282
return self.revision_tree(self.last_revision())
974
return self.repository.revision_tree(self.last_revision())
1284
976
def working_tree(self):
1285
977
"""See Branch.working_tree."""
1286
978
from bzrlib.workingtree import WorkingTree
1287
979
from bzrlib.transport.local import LocalTransport
1288
if (self._transport.base.find('://') != -1 or
980
if (self.base.find('://') != -1 or
1289
981
not isinstance(self._transport, LocalTransport)):
1290
982
raise NoWorkingTree(self.base)
1291
983
return WorkingTree(self.base, branch=self)
1334
1026
def set_parent(self, url):
1335
1027
"""See Branch.set_parent."""
1336
1028
# TODO: Maybe delete old location files?
1337
from bzrlib.atomicfile import AtomicFile
1338
f = AtomicFile(self.controlfilename('parent'))
1029
# URLs should never be unicode, even on the local fs,
1030
# FIXUP this and get_parent in a future branch format bump:
1031
# read and rewrite the file, and have the new format code read
1032
# using .get not .get_utf8. RBC 20060125
1033
self.control_files.put_utf8('parent', url + '\n')
1345
1035
def tree_config(self):
1346
1036
return TreeConfig(self)
1348
def sign_revision(self, revision_id, gpg_strategy):
1349
"""See Branch.sign_revision."""
1350
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1351
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1354
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1355
"""See Branch.store_revision_signature."""
1356
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1038
def _get_truncated_history(self, revision_id):
1039
history = self.revision_history()
1040
if revision_id is None:
1043
idx = history.index(revision_id)
1045
raise InvalidRevisionId(revision_id=revision, branch=self)
1046
return history[:idx+1]
1049
def _clone_weave(self, to_location, revision=None, basis_branch=None):
1051
from bzrlib.workingtree import WorkingTree
1052
assert isinstance(to_location, basestring)
1053
if basis_branch is not None:
1054
note("basis_branch is not supported for fast weave copy yet.")
1056
history = self._get_truncated_history(revision)
1057
if not bzrlib.osutils.lexists(to_location):
1058
os.mkdir(to_location)
1059
branch_to = Branch.initialize(to_location)
1060
mutter("copy branch from %s to %s", self, branch_to)
1062
self.repository.copy(branch_to.repository)
1064
# must be done *after* history is copied across
1065
# FIXME duplicate code with base .clone().
1066
# .. would template method be useful here? RBC 20051207
1067
branch_to.set_parent(self.base)
1068
branch_to.append_revision(*history)
1069
# FIXME: this should be in workingtree.clone
1070
WorkingTree.create(branch_to, to_location).set_root_id(self.get_root_id())
1074
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
1075
print "FIXME: clone via create and fetch is probably faster when versioned file comes in."
1076
if to_branch_type is None:
1077
to_branch_type = BzrBranch
1079
if to_branch_type == BzrBranch \
1080
and self.repository.weave_store.listable() \
1081
and self.repository.revision_store.listable():
1082
return self._clone_weave(to_location, revision, basis_branch)
1084
return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
1359
1086
def fileid_involved_between_revs(self, from_revid, to_revid):
1360
1087
"""Find file_id(s) which are involved in the changes between revisions.