15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from copy import deepcopy
19
from cStringIO import StringIO
24
from unittest import TestSuite
22
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."
23
33
from cStringIO import StringIO
27
import bzrlib.inventory as inventory
28
from bzrlib.trace import mutter, note
29
from bzrlib.osutils import (isdir, quotefn,
30
rename, splitpath, sha_file,
31
file_kind, abspath, normpath, pathjoin)
37
from bzrlib.config import TreeConfig
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
from bzrlib.delta import compare_trees
32
40
import bzrlib.errors as errors
33
41
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
42
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError, UnlistableStore,
43
DivergedBranches, LockError,
44
UninitializableFormat,
36
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,
38
56
from bzrlib.textui import show_status
39
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
42
from bzrlib.delta import compare_trees
57
from bzrlib.trace import mutter, note
43
58
from bzrlib.tree import EmptyTree, RevisionTree
44
from bzrlib.inventory import Inventory
59
from bzrlib.repository import Repository
60
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
45
61
from bzrlib.store import copy_all
46
from bzrlib.store.text import TextStore
47
from bzrlib.store.weave import WeaveStore
48
from bzrlib.testament import Testament
62
from bzrlib.symbol_versioning import *
49
63
import bzrlib.transactions as transactions
50
64
from bzrlib.transport import Transport, get_transport
65
from bzrlib.tree import EmptyTree, RevisionTree
53
from config import TreeConfig
56
70
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
57
71
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
58
72
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
59
## TODO: Maybe include checks for common corruption of newlines, etc?
75
# TODO: Maybe include checks for common corruption of newlines, etc?
62
77
# TODO: Some operations like log might retrieve the same revisions
63
78
# repeatedly to calculate deltas. We could perhaps have a weakref
64
79
# cache in memory to make this faster. In general anything can be
65
# cached in memory between lock and unlock operations.
67
def find_branch(*ignored, **ignored_too):
68
# XXX: leave this here for about one release, then remove it
69
raise NotImplementedError('find_branch() is not supported anymore, '
70
'please use one of the new branch constructors')
73
def needs_read_lock(unbound):
74
"""Decorate unbound to take out and release a read lock."""
75
def decorated(self, *args, **kwargs):
78
return unbound(self, *args, **kwargs)
84
def needs_write_lock(unbound):
85
"""Decorate unbound to take out and release a write lock."""
86
def decorated(self, *args, **kwargs):
89
return unbound(self, *args, **kwargs)
80
# cached in memory between lock and unlock operations. .. nb thats
81
# what the transaction identity map provides
94
84
######################################################################
194
226
raise NotImplementedError('abspath is abstract')
196
def controlfilename(self, file_or_path):
197
"""Return location relative to branch."""
198
raise NotImplementedError('controlfilename is abstract')
200
def controlfile(self, file_or_path, mode='r'):
201
"""Open a control file for this branch.
203
There are two classes of file in the control directory: text
204
and binary. binary files are untranslated byte streams. Text
205
control files are stored with Unix newlines and in UTF-8, even
206
if the platform or locale defaults are different.
208
Controlfiles should almost never be opened in write mode but
209
rather should be atomically copied and replaced using atomicfile.
211
raise NotImplementedError('controlfile is abstract')
213
def put_controlfile(self, path, f, encode=True):
214
"""Write an entry as a controlfile.
216
:param path: The path to put the file, relative to the .bzr control
218
:param f: A file-like or string object whose contents should be copied.
219
:param encode: If true, encode the contents as utf-8
221
raise NotImplementedError('put_controlfile is abstract')
223
def put_controlfiles(self, files, encode=True):
224
"""Write several entries as controlfiles.
226
:param files: A list of [(path, file)] pairs, where the path is the directory
227
underneath the bzr control directory
228
:param encode: If true, encode the contents as utf-8
230
raise NotImplementedError('put_controlfiles is abstract')
232
228
def get_root_id(self):
233
229
"""Return the id of this branches root"""
234
230
raise NotImplementedError('get_root_id is abstract')
236
def set_root_id(self, file_id):
237
raise NotImplementedError('set_root_id is abstract')
239
232
def print_file(self, file, revision_id):
240
233
"""Print `file` to stdout."""
241
234
raise NotImplementedError('print_file is abstract')
246
239
def set_revision_history(self, rev_history):
247
240
raise NotImplementedError('set_revision_history is abstract')
249
def has_revision(self, revision_id):
250
"""True if this branch has a copy of the revision.
252
This does not necessarily imply the revision is merge
253
or on the mainline."""
254
raise NotImplementedError('has_revision is abstract')
256
def get_revision_xml(self, revision_id):
257
raise NotImplementedError('get_revision_xml is abstract')
259
def get_revision(self, revision_id):
260
"""Return the Revision object for a named revision"""
261
raise NotImplementedError('get_revision is abstract')
263
def get_revision_delta(self, revno):
264
"""Return the delta for one revision.
266
The delta is relative to its mainline predecessor, or the
267
empty tree for revision 1.
269
assert isinstance(revno, int)
270
rh = self.revision_history()
271
if not (1 <= revno <= len(rh)):
272
raise InvalidRevisionNumber(revno)
274
# revno is 1-based; list is 0-based
276
new_tree = self.revision_tree(rh[revno-1])
278
old_tree = EmptyTree()
280
old_tree = self.revision_tree(rh[revno-2])
282
return compare_trees(old_tree, new_tree)
284
def get_revision_sha1(self, revision_id):
285
"""Hash the stored value of a revision, and return it."""
286
raise NotImplementedError('get_revision_sha1 is abstract')
288
def get_ancestry(self, revision_id):
289
"""Return a list of revision-ids integrated by a revision.
291
This currently returns a list, but the ordering is not guaranteed:
294
raise NotImplementedError('get_ancestry is abstract')
296
def get_inventory(self, revision_id):
297
"""Get Inventory object by hash."""
298
raise NotImplementedError('get_inventory is abstract')
300
def get_inventory_xml(self, revision_id):
301
"""Get inventory XML as a file object."""
302
raise NotImplementedError('get_inventory_xml is abstract')
304
def get_inventory_sha1(self, revision_id):
305
"""Return the sha1 hash of the inventory entry."""
306
raise NotImplementedError('get_inventory_sha1 is abstract')
308
def get_revision_inventory(self, revision_id):
309
"""Return inventory of a past revision."""
310
raise NotImplementedError('get_revision_inventory is abstract')
312
242
def revision_history(self):
313
243
"""Return sequence of revision hashes on to this branch."""
314
244
raise NotImplementedError('revision_history is abstract')
481
403
if revno < 1 or revno > self.revno():
482
404
raise InvalidRevisionNumber(revno)
484
def sign_revision(self, revision_id, gpg_strategy):
485
raise NotImplementedError('sign_revision is abstract')
487
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
488
raise NotImplementedError('store_revision_signature is abstract')
406
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
407
"""Copy this branch into the existing directory to_location.
409
Returns the newly created branch object.
412
If not None, only revisions up to this point will be copied.
413
The head of the new branch will be that revision. Must be a
416
to_location -- The destination directory; must either exist and be
417
empty, or not exist, in which case it is created.
420
A local branch to copy revisions from, related to this branch.
421
This is used when branching from a remote (slow) branch, and we have
422
a local branch that might contain some relevant revisions.
425
Branch type of destination branch
427
from bzrlib.workingtree import WorkingTree
428
assert isinstance(to_location, basestring)
429
if not bzrlib.osutils.lexists(to_location):
430
os.mkdir(to_location)
431
if to_branch_type is None:
432
to_branch_type = BzrBranch
433
print "FIXME use a branch format here"
434
br_to = to_branch_type.initialize(to_location)
435
mutter("copy branch from %s to %s", self, br_to)
436
if basis_branch is not None:
437
basis_branch.push_stores(br_to)
439
revision = self.last_revision()
440
br_to.update_revisions(self, stop_revision=revision)
441
br_to.set_parent(self.base)
442
WorkingTree.create(br_to, to_location).set_root_id(self.get_root_id())
446
def fileid_involved_between_revs(self, from_revid, to_revid):
447
""" This function returns the file_id(s) involved in the
448
changes between the from_revid revision and the to_revid
451
raise NotImplementedError('fileid_involved_between_revs is abstract')
453
def fileid_involved(self, last_revid=None):
454
""" This function returns the file_id(s) involved in the
455
changes up to the revision last_revid
456
If no parametr is passed, then all file_id[s] present in the
457
repository are returned
459
raise NotImplementedError('fileid_involved is abstract')
461
def fileid_involved_by_set(self, changes):
462
""" This function returns the file_id(s) involved in the
463
changes present in the set 'changes'
465
raise NotImplementedError('fileid_involved_by_set is abstract')
467
def fileid_involved_between_revs(self, from_revid, to_revid):
468
""" This function returns the file_id(s) involved in the
469
changes between the from_revid revision and the to_revid
472
raise NotImplementedError('fileid_involved_between_revs is abstract')
474
def fileid_involved(self, last_revid=None):
475
""" This function returns the file_id(s) involved in the
476
changes up to the revision last_revid
477
If no parametr is passed, then all file_id[s] present in the
478
repository are returned
480
raise NotImplementedError('fileid_involved is abstract')
482
def fileid_involved_by_set(self, changes):
483
""" This function returns the file_id(s) involved in the
484
changes present in the set 'changes'
486
raise NotImplementedError('fileid_involved_by_set is abstract')
488
class BzrBranchFormat(object):
489
"""An encapsulation of the initialization and open routines for a format.
491
Formats provide three things:
492
* An initialization routine,
496
Formats are placed in an dict by their format string for reference
497
during branch opening. Its not required that these be instances, they
498
can be classes themselves with class methods - it simply depends on
499
whether state is needed for a given format or not.
501
Once a format is deprecated, just deprecate the initialize and open
502
methods on the format class. Do not deprecate the object, as the
503
object will be created every time regardless.
507
"""The known formats."""
510
def find_format(klass, transport):
511
"""Return the format registered for URL."""
513
format_string = transport.get(".bzr/branch-format").read()
514
return klass._formats[format_string]
516
raise NotBranchError(path=transport.base)
518
raise errors.UnknownFormatError(format_string)
520
def get_format_string(self):
521
"""Return the ASCII format string that identifies this format."""
522
raise NotImplementedError(self.get_format_string)
524
def _find_modes(self, t):
525
"""Determine the appropriate modes for files and directories.
527
FIXME: When this merges into, or from storage,
528
this code becomes delgatable to a LockableFiles instance.
530
For now its cribbed and returns (dir_mode, file_mode)
534
except errors.TransportNotPossible:
538
dir_mode = st.st_mode & 07777
539
# Remove the sticky and execute bits for files
540
file_mode = dir_mode & ~07111
541
if not BzrBranch._set_dir_mode:
543
if not BzrBranch._set_file_mode:
545
return dir_mode, file_mode
547
def initialize(self, url):
548
"""Create a branch of this format at url and return an open branch."""
549
t = get_transport(url)
550
from bzrlib.weavefile import write_weave_v5
551
from bzrlib.weave import Weave
553
# Create an empty weave
555
bzrlib.weavefile.write_weave_v5(Weave(), sio)
556
empty_weave = sio.getvalue()
558
# Since we don't have a .bzr directory, inherit the
559
# mode from the root directory
560
temp_control = LockableFiles(t, '')
561
temp_control._transport.mkdir('.bzr',
562
mode=temp_control._dir_mode)
563
file_mode = temp_control._file_mode
565
mutter('created control directory in ' + t.base)
566
control = t.clone('.bzr')
567
dirs = ['revision-store', 'weaves']
568
lock_file = 'branch-lock'
569
utf8_files = [('README',
570
"This is a Bazaar-NG control directory.\n"
571
"Do not change any files in this directory.\n"),
572
('branch-format', self.get_format_string()),
573
('revision-history', ''),
576
files = [('inventory.weave', StringIO(empty_weave)),
579
# FIXME: RBC 20060125 dont peek under the covers
580
# NB: no need to escape relative paths that are url safe.
581
control.put(lock_file, StringIO(), mode=file_mode)
582
control_files = LockableFiles(control, lock_file)
583
control_files.lock_write()
584
control_files._transport.mkdir_multi(dirs,
585
mode=control_files._dir_mode)
587
for file, content in utf8_files:
588
control_files.put_utf8(file, content)
589
for file, content in files:
590
control_files.put(file, content)
592
control_files.unlock()
593
return BzrBranch(t, _format=self, _control_files=control_files)
595
def is_supported(self):
596
"""Is this format supported?
598
Supported formats can be initialized and opened.
599
Unsupported formats may not support initialization or committing or
600
some other features depending on the reason for not being supported.
604
def open(self, transport):
605
"""Fill out the data in branch for the branch at url."""
606
return BzrBranch(transport, _format=self)
609
def register_format(klass, format):
610
klass._formats[format.get_format_string()] = format
613
def unregister_format(klass, format):
614
assert klass._formats[format.get_format_string()] is format
615
del klass._formats[format.get_format_string()]
618
class BzrBranchFormat4(BzrBranchFormat):
619
"""Bzr branch format 4.
623
- TextStores for texts, inventories,revisions.
625
This format is deprecated: it indexes texts using a text it which is
626
removed in format 5; write support for this format has been removed.
629
def get_format_string(self):
630
"""See BzrBranchFormat.get_format_string()."""
631
return BZR_BRANCH_FORMAT_4
633
def initialize(self, url):
634
"""Format 4 branches cannot be created."""
635
raise UninitializableFormat(self)
637
def is_supported(self):
638
"""Format 4 is not supported.
640
It is not supported because the model changed from 4 to 5 and the
641
conversion logic is expensive - so doing it on the fly was not
647
class BzrBranchFormat5(BzrBranchFormat):
648
"""Bzr branch format 5.
651
- weaves for file texts and inventory
653
- TextStores for revisions and signatures.
656
def get_format_string(self):
657
"""See BzrBranchFormat.get_format_string()."""
658
return BZR_BRANCH_FORMAT_5
661
class BzrBranchFormat6(BzrBranchFormat):
662
"""Bzr branch format 6.
665
- weaves for file texts and inventory
666
- hash subdirectory based stores.
667
- TextStores for revisions and signatures.
670
def get_format_string(self):
671
"""See BzrBranchFormat.get_format_string()."""
672
return BZR_BRANCH_FORMAT_6
675
BzrBranchFormat.register_format(BzrBranchFormat4())
676
BzrBranchFormat.register_format(BzrBranchFormat5())
677
BzrBranchFormat.register_format(BzrBranchFormat6())
679
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
680
# make sure that ancestry.weave is deleted (it is never used, but
681
# used to be created)
490
684
class BzrBranch(Branch):
491
685
"""A branch stored in the actual filesystem.
563
743
assert isinstance(transport, Transport), \
564
744
"%r is not a Transport" % transport
565
745
self._transport = transport
568
self._check_format(relax_version_check)
571
def get_store(name, compressed=True, prefixed=False):
572
relpath = self._rel_controlfilename(unicode(name))
573
store = TextStore(self._transport.clone(relpath),
574
dir_mode=self._dir_mode,
575
file_mode=self._file_mode,
577
compressed=compressed)
580
def get_weave(name, prefixed=False):
581
relpath = self._rel_controlfilename(unicode(name))
582
ws = WeaveStore(self._transport.clone(relpath),
584
dir_mode=self._dir_mode,
585
file_mode=self._file_mode)
586
if self._transport.should_cache():
587
ws.enable_cache = True
590
if self._branch_format == 4:
591
self.inventory_store = get_store('inventory-store')
592
self.text_store = get_store('text-store')
593
self.revision_store = get_store('revision-store')
594
elif self._branch_format == 5:
595
self.control_weaves = get_weave(u'')
596
self.weave_store = get_weave(u'weaves')
597
self.revision_store = get_store(u'revision-store', compressed=False)
598
elif self._branch_format == 6:
599
self.control_weaves = get_weave(u'')
600
self.weave_store = get_weave(u'weaves', prefixed=True)
601
self.revision_store = get_store(u'revision-store', compressed=False,
603
self.revision_store.register_suffix('sig')
604
self._transaction = None
746
self._base = self._transport.base
747
if _control_files is None:
748
_control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
750
self.control_files = _control_files
751
if deprecated_passed(init):
752
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
753
"deprecated as of bzr 0.8. Please use Branch.create().",
757
# this is slower than before deprecation, oh well never mind.
759
self._initialize(transport.base)
760
self._check_format(_format)
761
if deprecated_passed(relax_version_check):
762
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
763
"relax_version_check parameter is deprecated as of bzr 0.8. "
764
"Please use Branch.open_downlevel, or a BzrBranchFormat's "
768
if (not relax_version_check
769
and not self._branch_format.is_supported()):
770
raise errors.UnsupportedFormatError(
771
'sorry, branch format %r not supported' % fmt,
772
['use a different bzr version',
773
'or remove the .bzr directory'
774
' and "bzr init" again'])
775
self.repository = Repository(transport, self._branch_format)
779
def _initialize(base):
780
"""Create a bzr branch in the latest format."""
781
return BzrBranchFormat6().initialize(base)
606
783
def __str__(self):
607
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
784
return '%s(%r)' % (self.__class__.__name__, self.base)
609
786
__repr__ = __str__
611
788
def __del__(self):
612
if self._lock_mode or self._lock:
613
# XXX: This should show something every time, and be suitable for
614
# headless operation and embedding
615
warn("branch %r was not explicitly unlocked" % self)
618
789
# TODO: It might be best to do this somewhere else,
619
790
# but it is nice for a Branch object to automatically
620
791
# cache it's information.
621
792
# Alternatively, we could have the Transport objects cache requests
622
793
# See the earlier discussion about how major objects (like Branch)
623
794
# should never expect their __del__ function to run.
795
# XXX: cache_root seems to be unused, 2006-01-13 mbp
624
796
if hasattr(self, 'cache_root') and self.cache_root is not None:
626
798
shutil.rmtree(self.cache_root)
629
801
self.cache_root = None
631
803
def _get_base(self):
633
return self._transport.base
636
806
base = property(_get_base, doc="The URL for the root of this branch.")
638
808
def _finish_transaction(self):
639
809
"""Exit the current transaction."""
640
if self._transaction is None:
641
raise errors.LockError('Branch %s is not in a transaction' %
643
transaction = self._transaction
644
self._transaction = None
810
return self.control_files._finish_transaction()
647
812
def get_transaction(self):
648
"""See Branch.get_transaction."""
649
if self._transaction is None:
650
return transactions.PassThroughTransaction()
652
return self._transaction
654
def _set_transaction(self, new_transaction):
813
"""Return the current active transaction.
815
If no transaction is active, this returns a passthrough object
816
for which all data is immediately flushed and no caching happens.
818
# this is an explicit function so that we can do tricky stuff
819
# when the storage in rev_storage is elsewhere.
820
# we probably need to hook the two 'lock a location' and
821
# 'have a transaction' together more delicately, so that
822
# we can have two locks (branch and storage) and one transaction
823
# ... and finishing the transaction unlocks both, but unlocking
824
# does not. - RBC 20051121
825
return self.control_files.get_transaction()
827
def _set_transaction(self, transaction):
655
828
"""Set a new active transaction."""
656
if self._transaction is not None:
657
raise errors.LockError('Branch %s is in a transaction already.' %
659
self._transaction = new_transaction
661
def lock_write(self):
662
#mutter("lock write: %s (%s)", self, self._lock_count)
663
# TODO: Upgrade locking to support using a Transport,
664
# and potentially a remote locking protocol
666
if self._lock_mode != 'w':
667
raise LockError("can't upgrade to a write lock from %r" %
669
self._lock_count += 1
671
self._lock = self._transport.lock_write(
672
self._rel_controlfilename('branch-lock'))
673
self._lock_mode = 'w'
675
self._set_transaction(transactions.PassThroughTransaction())
678
#mutter("lock read: %s (%s)", self, self._lock_count)
680
assert self._lock_mode in ('r', 'w'), \
681
"invalid lock mode %r" % self._lock_mode
682
self._lock_count += 1
684
self._lock = self._transport.lock_read(
685
self._rel_controlfilename('branch-lock'))
686
self._lock_mode = 'r'
688
self._set_transaction(transactions.ReadOnlyTransaction())
689
# 5K may be excessive, but hey, its a knob.
690
self.get_transaction().set_cache_size(5000)
693
#mutter("unlock: %s (%s)", self, self._lock_count)
694
if not self._lock_mode:
695
raise LockError('branch %r is not locked' % (self))
697
if self._lock_count > 1:
698
self._lock_count -= 1
700
self._finish_transaction()
703
self._lock_mode = self._lock_count = None
829
return self.control_files._set_transaction(transaction)
705
831
def abspath(self, name):
706
832
"""See Branch.abspath."""
707
return self._transport.abspath(name)
709
def _rel_controlfilename(self, file_or_path):
710
if not isinstance(file_or_path, basestring):
711
file_or_path = u'/'.join(file_or_path)
712
if file_or_path == '':
714
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
716
def controlfilename(self, file_or_path):
717
"""See Branch.controlfilename."""
718
return self._transport.abspath(self._rel_controlfilename(file_or_path))
720
def controlfile(self, file_or_path, mode='r'):
721
"""See Branch.controlfile."""
724
relpath = self._rel_controlfilename(file_or_path)
725
#TODO: codecs.open() buffers linewise, so it was overloaded with
726
# a much larger buffer, do we need to do the same for getreader/getwriter?
728
return self._transport.get(relpath)
730
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
732
# XXX: Do we really want errors='replace'? Perhaps it should be
733
# an error, or at least reported, if there's incorrectly-encoded
734
# data inside a file.
735
# <https://launchpad.net/products/bzr/+bug/3823>
736
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
738
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
740
raise BzrError("invalid controlfile mode %r" % mode)
742
def put_controlfile(self, path, f, encode=True):
743
"""See Branch.put_controlfile."""
744
self.put_controlfiles([(path, f)], encode=encode)
746
def put_controlfiles(self, files, encode=True):
747
"""See Branch.put_controlfiles."""
750
for path, f in files:
752
if isinstance(f, basestring):
753
f = f.encode('utf-8', 'replace')
755
f = codecs.getwriter('utf-8')(f, errors='replace')
756
path = self._rel_controlfilename(path)
757
ctrl_files.append((path, f))
758
self._transport.put_multi(ctrl_files, mode=self._file_mode)
760
def _find_modes(self, path=None):
761
"""Determine the appropriate modes for files and directories."""
764
path = self._rel_controlfilename('')
765
st = self._transport.stat(path)
766
except errors.TransportNotPossible:
767
self._dir_mode = 0755
768
self._file_mode = 0644
770
self._dir_mode = st.st_mode & 07777
771
# Remove the sticky and execute bits for files
772
self._file_mode = self._dir_mode & ~07111
773
if not self._set_dir_mode:
774
self._dir_mode = None
775
if not self._set_file_mode:
776
self._file_mode = None
778
def _make_control(self):
779
from bzrlib.inventory import Inventory
780
from bzrlib.weavefile import write_weave_v5
781
from bzrlib.weave import Weave
783
# Create an empty inventory
785
# if we want per-tree root ids then this is the place to set
786
# them; they're not needed for now and so ommitted for
788
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
789
empty_inv = sio.getvalue()
791
bzrlib.weavefile.write_weave_v5(Weave(), sio)
792
empty_weave = sio.getvalue()
794
cfn = self._rel_controlfilename
795
# Since we don't have a .bzr directory, inherit the
796
# mode from the root directory
797
self._find_modes(u'.')
799
dirs = ['', 'revision-store', 'weaves']
801
"This is a Bazaar-NG control directory.\n"
802
"Do not change any files in this directory.\n"),
803
('branch-format', BZR_BRANCH_FORMAT_6),
804
('revision-history', ''),
807
('pending-merges', ''),
808
('inventory', empty_inv),
809
('inventory.weave', empty_weave),
810
('ancestry.weave', empty_weave)
812
self._transport.mkdir_multi([cfn(d) for d in dirs], mode=self._dir_mode)
813
self.put_controlfiles(files)
814
mutter('created control directory in ' + self._transport.base)
816
def _check_format(self, relax_version_check):
817
"""Check this branch format is supported.
819
The format level is stored, as an integer, in
833
return self.control_files._transport.abspath(name)
835
def _check_format(self, format):
836
"""Identify the branch format if needed.
838
The format is stored as a reference to the format object in
820
839
self._branch_format for code that needs to check it later.
822
In the future, we might need different in-memory Branch
823
classes to support downlevel branches. But not yet.
841
The format parameter is either None or the branch format class
842
used to open this branch.
826
fmt = self.controlfile('branch-format', 'r').read()
828
raise NotBranchError(path=self.base)
829
mutter("got branch format %r", fmt)
830
if fmt == BZR_BRANCH_FORMAT_6:
831
self._branch_format = 6
832
elif fmt == BZR_BRANCH_FORMAT_5:
833
self._branch_format = 5
834
elif fmt == BZR_BRANCH_FORMAT_4:
835
self._branch_format = 4
837
if (not relax_version_check
838
and self._branch_format not in (5, 6)):
839
raise errors.UnsupportedFormatError(
840
'sorry, branch format %r not supported' % fmt,
841
['use a different bzr version',
842
'or remove the .bzr directory'
843
' and "bzr init" again'])
845
format = BzrBranchFormat.find_format(self._transport)
846
self._branch_format = format
847
mutter("got branch format %s", self._branch_format)
846
850
def get_root_id(self):
847
851
"""See Branch.get_root_id."""
848
inv = self.get_inventory(self.last_revision())
849
return inv.root.file_id
852
tree = self.repository.revision_tree(self.last_revision())
853
return tree.inventory.root.file_id
855
def lock_write(self):
856
# TODO: test for failed two phase locks. This is known broken.
857
self.control_files.lock_write()
858
self.repository.lock_write()
861
# TODO: test for failed two phase locks. This is known broken.
862
self.control_files.lock_read()
863
self.repository.lock_read()
866
# TODO: test for failed two phase locks. This is known broken.
867
self.repository.unlock()
868
self.control_files.unlock()
870
def peek_lock_mode(self):
871
if self.control_files._lock_count == 0:
874
return self.control_files._lock_mode
852
877
def print_file(self, file, revision_id):
853
878
"""See Branch.print_file."""
854
tree = self.revision_tree(revision_id)
855
# use inventory as it was in that revision
856
file_id = tree.inventory.path2id(file)
859
revno = self.revision_id_to_revno(revision_id)
860
except errors.NoSuchRevision:
861
# TODO: This should not be BzrError,
862
# but NoSuchFile doesn't fit either
863
raise BzrError('%r is not present in revision %s'
864
% (file, revision_id))
866
raise BzrError('%r is not present in revision %s'
868
tree.print_file(file_id)
879
return self.repository.print_file(file, revision_id)
870
881
@needs_write_lock
871
882
def append_revision(self, *revision_ids):
879
890
@needs_write_lock
880
891
def set_revision_history(self, rev_history):
881
892
"""See Branch.set_revision_history."""
882
old_revision = self.last_revision()
883
new_revision = rev_history[-1]
884
self.put_controlfile('revision-history', '\n'.join(rev_history))
886
self.working_tree().set_last_revision(new_revision, old_revision)
887
except NoWorkingTree:
888
mutter('Unable to set_last_revision without a working tree.')
890
def has_revision(self, revision_id):
891
"""See Branch.has_revision."""
892
return (revision_id is None
893
or self.revision_store.has_id(revision_id))
896
def _get_revision_xml_file(self, revision_id):
897
if not revision_id or not isinstance(revision_id, basestring):
898
raise InvalidRevisionId(revision_id=revision_id, branch=self)
900
return self.revision_store.get(revision_id)
901
except (IndexError, KeyError):
902
raise bzrlib.errors.NoSuchRevision(self, revision_id)
904
def get_revision_xml(self, revision_id):
905
"""See Branch.get_revision_xml."""
906
return self._get_revision_xml_file(revision_id).read()
908
def get_revision(self, revision_id):
909
"""See Branch.get_revision."""
910
xml_file = self._get_revision_xml_file(revision_id)
913
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
914
except SyntaxError, e:
915
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
919
assert r.revision_id == revision_id
922
def get_revision_sha1(self, revision_id):
923
"""See Branch.get_revision_sha1."""
924
# In the future, revision entries will be signed. At that
925
# point, it is probably best *not* to include the signature
926
# in the revision hash. Because that lets you re-sign
927
# the revision, (add signatures/remove signatures) and still
928
# have all hash pointers stay consistent.
929
# But for now, just hash the contents.
930
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
932
def get_ancestry(self, revision_id):
933
"""See Branch.get_ancestry."""
934
if revision_id is None:
936
w = self._get_inventory_weave()
937
return [None] + map(w.idx_to_name,
938
w.inclusions([w.lookup(revision_id)]))
940
def _get_inventory_weave(self):
941
return self.control_weaves.get_weave('inventory',
942
self.get_transaction())
944
def get_inventory(self, revision_id):
945
"""See Branch.get_inventory."""
946
xml = self.get_inventory_xml(revision_id)
947
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
949
def get_inventory_xml(self, revision_id):
950
"""See Branch.get_inventory_xml."""
952
assert isinstance(revision_id, basestring), type(revision_id)
953
iw = self._get_inventory_weave()
954
return iw.get_text(iw.lookup(revision_id))
956
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
958
def get_inventory_sha1(self, revision_id):
959
"""See Branch.get_inventory_sha1."""
960
return self.get_revision(revision_id).inventory_sha1
962
def get_revision_inventory(self, revision_id):
963
"""See Branch.get_revision_inventory."""
964
# TODO: Unify this with get_inventory()
965
# bzr 0.0.6 and later imposes the constraint that the inventory_id
966
# must be the same as its revision, so this is trivial.
967
if revision_id == None:
968
# This does not make sense: if there is no revision,
969
# then it is the current tree inventory surely ?!
970
# and thus get_root_id() is something that looks at the last
971
# commit on the branch, and the get_root_id is an inventory check.
972
raise NotImplementedError
973
# return Inventory(self.get_root_id())
893
self.control_files.put_utf8(
894
'revision-history', '\n'.join(rev_history))
896
def get_revision_delta(self, revno):
897
"""Return the delta for one revision.
899
The delta is relative to its mainline predecessor, or the
900
empty tree for revision 1.
902
assert isinstance(revno, int)
903
rh = self.revision_history()
904
if not (1 <= revno <= len(rh)):
905
raise InvalidRevisionNumber(revno)
907
# revno is 1-based; list is 0-based
909
new_tree = self.repository.revision_tree(rh[revno-1])
911
old_tree = EmptyTree()
975
return self.get_inventory(revision_id)
913
old_tree = self.repository.revision_tree(rh[revno-2])
914
return compare_trees(old_tree, new_tree)
978
917
def revision_history(self):
979
918
"""See Branch.revision_history."""
919
# FIXME are transactions bound to control files ? RBC 20051121
980
920
transaction = self.get_transaction()
981
921
history = transaction.map.find_revision_history()
982
922
if history is not None:
983
923
mutter("cache hit for revision-history in %s", self)
984
924
return list(history)
985
925
history = [l.rstrip('\r\n') for l in
986
self.controlfile('revision-history', 'r').readlines()]
926
self.control_files.get_utf8('revision-history').readlines()]
987
927
transaction.map.add_revision_history(history)
988
928
# this call is disabled because revision_history is
989
929
# not really an object yet, and the transaction is for objects.
1093
1030
def set_parent(self, url):
1094
1031
"""See Branch.set_parent."""
1095
1032
# TODO: Maybe delete old location files?
1096
from bzrlib.atomicfile import AtomicFile
1097
f = AtomicFile(self.controlfilename('parent'))
1033
# URLs should never be unicode, even on the local fs,
1034
# FIXUP this and get_parent in a future branch format bump:
1035
# read and rewrite the file, and have the new format code read
1036
# using .get not .get_utf8. RBC 20060125
1037
self.control_files.put_utf8('parent', url + '\n')
1104
1039
def tree_config(self):
1105
1040
return TreeConfig(self)
1107
def sign_revision(self, revision_id, gpg_strategy):
1108
"""See Branch.sign_revision."""
1109
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1110
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1113
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1114
"""See Branch.store_revision_signature."""
1115
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1042
def _get_truncated_history(self, revision_id):
1043
history = self.revision_history()
1044
if revision_id is None:
1047
idx = history.index(revision_id)
1049
raise InvalidRevisionId(revision_id=revision, branch=self)
1050
return history[:idx+1]
1053
def _clone_weave(self, to_location, revision=None, basis_branch=None):
1055
from bzrlib.workingtree import WorkingTree
1056
assert isinstance(to_location, basestring)
1057
if basis_branch is not None:
1058
note("basis_branch is not supported for fast weave copy yet.")
1060
history = self._get_truncated_history(revision)
1061
if not bzrlib.osutils.lexists(to_location):
1062
os.mkdir(to_location)
1063
branch_to = Branch.initialize(to_location)
1064
mutter("copy branch from %s to %s", self, branch_to)
1066
self.repository.copy(branch_to.repository)
1068
# must be done *after* history is copied across
1069
# FIXME duplicate code with base .clone().
1070
# .. would template method be useful here? RBC 20051207
1071
branch_to.set_parent(self.base)
1072
branch_to.append_revision(*history)
1073
# FIXME: this should be in workingtree.clone
1074
WorkingTree.create(branch_to, to_location).set_root_id(self.get_root_id())
1078
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
1079
print "FIXME: clone via create and fetch is probably faster when versioned file comes in."
1080
if to_branch_type is None:
1081
to_branch_type = BzrBranch
1083
if to_branch_type == BzrBranch \
1084
and self.repository.weave_store.listable() \
1085
and self.repository.revision_store.listable():
1086
return self._clone_weave(to_location, revision, basis_branch)
1088
return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
1090
def fileid_involved_between_revs(self, from_revid, to_revid):
1091
"""Find file_id(s) which are involved in the changes between revisions.
1093
This determines the set of revisions which are involved, and then
1094
finds all file ids affected by those revisions.
1096
# TODO: jam 20060119 This code assumes that w.inclusions will
1097
# always be correct. But because of the presence of ghosts
1098
# it is possible to be wrong.
1099
# One specific example from Robert Collins:
1100
# Two branches, with revisions ABC, and AD
1101
# C is a ghost merge of D.
1102
# Inclusions doesn't recognize D as an ancestor.
1103
# If D is ever merged in the future, the weave
1104
# won't be fixed, because AD never saw revision C
1105
# to cause a conflict which would force a reweave.
1106
w = self.repository.get_inventory_weave()
1107
from_set = set(w.inclusions([w.lookup(from_revid)]))
1108
to_set = set(w.inclusions([w.lookup(to_revid)]))
1109
included = to_set.difference(from_set)
1110
changed = map(w.idx_to_name, included)
1111
return self._fileid_involved_by_set(changed)
1113
def fileid_involved(self, last_revid=None):
1114
"""Find all file_ids modified in the ancestry of last_revid.
1116
:param last_revid: If None, last_revision() will be used.
1118
w = self.repository.get_inventory_weave()
1120
changed = set(w._names)
1122
included = w.inclusions([w.lookup(last_revid)])
1123
changed = map(w.idx_to_name, included)
1124
return self._fileid_involved_by_set(changed)
1126
def fileid_involved_by_set(self, changes):
1127
"""Find all file_ids modified by the set of revisions passed in.
1129
:param changes: A set() of revision ids
1131
# TODO: jam 20060119 This line does *nothing*, remove it.
1132
# or better yet, change _fileid_involved_by_set so
1133
# that it takes the inventory weave, rather than
1134
# pulling it out by itself.
1135
w = self.repository.get_inventory_weave()
1136
return self._fileid_involved_by_set(changes)
1138
def _fileid_involved_by_set(self, changes):
1139
"""Find the set of file-ids affected by the set of revisions.
1141
:param changes: A set() of revision ids.
1142
:return: A set() of file ids.
1144
This peaks at the Weave, interpreting each line, looking to
1145
see if it mentions one of the revisions. And if so, includes
1146
the file id mentioned.
1147
This expects both the Weave format, and the serialization
1148
to have a single line per file/directory, and to have
1149
fileid="" and revision="" on that line.
1151
assert (isinstance(self._branch_format, BzrBranchFormat5) or
1152
isinstance(self._branch_format, BzrBranchFormat6)), \
1153
"fileid_involved only supported for branches which store inventory as xml"
1155
w = self.repository.get_inventory_weave()
1157
for line in w._weave:
1159
# it is ugly, but it is due to the weave structure
1160
if not isinstance(line, basestring): continue
1162
start = line.find('file_id="')+9
1163
if start < 9: continue
1164
end = line.find('"', start)
1166
file_id = xml.sax.saxutils.unescape(line[start:end])
1168
# check if file_id is already present
1169
if file_id in file_ids: continue
1171
start = line.find('revision="')+10
1172
if start < 10: continue
1173
end = line.find('"', start)
1175
revision_id = xml.sax.saxutils.unescape(line[start:end])
1177
if revision_id in changes:
1178
file_ids.add(file_id)
1183
Branch.set_default_initializer(BzrBranch._initialize)
1186
class BranchTestProviderAdapter(object):
1187
"""A tool to generate a suite testing multiple branch formats at once.
1189
This is done by copying the test once for each transport and injecting
1190
the transport_server, transport_readonly_server, and branch_format
1191
classes into each copy. Each copy is also given a new id() to make it
1195
def __init__(self, transport_server, transport_readonly_server, formats):
1196
self._transport_server = transport_server
1197
self._transport_readonly_server = transport_readonly_server
1198
self._formats = formats
1200
def adapt(self, test):
1201
result = TestSuite()
1202
for format in self._formats:
1203
new_test = deepcopy(test)
1204
new_test.transport_server = self._transport_server
1205
new_test.transport_readonly_server = self._transport_readonly_server
1206
new_test.branch_format = format
1207
def make_new_test_id():
1208
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1209
return lambda: new_id
1210
new_test.id = make_new_test_id()
1211
result.addTest(new_test)
1119
1215
class ScratchBranch(BzrBranch):