37
36
UnlistableBranch, NoSuchFile, NotVersionedError,
39
38
from bzrlib.textui import show_status
40
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
39
from bzrlib.config import TreeConfig
40
from bzrlib.decorators import needs_read_lock, needs_write_lock
43
41
from bzrlib.delta import compare_trees
44
from bzrlib.tree import EmptyTree, RevisionTree
42
import bzrlib.inventory as inventory
45
43
from bzrlib.inventory import Inventory
44
from bzrlib.lockable_files import LockableFiles
45
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
46
from bzrlib.repository import Repository
46
47
from bzrlib.store import copy_all
47
from bzrlib.store.text import TextStore
48
from bzrlib.store.weave import WeaveStore
49
from bzrlib.testament import Testament
50
48
import bzrlib.transactions as transactions
51
49
from bzrlib.transport import Transport, get_transport
50
from bzrlib.tree import EmptyTree, RevisionTree
54
from config import TreeConfig
57
55
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
58
56
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
59
57
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
60
## TODO: Maybe include checks for common corruption of newlines, etc?
60
# TODO: Maybe include checks for common corruption of newlines, etc?
63
62
# TODO: Some operations like log might retrieve the same revisions
64
63
# repeatedly to calculate deltas. We could perhaps have a weakref
65
64
# cache in memory to make this faster. In general anything can be
66
# cached in memory between lock and unlock operations.
68
def find_branch(*ignored, **ignored_too):
69
# XXX: leave this here for about one release, then remove it
70
raise NotImplementedError('find_branch() is not supported anymore, '
71
'please use one of the new branch constructors')
74
def needs_read_lock(unbound):
75
"""Decorate unbound to take out and release a read lock."""
76
def decorated(self, *args, **kwargs):
79
return unbound(self, *args, **kwargs)
85
def needs_write_lock(unbound):
86
"""Decorate unbound to take out and release a write lock."""
87
def decorated(self, *args, **kwargs):
90
return unbound(self, *args, **kwargs)
65
# cached in memory between lock and unlock operations. .. nb thats
66
# what the transaction identity map provides
95
69
######################################################################
195
167
raise NotImplementedError('abspath is abstract')
197
def controlfilename(self, file_or_path):
198
"""Return location relative to branch."""
199
raise NotImplementedError('controlfilename is abstract')
201
def controlfile(self, file_or_path, mode='r'):
202
"""Open a control file for this branch.
204
There are two classes of file in the control directory: text
205
and binary. binary files are untranslated byte streams. Text
206
control files are stored with Unix newlines and in UTF-8, even
207
if the platform or locale defaults are different.
209
Controlfiles should almost never be opened in write mode but
210
rather should be atomically copied and replaced using atomicfile.
212
raise NotImplementedError('controlfile is abstract')
214
def put_controlfile(self, path, f, encode=True):
215
"""Write an entry as a controlfile.
217
:param path: The path to put the file, relative to the .bzr control
219
:param f: A file-like or string object whose contents should be copied.
220
:param encode: If true, encode the contents as utf-8
222
raise NotImplementedError('put_controlfile is abstract')
224
def put_controlfiles(self, files, encode=True):
225
"""Write several entries as controlfiles.
227
:param files: A list of [(path, file)] pairs, where the path is the directory
228
underneath the bzr control directory
229
:param encode: If true, encode the contents as utf-8
231
raise NotImplementedError('put_controlfiles is abstract')
233
169
def get_root_id(self):
234
170
"""Return the id of this branches root"""
235
171
raise NotImplementedError('get_root_id is abstract')
237
def set_root_id(self, file_id):
238
raise NotImplementedError('set_root_id is abstract')
240
173
def print_file(self, file, revision_id):
241
174
"""Print `file` to stdout."""
242
175
raise NotImplementedError('print_file is abstract')
247
180
def set_revision_history(self, rev_history):
248
181
raise NotImplementedError('set_revision_history is abstract')
250
def has_revision(self, revision_id):
251
"""True if this branch has a copy of the revision.
253
This does not necessarily imply the revision is merge
254
or on the mainline."""
255
raise NotImplementedError('has_revision is abstract')
257
def get_revision_xml(self, revision_id):
258
raise NotImplementedError('get_revision_xml is abstract')
260
def get_revision(self, revision_id):
261
"""Return the Revision object for a named revision"""
262
raise NotImplementedError('get_revision is abstract')
264
def get_revision_delta(self, revno):
265
"""Return the delta for one revision.
267
The delta is relative to its mainline predecessor, or the
268
empty tree for revision 1.
270
assert isinstance(revno, int)
271
rh = self.revision_history()
272
if not (1 <= revno <= len(rh)):
273
raise InvalidRevisionNumber(revno)
275
# revno is 1-based; list is 0-based
277
new_tree = self.revision_tree(rh[revno-1])
279
old_tree = EmptyTree()
281
old_tree = self.revision_tree(rh[revno-2])
283
return compare_trees(old_tree, new_tree)
285
def get_revision_sha1(self, revision_id):
286
"""Hash the stored value of a revision, and return it."""
287
raise NotImplementedError('get_revision_sha1 is abstract')
289
def get_ancestry(self, revision_id):
290
"""Return a list of revision-ids integrated by a revision.
292
This currently returns a list, but the ordering is not guaranteed:
295
raise NotImplementedError('get_ancestry is abstract')
297
def get_inventory(self, revision_id):
298
"""Get Inventory object by hash."""
299
raise NotImplementedError('get_inventory is abstract')
301
def get_inventory_xml(self, revision_id):
302
"""Get inventory XML as a file object."""
303
raise NotImplementedError('get_inventory_xml is abstract')
305
def get_inventory_sha1(self, revision_id):
306
"""Return the sha1 hash of the inventory entry."""
307
raise NotImplementedError('get_inventory_sha1 is abstract')
309
def get_revision_inventory(self, revision_id):
310
"""Return inventory of a past revision."""
311
raise NotImplementedError('get_revision_inventory is abstract')
313
183
def revision_history(self):
314
184
"""Return sequence of revision hashes on to this branch."""
315
185
raise NotImplementedError('revision_history is abstract')
488
351
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
489
352
raise NotImplementedError('store_revision_signature is abstract')
354
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
355
"""Copy this branch into the existing directory to_location.
357
Returns the newly created branch object.
360
If not None, only revisions up to this point will be copied.
361
The head of the new branch will be that revision. Must be a
364
to_location -- The destination directory; must either exist and be
365
empty, or not exist, in which case it is created.
368
A local branch to copy revisions from, related to this branch.
369
This is used when branching from a remote (slow) branch, and we have
370
a local branch that might contain some relevant revisions.
373
Branch type of destination branch
375
# circular import protection
376
from bzrlib.merge import build_working_dir
378
assert isinstance(to_location, basestring)
379
if not bzrlib.osutils.lexists(to_location):
380
os.mkdir(to_location)
381
if to_branch_type is None:
382
to_branch_type = BzrBranch
383
br_to = to_branch_type.initialize(to_location)
384
mutter("copy branch from %s to %s", self, br_to)
385
if basis_branch is not None:
386
basis_branch.push_stores(br_to)
387
br_to.working_tree().set_root_id(self.get_root_id())
389
revision = self.last_revision()
390
br_to.update_revisions(self, stop_revision=revision)
391
br_to.set_parent(self.base)
392
build_working_dir(to_location)
491
396
def fileid_involved_between_revs(self, from_revid, to_revid):
492
397
""" This function returns the file_id(s) involved in the
493
398
changes between the from_revid revision and the to_revid
607
496
assert isinstance(transport, Transport), \
608
497
"%r is not a Transport" % transport
609
self._transport = transport
498
# TODO: jam 20060103 We create a clone of this transport at .bzr/
499
# and then we forget about it, should we keep a handle to it?
500
self._base = transport.base
501
self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR),
611
504
self._make_control()
612
505
self._check_format(relax_version_check)
615
def get_store(name, compressed=True, prefixed=False):
616
relpath = self._rel_controlfilename(unicode(name))
617
store = TextStore(self._transport.clone(relpath),
618
dir_mode=self._dir_mode,
619
file_mode=self._file_mode,
621
compressed=compressed)
624
def get_weave(name, prefixed=False):
625
relpath = self._rel_controlfilename(unicode(name))
626
ws = WeaveStore(self._transport.clone(relpath),
628
dir_mode=self._dir_mode,
629
file_mode=self._file_mode)
630
if self._transport.should_cache():
631
ws.enable_cache = True
634
if self._branch_format == 4:
635
self.inventory_store = get_store('inventory-store')
636
self.text_store = get_store('text-store')
637
self.revision_store = get_store('revision-store')
638
elif self._branch_format == 5:
639
self.control_weaves = get_weave(u'')
640
self.weave_store = get_weave(u'weaves')
641
self.revision_store = get_store(u'revision-store', compressed=False)
642
elif self._branch_format == 6:
643
self.control_weaves = get_weave(u'')
644
self.weave_store = get_weave(u'weaves', prefixed=True)
645
self.revision_store = get_store(u'revision-store', compressed=False,
647
self.revision_store.register_suffix('sig')
648
self._transaction = None
506
self.repository = Repository(transport, self._branch_format)
650
508
def __str__(self):
651
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
509
return '%s(%r)' % (self.__class__.__name__, self.base)
653
511
__repr__ = __str__
655
513
def __del__(self):
656
if self._lock_mode or self._lock:
657
# XXX: This should show something every time, and be suitable for
658
# headless operation and embedding
659
warn("branch %r was not explicitly unlocked" % self)
662
514
# TODO: It might be best to do this somewhere else,
663
515
# but it is nice for a Branch object to automatically
664
516
# cache it's information.
665
517
# Alternatively, we could have the Transport objects cache requests
666
518
# See the earlier discussion about how major objects (like Branch)
667
519
# should never expect their __del__ function to run.
520
# XXX: cache_root seems to be unused, 2006-01-13 mbp
668
521
if hasattr(self, 'cache_root') and self.cache_root is not None:
670
523
shutil.rmtree(self.cache_root)
673
526
self.cache_root = None
675
528
def _get_base(self):
677
return self._transport.base
680
531
base = property(_get_base, doc="The URL for the root of this branch.")
682
533
def _finish_transaction(self):
683
534
"""Exit the current transaction."""
684
if self._transaction is None:
685
raise errors.LockError('Branch %s is not in a transaction' %
687
transaction = self._transaction
688
self._transaction = None
535
return self.control_files._finish_transaction()
691
537
def get_transaction(self):
692
"""See Branch.get_transaction."""
693
if self._transaction is None:
694
return transactions.PassThroughTransaction()
696
return self._transaction
698
def _set_transaction(self, new_transaction):
538
"""Return the current active transaction.
540
If no transaction is active, this returns a passthrough object
541
for which all data is immediately flushed and no caching happens.
543
# this is an explicit function so that we can do tricky stuff
544
# when the storage in rev_storage is elsewhere.
545
# we probably need to hook the two 'lock a location' and
546
# 'have a transaction' together more delicately, so that
547
# we can have two locks (branch and storage) and one transaction
548
# ... and finishing the transaction unlocks both, but unlocking
549
# does not. - RBC 20051121
550
return self.control_files.get_transaction()
552
def _set_transaction(self, transaction):
699
553
"""Set a new active transaction."""
700
if self._transaction is not None:
701
raise errors.LockError('Branch %s is in a transaction already.' %
703
self._transaction = new_transaction
705
def lock_write(self):
706
#mutter("lock write: %s (%s)", self, self._lock_count)
707
# TODO: Upgrade locking to support using a Transport,
708
# and potentially a remote locking protocol
710
if self._lock_mode != 'w':
711
raise LockError("can't upgrade to a write lock from %r" %
713
self._lock_count += 1
715
self._lock = self._transport.lock_write(
716
self._rel_controlfilename('branch-lock'))
717
self._lock_mode = 'w'
719
self._set_transaction(transactions.PassThroughTransaction())
722
#mutter("lock read: %s (%s)", self, self._lock_count)
724
assert self._lock_mode in ('r', 'w'), \
725
"invalid lock mode %r" % self._lock_mode
726
self._lock_count += 1
728
self._lock = self._transport.lock_read(
729
self._rel_controlfilename('branch-lock'))
730
self._lock_mode = 'r'
732
self._set_transaction(transactions.ReadOnlyTransaction())
733
# 5K may be excessive, but hey, its a knob.
734
self.get_transaction().set_cache_size(5000)
737
#mutter("unlock: %s (%s)", self, self._lock_count)
738
if not self._lock_mode:
739
raise LockError('branch %r is not locked' % (self))
741
if self._lock_count > 1:
742
self._lock_count -= 1
744
self._finish_transaction()
747
self._lock_mode = self._lock_count = None
554
return self.control_files._set_transaction(transaction)
749
556
def abspath(self, name):
750
557
"""See Branch.abspath."""
751
return self._transport.abspath(name)
753
def _rel_controlfilename(self, file_or_path):
754
if not isinstance(file_or_path, basestring):
755
file_or_path = u'/'.join(file_or_path)
756
if file_or_path == '':
758
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
760
def controlfilename(self, file_or_path):
761
"""See Branch.controlfilename."""
762
return self._transport.abspath(self._rel_controlfilename(file_or_path))
764
def controlfile(self, file_or_path, mode='r'):
765
"""See Branch.controlfile."""
768
relpath = self._rel_controlfilename(file_or_path)
769
#TODO: codecs.open() buffers linewise, so it was overloaded with
770
# a much larger buffer, do we need to do the same for getreader/getwriter?
772
return self._transport.get(relpath)
774
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
776
# XXX: Do we really want errors='replace'? Perhaps it should be
777
# an error, or at least reported, if there's incorrectly-encoded
778
# data inside a file.
779
# <https://launchpad.net/products/bzr/+bug/3823>
780
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
782
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
784
raise BzrError("invalid controlfile mode %r" % mode)
786
def put_controlfile(self, path, f, encode=True):
787
"""See Branch.put_controlfile."""
788
self.put_controlfiles([(path, f)], encode=encode)
790
def put_controlfiles(self, files, encode=True):
791
"""See Branch.put_controlfiles."""
794
for path, f in files:
796
if isinstance(f, basestring):
797
f = f.encode('utf-8', 'replace')
799
f = codecs.getwriter('utf-8')(f, errors='replace')
800
path = self._rel_controlfilename(path)
801
ctrl_files.append((path, f))
802
self._transport.put_multi(ctrl_files, mode=self._file_mode)
804
def _find_modes(self, path=None):
805
"""Determine the appropriate modes for files and directories."""
808
path = self._rel_controlfilename('')
809
st = self._transport.stat(path)
810
except errors.TransportNotPossible:
811
self._dir_mode = 0755
812
self._file_mode = 0644
814
self._dir_mode = st.st_mode & 07777
815
# Remove the sticky and execute bits for files
816
self._file_mode = self._dir_mode & ~07111
817
if not self._set_dir_mode:
818
self._dir_mode = None
819
if not self._set_file_mode:
820
self._file_mode = None
558
return self.control_files._transport.abspath(name)
822
560
def _make_control(self):
823
561
from bzrlib.inventory import Inventory
852
585
('inventory', empty_inv),
853
586
('inventory.weave', empty_weave),
855
self._transport.mkdir_multi([cfn(d) for d in dirs], mode=self._dir_mode)
856
self.put_controlfiles(files)
857
mutter('created control directory in ' + self._transport.base)
588
cfe = self.control_files._escape
589
# FIXME: RBC 20060125 dont peek under the covers
590
self.control_files._transport.mkdir_multi([cfe(d) for d in dirs],
591
mode=self.control_files._dir_mode)
592
self.control_files.lock_write()
594
for file, content in files:
595
self.control_files.put_utf8(file, content)
596
mutter('created control directory in ' + self.base)
598
self.control_files.unlock()
859
600
def _check_format(self, relax_version_check):
860
601
"""Check this branch format is supported.
889
630
def get_root_id(self):
890
631
"""See Branch.get_root_id."""
891
inv = self.get_inventory(self.last_revision())
632
inv = self.repository.get_inventory(self.last_revision())
892
633
return inv.root.file_id
635
def lock_write(self):
636
# TODO: test for failed two phase locks. This is known broken.
637
self.control_files.lock_write()
638
self.repository.lock_write()
641
# TODO: test for failed two phase locks. This is known broken.
642
self.control_files.lock_read()
643
self.repository.lock_read()
646
# TODO: test for failed two phase locks. This is known broken.
647
self.repository.unlock()
648
self.control_files.unlock()
650
def peek_lock_mode(self):
651
if self.control_files._lock_count == 0:
654
return self.control_files._lock_mode
895
657
def print_file(self, file, revision_id):
896
658
"""See Branch.print_file."""
897
tree = self.revision_tree(revision_id)
898
# use inventory as it was in that revision
899
file_id = tree.inventory.path2id(file)
902
revno = self.revision_id_to_revno(revision_id)
903
except errors.NoSuchRevision:
904
# TODO: This should not be BzrError,
905
# but NoSuchFile doesn't fit either
906
raise BzrError('%r is not present in revision %s'
907
% (file, revision_id))
909
raise BzrError('%r is not present in revision %s'
911
tree.print_file(file_id)
659
return self.repository.print_file(file, revision_id)
913
661
@needs_write_lock
914
662
def append_revision(self, *revision_ids):
924
672
"""See Branch.set_revision_history."""
925
673
old_revision = self.last_revision()
926
674
new_revision = rev_history[-1]
927
self.put_controlfile('revision-history', '\n'.join(rev_history))
675
self.control_files.put_utf8(
676
'revision-history', '\n'.join(rev_history))
678
# FIXME: RBC 20051207 this smells wrong, last_revision in the
679
# working tree may be != to last_revision in the branch - so
680
# why is this passing in the branches last_revision ?
929
681
self.working_tree().set_last_revision(new_revision, old_revision)
930
682
except NoWorkingTree:
931
683
mutter('Unable to set_last_revision without a working tree.')
933
def has_revision(self, revision_id):
934
"""See Branch.has_revision."""
935
return (revision_id is None
936
or self.revision_store.has_id(revision_id))
939
def _get_revision_xml_file(self, revision_id):
940
if not revision_id or not isinstance(revision_id, basestring):
941
raise InvalidRevisionId(revision_id=revision_id, branch=self)
943
return self.revision_store.get(revision_id)
944
except (IndexError, KeyError):
945
raise bzrlib.errors.NoSuchRevision(self, revision_id)
947
def get_revision_xml(self, revision_id):
948
"""See Branch.get_revision_xml."""
949
return self._get_revision_xml_file(revision_id).read()
951
def get_revision(self, revision_id):
952
"""See Branch.get_revision."""
953
xml_file = self._get_revision_xml_file(revision_id)
956
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
957
except SyntaxError, e:
958
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
962
assert r.revision_id == revision_id
965
def get_revision_sha1(self, revision_id):
966
"""See Branch.get_revision_sha1."""
967
# In the future, revision entries will be signed. At that
968
# point, it is probably best *not* to include the signature
969
# in the revision hash. Because that lets you re-sign
970
# the revision, (add signatures/remove signatures) and still
971
# have all hash pointers stay consistent.
972
# But for now, just hash the contents.
973
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
975
def get_ancestry(self, revision_id):
976
"""See Branch.get_ancestry."""
977
if revision_id is None:
979
w = self._get_inventory_weave()
980
return [None] + map(w.idx_to_name,
981
w.inclusions([w.lookup(revision_id)]))
983
def _get_inventory_weave(self):
984
return self.control_weaves.get_weave('inventory',
985
self.get_transaction())
987
def get_inventory(self, revision_id):
988
"""See Branch.get_inventory."""
989
xml = self.get_inventory_xml(revision_id)
990
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
992
def get_inventory_xml(self, revision_id):
993
"""See Branch.get_inventory_xml."""
995
assert isinstance(revision_id, basestring), type(revision_id)
996
iw = self._get_inventory_weave()
997
return iw.get_text(iw.lookup(revision_id))
999
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1001
def get_inventory_sha1(self, revision_id):
1002
"""See Branch.get_inventory_sha1."""
1003
return self.get_revision(revision_id).inventory_sha1
1005
def get_revision_inventory(self, revision_id):
1006
"""See Branch.get_revision_inventory."""
1007
# TODO: Unify this with get_inventory()
1008
# bzr 0.0.6 and later imposes the constraint that the inventory_id
1009
# must be the same as its revision, so this is trivial.
1010
if revision_id == None:
1011
# This does not make sense: if there is no revision,
1012
# then it is the current tree inventory surely ?!
1013
# and thus get_root_id() is something that looks at the last
1014
# commit on the branch, and the get_root_id is an inventory check.
1015
raise NotImplementedError
1016
# return Inventory(self.get_root_id())
685
def get_revision_delta(self, revno):
686
"""Return the delta for one revision.
688
The delta is relative to its mainline predecessor, or the
689
empty tree for revision 1.
691
assert isinstance(revno, int)
692
rh = self.revision_history()
693
if not (1 <= revno <= len(rh)):
694
raise InvalidRevisionNumber(revno)
696
# revno is 1-based; list is 0-based
698
new_tree = self.repository.revision_tree(rh[revno-1])
700
old_tree = EmptyTree()
1018
return self.get_inventory(revision_id)
702
old_tree = self.repository.revision_tree(rh[revno-2])
703
return compare_trees(old_tree, new_tree)
1020
705
@needs_read_lock
1021
706
def revision_history(self):
1022
707
"""See Branch.revision_history."""
708
# FIXME are transactions bound to control files ? RBC 20051121
1023
709
transaction = self.get_transaction()
1024
710
history = transaction.map.find_revision_history()
1025
711
if history is not None:
1026
712
mutter("cache hit for revision-history in %s", self)
1027
713
return list(history)
1028
714
history = [l.rstrip('\r\n') for l in
1029
self.controlfile('revision-history', 'r').readlines()]
715
self.control_files.get_utf8('revision-history').readlines()]
1030
716
transaction.map.add_revision_history(history)
1031
717
# this call is disabled because revision_history is
1032
718
# not really an object yet, and the transaction is for objects.
1068
def revision_tree(self, revision_id):
1069
"""See Branch.revision_tree."""
1070
# TODO: refactor this to use an existing revision object
1071
# so we don't need to read it in twice.
1072
if revision_id == None or revision_id == NULL_REVISION:
1075
inv = self.get_revision_inventory(revision_id)
1076
return RevisionTree(self, inv, revision_id)
1078
755
def basis_tree(self):
1079
756
"""See Branch.basis_tree."""
1081
758
revision_id = self.revision_history()[-1]
759
# FIXME: This is an abstraction violation, the basis tree
760
# here as defined is on the working tree, the method should
761
# be too. The basis tree for a branch can be different than
762
# that for a working tree. RBC 20051207
1082
763
xml = self.working_tree().read_basis_inventory(revision_id)
1083
764
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1084
return RevisionTree(self, inv, revision_id)
765
return RevisionTree(self.repository, inv, revision_id)
1085
766
except (IndexError, NoSuchFile, NoWorkingTree), e:
1086
return self.revision_tree(self.last_revision())
767
return self.repository.revision_tree(self.last_revision())
1088
769
def working_tree(self):
1089
770
"""See Branch.working_tree."""
1090
771
from bzrlib.workingtree import WorkingTree
1091
if self._transport.base.find('://') != -1:
772
if self.base.find('://') != -1:
1092
773
raise NoWorkingTree(self.base)
1093
774
return WorkingTree(self.base, branch=self)
1136
817
def set_parent(self, url):
1137
818
"""See Branch.set_parent."""
1138
819
# TODO: Maybe delete old location files?
1139
from bzrlib.atomicfile import AtomicFile
1140
f = AtomicFile(self.controlfilename('parent'))
820
# URLs should never be unicode, even on the local fs,
821
# FIXUP this and get_parent in a future branch format bump:
822
# read and rewrite the file, and have the new format code read
823
# using .get not .get_utf8. RBC 20060125
824
self.control_files.put_utf8('parent', url + '\n')
1147
826
def tree_config(self):
1148
827
return TreeConfig(self)
1150
def sign_revision(self, revision_id, gpg_strategy):
1151
"""See Branch.sign_revision."""
1152
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1153
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1156
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1157
"""See Branch.store_revision_signature."""
1158
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
829
def _get_truncated_history(self, revision_id):
830
history = self.revision_history()
831
if revision_id is None:
834
idx = history.index(revision_id)
836
raise InvalidRevisionId(revision_id=revision, branch=self)
837
return history[:idx+1]
840
def _clone_weave(self, to_location, revision=None, basis_branch=None):
841
assert isinstance(to_location, basestring)
842
if basis_branch is not None:
843
note("basis_branch is not supported for fast weave copy yet.")
845
history = self._get_truncated_history(revision)
846
if not bzrlib.osutils.lexists(to_location):
847
os.mkdir(to_location)
848
branch_to = Branch.initialize(to_location)
849
mutter("copy branch from %s to %s", self, branch_to)
850
branch_to.working_tree().set_root_id(self.get_root_id())
852
self.repository.copy(branch_to.repository)
854
# must be done *after* history is copied across
855
# FIXME duplicate code with base .clone().
856
# .. would template method be useful here. RBC 20051207
857
branch_to.set_parent(self.base)
858
branch_to.append_revision(*history)
859
# circular import protection
860
from bzrlib.merge import build_working_dir
861
build_working_dir(to_location)
865
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
866
if to_branch_type is None:
867
to_branch_type = BzrBranch
869
if to_branch_type == BzrBranch \
870
and self.repository.weave_store.listable() \
871
and self.repository.revision_store.listable():
872
return self._clone_weave(to_location, revision, basis_branch)
874
return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
1161
876
def fileid_involved_between_revs(self, from_revid, to_revid):
1162
877
"""Find file_id(s) which are involved in the changes between revisions.