1
# Copyright (C) 2005 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
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
25
from warnings import warn
29
from bzrlib.config import TreeConfig
30
from bzrlib.delta import compare_trees
31
import bzrlib.errors as errors
32
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
33
NoSuchRevision, HistoryMissing, NotBranchError,
34
DivergedBranches, LockError,
35
UninitializableFormat,
37
UnlistableBranch, NoSuchFile, NotVersionedError,
39
import bzrlib.inventory as inventory
40
from bzrlib.inventory import Inventory
41
from bzrlib.osutils import (isdir, quotefn,
42
rename, splitpath, sha_file,
43
file_kind, abspath, normpath, pathjoin,
46
from bzrlib.textui import show_status
47
from bzrlib.trace import mutter, note
48
from bzrlib.tree import EmptyTree, RevisionTree
49
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
51
from bzrlib.store import copy_all
52
from bzrlib.store.text import TextStore
53
from bzrlib.store.weave import WeaveStore
54
from bzrlib.symbol_versioning import deprecated_nonce, deprecated_passed
55
from bzrlib.testament import Testament
56
import bzrlib.transactions as transactions
57
from bzrlib.transport import Transport, get_transport
62
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
63
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
64
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
65
## TODO: Maybe include checks for common corruption of newlines, etc?
68
# TODO: Some operations like log might retrieve the same revisions
69
# repeatedly to calculate deltas. We could perhaps have a weakref
70
# cache in memory to make this faster. In general anything can be
71
# cached in memory between lock and unlock operations.
73
def find_branch(*ignored, **ignored_too):
74
# XXX: leave this here for about one release, then remove it
75
raise NotImplementedError('find_branch() is not supported anymore, '
76
'please use one of the new branch constructors')
79
def needs_read_lock(unbound):
80
"""Decorate unbound to take out and release a read lock."""
81
def decorated(self, *args, **kwargs):
84
return unbound(self, *args, **kwargs)
90
def needs_write_lock(unbound):
91
"""Decorate unbound to take out and release a write lock."""
92
def decorated(self, *args, **kwargs):
95
return unbound(self, *args, **kwargs)
100
######################################################################
103
class Branch(object):
104
"""Branch holding a history of revisions.
107
Base directory/url of the branch.
109
# this is really an instance variable - FIXME move it there
113
_default_initializer = None
114
"""The default initializer for making new branches."""
116
def __init__(self, *ignored, **ignored_too):
117
raise NotImplementedError('The Branch class is abstract')
120
def open_downlevel(base):
121
"""Open a branch which may be of an old format."""
122
return Branch.open(base, _unsupported=True)
125
def open(base, _unsupported=False):
126
"""Open an existing branch, rooted at 'base' (url)
128
_unsupported is a private parameter to the Branch class.
130
t = get_transport(base)
131
mutter("trying to open %r with transport %r", base, t)
132
format = BzrBranchFormat.find_format(t)
133
if not _unsupported and not format.is_supported():
134
# see open_downlevel to open legacy branches.
135
raise errors.UnsupportedFormatError(
136
'sorry, branch format %s not supported' % format,
137
['use a different bzr version',
138
'or remove the .bzr directory'
139
' and "bzr init" again'])
140
return format.open(t)
143
def open_containing(url):
144
"""Open an existing branch which contains url.
146
This probes for a branch at url, and searches upwards from there.
148
Basically we keep looking up until we find the control directory or
149
run into the root. If there isn't one, raises NotBranchError.
150
If there is one, it is returned, along with the unused portion of url.
152
t = get_transport(url)
156
format = BzrBranchFormat.find_format(t)
157
return format.open(t), t.relpath(url)
158
# TODO FIXME, distinguish between formats that cannot be
159
# identified, and a lack of format.
160
except NotBranchError, e:
161
mutter('not a branch in: %r %s', t.base, e)
162
new_t = t.clone('..')
163
if new_t.base == t.base:
164
# reached the root, whatever that may be
165
raise NotBranchError(path=url)
169
def initialize(base):
170
"""Create a new branch, rooted at 'base' (url)
172
This will call the current default initializer with base
173
as the only parameter.
175
return Branch._default_initializer(safe_unicode(base))
178
def get_default_initializer():
179
"""Return the initializer being used for new branches."""
180
return Branch._default_initializer
183
def set_default_initializer(initializer):
184
"""Set the initializer to be used for new branches."""
185
Branch._default_initializer = staticmethod(initializer)
187
def setup_caching(self, cache_root):
188
"""Subclasses that care about caching should override this, and set
189
up cached stores located under cache_root.
191
self.cache_root = cache_root
194
cfg = self.tree_config()
195
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
197
def _set_nick(self, nick):
198
cfg = self.tree_config()
199
cfg.set_option(nick, "nickname")
200
assert cfg.get_option("nickname") == nick
202
nick = property(_get_nick, _set_nick)
204
def push_stores(self, branch_to):
205
"""Copy the content of this branches store to branch_to."""
206
raise NotImplementedError('push_stores is abstract')
208
def get_transaction(self):
209
"""Return the current active transaction.
211
If no transaction is active, this returns a passthrough object
212
for which all data is immediately flushed and no caching happens.
214
raise NotImplementedError('get_transaction is abstract')
216
def lock_write(self):
217
raise NotImplementedError('lock_write is abstract')
220
raise NotImplementedError('lock_read is abstract')
223
raise NotImplementedError('unlock is abstract')
225
def abspath(self, name):
226
"""Return absolute filename for something in the branch
228
XXX: Robert Collins 20051017 what is this used for? why is it a branch
229
method and not a tree method.
231
raise NotImplementedError('abspath is abstract')
233
def controlfilename(self, file_or_path):
234
"""Return location relative to branch."""
235
raise NotImplementedError('controlfilename is abstract')
237
def controlfile(self, file_or_path, mode='r'):
238
"""Open a control file for this branch.
240
There are two classes of file in the control directory: text
241
and binary. binary files are untranslated byte streams. Text
242
control files are stored with Unix newlines and in UTF-8, even
243
if the platform or locale defaults are different.
245
Controlfiles should almost never be opened in write mode but
246
rather should be atomically copied and replaced using atomicfile.
248
raise NotImplementedError('controlfile is abstract')
250
def put_controlfile(self, path, f, encode=True):
251
"""Write an entry as a controlfile.
253
:param path: The path to put the file, relative to the .bzr control
255
:param f: A file-like or string object whose contents should be copied.
256
:param encode: If true, encode the contents as utf-8
258
raise NotImplementedError('put_controlfile is abstract')
260
def put_controlfiles(self, files, encode=True):
261
"""Write several entries as controlfiles.
263
:param files: A list of [(path, file)] pairs, where the path is the directory
264
underneath the bzr control directory
265
:param encode: If true, encode the contents as utf-8
267
raise NotImplementedError('put_controlfiles is abstract')
269
def get_root_id(self):
270
"""Return the id of this branches root"""
271
raise NotImplementedError('get_root_id is abstract')
273
def set_root_id(self, file_id):
274
raise NotImplementedError('set_root_id is abstract')
276
def print_file(self, file, revision_id):
277
"""Print `file` to stdout."""
278
raise NotImplementedError('print_file is abstract')
280
def append_revision(self, *revision_ids):
281
raise NotImplementedError('append_revision is abstract')
283
def set_revision_history(self, rev_history):
284
raise NotImplementedError('set_revision_history is abstract')
286
def has_revision(self, revision_id):
287
"""True if this branch has a copy of the revision.
289
This does not necessarily imply the revision is merge
290
or on the mainline."""
291
raise NotImplementedError('has_revision is abstract')
293
def get_revision_xml(self, revision_id):
294
raise NotImplementedError('get_revision_xml is abstract')
296
def get_revision(self, revision_id):
297
"""Return the Revision object for a named revision"""
298
raise NotImplementedError('get_revision is abstract')
300
def get_revision_delta(self, revno):
301
"""Return the delta for one revision.
303
The delta is relative to its mainline predecessor, or the
304
empty tree for revision 1.
306
assert isinstance(revno, int)
307
rh = self.revision_history()
308
if not (1 <= revno <= len(rh)):
309
raise InvalidRevisionNumber(revno)
311
# revno is 1-based; list is 0-based
313
new_tree = self.revision_tree(rh[revno-1])
315
old_tree = EmptyTree()
317
old_tree = self.revision_tree(rh[revno-2])
319
return compare_trees(old_tree, new_tree)
321
def get_revision_sha1(self, revision_id):
322
"""Hash the stored value of a revision, and return it."""
323
raise NotImplementedError('get_revision_sha1 is abstract')
325
def get_ancestry(self, revision_id):
326
"""Return a list of revision-ids integrated by a revision.
328
This currently returns a list, but the ordering is not guaranteed:
331
raise NotImplementedError('get_ancestry is abstract')
333
def get_inventory(self, revision_id):
334
"""Get Inventory object by hash."""
335
raise NotImplementedError('get_inventory is abstract')
337
def get_inventory_xml(self, revision_id):
338
"""Get inventory XML as a file object."""
339
raise NotImplementedError('get_inventory_xml is abstract')
341
def get_inventory_sha1(self, revision_id):
342
"""Return the sha1 hash of the inventory entry."""
343
raise NotImplementedError('get_inventory_sha1 is abstract')
345
def get_revision_inventory(self, revision_id):
346
"""Return inventory of a past revision."""
347
raise NotImplementedError('get_revision_inventory is abstract')
349
def revision_history(self):
350
"""Return sequence of revision hashes on to this branch."""
351
raise NotImplementedError('revision_history is abstract')
354
"""Return current revision number for this branch.
356
That is equivalent to the number of revisions committed to
359
return len(self.revision_history())
361
def last_revision(self):
362
"""Return last patch hash, or None if no history."""
363
ph = self.revision_history()
369
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
370
"""Return a list of new revisions that would perfectly fit.
372
If self and other have not diverged, return a list of the revisions
373
present in other, but missing from self.
375
>>> from bzrlib.commit import commit
376
>>> bzrlib.trace.silent = True
377
>>> br1 = ScratchBranch()
378
>>> br2 = ScratchBranch()
379
>>> br1.missing_revisions(br2)
381
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
382
>>> br1.missing_revisions(br2)
384
>>> br2.missing_revisions(br1)
386
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
387
>>> br1.missing_revisions(br2)
389
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
390
>>> br1.missing_revisions(br2)
392
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
393
>>> br1.missing_revisions(br2)
394
Traceback (most recent call last):
395
DivergedBranches: These branches have diverged. Try merge.
397
self_history = self.revision_history()
398
self_len = len(self_history)
399
other_history = other.revision_history()
400
other_len = len(other_history)
401
common_index = min(self_len, other_len) -1
402
if common_index >= 0 and \
403
self_history[common_index] != other_history[common_index]:
404
raise DivergedBranches(self, other)
406
if stop_revision is None:
407
stop_revision = other_len
409
assert isinstance(stop_revision, int)
410
if stop_revision > other_len:
411
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
412
return other_history[self_len:stop_revision]
414
def update_revisions(self, other, stop_revision=None):
415
"""Pull in new perfect-fit revisions."""
416
raise NotImplementedError('update_revisions is abstract')
418
def pullable_revisions(self, other, stop_revision):
419
raise NotImplementedError('pullable_revisions is abstract')
421
def revision_id_to_revno(self, revision_id):
422
"""Given a revision id, return its revno"""
423
if revision_id is None:
425
history = self.revision_history()
427
return history.index(revision_id) + 1
429
raise bzrlib.errors.NoSuchRevision(self, revision_id)
431
def get_rev_id(self, revno, history=None):
432
"""Find the revision id of the specified revno."""
436
history = self.revision_history()
437
elif revno <= 0 or revno > len(history):
438
raise bzrlib.errors.NoSuchRevision(self, revno)
439
return history[revno - 1]
441
def revision_tree(self, revision_id):
442
"""Return Tree for a revision on this branch.
444
`revision_id` may be None for the null revision, in which case
445
an `EmptyTree` is returned."""
446
raise NotImplementedError('revision_tree is abstract')
448
def working_tree(self):
449
"""Return a `Tree` for the working copy if this is a local branch."""
450
raise NotImplementedError('working_tree is abstract')
452
def pull(self, source, overwrite=False):
453
raise NotImplementedError('pull is abstract')
455
def basis_tree(self):
456
"""Return `Tree` object for last revision.
458
If there are no revisions yet, return an `EmptyTree`.
460
return self.revision_tree(self.last_revision())
462
def rename_one(self, from_rel, to_rel):
465
This can change the directory or the filename or both.
467
raise NotImplementedError('rename_one is abstract')
469
def move(self, from_paths, to_name):
472
to_name must exist as a versioned directory.
474
If to_name exists and is a directory, the files are moved into
475
it, keeping their old names. If it is a directory,
477
Note that to_name is only the last component of the new name;
478
this doesn't change the directory.
480
This returns a list of (from_path, to_path) pairs for each
483
raise NotImplementedError('move is abstract')
485
def get_parent(self):
486
"""Return the parent location of the branch.
488
This is the default location for push/pull/missing. The usual
489
pattern is that the user can override it by specifying a
492
raise NotImplementedError('get_parent is abstract')
494
def get_push_location(self):
495
"""Return the None or the location to push this branch to."""
496
raise NotImplementedError('get_push_location is abstract')
498
def set_push_location(self, location):
499
"""Set a new push location for this branch."""
500
raise NotImplementedError('set_push_location is abstract')
502
def set_parent(self, url):
503
raise NotImplementedError('set_parent is abstract')
505
def check_revno(self, revno):
507
Check whether a revno corresponds to any revision.
508
Zero (the NULL revision) is considered valid.
511
self.check_real_revno(revno)
513
def check_real_revno(self, revno):
515
Check whether a revno corresponds to a real revision.
516
Zero (the NULL revision) is considered invalid
518
if revno < 1 or revno > self.revno():
519
raise InvalidRevisionNumber(revno)
521
def sign_revision(self, revision_id, gpg_strategy):
522
raise NotImplementedError('sign_revision is abstract')
524
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
525
raise NotImplementedError('store_revision_signature is abstract')
528
class BzrBranchFormat(object):
529
"""An encapsulation of the initialization and open routines for a format.
531
Formats provide three things:
532
* An initialization routine,
536
Formats are placed in an dict by their format string for reference
537
during branch opening. Its not required that these be instances, they
538
can be classes themselves with class methods - it simply depends on
539
whether state is needed for a given format or not.
541
Once a format is deprecated, just deprecate the initialize and open
542
methods on the format class. Do not deprecate the object, as the
543
object will be created every time regardless.
547
"""The known formats."""
550
def find_format(klass, transport):
551
"""Return the format registered for URL."""
553
format_string = transport.get(".bzr/branch-format").read()
554
return klass._formats[format_string]
556
raise NotBranchError(path=transport.base)
558
raise errors.UnknownFormatError(format_string)
560
def get_format_string(self):
561
"""Return the ASCII format string that identifies this format."""
562
raise NotImplementedError(self.get_format_string)
564
def _find_modes(self, t):
565
"""Determine the appropriate modes for files and directories.
567
FIXME: When this merges into, or from storage,
568
this code becomes delgatable to a LockableFiles instance.
570
For now its cribbed and returns (dir_mode, file_mode)
574
except errors.TransportNotPossible:
578
dir_mode = st.st_mode & 07777
579
# Remove the sticky and execute bits for files
580
file_mode = dir_mode & ~07111
581
if not BzrBranch._set_dir_mode:
583
if not BzrBranch._set_file_mode:
585
return dir_mode, file_mode
587
def initialize(self, url):
588
"""Create a branch of this format at url and return an open branch."""
589
t = get_transport(url)
590
from bzrlib.inventory import Inventory
591
from bzrlib.weavefile import write_weave_v5
592
from bzrlib.weave import Weave
594
# Create an empty inventory
596
# if we want per-tree root ids then this is the place to set
597
# them; they're not needed for now and so ommitted for
599
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
600
empty_inv = sio.getvalue()
602
bzrlib.weavefile.write_weave_v5(Weave(), sio)
603
empty_weave = sio.getvalue()
605
# Since we don't have a .bzr directory, inherit the
606
# mode from the root directory
607
dir_mode, file_mode = self._find_modes(t)
609
t.mkdir('.bzr', mode=dir_mode)
610
control = t.clone('.bzr')
611
dirs = ['revision-store', 'weaves']
613
StringIO("This is a Bazaar-NG control directory.\n"
614
"Do not change any files in this directory.\n")),
615
('branch-format', StringIO(self.get_format_string())),
616
('revision-history', StringIO('')),
617
('branch-name', StringIO('')),
618
('branch-lock', StringIO('')),
619
('pending-merges', StringIO('')),
620
('inventory', StringIO(empty_inv)),
621
('inventory.weave', StringIO(empty_weave)),
622
('ancestry.weave', StringIO(empty_weave))
624
control.mkdir_multi(dirs, mode=dir_mode)
625
control.put_multi(files, mode=file_mode)
626
mutter('created control directory in ' + t.base)
627
return BzrBranch(t, format=self)
629
def is_supported(self):
630
"""Is this format supported?
632
Supported formats can be initialized and opened.
633
Unsupported formats may not support initialization or committing or
634
some other features depending on the reason for not being supported.
638
def open(self, transport):
639
"""Fill out the data in branch for the branch at url."""
640
return BzrBranch(transport, format=self)
643
def register_format(klass, format):
644
klass._formats[format.get_format_string()] = format
647
def unregister_format(klass, format):
648
assert klass._formats[format.get_format_string()] is format
649
del klass._formats[format.get_format_string()]
652
class BzrBranchFormat4(BzrBranchFormat):
653
"""Bzr branch format 4.
657
- TextStores for texts, inventories,revisions.
659
This format is deprecated: it indexes texts using a text it which is
660
removed in format 5; write support for this format has been removed.
663
def get_format_string(self):
664
"""See BzrBranchFormat.get_format_string()."""
665
return BZR_BRANCH_FORMAT_4
667
def initialize(self, url):
668
"""Format 4 branches cannot be created."""
669
raise UninitializableFormat(self)
671
def is_supported(self):
672
"""Format 4 is not supported.
674
It is not supported because the model changed from 4 to 5 and the
675
conversion logic is expensive - so doing it on the fly was not
681
class BzrBranchFormat5(BzrBranchFormat):
682
"""Bzr branch format 5.
685
- weaves for file texts and inventory
687
- TextStores for revisions and signatures.
690
def get_format_string(self):
691
"""See BzrBranchFormat.get_format_string()."""
692
return BZR_BRANCH_FORMAT_5
695
class BzrBranchFormat6(BzrBranchFormat):
696
"""Bzr branch format 6.
699
- weaves for file texts and inventory
700
- hash subdirectory based stores.
701
- TextStores for revisions and signatures.
704
def get_format_string(self):
705
"""See BzrBranchFormat.get_format_string()."""
706
return BZR_BRANCH_FORMAT_6
709
BzrBranchFormat.register_format(BzrBranchFormat4())
710
BzrBranchFormat.register_format(BzrBranchFormat5())
711
BzrBranchFormat.register_format(BzrBranchFormat6())
714
class BzrBranch(Branch):
715
"""A branch stored in the actual filesystem.
717
Note that it's "local" in the context of the filesystem; it doesn't
718
really matter if it's on an nfs/smb/afs/coda/... share, as long as
719
it's writable, and can be accessed via the normal filesystem API.
725
If _lock_mode is true, a positive count of the number of times the
729
Lock object from bzrlib.lock.
731
# We actually expect this class to be somewhat short-lived; part of its
732
# purpose is to try to isolate what bits of the branch logic are tied to
733
# filesystem access, so that in a later step, we can extricate them to
734
# a separarte ("storage") class.
738
_inventory_weave = None
739
# If set to False (by a plugin, etc) BzrBranch will not set the
740
# mode on created files or directories
741
_set_file_mode = True
744
# Map some sort of prefix into a namespace
745
# stuff like "revno:10", "revid:", etc.
746
# This should match a prefix with a function which accepts
747
REVISION_NAMESPACES = {}
749
def push_stores(self, branch_to):
750
"""See Branch.push_stores."""
751
if (not isinstance(self._branch_format, BzrBranchFormat4) or
752
self._branch_format != branch_to._branch_format):
753
from bzrlib.fetch import greedy_fetch
754
mutter("Using fetch logic to push between %s(%s) and %s(%s)",
755
self, self._branch_format, branch_to, branch_to._branch_format)
756
greedy_fetch(to_branch=branch_to, from_branch=self,
757
revision=self.last_revision())
760
# format 4 to format 4 logic only.
761
store_pairs = ((self.text_store, branch_to.text_store),
762
(self.inventory_store, branch_to.inventory_store),
763
(self.revision_store, branch_to.revision_store))
765
for from_store, to_store in store_pairs:
766
copy_all(from_store, to_store)
767
except UnlistableStore:
768
raise UnlistableBranch(from_store)
770
def __init__(self, transport, init=deprecated_nonce,
771
relax_version_check=deprecated_nonce, format=None):
772
"""Create new branch object at a particular location.
774
transport -- A Transport object, defining how to access files.
776
init -- If True, create new control files in a previously
777
unversioned directory. If False, the branch must already
780
relax_version_check -- If true, the usual check for the branch
781
version is not applied. This is intended only for
782
upgrade/recovery type use; it's not guaranteed that
783
all operations will work on old format branches.
785
In the test suite, creation of new trees is tested using the
786
`ScratchBranch` class.
788
assert isinstance(transport, Transport), \
789
"%r is not a Transport" % transport
790
self._transport = transport
791
if deprecated_passed(init):
792
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
793
"deprecated as of bzr 0.8. Please use Branch.initialize().",
796
# this is slower than before deprecation, oh well never mind.
798
self._initialize(transport.base)
800
self._check_format(format)
801
if deprecated_passed(relax_version_check):
802
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
803
"relax_version_check parameter is deprecated as of bzr 0.8. "
804
"Please use Branch.open_downlevel, or a BzrBranchFormat's "
805
"open() method.", DeprecationWarning)
806
if (not relax_version_check
807
and not self._branch_format.is_supported()):
808
raise errors.UnsupportedFormatError(
809
'sorry, branch format %r not supported' % fmt,
810
['use a different bzr version',
811
'or remove the .bzr directory'
812
' and "bzr init" again'])
814
def get_store(name, compressed=True, prefixed=False):
815
relpath = self._rel_controlfilename(safe_unicode(name))
816
store = TextStore(self._transport.clone(relpath),
817
dir_mode=self._dir_mode,
818
file_mode=self._file_mode,
820
compressed=compressed)
823
def get_weave(name, prefixed=False):
824
relpath = self._rel_controlfilename(unicode(name))
825
ws = WeaveStore(self._transport.clone(relpath),
827
dir_mode=self._dir_mode,
828
file_mode=self._file_mode)
829
if self._transport.should_cache():
830
ws.enable_cache = True
833
if isinstance(self._branch_format, BzrBranchFormat4):
834
self.inventory_store = get_store('inventory-store')
835
self.text_store = get_store('text-store')
836
self.revision_store = get_store('revision-store')
837
elif isinstance(self._branch_format, BzrBranchFormat5):
838
self.control_weaves = get_weave(u'')
839
self.weave_store = get_weave(u'weaves')
840
self.revision_store = get_store(u'revision-store', compressed=False)
841
elif isinstance(self._branch_format, BzrBranchFormat6):
842
self.control_weaves = get_weave(u'')
843
self.weave_store = get_weave(u'weaves', prefixed=True)
844
self.revision_store = get_store(u'revision-store', compressed=False,
846
self.revision_store.register_suffix('sig')
847
self._transaction = None
850
def _initialize(base):
851
"""Create a bzr branch in the latest format."""
852
return BzrBranchFormat6().initialize(base)
855
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
860
if self._lock_mode or self._lock:
861
# XXX: This should show something every time, and be suitable for
862
# headless operation and embedding
863
warn("branch %r was not explicitly unlocked" % self)
866
# TODO: It might be best to do this somewhere else,
867
# but it is nice for a Branch object to automatically
868
# cache it's information.
869
# Alternatively, we could have the Transport objects cache requests
870
# See the earlier discussion about how major objects (like Branch)
871
# should never expect their __del__ function to run.
872
if hasattr(self, 'cache_root') and self.cache_root is not None:
874
shutil.rmtree(self.cache_root)
877
self.cache_root = None
881
return self._transport.base
884
base = property(_get_base, doc="The URL for the root of this branch.")
886
def _finish_transaction(self):
887
"""Exit the current transaction."""
888
if self._transaction is None:
889
raise errors.LockError('Branch %s is not in a transaction' %
891
transaction = self._transaction
892
self._transaction = None
895
def get_transaction(self):
896
"""See Branch.get_transaction."""
897
if self._transaction is None:
898
return transactions.PassThroughTransaction()
900
return self._transaction
902
def _set_transaction(self, new_transaction):
903
"""Set a new active transaction."""
904
if self._transaction is not None:
905
raise errors.LockError('Branch %s is in a transaction already.' %
907
self._transaction = new_transaction
909
def lock_write(self):
910
#mutter("lock write: %s (%s)", self, self._lock_count)
911
# TODO: Upgrade locking to support using a Transport,
912
# and potentially a remote locking protocol
914
if self._lock_mode != 'w':
915
raise LockError("can't upgrade to a write lock from %r" %
917
self._lock_count += 1
919
self._lock = self._transport.lock_write(
920
self._rel_controlfilename('branch-lock'))
921
self._lock_mode = 'w'
923
self._set_transaction(transactions.PassThroughTransaction())
926
#mutter("lock read: %s (%s)", self, self._lock_count)
928
assert self._lock_mode in ('r', 'w'), \
929
"invalid lock mode %r" % self._lock_mode
930
self._lock_count += 1
932
self._lock = self._transport.lock_read(
933
self._rel_controlfilename('branch-lock'))
934
self._lock_mode = 'r'
936
self._set_transaction(transactions.ReadOnlyTransaction())
937
# 5K may be excessive, but hey, its a knob.
938
self.get_transaction().set_cache_size(5000)
941
#mutter("unlock: %s (%s)", self, self._lock_count)
942
if not self._lock_mode:
943
raise LockError('branch %r is not locked' % (self))
945
if self._lock_count > 1:
946
self._lock_count -= 1
948
self._finish_transaction()
951
self._lock_mode = self._lock_count = None
953
def abspath(self, name):
954
"""See Branch.abspath."""
955
return self._transport.abspath(name)
957
def _rel_controlfilename(self, file_or_path):
958
if not isinstance(file_or_path, basestring):
959
file_or_path = u'/'.join(file_or_path)
960
if file_or_path == '':
962
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
964
def controlfilename(self, file_or_path):
965
"""See Branch.controlfilename."""
966
return self._transport.abspath(self._rel_controlfilename(file_or_path))
968
def controlfile(self, file_or_path, mode='r'):
969
"""See Branch.controlfile."""
972
relpath = self._rel_controlfilename(file_or_path)
973
#TODO: codecs.open() buffers linewise, so it was overloaded with
974
# a much larger buffer, do we need to do the same for getreader/getwriter?
976
return self._transport.get(relpath)
978
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
980
# XXX: Do we really want errors='replace'? Perhaps it should be
981
# an error, or at least reported, if there's incorrectly-encoded
982
# data inside a file.
983
# <https://launchpad.net/products/bzr/+bug/3823>
984
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
986
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
988
raise BzrError("invalid controlfile mode %r" % mode)
990
def put_controlfile(self, path, f, encode=True):
991
"""See Branch.put_controlfile."""
992
self.put_controlfiles([(path, f)], encode=encode)
994
def put_controlfiles(self, files, encode=True):
995
"""See Branch.put_controlfiles."""
998
for path, f in files:
1000
if isinstance(f, basestring):
1001
f = f.encode('utf-8', 'replace')
1003
f = codecs.getwriter('utf-8')(f, errors='replace')
1004
path = self._rel_controlfilename(path)
1005
ctrl_files.append((path, f))
1006
self._transport.put_multi(ctrl_files, mode=self._file_mode)
1008
def _find_modes(self, path=None):
1009
"""Determine the appropriate modes for files and directories."""
1012
path = self._rel_controlfilename('')
1013
st = self._transport.stat(path)
1014
except errors.TransportNotPossible:
1015
self._dir_mode = 0755
1016
self._file_mode = 0644
1018
self._dir_mode = st.st_mode & 07777
1019
# Remove the sticky and execute bits for files
1020
self._file_mode = self._dir_mode & ~07111
1021
if not self._set_dir_mode:
1022
self._dir_mode = None
1023
if not self._set_file_mode:
1024
self._file_mode = None
1026
def _check_format(self, format):
1027
"""Identify the branch format if needed.
1029
The format is stored as a reference to the format object in
1030
self._branch_format for code that needs to check it later.
1032
The format parameter is either None or the branch format class
1033
used to open this branch.
1036
format = BzrBranchFormat.find_format(self._transport)
1037
self._branch_format = format
1038
mutter("got branch format %s", self._branch_format)
1041
def get_root_id(self):
1042
"""See Branch.get_root_id."""
1043
inv = self.get_inventory(self.last_revision())
1044
return inv.root.file_id
1047
def print_file(self, file, revision_id):
1048
"""See Branch.print_file."""
1049
tree = self.revision_tree(revision_id)
1050
# use inventory as it was in that revision
1051
file_id = tree.inventory.path2id(file)
1054
revno = self.revision_id_to_revno(revision_id)
1055
except errors.NoSuchRevision:
1056
# TODO: This should not be BzrError,
1057
# but NoSuchFile doesn't fit either
1058
raise BzrError('%r is not present in revision %s'
1059
% (file, revision_id))
1061
raise BzrError('%r is not present in revision %s'
1063
tree.print_file(file_id)
1066
def append_revision(self, *revision_ids):
1067
"""See Branch.append_revision."""
1068
for revision_id in revision_ids:
1069
mutter("add {%s} to revision-history" % revision_id)
1070
rev_history = self.revision_history()
1071
rev_history.extend(revision_ids)
1072
self.set_revision_history(rev_history)
1075
def set_revision_history(self, rev_history):
1076
"""See Branch.set_revision_history."""
1077
old_revision = self.last_revision()
1078
new_revision = rev_history[-1]
1079
self.put_controlfile('revision-history', '\n'.join(rev_history))
1081
self.working_tree().set_last_revision(new_revision, old_revision)
1082
except NoWorkingTree:
1083
mutter('Unable to set_last_revision without a working tree.')
1085
def has_revision(self, revision_id):
1086
"""See Branch.has_revision."""
1087
return (revision_id is None
1088
or self.revision_store.has_id(revision_id))
1091
def _get_revision_xml_file(self, revision_id):
1092
if not revision_id or not isinstance(revision_id, basestring):
1093
raise InvalidRevisionId(revision_id=revision_id, branch=self)
1095
return self.revision_store.get(revision_id)
1096
except (IndexError, KeyError):
1097
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1099
def get_revision_xml(self, revision_id):
1100
"""See Branch.get_revision_xml."""
1101
return self._get_revision_xml_file(revision_id).read()
1103
def get_revision(self, revision_id):
1104
"""See Branch.get_revision."""
1105
xml_file = self._get_revision_xml_file(revision_id)
1108
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1109
except SyntaxError, e:
1110
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
1114
assert r.revision_id == revision_id
1117
def get_revision_sha1(self, revision_id):
1118
"""See Branch.get_revision_sha1."""
1119
# In the future, revision entries will be signed. At that
1120
# point, it is probably best *not* to include the signature
1121
# in the revision hash. Because that lets you re-sign
1122
# the revision, (add signatures/remove signatures) and still
1123
# have all hash pointers stay consistent.
1124
# But for now, just hash the contents.
1125
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
1127
def get_ancestry(self, revision_id):
1128
"""See Branch.get_ancestry."""
1129
if revision_id is None:
1131
w = self._get_inventory_weave()
1132
return [None] + map(w.idx_to_name,
1133
w.inclusions([w.lookup(revision_id)]))
1135
def _get_inventory_weave(self):
1136
return self.control_weaves.get_weave('inventory',
1137
self.get_transaction())
1139
def get_inventory(self, revision_id):
1140
"""See Branch.get_inventory."""
1141
xml = self.get_inventory_xml(revision_id)
1142
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1144
def get_inventory_xml(self, revision_id):
1145
"""See Branch.get_inventory_xml."""
1147
assert isinstance(revision_id, basestring), type(revision_id)
1148
iw = self._get_inventory_weave()
1149
return iw.get_text(iw.lookup(revision_id))
1151
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1153
def get_inventory_sha1(self, revision_id):
1154
"""See Branch.get_inventory_sha1."""
1155
return self.get_revision(revision_id).inventory_sha1
1157
def get_revision_inventory(self, revision_id):
1158
"""See Branch.get_revision_inventory."""
1159
# TODO: Unify this with get_inventory()
1160
# bzr 0.0.6 and later imposes the constraint that the inventory_id
1161
# must be the same as its revision, so this is trivial.
1162
if revision_id == None:
1163
# This does not make sense: if there is no revision,
1164
# then it is the current tree inventory surely ?!
1165
# and thus get_root_id() is something that looks at the last
1166
# commit on the branch, and the get_root_id is an inventory check.
1167
raise NotImplementedError
1168
# return Inventory(self.get_root_id())
1170
return self.get_inventory(revision_id)
1173
def revision_history(self):
1174
"""See Branch.revision_history."""
1175
transaction = self.get_transaction()
1176
history = transaction.map.find_revision_history()
1177
if history is not None:
1178
mutter("cache hit for revision-history in %s", self)
1179
return list(history)
1180
history = [l.rstrip('\r\n') for l in
1181
self.controlfile('revision-history', 'r').readlines()]
1182
transaction.map.add_revision_history(history)
1183
# this call is disabled because revision_history is
1184
# not really an object yet, and the transaction is for objects.
1185
# transaction.register_clean(history, precious=True)
1186
return list(history)
1188
def update_revisions(self, other, stop_revision=None):
1189
"""See Branch.update_revisions."""
1190
from bzrlib.fetch import greedy_fetch
1191
if stop_revision is None:
1192
stop_revision = other.last_revision()
1193
### Should this be checking is_ancestor instead of revision_history?
1194
if (stop_revision is not None and
1195
stop_revision in self.revision_history()):
1197
greedy_fetch(to_branch=self, from_branch=other,
1198
revision=stop_revision)
1199
pullable_revs = self.pullable_revisions(other, stop_revision)
1200
if len(pullable_revs) > 0:
1201
self.append_revision(*pullable_revs)
1203
def pullable_revisions(self, other, stop_revision):
1204
"""See Branch.pullable_revisions."""
1205
other_revno = other.revision_id_to_revno(stop_revision)
1207
return self.missing_revisions(other, other_revno)
1208
except DivergedBranches, e:
1210
pullable_revs = get_intervening_revisions(self.last_revision(),
1211
stop_revision, self)
1212
assert self.last_revision() not in pullable_revs
1213
return pullable_revs
1214
except bzrlib.errors.NotAncestor:
1215
if is_ancestor(self.last_revision(), stop_revision, self):
1220
def revision_tree(self, revision_id):
1221
"""See Branch.revision_tree."""
1222
# TODO: refactor this to use an existing revision object
1223
# so we don't need to read it in twice.
1224
if revision_id == None or revision_id == NULL_REVISION:
1227
inv = self.get_revision_inventory(revision_id)
1228
return RevisionTree(self, inv, revision_id)
1230
def basis_tree(self):
1231
"""See Branch.basis_tree."""
1233
revision_id = self.revision_history()[-1]
1234
xml = self.working_tree().read_basis_inventory(revision_id)
1235
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1236
return RevisionTree(self, inv, revision_id)
1237
except (IndexError, NoSuchFile, NoWorkingTree), e:
1238
return self.revision_tree(self.last_revision())
1240
def working_tree(self):
1241
"""See Branch.working_tree."""
1242
from bzrlib.workingtree import WorkingTree
1243
if self._transport.base.find('://') != -1:
1244
raise NoWorkingTree(self.base)
1245
return WorkingTree(self.base, branch=self)
1248
def pull(self, source, overwrite=False):
1249
"""See Branch.pull."""
1252
old_count = len(self.revision_history())
1254
self.update_revisions(source)
1255
except DivergedBranches:
1259
self.set_revision_history(source.revision_history())
1260
new_count = len(self.revision_history())
1261
return new_count - old_count
1265
def get_parent(self):
1266
"""See Branch.get_parent."""
1268
_locs = ['parent', 'pull', 'x-pull']
1271
return self.controlfile(l, 'r').read().strip('\n')
1276
def get_push_location(self):
1277
"""See Branch.get_push_location."""
1278
config = bzrlib.config.BranchConfig(self)
1279
push_loc = config.get_user_option('push_location')
1282
def set_push_location(self, location):
1283
"""See Branch.set_push_location."""
1284
config = bzrlib.config.LocationConfig(self.base)
1285
config.set_user_option('push_location', location)
1288
def set_parent(self, url):
1289
"""See Branch.set_parent."""
1290
# TODO: Maybe delete old location files?
1291
from bzrlib.atomicfile import AtomicFile
1292
f = AtomicFile(self.controlfilename('parent'))
1299
def tree_config(self):
1300
return TreeConfig(self)
1302
def sign_revision(self, revision_id, gpg_strategy):
1303
"""See Branch.sign_revision."""
1304
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1305
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1308
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1309
"""See Branch.store_revision_signature."""
1310
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1314
Branch.set_default_initializer(BzrBranch._initialize)
1317
class BranchTestProviderAdapter(object):
1318
"""A tool to generate a suite testing multiple branch formats at once.
1320
This is done by copying the test once for each transport and injecting
1321
the transport_server, transport_readonly_server, and branch_format
1322
classes into each copy. Each copy is also given a new id() to make it
1326
def __init__(self, transport_server, transport_readonly_server, formats):
1327
self._transport_server = transport_server
1328
self._transport_readonly_server = transport_readonly_server
1329
self._formats = formats
1331
def adapt(self, test):
1332
result = TestSuite()
1333
for format in self._formats:
1334
new_test = deepcopy(test)
1335
new_test.transport_server = self._transport_server
1336
new_test.transport_readonly_server = self._transport_readonly_server
1337
new_test.branch_format = format
1338
def make_new_test_id():
1339
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1340
return lambda: new_id
1341
new_test.id = make_new_test_id()
1342
result.addTest(new_test)
1346
class ScratchBranch(BzrBranch):
1347
"""Special test class: a branch that cleans up after itself.
1349
>>> b = ScratchBranch()
1353
>>> b._transport.__del__()
1358
def __init__(self, files=[], dirs=[], transport=None):
1359
"""Make a test branch.
1361
This creates a temporary directory and runs init-tree in it.
1363
If any files are listed, they are created in the working copy.
1365
if transport is None:
1366
transport = bzrlib.transport.local.ScratchTransport()
1367
Branch.initialize(transport.base)
1368
super(ScratchBranch, self).__init__(transport)
1370
super(ScratchBranch, self).__init__(transport)
1373
self._transport.mkdir(d)
1376
self._transport.put(f, 'content of %s' % f)
1381
>>> orig = ScratchBranch(files=["file1", "file2"])
1382
>>> clone = orig.clone()
1383
>>> if os.name != 'nt':
1384
... os.path.samefile(orig.base, clone.base)
1386
... orig.base == clone.base
1389
>>> os.path.isfile(pathjoin(clone.base, "file1"))
1392
from shutil import copytree
1393
from bzrlib.osutils import mkdtemp
1396
copytree(self.base, base, symlinks=True)
1397
return ScratchBranch(
1398
transport=bzrlib.transport.local.ScratchTransport(base))
1401
######################################################################
1405
def is_control_file(filename):
1406
## FIXME: better check
1407
filename = normpath(filename)
1408
while filename != '':
1409
head, tail = os.path.split(filename)
1410
## mutter('check %r for control file' % ((head, tail), ))
1411
if tail == bzrlib.BZRDIR:
1413
if filename == head: