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 (self._branch_format != branch_to._branch_format
751
or self._branch_format != 4):
752
from bzrlib.fetch import greedy_fetch
753
mutter("falling back to 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
store_pairs = ((self.text_store, branch_to.text_store),
760
(self.inventory_store, branch_to.inventory_store),
761
(self.revision_store, branch_to.revision_store))
763
for from_store, to_store in store_pairs:
764
copy_all(from_store, to_store)
765
except UnlistableStore:
766
raise UnlistableBranch(from_store)
768
def __init__(self, transport, init=deprecated_nonce,
769
relax_version_check=deprecated_nonce, format=None):
770
"""Create new branch object at a particular location.
772
transport -- A Transport object, defining how to access files.
774
init -- If True, create new control files in a previously
775
unversioned directory. If False, the branch must already
778
relax_version_check -- If true, the usual check for the branch
779
version is not applied. This is intended only for
780
upgrade/recovery type use; it's not guaranteed that
781
all operations will work on old format branches.
783
In the test suite, creation of new trees is tested using the
784
`ScratchBranch` class.
786
assert isinstance(transport, Transport), \
787
"%r is not a Transport" % transport
788
self._transport = transport
789
if deprecated_passed(init):
790
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
791
"deprecated as of bzr 0.8. Please use Branch.initialize().",
794
# this is slower than before deprecation, oh well never mind.
796
self._initialize(transport.base)
798
self._check_format(format)
799
if deprecated_passed(relax_version_check):
800
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
801
"relax_version_check parameter is deprecated as of bzr 0.8. "
802
"Please use Branch.open_downlevel, or a BzrBranchFormat's "
803
"open() method.", DeprecationWarning)
804
if (not relax_version_check
805
and self._branch_format not in (5, 6)):
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 self._branch_format == 4:
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 self._branch_format == 5:
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 self._branch_format == 6:
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
"""Check this branch format is supported.
1028
The format level is stored, as an integer, in
1029
self._branch_format for code that needs to check it later.
1031
In the future, we might need different in-memory Branch
1032
classes to support downlevel branches. But not yet.
1034
The format parameter is either None or the branch format class
1035
used to open this branch.
1038
format = BzrBranchFormat.find_format(self._transport)
1039
fmt = format.get_format_string()
1040
mutter("got branch format %r", fmt)
1041
if fmt == BZR_BRANCH_FORMAT_6:
1042
self._branch_format = 6
1043
elif fmt == BZR_BRANCH_FORMAT_5:
1044
self._branch_format = 5
1045
elif fmt == BZR_BRANCH_FORMAT_4:
1046
self._branch_format = 4
1049
def get_root_id(self):
1050
"""See Branch.get_root_id."""
1051
inv = self.get_inventory(self.last_revision())
1052
return inv.root.file_id
1055
def print_file(self, file, revision_id):
1056
"""See Branch.print_file."""
1057
tree = self.revision_tree(revision_id)
1058
# use inventory as it was in that revision
1059
file_id = tree.inventory.path2id(file)
1062
revno = self.revision_id_to_revno(revision_id)
1063
except errors.NoSuchRevision:
1064
# TODO: This should not be BzrError,
1065
# but NoSuchFile doesn't fit either
1066
raise BzrError('%r is not present in revision %s'
1067
% (file, revision_id))
1069
raise BzrError('%r is not present in revision %s'
1071
tree.print_file(file_id)
1074
def append_revision(self, *revision_ids):
1075
"""See Branch.append_revision."""
1076
for revision_id in revision_ids:
1077
mutter("add {%s} to revision-history" % revision_id)
1078
rev_history = self.revision_history()
1079
rev_history.extend(revision_ids)
1080
self.set_revision_history(rev_history)
1083
def set_revision_history(self, rev_history):
1084
"""See Branch.set_revision_history."""
1085
old_revision = self.last_revision()
1086
new_revision = rev_history[-1]
1087
self.put_controlfile('revision-history', '\n'.join(rev_history))
1089
self.working_tree().set_last_revision(new_revision, old_revision)
1090
except NoWorkingTree:
1091
mutter('Unable to set_last_revision without a working tree.')
1093
def has_revision(self, revision_id):
1094
"""See Branch.has_revision."""
1095
return (revision_id is None
1096
or self.revision_store.has_id(revision_id))
1099
def _get_revision_xml_file(self, revision_id):
1100
if not revision_id or not isinstance(revision_id, basestring):
1101
raise InvalidRevisionId(revision_id=revision_id, branch=self)
1103
return self.revision_store.get(revision_id)
1104
except (IndexError, KeyError):
1105
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1107
def get_revision_xml(self, revision_id):
1108
"""See Branch.get_revision_xml."""
1109
return self._get_revision_xml_file(revision_id).read()
1111
def get_revision(self, revision_id):
1112
"""See Branch.get_revision."""
1113
xml_file = self._get_revision_xml_file(revision_id)
1116
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1117
except SyntaxError, e:
1118
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
1122
assert r.revision_id == revision_id
1125
def get_revision_sha1(self, revision_id):
1126
"""See Branch.get_revision_sha1."""
1127
# In the future, revision entries will be signed. At that
1128
# point, it is probably best *not* to include the signature
1129
# in the revision hash. Because that lets you re-sign
1130
# the revision, (add signatures/remove signatures) and still
1131
# have all hash pointers stay consistent.
1132
# But for now, just hash the contents.
1133
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
1135
def get_ancestry(self, revision_id):
1136
"""See Branch.get_ancestry."""
1137
if revision_id is None:
1139
w = self._get_inventory_weave()
1140
return [None] + map(w.idx_to_name,
1141
w.inclusions([w.lookup(revision_id)]))
1143
def _get_inventory_weave(self):
1144
return self.control_weaves.get_weave('inventory',
1145
self.get_transaction())
1147
def get_inventory(self, revision_id):
1148
"""See Branch.get_inventory."""
1149
xml = self.get_inventory_xml(revision_id)
1150
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1152
def get_inventory_xml(self, revision_id):
1153
"""See Branch.get_inventory_xml."""
1155
assert isinstance(revision_id, basestring), type(revision_id)
1156
iw = self._get_inventory_weave()
1157
return iw.get_text(iw.lookup(revision_id))
1159
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1161
def get_inventory_sha1(self, revision_id):
1162
"""See Branch.get_inventory_sha1."""
1163
return self.get_revision(revision_id).inventory_sha1
1165
def get_revision_inventory(self, revision_id):
1166
"""See Branch.get_revision_inventory."""
1167
# TODO: Unify this with get_inventory()
1168
# bzr 0.0.6 and later imposes the constraint that the inventory_id
1169
# must be the same as its revision, so this is trivial.
1170
if revision_id == None:
1171
# This does not make sense: if there is no revision,
1172
# then it is the current tree inventory surely ?!
1173
# and thus get_root_id() is something that looks at the last
1174
# commit on the branch, and the get_root_id is an inventory check.
1175
raise NotImplementedError
1176
# return Inventory(self.get_root_id())
1178
return self.get_inventory(revision_id)
1181
def revision_history(self):
1182
"""See Branch.revision_history."""
1183
transaction = self.get_transaction()
1184
history = transaction.map.find_revision_history()
1185
if history is not None:
1186
mutter("cache hit for revision-history in %s", self)
1187
return list(history)
1188
history = [l.rstrip('\r\n') for l in
1189
self.controlfile('revision-history', 'r').readlines()]
1190
transaction.map.add_revision_history(history)
1191
# this call is disabled because revision_history is
1192
# not really an object yet, and the transaction is for objects.
1193
# transaction.register_clean(history, precious=True)
1194
return list(history)
1196
def update_revisions(self, other, stop_revision=None):
1197
"""See Branch.update_revisions."""
1198
from bzrlib.fetch import greedy_fetch
1199
if stop_revision is None:
1200
stop_revision = other.last_revision()
1201
### Should this be checking is_ancestor instead of revision_history?
1202
if (stop_revision is not None and
1203
stop_revision in self.revision_history()):
1205
greedy_fetch(to_branch=self, from_branch=other,
1206
revision=stop_revision)
1207
pullable_revs = self.pullable_revisions(other, stop_revision)
1208
if len(pullable_revs) > 0:
1209
self.append_revision(*pullable_revs)
1211
def pullable_revisions(self, other, stop_revision):
1212
"""See Branch.pullable_revisions."""
1213
other_revno = other.revision_id_to_revno(stop_revision)
1215
return self.missing_revisions(other, other_revno)
1216
except DivergedBranches, e:
1218
pullable_revs = get_intervening_revisions(self.last_revision(),
1219
stop_revision, self)
1220
assert self.last_revision() not in pullable_revs
1221
return pullable_revs
1222
except bzrlib.errors.NotAncestor:
1223
if is_ancestor(self.last_revision(), stop_revision, self):
1228
def revision_tree(self, revision_id):
1229
"""See Branch.revision_tree."""
1230
# TODO: refactor this to use an existing revision object
1231
# so we don't need to read it in twice.
1232
if revision_id == None or revision_id == NULL_REVISION:
1235
inv = self.get_revision_inventory(revision_id)
1236
return RevisionTree(self, inv, revision_id)
1238
def basis_tree(self):
1239
"""See Branch.basis_tree."""
1241
revision_id = self.revision_history()[-1]
1242
xml = self.working_tree().read_basis_inventory(revision_id)
1243
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1244
return RevisionTree(self, inv, revision_id)
1245
except (IndexError, NoSuchFile, NoWorkingTree), e:
1246
return self.revision_tree(self.last_revision())
1248
def working_tree(self):
1249
"""See Branch.working_tree."""
1250
from bzrlib.workingtree import WorkingTree
1251
if self._transport.base.find('://') != -1:
1252
raise NoWorkingTree(self.base)
1253
return WorkingTree(self.base, branch=self)
1256
def pull(self, source, overwrite=False):
1257
"""See Branch.pull."""
1260
old_count = len(self.revision_history())
1262
self.update_revisions(source)
1263
except DivergedBranches:
1267
self.set_revision_history(source.revision_history())
1268
new_count = len(self.revision_history())
1269
return new_count - old_count
1273
def get_parent(self):
1274
"""See Branch.get_parent."""
1276
_locs = ['parent', 'pull', 'x-pull']
1279
return self.controlfile(l, 'r').read().strip('\n')
1284
def get_push_location(self):
1285
"""See Branch.get_push_location."""
1286
config = bzrlib.config.BranchConfig(self)
1287
push_loc = config.get_user_option('push_location')
1290
def set_push_location(self, location):
1291
"""See Branch.set_push_location."""
1292
config = bzrlib.config.LocationConfig(self.base)
1293
config.set_user_option('push_location', location)
1296
def set_parent(self, url):
1297
"""See Branch.set_parent."""
1298
# TODO: Maybe delete old location files?
1299
from bzrlib.atomicfile import AtomicFile
1300
f = AtomicFile(self.controlfilename('parent'))
1307
def tree_config(self):
1308
return TreeConfig(self)
1310
def sign_revision(self, revision_id, gpg_strategy):
1311
"""See Branch.sign_revision."""
1312
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1313
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1316
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1317
"""See Branch.store_revision_signature."""
1318
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1322
Branch.set_default_initializer(BzrBranch._initialize)
1325
class BranchTestProviderAdapter(object):
1326
"""A tool to generate a suite testing multiple branch formats at once.
1328
This is done by copying the test once for each transport and injecting
1329
the transport_server, transport_readonly_server, and branch_format
1330
classes into each copy. Each copy is also given a new id() to make it
1334
def __init__(self, transport_server, transport_readonly_server, formats):
1335
self._transport_server = transport_server
1336
self._transport_readonly_server = transport_readonly_server
1337
self._formats = formats
1339
def adapt(self, test):
1340
result = TestSuite()
1341
for format in self._formats:
1342
new_test = deepcopy(test)
1343
new_test.transport_server = self._transport_server
1344
new_test.transport_readonly_server = self._transport_readonly_server
1345
new_test.branch_format = format
1346
def make_new_test_id():
1347
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1348
return lambda: new_id
1349
new_test.id = make_new_test_id()
1350
result.addTest(new_test)
1354
class ScratchBranch(BzrBranch):
1355
"""Special test class: a branch that cleans up after itself.
1357
>>> b = ScratchBranch()
1361
>>> b._transport.__del__()
1366
def __init__(self, files=[], dirs=[], transport=None):
1367
"""Make a test branch.
1369
This creates a temporary directory and runs init-tree in it.
1371
If any files are listed, they are created in the working copy.
1373
if transport is None:
1374
transport = bzrlib.transport.local.ScratchTransport()
1375
Branch.initialize(transport.base)
1376
super(ScratchBranch, self).__init__(transport)
1378
super(ScratchBranch, self).__init__(transport)
1381
self._transport.mkdir(d)
1384
self._transport.put(f, 'content of %s' % f)
1389
>>> orig = ScratchBranch(files=["file1", "file2"])
1390
>>> clone = orig.clone()
1391
>>> if os.name != 'nt':
1392
... os.path.samefile(orig.base, clone.base)
1394
... orig.base == clone.base
1397
>>> os.path.isfile(pathjoin(clone.base, "file1"))
1400
from shutil import copytree
1401
from bzrlib.osutils import mkdtemp
1404
copytree(self.base, base, symlinks=True)
1405
return ScratchBranch(
1406
transport=bzrlib.transport.local.ScratchTransport(base))
1409
######################################################################
1413
def is_control_file(filename):
1414
## FIXME: better check
1415
filename = normpath(filename)
1416
while filename != '':
1417
head, tail = os.path.split(filename)
1418
## mutter('check %r for control file' % ((head, tail), ))
1419
if tail == bzrlib.BZRDIR:
1421
if filename == head: