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)
155
format = BzrBranchFormat.find_format(t)
156
return format.open(t), t.relpath(url)
157
# TODO FIXME, distinguish between formats that cannot be
158
# identified, and a lack of format.
159
except NotBranchError, e:
160
mutter('not a branch in: %r %s', t.base, e)
161
new_t = t.clone('..')
162
if new_t.base == t.base:
163
# reached the root, whatever that may be
164
raise NotBranchError(path=url)
168
def initialize(base):
169
"""Create a new branch, rooted at 'base' (url)
171
This will call the current default initializer with base
172
as the only parameter.
174
return Branch._default_initializer(safe_unicode(base))
177
def get_default_initializer():
178
"""Return the initializer being used for new branches."""
179
return Branch._default_initializer
182
def set_default_initializer(initializer):
183
"""Set the initializer to be used for new branches."""
184
Branch._default_initializer = staticmethod(initializer)
186
def setup_caching(self, cache_root):
187
"""Subclasses that care about caching should override this, and set
188
up cached stores located under cache_root.
190
self.cache_root = cache_root
193
cfg = self.tree_config()
194
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
196
def _set_nick(self, nick):
197
cfg = self.tree_config()
198
cfg.set_option(nick, "nickname")
199
assert cfg.get_option("nickname") == nick
201
nick = property(_get_nick, _set_nick)
203
def push_stores(self, branch_to):
204
"""Copy the content of this branches store to branch_to."""
205
raise NotImplementedError('push_stores is abstract')
207
def get_transaction(self):
208
"""Return the current active transaction.
210
If no transaction is active, this returns a passthrough object
211
for which all data is immediately flushed and no caching happens.
213
raise NotImplementedError('get_transaction is abstract')
215
def lock_write(self):
216
raise NotImplementedError('lock_write is abstract')
219
raise NotImplementedError('lock_read is abstract')
222
raise NotImplementedError('unlock is abstract')
224
def abspath(self, name):
225
"""Return absolute filename for something in the branch
227
XXX: Robert Collins 20051017 what is this used for? why is it a branch
228
method and not a tree method.
230
raise NotImplementedError('abspath is abstract')
232
def controlfilename(self, file_or_path):
233
"""Return location relative to branch."""
234
raise NotImplementedError('controlfilename is abstract')
236
def controlfile(self, file_or_path, mode='r'):
237
"""Open a control file for this branch.
239
There are two classes of file in the control directory: text
240
and binary. binary files are untranslated byte streams. Text
241
control files are stored with Unix newlines and in UTF-8, even
242
if the platform or locale defaults are different.
244
Controlfiles should almost never be opened in write mode but
245
rather should be atomically copied and replaced using atomicfile.
247
raise NotImplementedError('controlfile is abstract')
249
def put_controlfile(self, path, f, encode=True):
250
"""Write an entry as a controlfile.
252
:param path: The path to put the file, relative to the .bzr control
254
:param f: A file-like or string object whose contents should be copied.
255
:param encode: If true, encode the contents as utf-8
257
raise NotImplementedError('put_controlfile is abstract')
259
def put_controlfiles(self, files, encode=True):
260
"""Write several entries as controlfiles.
262
:param files: A list of [(path, file)] pairs, where the path is the directory
263
underneath the bzr control directory
264
:param encode: If true, encode the contents as utf-8
266
raise NotImplementedError('put_controlfiles is abstract')
268
def get_root_id(self):
269
"""Return the id of this branches root"""
270
raise NotImplementedError('get_root_id is abstract')
272
def set_root_id(self, file_id):
273
raise NotImplementedError('set_root_id is abstract')
275
def print_file(self, file, revision_id):
276
"""Print `file` to stdout."""
277
raise NotImplementedError('print_file is abstract')
279
def append_revision(self, *revision_ids):
280
raise NotImplementedError('append_revision is abstract')
282
def set_revision_history(self, rev_history):
283
raise NotImplementedError('set_revision_history is abstract')
285
def has_revision(self, revision_id):
286
"""True if this branch has a copy of the revision.
288
This does not necessarily imply the revision is merge
289
or on the mainline."""
290
raise NotImplementedError('has_revision is abstract')
292
def get_revision_xml(self, revision_id):
293
raise NotImplementedError('get_revision_xml is abstract')
295
def get_revision(self, revision_id):
296
"""Return the Revision object for a named revision"""
297
raise NotImplementedError('get_revision is abstract')
299
def get_revision_delta(self, revno):
300
"""Return the delta for one revision.
302
The delta is relative to its mainline predecessor, or the
303
empty tree for revision 1.
305
assert isinstance(revno, int)
306
rh = self.revision_history()
307
if not (1 <= revno <= len(rh)):
308
raise InvalidRevisionNumber(revno)
310
# revno is 1-based; list is 0-based
312
new_tree = self.revision_tree(rh[revno-1])
314
old_tree = EmptyTree()
316
old_tree = self.revision_tree(rh[revno-2])
318
return compare_trees(old_tree, new_tree)
320
def get_revision_sha1(self, revision_id):
321
"""Hash the stored value of a revision, and return it."""
322
raise NotImplementedError('get_revision_sha1 is abstract')
324
def get_ancestry(self, revision_id):
325
"""Return a list of revision-ids integrated by a revision.
327
This currently returns a list, but the ordering is not guaranteed:
330
raise NotImplementedError('get_ancestry is abstract')
332
def get_inventory(self, revision_id):
333
"""Get Inventory object by hash."""
334
raise NotImplementedError('get_inventory is abstract')
336
def get_inventory_xml(self, revision_id):
337
"""Get inventory XML as a file object."""
338
raise NotImplementedError('get_inventory_xml is abstract')
340
def get_inventory_sha1(self, revision_id):
341
"""Return the sha1 hash of the inventory entry."""
342
raise NotImplementedError('get_inventory_sha1 is abstract')
344
def get_revision_inventory(self, revision_id):
345
"""Return inventory of a past revision."""
346
raise NotImplementedError('get_revision_inventory is abstract')
348
def revision_history(self):
349
"""Return sequence of revision hashes on to this branch."""
350
raise NotImplementedError('revision_history is abstract')
353
"""Return current revision number for this branch.
355
That is equivalent to the number of revisions committed to
358
return len(self.revision_history())
360
def last_revision(self):
361
"""Return last patch hash, or None if no history."""
362
ph = self.revision_history()
368
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
369
"""Return a list of new revisions that would perfectly fit.
371
If self and other have not diverged, return a list of the revisions
372
present in other, but missing from self.
374
>>> from bzrlib.commit import commit
375
>>> bzrlib.trace.silent = True
376
>>> br1 = ScratchBranch()
377
>>> br2 = ScratchBranch()
378
>>> br1.missing_revisions(br2)
380
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
381
>>> br1.missing_revisions(br2)
383
>>> br2.missing_revisions(br1)
385
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
386
>>> br1.missing_revisions(br2)
388
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
389
>>> br1.missing_revisions(br2)
391
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
392
>>> br1.missing_revisions(br2)
393
Traceback (most recent call last):
394
DivergedBranches: These branches have diverged. Try merge.
396
self_history = self.revision_history()
397
self_len = len(self_history)
398
other_history = other.revision_history()
399
other_len = len(other_history)
400
common_index = min(self_len, other_len) -1
401
if common_index >= 0 and \
402
self_history[common_index] != other_history[common_index]:
403
raise DivergedBranches(self, other)
405
if stop_revision is None:
406
stop_revision = other_len
408
assert isinstance(stop_revision, int)
409
if stop_revision > other_len:
410
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
411
return other_history[self_len:stop_revision]
413
def update_revisions(self, other, stop_revision=None):
414
"""Pull in new perfect-fit revisions."""
415
raise NotImplementedError('update_revisions is abstract')
417
def pullable_revisions(self, other, stop_revision):
418
raise NotImplementedError('pullable_revisions is abstract')
420
def revision_id_to_revno(self, revision_id):
421
"""Given a revision id, return its revno"""
422
if revision_id is None:
424
history = self.revision_history()
426
return history.index(revision_id) + 1
428
raise bzrlib.errors.NoSuchRevision(self, revision_id)
430
def get_rev_id(self, revno, history=None):
431
"""Find the revision id of the specified revno."""
435
history = self.revision_history()
436
elif revno <= 0 or revno > len(history):
437
raise bzrlib.errors.NoSuchRevision(self, revno)
438
return history[revno - 1]
440
def revision_tree(self, revision_id):
441
"""Return Tree for a revision on this branch.
443
`revision_id` may be None for the null revision, in which case
444
an `EmptyTree` is returned."""
445
raise NotImplementedError('revision_tree is abstract')
447
def working_tree(self):
448
"""Return a `Tree` for the working copy if this is a local branch."""
449
raise NotImplementedError('working_tree is abstract')
451
def pull(self, source, overwrite=False):
452
raise NotImplementedError('pull is abstract')
454
def basis_tree(self):
455
"""Return `Tree` object for last revision.
457
If there are no revisions yet, return an `EmptyTree`.
459
return self.revision_tree(self.last_revision())
461
def rename_one(self, from_rel, to_rel):
464
This can change the directory or the filename or both.
466
raise NotImplementedError('rename_one is abstract')
468
def move(self, from_paths, to_name):
471
to_name must exist as a versioned directory.
473
If to_name exists and is a directory, the files are moved into
474
it, keeping their old names. If it is a directory,
476
Note that to_name is only the last component of the new name;
477
this doesn't change the directory.
479
This returns a list of (from_path, to_path) pairs for each
482
raise NotImplementedError('move is abstract')
484
def get_parent(self):
485
"""Return the parent location of the branch.
487
This is the default location for push/pull/missing. The usual
488
pattern is that the user can override it by specifying a
491
raise NotImplementedError('get_parent is abstract')
493
def get_push_location(self):
494
"""Return the None or the location to push this branch to."""
495
raise NotImplementedError('get_push_location is abstract')
497
def set_push_location(self, location):
498
"""Set a new push location for this branch."""
499
raise NotImplementedError('set_push_location is abstract')
501
def set_parent(self, url):
502
raise NotImplementedError('set_parent is abstract')
504
def check_revno(self, revno):
506
Check whether a revno corresponds to any revision.
507
Zero (the NULL revision) is considered valid.
510
self.check_real_revno(revno)
512
def check_real_revno(self, revno):
514
Check whether a revno corresponds to a real revision.
515
Zero (the NULL revision) is considered invalid
517
if revno < 1 or revno > self.revno():
518
raise InvalidRevisionNumber(revno)
520
def sign_revision(self, revision_id, gpg_strategy):
521
raise NotImplementedError('sign_revision is abstract')
523
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
524
raise NotImplementedError('store_revision_signature is abstract')
527
class BzrBranchFormat(object):
528
"""An encapsulation of the initialization and open routines for a format.
530
Formats provide three things:
531
* An initialization routine,
535
Formats are placed in an dict by their format string for reference
536
during branch opening. Its not required that these be instances, they
537
can be classes themselves with class methods - it simply depends on
538
whether state is needed for a given format or not.
540
Once a format is deprecated, just deprecate the initialize and open
541
methods on the format class. Do not deprecate the object, as the
542
object will be created every time regardless.
546
"""The known formats."""
549
def find_format(klass, transport):
550
"""Return the format registered for URL."""
552
format_string = transport.get(".bzr/branch-format").read()
553
return klass._formats[format_string]
555
raise NotBranchError(path=transport.base)
557
raise errors.UnknownFormatError(format_string)
559
def get_format_string(self):
560
"""Return the ASCII format string that identifies this format."""
561
raise NotImplementedError(self.get_format_string)
563
def _find_modes(self, t):
564
"""Determine the appropriate modes for files and directories.
566
FIXME: When this merges into, or from storage,
567
this code becomes delgatable to a LockableFiles instance.
569
For now its cribbed and returns (dir_mode, file_mode)
573
except errors.TransportNotPossible:
577
dir_mode = st.st_mode & 07777
578
# Remove the sticky and execute bits for files
579
file_mode = dir_mode & ~07111
580
if not BzrBranch._set_dir_mode:
582
if not BzrBranch._set_file_mode:
584
return dir_mode, file_mode
586
def initialize(self, url):
587
"""Create a branch of this format at url and return an open branch."""
588
t = get_transport(url)
589
from bzrlib.inventory import Inventory
590
from bzrlib.weavefile import write_weave_v5
591
from bzrlib.weave import Weave
593
# Create an empty inventory
595
# if we want per-tree root ids then this is the place to set
596
# them; they're not needed for now and so ommitted for
598
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
599
empty_inv = sio.getvalue()
601
bzrlib.weavefile.write_weave_v5(Weave(), sio)
602
empty_weave = sio.getvalue()
604
# Since we don't have a .bzr directory, inherit the
605
# mode from the root directory
606
dir_mode, file_mode = self._find_modes(t)
608
t.mkdir('.bzr', mode=dir_mode)
609
control = t.clone('.bzr')
610
dirs = ['revision-store', 'weaves']
612
StringIO("This is a Bazaar-NG control directory.\n"
613
"Do not change any files in this directory.\n")),
614
('branch-format', StringIO(self.get_format_string())),
615
('revision-history', StringIO('')),
616
('branch-name', StringIO('')),
617
('branch-lock', StringIO('')),
618
('pending-merges', StringIO('')),
619
('inventory', StringIO(empty_inv)),
620
('inventory.weave', StringIO(empty_weave)),
621
('ancestry.weave', StringIO(empty_weave))
623
control.mkdir_multi(dirs, mode=dir_mode)
624
control.put_multi(files, mode=file_mode)
625
mutter('created control directory in ' + t.base)
626
return BzrBranch(t, format=self)
628
def is_supported(self):
629
"""Is this format supported?
631
Supported formats can be initialized and opened.
632
Unsupported formats may not support initialization or committing or
633
some other features depending on the reason for not being supported.
637
def open(self, transport):
638
"""Fill out the data in branch for the branch at url."""
639
return BzrBranch(transport, format=self)
642
def register_format(klass, format):
643
klass._formats[format.get_format_string()] = format
646
def unregister_format(klass, format):
647
assert klass._formats[format.get_format_string()] is format
648
del klass._formats[format.get_format_string()]
651
class BzrBranchFormat4(BzrBranchFormat):
652
"""Bzr branch format 4.
656
- TextStores for texts, inventories,revisions.
658
This format is deprecated: it indexes texts using a text it which is
659
removed in format 5; write support for this format has been removed.
662
def get_format_string(self):
663
"""See BzrBranchFormat.get_format_string()."""
664
return BZR_BRANCH_FORMAT_4
666
def initialize(self, url):
667
"""Format 4 branches cannot be created."""
668
raise UninitializableFormat(self)
670
def is_supported(self):
671
"""Format 4 is not supported.
673
It is not supported because the model changed from 4 to 5 and the
674
conversion logic is expensive - so doing it on the fly was not
680
class BzrBranchFormat5(BzrBranchFormat):
681
"""Bzr branch format 5.
684
- weaves for file texts and inventory
686
- TextStores for revisions and signatures.
689
def get_format_string(self):
690
"""See BzrBranchFormat.get_format_string()."""
691
return BZR_BRANCH_FORMAT_5
694
class BzrBranchFormat6(BzrBranchFormat):
695
"""Bzr branch format 6.
698
- weaves for file texts and inventory
699
- hash subdirectory based stores.
700
- TextStores for revisions and signatures.
703
def get_format_string(self):
704
"""See BzrBranchFormat.get_format_string()."""
705
return BZR_BRANCH_FORMAT_6
708
BzrBranchFormat.register_format(BzrBranchFormat4())
709
BzrBranchFormat.register_format(BzrBranchFormat5())
710
BzrBranchFormat.register_format(BzrBranchFormat6())
713
class BzrBranch(Branch):
714
"""A branch stored in the actual filesystem.
716
Note that it's "local" in the context of the filesystem; it doesn't
717
really matter if it's on an nfs/smb/afs/coda/... share, as long as
718
it's writable, and can be accessed via the normal filesystem API.
724
If _lock_mode is true, a positive count of the number of times the
728
Lock object from bzrlib.lock.
730
# We actually expect this class to be somewhat short-lived; part of its
731
# purpose is to try to isolate what bits of the branch logic are tied to
732
# filesystem access, so that in a later step, we can extricate them to
733
# a separarte ("storage") class.
737
_inventory_weave = None
738
# If set to False (by a plugin, etc) BzrBranch will not set the
739
# mode on created files or directories
740
_set_file_mode = True
743
# Map some sort of prefix into a namespace
744
# stuff like "revno:10", "revid:", etc.
745
# This should match a prefix with a function which accepts
746
REVISION_NAMESPACES = {}
748
def push_stores(self, branch_to):
749
"""See Branch.push_stores."""
750
if (not isinstance(self._branch_format, BzrBranchFormat4) or
751
self._branch_format != branch_to._branch_format):
752
from bzrlib.fetch import greedy_fetch
753
mutter("Using fetch logic to push between %s(%s) and %s(%s)",
754
self, self._branch_format, branch_to, branch_to._branch_format)
755
greedy_fetch(to_branch=branch_to, from_branch=self,
756
revision=self.last_revision())
759
# format 4 to format 4 logic only.
760
store_pairs = ((self.text_store, branch_to.text_store),
761
(self.inventory_store, branch_to.inventory_store),
762
(self.revision_store, branch_to.revision_store))
764
for from_store, to_store in store_pairs:
765
copy_all(from_store, to_store)
766
except UnlistableStore:
767
raise UnlistableBranch(from_store)
769
def __init__(self, transport, init=deprecated_nonce,
770
relax_version_check=deprecated_nonce, format=None):
771
"""Create new branch object at a particular location.
773
transport -- A Transport object, defining how to access files.
775
init -- If True, create new control files in a previously
776
unversioned directory. If False, the branch must already
779
relax_version_check -- If true, the usual check for the branch
780
version is not applied. This is intended only for
781
upgrade/recovery type use; it's not guaranteed that
782
all operations will work on old format branches.
784
In the test suite, creation of new trees is tested using the
785
`ScratchBranch` class.
787
assert isinstance(transport, Transport), \
788
"%r is not a Transport" % transport
789
self._transport = transport
790
if deprecated_passed(init):
791
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
792
"deprecated as of bzr 0.8. Please use Branch.initialize().",
795
# this is slower than before deprecation, oh well never mind.
797
self._initialize(transport.base)
799
self._check_format(format)
800
if deprecated_passed(relax_version_check):
801
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
802
"relax_version_check parameter is deprecated as of bzr 0.8. "
803
"Please use Branch.open_downlevel, or a BzrBranchFormat's "
804
"open() method.", DeprecationWarning)
805
if (not relax_version_check
806
and not self._branch_format.is_supported()):
807
raise errors.UnsupportedFormatError(
808
'sorry, branch format %r not supported' % fmt,
809
['use a different bzr version',
810
'or remove the .bzr directory'
811
' and "bzr init" again'])
813
def get_store(name, compressed=True, prefixed=False):
814
relpath = self._rel_controlfilename(safe_unicode(name))
815
store = TextStore(self._transport.clone(relpath),
816
dir_mode=self._dir_mode,
817
file_mode=self._file_mode,
819
compressed=compressed)
822
def get_weave(name, prefixed=False):
823
relpath = self._rel_controlfilename(unicode(name))
824
ws = WeaveStore(self._transport.clone(relpath),
826
dir_mode=self._dir_mode,
827
file_mode=self._file_mode)
828
if self._transport.should_cache():
829
ws.enable_cache = True
832
if isinstance(self._branch_format, BzrBranchFormat4):
833
self.inventory_store = get_store('inventory-store')
834
self.text_store = get_store('text-store')
835
self.revision_store = get_store('revision-store')
836
elif isinstance(self._branch_format, BzrBranchFormat5):
837
self.control_weaves = get_weave(u'')
838
self.weave_store = get_weave(u'weaves')
839
self.revision_store = get_store(u'revision-store', compressed=False)
840
elif isinstance(self._branch_format, BzrBranchFormat6):
841
self.control_weaves = get_weave(u'')
842
self.weave_store = get_weave(u'weaves', prefixed=True)
843
self.revision_store = get_store(u'revision-store', compressed=False,
845
self.revision_store.register_suffix('sig')
846
self._transaction = None
849
def _initialize(base):
850
"""Create a bzr branch in the latest format."""
851
return BzrBranchFormat6().initialize(base)
854
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
859
if self._lock_mode or self._lock:
860
# XXX: This should show something every time, and be suitable for
861
# headless operation and embedding
862
warn("branch %r was not explicitly unlocked" % self)
865
# TODO: It might be best to do this somewhere else,
866
# but it is nice for a Branch object to automatically
867
# cache it's information.
868
# Alternatively, we could have the Transport objects cache requests
869
# See the earlier discussion about how major objects (like Branch)
870
# should never expect their __del__ function to run.
871
if hasattr(self, 'cache_root') and self.cache_root is not None:
873
shutil.rmtree(self.cache_root)
876
self.cache_root = None
880
return self._transport.base
883
base = property(_get_base, doc="The URL for the root of this branch.")
885
def _finish_transaction(self):
886
"""Exit the current transaction."""
887
if self._transaction is None:
888
raise errors.LockError('Branch %s is not in a transaction' %
890
transaction = self._transaction
891
self._transaction = None
894
def get_transaction(self):
895
"""See Branch.get_transaction."""
896
if self._transaction is None:
897
return transactions.PassThroughTransaction()
899
return self._transaction
901
def _set_transaction(self, new_transaction):
902
"""Set a new active transaction."""
903
if self._transaction is not None:
904
raise errors.LockError('Branch %s is in a transaction already.' %
906
self._transaction = new_transaction
908
def lock_write(self):
909
#mutter("lock write: %s (%s)", self, self._lock_count)
910
# TODO: Upgrade locking to support using a Transport,
911
# and potentially a remote locking protocol
913
if self._lock_mode != 'w':
914
raise LockError("can't upgrade to a write lock from %r" %
916
self._lock_count += 1
918
self._lock = self._transport.lock_write(
919
self._rel_controlfilename('branch-lock'))
920
self._lock_mode = 'w'
922
self._set_transaction(transactions.PassThroughTransaction())
925
#mutter("lock read: %s (%s)", self, self._lock_count)
927
assert self._lock_mode in ('r', 'w'), \
928
"invalid lock mode %r" % self._lock_mode
929
self._lock_count += 1
931
self._lock = self._transport.lock_read(
932
self._rel_controlfilename('branch-lock'))
933
self._lock_mode = 'r'
935
self._set_transaction(transactions.ReadOnlyTransaction())
936
# 5K may be excessive, but hey, its a knob.
937
self.get_transaction().set_cache_size(5000)
940
#mutter("unlock: %s (%s)", self, self._lock_count)
941
if not self._lock_mode:
942
raise LockError('branch %r is not locked' % (self))
944
if self._lock_count > 1:
945
self._lock_count -= 1
947
self._finish_transaction()
950
self._lock_mode = self._lock_count = None
952
def abspath(self, name):
953
"""See Branch.abspath."""
954
return self._transport.abspath(name)
956
def _rel_controlfilename(self, file_or_path):
957
if not isinstance(file_or_path, basestring):
958
file_or_path = u'/'.join(file_or_path)
959
if file_or_path == '':
961
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
963
def controlfilename(self, file_or_path):
964
"""See Branch.controlfilename."""
965
return self._transport.abspath(self._rel_controlfilename(file_or_path))
967
def controlfile(self, file_or_path, mode='r'):
968
"""See Branch.controlfile."""
971
relpath = self._rel_controlfilename(file_or_path)
972
#TODO: codecs.open() buffers linewise, so it was overloaded with
973
# a much larger buffer, do we need to do the same for getreader/getwriter?
975
return self._transport.get(relpath)
977
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
979
# XXX: Do we really want errors='replace'? Perhaps it should be
980
# an error, or at least reported, if there's incorrectly-encoded
981
# data inside a file.
982
# <https://launchpad.net/products/bzr/+bug/3823>
983
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
985
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
987
raise BzrError("invalid controlfile mode %r" % mode)
989
def put_controlfile(self, path, f, encode=True):
990
"""See Branch.put_controlfile."""
991
self.put_controlfiles([(path, f)], encode=encode)
993
def put_controlfiles(self, files, encode=True):
994
"""See Branch.put_controlfiles."""
997
for path, f in files:
999
if isinstance(f, basestring):
1000
f = f.encode('utf-8', 'replace')
1002
f = codecs.getwriter('utf-8')(f, errors='replace')
1003
path = self._rel_controlfilename(path)
1004
ctrl_files.append((path, f))
1005
self._transport.put_multi(ctrl_files, mode=self._file_mode)
1007
def _find_modes(self, path=None):
1008
"""Determine the appropriate modes for files and directories."""
1011
path = self._rel_controlfilename('')
1012
st = self._transport.stat(path)
1013
except errors.TransportNotPossible:
1014
self._dir_mode = 0755
1015
self._file_mode = 0644
1017
self._dir_mode = st.st_mode & 07777
1018
# Remove the sticky and execute bits for files
1019
self._file_mode = self._dir_mode & ~07111
1020
if not self._set_dir_mode:
1021
self._dir_mode = None
1022
if not self._set_file_mode:
1023
self._file_mode = None
1025
def _check_format(self, format):
1026
"""Identify the branch format if needed.
1028
The format is stored as a reference to the format object in
1029
self._branch_format for code that needs to check it later.
1031
The format parameter is either None or the branch format class
1032
used to open this branch.
1035
format = BzrBranchFormat.find_format(self._transport)
1036
self._branch_format = format
1037
mutter("got branch format %s", self._branch_format)
1040
def get_root_id(self):
1041
"""See Branch.get_root_id."""
1042
inv = self.get_inventory(self.last_revision())
1043
return inv.root.file_id
1046
def print_file(self, file, revision_id):
1047
"""See Branch.print_file."""
1048
tree = self.revision_tree(revision_id)
1049
# use inventory as it was in that revision
1050
file_id = tree.inventory.path2id(file)
1053
revno = self.revision_id_to_revno(revision_id)
1054
except errors.NoSuchRevision:
1055
# TODO: This should not be BzrError,
1056
# but NoSuchFile doesn't fit either
1057
raise BzrError('%r is not present in revision %s'
1058
% (file, revision_id))
1060
raise BzrError('%r is not present in revision %s'
1062
tree.print_file(file_id)
1065
def append_revision(self, *revision_ids):
1066
"""See Branch.append_revision."""
1067
for revision_id in revision_ids:
1068
mutter("add {%s} to revision-history" % revision_id)
1069
rev_history = self.revision_history()
1070
rev_history.extend(revision_ids)
1071
self.set_revision_history(rev_history)
1074
def set_revision_history(self, rev_history):
1075
"""See Branch.set_revision_history."""
1076
old_revision = self.last_revision()
1077
new_revision = rev_history[-1]
1078
self.put_controlfile('revision-history', '\n'.join(rev_history))
1080
self.working_tree().set_last_revision(new_revision, old_revision)
1081
except NoWorkingTree:
1082
mutter('Unable to set_last_revision without a working tree.')
1084
def has_revision(self, revision_id):
1085
"""See Branch.has_revision."""
1086
return (revision_id is None
1087
or self.revision_store.has_id(revision_id))
1090
def _get_revision_xml_file(self, revision_id):
1091
if not revision_id or not isinstance(revision_id, basestring):
1092
raise InvalidRevisionId(revision_id=revision_id, branch=self)
1094
return self.revision_store.get(revision_id)
1095
except (IndexError, KeyError):
1096
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1098
def get_revision_xml(self, revision_id):
1099
"""See Branch.get_revision_xml."""
1100
return self._get_revision_xml_file(revision_id).read()
1102
def get_revision(self, revision_id):
1103
"""See Branch.get_revision."""
1104
xml_file = self._get_revision_xml_file(revision_id)
1107
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1108
except SyntaxError, e:
1109
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
1113
assert r.revision_id == revision_id
1116
def get_revision_sha1(self, revision_id):
1117
"""See Branch.get_revision_sha1."""
1118
# In the future, revision entries will be signed. At that
1119
# point, it is probably best *not* to include the signature
1120
# in the revision hash. Because that lets you re-sign
1121
# the revision, (add signatures/remove signatures) and still
1122
# have all hash pointers stay consistent.
1123
# But for now, just hash the contents.
1124
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
1126
def get_ancestry(self, revision_id):
1127
"""See Branch.get_ancestry."""
1128
if revision_id is None:
1130
w = self._get_inventory_weave()
1131
return [None] + map(w.idx_to_name,
1132
w.inclusions([w.lookup(revision_id)]))
1134
def _get_inventory_weave(self):
1135
return self.control_weaves.get_weave('inventory',
1136
self.get_transaction())
1138
def get_inventory(self, revision_id):
1139
"""See Branch.get_inventory."""
1140
xml = self.get_inventory_xml(revision_id)
1141
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1143
def get_inventory_xml(self, revision_id):
1144
"""See Branch.get_inventory_xml."""
1146
assert isinstance(revision_id, basestring), type(revision_id)
1147
iw = self._get_inventory_weave()
1148
return iw.get_text(iw.lookup(revision_id))
1150
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1152
def get_inventory_sha1(self, revision_id):
1153
"""See Branch.get_inventory_sha1."""
1154
return self.get_revision(revision_id).inventory_sha1
1156
def get_revision_inventory(self, revision_id):
1157
"""See Branch.get_revision_inventory."""
1158
# TODO: Unify this with get_inventory()
1159
# bzr 0.0.6 and later imposes the constraint that the inventory_id
1160
# must be the same as its revision, so this is trivial.
1161
if revision_id == None:
1162
# This does not make sense: if there is no revision,
1163
# then it is the current tree inventory surely ?!
1164
# and thus get_root_id() is something that looks at the last
1165
# commit on the branch, and the get_root_id is an inventory check.
1166
raise NotImplementedError
1167
# return Inventory(self.get_root_id())
1169
return self.get_inventory(revision_id)
1172
def revision_history(self):
1173
"""See Branch.revision_history."""
1174
transaction = self.get_transaction()
1175
history = transaction.map.find_revision_history()
1176
if history is not None:
1177
mutter("cache hit for revision-history in %s", self)
1178
return list(history)
1179
history = [l.rstrip('\r\n') for l in
1180
self.controlfile('revision-history', 'r').readlines()]
1181
transaction.map.add_revision_history(history)
1182
# this call is disabled because revision_history is
1183
# not really an object yet, and the transaction is for objects.
1184
# transaction.register_clean(history, precious=True)
1185
return list(history)
1187
def update_revisions(self, other, stop_revision=None):
1188
"""See Branch.update_revisions."""
1189
from bzrlib.fetch import greedy_fetch
1190
if stop_revision is None:
1191
stop_revision = other.last_revision()
1192
### Should this be checking is_ancestor instead of revision_history?
1193
if (stop_revision is not None and
1194
stop_revision in self.revision_history()):
1196
greedy_fetch(to_branch=self, from_branch=other,
1197
revision=stop_revision)
1198
pullable_revs = self.pullable_revisions(other, stop_revision)
1199
if len(pullable_revs) > 0:
1200
self.append_revision(*pullable_revs)
1202
def pullable_revisions(self, other, stop_revision):
1203
"""See Branch.pullable_revisions."""
1204
other_revno = other.revision_id_to_revno(stop_revision)
1206
return self.missing_revisions(other, other_revno)
1207
except DivergedBranches, e:
1209
pullable_revs = get_intervening_revisions(self.last_revision(),
1210
stop_revision, self)
1211
assert self.last_revision() not in pullable_revs
1212
return pullable_revs
1213
except bzrlib.errors.NotAncestor:
1214
if is_ancestor(self.last_revision(), stop_revision, self):
1219
def revision_tree(self, revision_id):
1220
"""See Branch.revision_tree."""
1221
# TODO: refactor this to use an existing revision object
1222
# so we don't need to read it in twice.
1223
if revision_id == None or revision_id == NULL_REVISION:
1226
inv = self.get_revision_inventory(revision_id)
1227
return RevisionTree(self, inv, revision_id)
1229
def basis_tree(self):
1230
"""See Branch.basis_tree."""
1232
revision_id = self.revision_history()[-1]
1233
xml = self.working_tree().read_basis_inventory(revision_id)
1234
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1235
return RevisionTree(self, inv, revision_id)
1236
except (IndexError, NoSuchFile, NoWorkingTree), e:
1237
return self.revision_tree(self.last_revision())
1239
def working_tree(self):
1240
"""See Branch.working_tree."""
1241
from bzrlib.workingtree import WorkingTree
1242
if self._transport.base.find('://') != -1:
1243
raise NoWorkingTree(self.base)
1244
return WorkingTree(self.base, branch=self)
1247
def pull(self, source, overwrite=False):
1248
"""See Branch.pull."""
1251
old_count = len(self.revision_history())
1253
self.update_revisions(source)
1254
except DivergedBranches:
1258
self.set_revision_history(source.revision_history())
1259
new_count = len(self.revision_history())
1260
return new_count - old_count
1264
def get_parent(self):
1265
"""See Branch.get_parent."""
1267
_locs = ['parent', 'pull', 'x-pull']
1270
return self.controlfile(l, 'r').read().strip('\n')
1275
def get_push_location(self):
1276
"""See Branch.get_push_location."""
1277
config = bzrlib.config.BranchConfig(self)
1278
push_loc = config.get_user_option('push_location')
1281
def set_push_location(self, location):
1282
"""See Branch.set_push_location."""
1283
config = bzrlib.config.LocationConfig(self.base)
1284
config.set_user_option('push_location', location)
1287
def set_parent(self, url):
1288
"""See Branch.set_parent."""
1289
# TODO: Maybe delete old location files?
1290
from bzrlib.atomicfile import AtomicFile
1291
f = AtomicFile(self.controlfilename('parent'))
1298
def tree_config(self):
1299
return TreeConfig(self)
1301
def sign_revision(self, revision_id, gpg_strategy):
1302
"""See Branch.sign_revision."""
1303
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1304
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1307
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1308
"""See Branch.store_revision_signature."""
1309
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1313
Branch.set_default_initializer(BzrBranch._initialize)
1316
class BranchTestProviderAdapter(object):
1317
"""A tool to generate a suite testing multiple branch formats at once.
1319
This is done by copying the test once for each transport and injecting
1320
the transport_server, transport_readonly_server, and branch_format
1321
classes into each copy. Each copy is also given a new id() to make it
1325
def __init__(self, transport_server, transport_readonly_server, formats):
1326
self._transport_server = transport_server
1327
self._transport_readonly_server = transport_readonly_server
1328
self._formats = formats
1330
def adapt(self, test):
1331
result = TestSuite()
1332
for format in self._formats:
1333
new_test = deepcopy(test)
1334
new_test.transport_server = self._transport_server
1335
new_test.transport_readonly_server = self._transport_readonly_server
1336
new_test.branch_format = format
1337
def make_new_test_id():
1338
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1339
return lambda: new_id
1340
new_test.id = make_new_test_id()
1341
result.addTest(new_test)
1345
class ScratchBranch(BzrBranch):
1346
"""Special test class: a branch that cleans up after itself.
1348
>>> b = ScratchBranch()
1352
>>> b._transport.__del__()
1357
def __init__(self, files=[], dirs=[], transport=None):
1358
"""Make a test branch.
1360
This creates a temporary directory and runs init-tree in it.
1362
If any files are listed, they are created in the working copy.
1364
if transport is None:
1365
transport = bzrlib.transport.local.ScratchTransport()
1366
Branch.initialize(transport.base)
1367
super(ScratchBranch, self).__init__(transport)
1369
super(ScratchBranch, self).__init__(transport)
1372
self._transport.mkdir(d)
1375
self._transport.put(f, 'content of %s' % f)
1380
>>> orig = ScratchBranch(files=["file1", "file2"])
1381
>>> clone = orig.clone()
1382
>>> if os.name != 'nt':
1383
... os.path.samefile(orig.base, clone.base)
1385
... orig.base == clone.base
1388
>>> os.path.isfile(pathjoin(clone.base, "file1"))
1391
from shutil import copytree
1392
from bzrlib.osutils import mkdtemp
1395
copytree(self.base, base, symlinks=True)
1396
return ScratchBranch(
1397
transport=bzrlib.transport.local.ScratchTransport(base))
1400
######################################################################
1404
def is_control_file(filename):
1405
## FIXME: better check
1406
filename = normpath(filename)
1407
while filename != '':
1408
head, tail = os.path.split(filename)
1409
## mutter('check %r for control file' % ((head, tail), ))
1410
if tail == bzrlib.BZRDIR:
1412
if filename == head: