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.
123
Only local branches are supported."""
124
return BzrBranch(get_transport(base), relax_version_check=True)
128
"""Open an existing branch, rooted at 'base' (url)"""
129
t = get_transport(base)
130
mutter("trying to open %r with transport %r", base, t)
131
format = BzrBranchFormat.find_format(t)
132
return format.open(t)
135
def open_containing(url):
136
"""Open an existing branch which contains url.
138
This probes for a branch at url, and searches upwards from there.
140
Basically we keep looking up until we find the control directory or
141
run into the root. If there isn't one, raises NotBranchError.
142
If there is one, it is returned, along with the unused portion of url.
144
t = get_transport(url)
147
format = BzrBranchFormat.find_format(t)
148
return format.open(t), t.relpath(url)
149
# TODO FIXME, distinguish between formats that cannot be
150
# identified, and a lack of format.
151
except NotBranchError, e:
152
mutter('not a branch in: %r %s', t.base, e)
153
new_t = t.clone('..')
154
if new_t.base == t.base:
155
# reached the root, whatever that may be
156
raise NotBranchError(path=url)
160
def initialize(base):
161
"""Create a new branch, rooted at 'base' (url)
163
This will call the current default initializer with base
164
as the only parameter.
166
return Branch._default_initializer(safe_unicode(base))
169
def get_default_initializer():
170
"""Return the initializer being used for new branches."""
171
return Branch._default_initializer
174
def set_default_initializer(initializer):
175
"""Set the initializer to be used for new branches."""
176
Branch._default_initializer = staticmethod(initializer)
178
def setup_caching(self, cache_root):
179
"""Subclasses that care about caching should override this, and set
180
up cached stores located under cache_root.
182
self.cache_root = cache_root
185
cfg = self.tree_config()
186
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
188
def _set_nick(self, nick):
189
cfg = self.tree_config()
190
cfg.set_option(nick, "nickname")
191
assert cfg.get_option("nickname") == nick
193
nick = property(_get_nick, _set_nick)
195
def push_stores(self, branch_to):
196
"""Copy the content of this branches store to branch_to."""
197
raise NotImplementedError('push_stores is abstract')
199
def get_transaction(self):
200
"""Return the current active transaction.
202
If no transaction is active, this returns a passthrough object
203
for which all data is immediately flushed and no caching happens.
205
raise NotImplementedError('get_transaction is abstract')
207
def lock_write(self):
208
raise NotImplementedError('lock_write is abstract')
211
raise NotImplementedError('lock_read is abstract')
214
raise NotImplementedError('unlock is abstract')
216
def abspath(self, name):
217
"""Return absolute filename for something in the branch
219
XXX: Robert Collins 20051017 what is this used for? why is it a branch
220
method and not a tree method.
222
raise NotImplementedError('abspath is abstract')
224
def controlfilename(self, file_or_path):
225
"""Return location relative to branch."""
226
raise NotImplementedError('controlfilename is abstract')
228
def controlfile(self, file_or_path, mode='r'):
229
"""Open a control file for this branch.
231
There are two classes of file in the control directory: text
232
and binary. binary files are untranslated byte streams. Text
233
control files are stored with Unix newlines and in UTF-8, even
234
if the platform or locale defaults are different.
236
Controlfiles should almost never be opened in write mode but
237
rather should be atomically copied and replaced using atomicfile.
239
raise NotImplementedError('controlfile is abstract')
241
def put_controlfile(self, path, f, encode=True):
242
"""Write an entry as a controlfile.
244
:param path: The path to put the file, relative to the .bzr control
246
:param f: A file-like or string object whose contents should be copied.
247
:param encode: If true, encode the contents as utf-8
249
raise NotImplementedError('put_controlfile is abstract')
251
def put_controlfiles(self, files, encode=True):
252
"""Write several entries as controlfiles.
254
:param files: A list of [(path, file)] pairs, where the path is the directory
255
underneath the bzr control directory
256
:param encode: If true, encode the contents as utf-8
258
raise NotImplementedError('put_controlfiles is abstract')
260
def get_root_id(self):
261
"""Return the id of this branches root"""
262
raise NotImplementedError('get_root_id is abstract')
264
def set_root_id(self, file_id):
265
raise NotImplementedError('set_root_id is abstract')
267
def print_file(self, file, revision_id):
268
"""Print `file` to stdout."""
269
raise NotImplementedError('print_file is abstract')
271
def append_revision(self, *revision_ids):
272
raise NotImplementedError('append_revision is abstract')
274
def set_revision_history(self, rev_history):
275
raise NotImplementedError('set_revision_history is abstract')
277
def has_revision(self, revision_id):
278
"""True if this branch has a copy of the revision.
280
This does not necessarily imply the revision is merge
281
or on the mainline."""
282
raise NotImplementedError('has_revision is abstract')
284
def get_revision_xml(self, revision_id):
285
raise NotImplementedError('get_revision_xml is abstract')
287
def get_revision(self, revision_id):
288
"""Return the Revision object for a named revision"""
289
raise NotImplementedError('get_revision is abstract')
291
def get_revision_delta(self, revno):
292
"""Return the delta for one revision.
294
The delta is relative to its mainline predecessor, or the
295
empty tree for revision 1.
297
assert isinstance(revno, int)
298
rh = self.revision_history()
299
if not (1 <= revno <= len(rh)):
300
raise InvalidRevisionNumber(revno)
302
# revno is 1-based; list is 0-based
304
new_tree = self.revision_tree(rh[revno-1])
306
old_tree = EmptyTree()
308
old_tree = self.revision_tree(rh[revno-2])
310
return compare_trees(old_tree, new_tree)
312
def get_revision_sha1(self, revision_id):
313
"""Hash the stored value of a revision, and return it."""
314
raise NotImplementedError('get_revision_sha1 is abstract')
316
def get_ancestry(self, revision_id):
317
"""Return a list of revision-ids integrated by a revision.
319
This currently returns a list, but the ordering is not guaranteed:
322
raise NotImplementedError('get_ancestry is abstract')
324
def get_inventory(self, revision_id):
325
"""Get Inventory object by hash."""
326
raise NotImplementedError('get_inventory is abstract')
328
def get_inventory_xml(self, revision_id):
329
"""Get inventory XML as a file object."""
330
raise NotImplementedError('get_inventory_xml is abstract')
332
def get_inventory_sha1(self, revision_id):
333
"""Return the sha1 hash of the inventory entry."""
334
raise NotImplementedError('get_inventory_sha1 is abstract')
336
def get_revision_inventory(self, revision_id):
337
"""Return inventory of a past revision."""
338
raise NotImplementedError('get_revision_inventory is abstract')
340
def revision_history(self):
341
"""Return sequence of revision hashes on to this branch."""
342
raise NotImplementedError('revision_history is abstract')
345
"""Return current revision number for this branch.
347
That is equivalent to the number of revisions committed to
350
return len(self.revision_history())
352
def last_revision(self):
353
"""Return last patch hash, or None if no history."""
354
ph = self.revision_history()
360
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
361
"""Return a list of new revisions that would perfectly fit.
363
If self and other have not diverged, return a list of the revisions
364
present in other, but missing from self.
366
>>> from bzrlib.commit import commit
367
>>> bzrlib.trace.silent = True
368
>>> br1 = ScratchBranch()
369
>>> br2 = ScratchBranch()
370
>>> br1.missing_revisions(br2)
372
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
373
>>> br1.missing_revisions(br2)
375
>>> br2.missing_revisions(br1)
377
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
378
>>> br1.missing_revisions(br2)
380
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
381
>>> br1.missing_revisions(br2)
383
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
384
>>> br1.missing_revisions(br2)
385
Traceback (most recent call last):
386
DivergedBranches: These branches have diverged. Try merge.
388
self_history = self.revision_history()
389
self_len = len(self_history)
390
other_history = other.revision_history()
391
other_len = len(other_history)
392
common_index = min(self_len, other_len) -1
393
if common_index >= 0 and \
394
self_history[common_index] != other_history[common_index]:
395
raise DivergedBranches(self, other)
397
if stop_revision is None:
398
stop_revision = other_len
400
assert isinstance(stop_revision, int)
401
if stop_revision > other_len:
402
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
403
return other_history[self_len:stop_revision]
405
def update_revisions(self, other, stop_revision=None):
406
"""Pull in new perfect-fit revisions."""
407
raise NotImplementedError('update_revisions is abstract')
409
def pullable_revisions(self, other, stop_revision):
410
raise NotImplementedError('pullable_revisions is abstract')
412
def revision_id_to_revno(self, revision_id):
413
"""Given a revision id, return its revno"""
414
if revision_id is None:
416
history = self.revision_history()
418
return history.index(revision_id) + 1
420
raise bzrlib.errors.NoSuchRevision(self, revision_id)
422
def get_rev_id(self, revno, history=None):
423
"""Find the revision id of the specified revno."""
427
history = self.revision_history()
428
elif revno <= 0 or revno > len(history):
429
raise bzrlib.errors.NoSuchRevision(self, revno)
430
return history[revno - 1]
432
def revision_tree(self, revision_id):
433
"""Return Tree for a revision on this branch.
435
`revision_id` may be None for the null revision, in which case
436
an `EmptyTree` is returned."""
437
raise NotImplementedError('revision_tree is abstract')
439
def working_tree(self):
440
"""Return a `Tree` for the working copy if this is a local branch."""
441
raise NotImplementedError('working_tree is abstract')
443
def pull(self, source, overwrite=False):
444
raise NotImplementedError('pull is abstract')
446
def basis_tree(self):
447
"""Return `Tree` object for last revision.
449
If there are no revisions yet, return an `EmptyTree`.
451
return self.revision_tree(self.last_revision())
453
def rename_one(self, from_rel, to_rel):
456
This can change the directory or the filename or both.
458
raise NotImplementedError('rename_one is abstract')
460
def move(self, from_paths, to_name):
463
to_name must exist as a versioned directory.
465
If to_name exists and is a directory, the files are moved into
466
it, keeping their old names. If it is a directory,
468
Note that to_name is only the last component of the new name;
469
this doesn't change the directory.
471
This returns a list of (from_path, to_path) pairs for each
474
raise NotImplementedError('move is abstract')
476
def get_parent(self):
477
"""Return the parent location of the branch.
479
This is the default location for push/pull/missing. The usual
480
pattern is that the user can override it by specifying a
483
raise NotImplementedError('get_parent is abstract')
485
def get_push_location(self):
486
"""Return the None or the location to push this branch to."""
487
raise NotImplementedError('get_push_location is abstract')
489
def set_push_location(self, location):
490
"""Set a new push location for this branch."""
491
raise NotImplementedError('set_push_location is abstract')
493
def set_parent(self, url):
494
raise NotImplementedError('set_parent is abstract')
496
def check_revno(self, revno):
498
Check whether a revno corresponds to any revision.
499
Zero (the NULL revision) is considered valid.
502
self.check_real_revno(revno)
504
def check_real_revno(self, revno):
506
Check whether a revno corresponds to a real revision.
507
Zero (the NULL revision) is considered invalid
509
if revno < 1 or revno > self.revno():
510
raise InvalidRevisionNumber(revno)
512
def sign_revision(self, revision_id, gpg_strategy):
513
raise NotImplementedError('sign_revision is abstract')
515
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
516
raise NotImplementedError('store_revision_signature is abstract')
519
class BzrBranchFormat(object):
520
"""An encapsulation of the initialization and open routines for a format.
522
Formats provide three things:
523
* An initialization routine,
527
Formats are placed in an dict by their format string for reference
528
during branch opening. Its not required that these be instances, they
529
can be classes themselves with class methods - it simply depends on
530
whether state is needed for a given format or not.
532
Once a format is deprecated, just deprecate the initialize and open
533
methods on the format class. Do not deprecate the object, as the
534
object will be created every time regardless.
538
"""The known formats."""
541
def find_format(klass, transport):
542
"""Return the format registered for URL."""
544
return klass._formats[transport.get(".bzr/branch-format").read()]
546
raise NotBranchError(path=transport.base)
548
def get_format_string(self):
549
"""Return the ASCII format string that identifies this format."""
550
raise NotImplementedError(self.get_format_string)
552
def _find_modes(self, t):
553
"""Determine the appropriate modes for files and directories.
555
FIXME: When this merges into, or from storage,
556
this code becomes delgatable to a LockableFiles instance.
558
For now its cribbed and returns (dir_mode, file_mode)
562
except errors.TransportNotPossible:
566
dir_mode = st.st_mode & 07777
567
# Remove the sticky and execute bits for files
568
file_mode = dir_mode & ~07111
569
if not BzrBranch._set_dir_mode:
571
if not BzrBranch._set_file_mode:
573
return dir_mode, file_mode
575
def initialize(self, url):
576
"""Create a branch of this format at url and return an open branch."""
577
t = get_transport(url)
578
from bzrlib.inventory import Inventory
579
from bzrlib.weavefile import write_weave_v5
580
from bzrlib.weave import Weave
582
# Create an empty inventory
584
# if we want per-tree root ids then this is the place to set
585
# them; they're not needed for now and so ommitted for
587
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
588
empty_inv = sio.getvalue()
590
bzrlib.weavefile.write_weave_v5(Weave(), sio)
591
empty_weave = sio.getvalue()
593
# Since we don't have a .bzr directory, inherit the
594
# mode from the root directory
595
dir_mode, file_mode = self._find_modes(t)
597
t.mkdir('.bzr', mode=dir_mode)
598
control = t.clone('.bzr')
599
dirs = ['revision-store', 'weaves']
601
StringIO("This is a Bazaar-NG control directory.\n"
602
"Do not change any files in this directory.\n")),
603
('branch-format', StringIO(self.get_format_string())),
604
('revision-history', StringIO('')),
605
('branch-name', StringIO('')),
606
('branch-lock', StringIO('')),
607
('pending-merges', StringIO('')),
608
('inventory', StringIO(empty_inv)),
609
('inventory.weave', StringIO(empty_weave)),
610
('ancestry.weave', StringIO(empty_weave))
612
control.mkdir_multi(dirs, mode=dir_mode)
613
control.put_multi(files, mode=file_mode)
614
mutter('created control directory in ' + t.base)
615
return BzrBranch(t, format=self)
617
def open(self, transport):
618
"""Fill out the data in branch for the branch at url."""
619
return BzrBranch(transport, format=self)
622
def register_format(klass, format):
623
klass._formats[format.get_format_string()] = format
626
class BzrBranchFormat4(BzrBranchFormat):
627
"""Bzr branch format 4.
631
- TextStores for texts, inventories,revisions.
633
This format is deprecated: it indexes texts using a text it which is
634
removed in format 5; write support for this format has been removed.
637
def get_format_string(self):
638
"""See BzrBranchFormat.get_format_string()."""
639
return BZR_BRANCH_FORMAT_4
641
def initialize(self, url):
642
"""Format 4 branches cannot be created."""
643
raise UninitializableFormat(self)
646
class BzrBranchFormat5(BzrBranchFormat):
647
"""Bzr branch format 5.
650
- weaves for file texts and inventory
652
- TextStores for revisions and signatures.
655
def get_format_string(self):
656
"""See BzrBranchFormat.get_format_string()."""
657
return BZR_BRANCH_FORMAT_5
660
class BzrBranchFormat6(BzrBranchFormat):
661
"""Bzr branch format 6.
664
- weaves for file texts and inventory
665
- hash subdirectory based stores.
666
- TextStores for revisions and signatures.
669
def get_format_string(self):
670
"""See BzrBranchFormat.get_format_string()."""
671
return BZR_BRANCH_FORMAT_6
674
BzrBranchFormat.register_format(BzrBranchFormat4())
675
BzrBranchFormat.register_format(BzrBranchFormat5())
676
BzrBranchFormat.register_format(BzrBranchFormat6())
679
class BzrBranch(Branch):
680
"""A branch stored in the actual filesystem.
682
Note that it's "local" in the context of the filesystem; it doesn't
683
really matter if it's on an nfs/smb/afs/coda/... share, as long as
684
it's writable, and can be accessed via the normal filesystem API.
690
If _lock_mode is true, a positive count of the number of times the
694
Lock object from bzrlib.lock.
696
# We actually expect this class to be somewhat short-lived; part of its
697
# purpose is to try to isolate what bits of the branch logic are tied to
698
# filesystem access, so that in a later step, we can extricate them to
699
# a separarte ("storage") class.
703
_inventory_weave = None
704
# If set to False (by a plugin, etc) BzrBranch will not set the
705
# mode on created files or directories
706
_set_file_mode = True
709
# Map some sort of prefix into a namespace
710
# stuff like "revno:10", "revid:", etc.
711
# This should match a prefix with a function which accepts
712
REVISION_NAMESPACES = {}
714
def push_stores(self, branch_to):
715
"""See Branch.push_stores."""
716
if (self._branch_format != branch_to._branch_format
717
or self._branch_format != 4):
718
from bzrlib.fetch import greedy_fetch
719
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
720
self, self._branch_format, branch_to, branch_to._branch_format)
721
greedy_fetch(to_branch=branch_to, from_branch=self,
722
revision=self.last_revision())
725
store_pairs = ((self.text_store, branch_to.text_store),
726
(self.inventory_store, branch_to.inventory_store),
727
(self.revision_store, branch_to.revision_store))
729
for from_store, to_store in store_pairs:
730
copy_all(from_store, to_store)
731
except UnlistableStore:
732
raise UnlistableBranch(from_store)
734
def __init__(self, transport, init=deprecated_nonce,
735
relax_version_check=False, format=None):
736
"""Create new branch object at a particular location.
738
transport -- A Transport object, defining how to access files.
740
init -- If True, create new control files in a previously
741
unversioned directory. If False, the branch must already
744
relax_version_check -- If true, the usual check for the branch
745
version is not applied. This is intended only for
746
upgrade/recovery type use; it's not guaranteed that
747
all operations will work on old format branches.
749
In the test suite, creation of new trees is tested using the
750
`ScratchBranch` class.
752
assert isinstance(transport, Transport), \
753
"%r is not a Transport" % transport
754
self._transport = transport
755
if deprecated_passed(init):
756
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
757
"deprecated as of bzr 0.8. Please use Branch.initialize().",
760
# this is slower than before deprecation, oh well never mind.
762
self._initialize(transport.base)
764
self._check_format(relax_version_check, format)
766
def get_store(name, compressed=True, prefixed=False):
767
relpath = self._rel_controlfilename(safe_unicode(name))
768
store = TextStore(self._transport.clone(relpath),
769
dir_mode=self._dir_mode,
770
file_mode=self._file_mode,
772
compressed=compressed)
775
def get_weave(name, prefixed=False):
776
relpath = self._rel_controlfilename(unicode(name))
777
ws = WeaveStore(self._transport.clone(relpath),
779
dir_mode=self._dir_mode,
780
file_mode=self._file_mode)
781
if self._transport.should_cache():
782
ws.enable_cache = True
785
if self._branch_format == 4:
786
self.inventory_store = get_store('inventory-store')
787
self.text_store = get_store('text-store')
788
self.revision_store = get_store('revision-store')
789
elif self._branch_format == 5:
790
self.control_weaves = get_weave(u'')
791
self.weave_store = get_weave(u'weaves')
792
self.revision_store = get_store(u'revision-store', compressed=False)
793
elif self._branch_format == 6:
794
self.control_weaves = get_weave(u'')
795
self.weave_store = get_weave(u'weaves', prefixed=True)
796
self.revision_store = get_store(u'revision-store', compressed=False,
798
self.revision_store.register_suffix('sig')
799
self._transaction = None
802
def _initialize(base):
803
"""Create a bzr branch in the latest format."""
804
return BzrBranchFormat6().initialize(base)
807
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
812
if self._lock_mode or self._lock:
813
# XXX: This should show something every time, and be suitable for
814
# headless operation and embedding
815
warn("branch %r was not explicitly unlocked" % self)
818
# TODO: It might be best to do this somewhere else,
819
# but it is nice for a Branch object to automatically
820
# cache it's information.
821
# Alternatively, we could have the Transport objects cache requests
822
# See the earlier discussion about how major objects (like Branch)
823
# should never expect their __del__ function to run.
824
if hasattr(self, 'cache_root') and self.cache_root is not None:
826
shutil.rmtree(self.cache_root)
829
self.cache_root = None
833
return self._transport.base
836
base = property(_get_base, doc="The URL for the root of this branch.")
838
def _finish_transaction(self):
839
"""Exit the current transaction."""
840
if self._transaction is None:
841
raise errors.LockError('Branch %s is not in a transaction' %
843
transaction = self._transaction
844
self._transaction = None
847
def get_transaction(self):
848
"""See Branch.get_transaction."""
849
if self._transaction is None:
850
return transactions.PassThroughTransaction()
852
return self._transaction
854
def _set_transaction(self, new_transaction):
855
"""Set a new active transaction."""
856
if self._transaction is not None:
857
raise errors.LockError('Branch %s is in a transaction already.' %
859
self._transaction = new_transaction
861
def lock_write(self):
862
#mutter("lock write: %s (%s)", self, self._lock_count)
863
# TODO: Upgrade locking to support using a Transport,
864
# and potentially a remote locking protocol
866
if self._lock_mode != 'w':
867
raise LockError("can't upgrade to a write lock from %r" %
869
self._lock_count += 1
871
self._lock = self._transport.lock_write(
872
self._rel_controlfilename('branch-lock'))
873
self._lock_mode = 'w'
875
self._set_transaction(transactions.PassThroughTransaction())
878
#mutter("lock read: %s (%s)", self, self._lock_count)
880
assert self._lock_mode in ('r', 'w'), \
881
"invalid lock mode %r" % self._lock_mode
882
self._lock_count += 1
884
self._lock = self._transport.lock_read(
885
self._rel_controlfilename('branch-lock'))
886
self._lock_mode = 'r'
888
self._set_transaction(transactions.ReadOnlyTransaction())
889
# 5K may be excessive, but hey, its a knob.
890
self.get_transaction().set_cache_size(5000)
893
#mutter("unlock: %s (%s)", self, self._lock_count)
894
if not self._lock_mode:
895
raise LockError('branch %r is not locked' % (self))
897
if self._lock_count > 1:
898
self._lock_count -= 1
900
self._finish_transaction()
903
self._lock_mode = self._lock_count = None
905
def abspath(self, name):
906
"""See Branch.abspath."""
907
return self._transport.abspath(name)
909
def _rel_controlfilename(self, file_or_path):
910
if not isinstance(file_or_path, basestring):
911
file_or_path = u'/'.join(file_or_path)
912
if file_or_path == '':
914
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
916
def controlfilename(self, file_or_path):
917
"""See Branch.controlfilename."""
918
return self._transport.abspath(self._rel_controlfilename(file_or_path))
920
def controlfile(self, file_or_path, mode='r'):
921
"""See Branch.controlfile."""
924
relpath = self._rel_controlfilename(file_or_path)
925
#TODO: codecs.open() buffers linewise, so it was overloaded with
926
# a much larger buffer, do we need to do the same for getreader/getwriter?
928
return self._transport.get(relpath)
930
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
932
# XXX: Do we really want errors='replace'? Perhaps it should be
933
# an error, or at least reported, if there's incorrectly-encoded
934
# data inside a file.
935
# <https://launchpad.net/products/bzr/+bug/3823>
936
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
938
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
940
raise BzrError("invalid controlfile mode %r" % mode)
942
def put_controlfile(self, path, f, encode=True):
943
"""See Branch.put_controlfile."""
944
self.put_controlfiles([(path, f)], encode=encode)
946
def put_controlfiles(self, files, encode=True):
947
"""See Branch.put_controlfiles."""
950
for path, f in files:
952
if isinstance(f, basestring):
953
f = f.encode('utf-8', 'replace')
955
f = codecs.getwriter('utf-8')(f, errors='replace')
956
path = self._rel_controlfilename(path)
957
ctrl_files.append((path, f))
958
self._transport.put_multi(ctrl_files, mode=self._file_mode)
960
def _find_modes(self, path=None):
961
"""Determine the appropriate modes for files and directories."""
964
path = self._rel_controlfilename('')
965
st = self._transport.stat(path)
966
except errors.TransportNotPossible:
967
self._dir_mode = 0755
968
self._file_mode = 0644
970
self._dir_mode = st.st_mode & 07777
971
# Remove the sticky and execute bits for files
972
self._file_mode = self._dir_mode & ~07111
973
if not self._set_dir_mode:
974
self._dir_mode = None
975
if not self._set_file_mode:
976
self._file_mode = None
978
def _check_format(self, relax_version_check, format):
979
"""Check this branch format is supported.
981
The format level is stored, as an integer, in
982
self._branch_format for code that needs to check it later.
984
In the future, we might need different in-memory Branch
985
classes to support downlevel branches. But not yet.
987
The format parameter is either None or the branch format class
988
used to open this branch.
991
format = BzrBranchFormat.find_format(self._transport)
992
fmt = format.get_format_string()
993
mutter("got branch format %r", fmt)
994
if fmt == BZR_BRANCH_FORMAT_6:
995
self._branch_format = 6
996
elif fmt == BZR_BRANCH_FORMAT_5:
997
self._branch_format = 5
998
elif fmt == BZR_BRANCH_FORMAT_4:
999
self._branch_format = 4
1001
if (not relax_version_check
1002
and self._branch_format not in (5, 6)):
1003
raise errors.UnsupportedFormatError(
1004
'sorry, branch format %r not supported' % fmt,
1005
['use a different bzr version',
1006
'or remove the .bzr directory'
1007
' and "bzr init" again'])
1010
def get_root_id(self):
1011
"""See Branch.get_root_id."""
1012
inv = self.get_inventory(self.last_revision())
1013
return inv.root.file_id
1016
def print_file(self, file, revision_id):
1017
"""See Branch.print_file."""
1018
tree = self.revision_tree(revision_id)
1019
# use inventory as it was in that revision
1020
file_id = tree.inventory.path2id(file)
1023
revno = self.revision_id_to_revno(revision_id)
1024
except errors.NoSuchRevision:
1025
# TODO: This should not be BzrError,
1026
# but NoSuchFile doesn't fit either
1027
raise BzrError('%r is not present in revision %s'
1028
% (file, revision_id))
1030
raise BzrError('%r is not present in revision %s'
1032
tree.print_file(file_id)
1035
def append_revision(self, *revision_ids):
1036
"""See Branch.append_revision."""
1037
for revision_id in revision_ids:
1038
mutter("add {%s} to revision-history" % revision_id)
1039
rev_history = self.revision_history()
1040
rev_history.extend(revision_ids)
1041
self.set_revision_history(rev_history)
1044
def set_revision_history(self, rev_history):
1045
"""See Branch.set_revision_history."""
1046
old_revision = self.last_revision()
1047
new_revision = rev_history[-1]
1048
self.put_controlfile('revision-history', '\n'.join(rev_history))
1050
self.working_tree().set_last_revision(new_revision, old_revision)
1051
except NoWorkingTree:
1052
mutter('Unable to set_last_revision without a working tree.')
1054
def has_revision(self, revision_id):
1055
"""See Branch.has_revision."""
1056
return (revision_id is None
1057
or self.revision_store.has_id(revision_id))
1060
def _get_revision_xml_file(self, revision_id):
1061
if not revision_id or not isinstance(revision_id, basestring):
1062
raise InvalidRevisionId(revision_id=revision_id, branch=self)
1064
return self.revision_store.get(revision_id)
1065
except (IndexError, KeyError):
1066
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1068
def get_revision_xml(self, revision_id):
1069
"""See Branch.get_revision_xml."""
1070
return self._get_revision_xml_file(revision_id).read()
1072
def get_revision(self, revision_id):
1073
"""See Branch.get_revision."""
1074
xml_file = self._get_revision_xml_file(revision_id)
1077
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1078
except SyntaxError, e:
1079
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
1083
assert r.revision_id == revision_id
1086
def get_revision_sha1(self, revision_id):
1087
"""See Branch.get_revision_sha1."""
1088
# In the future, revision entries will be signed. At that
1089
# point, it is probably best *not* to include the signature
1090
# in the revision hash. Because that lets you re-sign
1091
# the revision, (add signatures/remove signatures) and still
1092
# have all hash pointers stay consistent.
1093
# But for now, just hash the contents.
1094
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
1096
def get_ancestry(self, revision_id):
1097
"""See Branch.get_ancestry."""
1098
if revision_id is None:
1100
w = self._get_inventory_weave()
1101
return [None] + map(w.idx_to_name,
1102
w.inclusions([w.lookup(revision_id)]))
1104
def _get_inventory_weave(self):
1105
return self.control_weaves.get_weave('inventory',
1106
self.get_transaction())
1108
def get_inventory(self, revision_id):
1109
"""See Branch.get_inventory."""
1110
xml = self.get_inventory_xml(revision_id)
1111
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1113
def get_inventory_xml(self, revision_id):
1114
"""See Branch.get_inventory_xml."""
1116
assert isinstance(revision_id, basestring), type(revision_id)
1117
iw = self._get_inventory_weave()
1118
return iw.get_text(iw.lookup(revision_id))
1120
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1122
def get_inventory_sha1(self, revision_id):
1123
"""See Branch.get_inventory_sha1."""
1124
return self.get_revision(revision_id).inventory_sha1
1126
def get_revision_inventory(self, revision_id):
1127
"""See Branch.get_revision_inventory."""
1128
# TODO: Unify this with get_inventory()
1129
# bzr 0.0.6 and later imposes the constraint that the inventory_id
1130
# must be the same as its revision, so this is trivial.
1131
if revision_id == None:
1132
# This does not make sense: if there is no revision,
1133
# then it is the current tree inventory surely ?!
1134
# and thus get_root_id() is something that looks at the last
1135
# commit on the branch, and the get_root_id is an inventory check.
1136
raise NotImplementedError
1137
# return Inventory(self.get_root_id())
1139
return self.get_inventory(revision_id)
1142
def revision_history(self):
1143
"""See Branch.revision_history."""
1144
transaction = self.get_transaction()
1145
history = transaction.map.find_revision_history()
1146
if history is not None:
1147
mutter("cache hit for revision-history in %s", self)
1148
return list(history)
1149
history = [l.rstrip('\r\n') for l in
1150
self.controlfile('revision-history', 'r').readlines()]
1151
transaction.map.add_revision_history(history)
1152
# this call is disabled because revision_history is
1153
# not really an object yet, and the transaction is for objects.
1154
# transaction.register_clean(history, precious=True)
1155
return list(history)
1157
def update_revisions(self, other, stop_revision=None):
1158
"""See Branch.update_revisions."""
1159
from bzrlib.fetch import greedy_fetch
1160
if stop_revision is None:
1161
stop_revision = other.last_revision()
1162
### Should this be checking is_ancestor instead of revision_history?
1163
if (stop_revision is not None and
1164
stop_revision in self.revision_history()):
1166
greedy_fetch(to_branch=self, from_branch=other,
1167
revision=stop_revision)
1168
pullable_revs = self.pullable_revisions(other, stop_revision)
1169
if len(pullable_revs) > 0:
1170
self.append_revision(*pullable_revs)
1172
def pullable_revisions(self, other, stop_revision):
1173
"""See Branch.pullable_revisions."""
1174
other_revno = other.revision_id_to_revno(stop_revision)
1176
return self.missing_revisions(other, other_revno)
1177
except DivergedBranches, e:
1179
pullable_revs = get_intervening_revisions(self.last_revision(),
1180
stop_revision, self)
1181
assert self.last_revision() not in pullable_revs
1182
return pullable_revs
1183
except bzrlib.errors.NotAncestor:
1184
if is_ancestor(self.last_revision(), stop_revision, self):
1189
def revision_tree(self, revision_id):
1190
"""See Branch.revision_tree."""
1191
# TODO: refactor this to use an existing revision object
1192
# so we don't need to read it in twice.
1193
if revision_id == None or revision_id == NULL_REVISION:
1196
inv = self.get_revision_inventory(revision_id)
1197
return RevisionTree(self, inv, revision_id)
1199
def basis_tree(self):
1200
"""See Branch.basis_tree."""
1202
revision_id = self.revision_history()[-1]
1203
xml = self.working_tree().read_basis_inventory(revision_id)
1204
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1205
return RevisionTree(self, inv, revision_id)
1206
except (IndexError, NoSuchFile, NoWorkingTree), e:
1207
return self.revision_tree(self.last_revision())
1209
def working_tree(self):
1210
"""See Branch.working_tree."""
1211
from bzrlib.workingtree import WorkingTree
1212
if self._transport.base.find('://') != -1:
1213
raise NoWorkingTree(self.base)
1214
return WorkingTree(self.base, branch=self)
1217
def pull(self, source, overwrite=False):
1218
"""See Branch.pull."""
1221
old_count = len(self.revision_history())
1223
self.update_revisions(source)
1224
except DivergedBranches:
1228
self.set_revision_history(source.revision_history())
1229
new_count = len(self.revision_history())
1230
return new_count - old_count
1234
def get_parent(self):
1235
"""See Branch.get_parent."""
1237
_locs = ['parent', 'pull', 'x-pull']
1240
return self.controlfile(l, 'r').read().strip('\n')
1245
def get_push_location(self):
1246
"""See Branch.get_push_location."""
1247
config = bzrlib.config.BranchConfig(self)
1248
push_loc = config.get_user_option('push_location')
1251
def set_push_location(self, location):
1252
"""See Branch.set_push_location."""
1253
config = bzrlib.config.LocationConfig(self.base)
1254
config.set_user_option('push_location', location)
1257
def set_parent(self, url):
1258
"""See Branch.set_parent."""
1259
# TODO: Maybe delete old location files?
1260
from bzrlib.atomicfile import AtomicFile
1261
f = AtomicFile(self.controlfilename('parent'))
1268
def tree_config(self):
1269
return TreeConfig(self)
1271
def sign_revision(self, revision_id, gpg_strategy):
1272
"""See Branch.sign_revision."""
1273
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1274
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1277
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1278
"""See Branch.store_revision_signature."""
1279
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1283
Branch.set_default_initializer(BzrBranch._initialize)
1286
class BranchTestProviderAdapter(object):
1287
"""A tool to generate a suite testing multiple branch formats at once.
1289
This is done by copying the test once for each transport and injecting
1290
the transport_server, transport_readonly_server, and branch_format
1291
classes into each copy. Each copy is also given a new id() to make it
1295
def __init__(self, transport_server, transport_readonly_server, formats):
1296
self._transport_server = transport_server
1297
self._transport_readonly_server = transport_readonly_server
1298
self._formats = formats
1300
def adapt(self, test):
1301
result = TestSuite()
1302
for format in self._formats:
1303
new_test = deepcopy(test)
1304
new_test.transport_server = self._transport_server
1305
new_test.transport_readonly_server = self._transport_readonly_server
1306
new_test.branch_format = format
1307
def make_new_test_id():
1308
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1309
return lambda: new_id
1310
new_test.id = make_new_test_id()
1311
result.addTest(new_test)
1315
class ScratchBranch(BzrBranch):
1316
"""Special test class: a branch that cleans up after itself.
1318
>>> b = ScratchBranch()
1322
>>> b._transport.__del__()
1327
def __init__(self, files=[], dirs=[], transport=None):
1328
"""Make a test branch.
1330
This creates a temporary directory and runs init-tree in it.
1332
If any files are listed, they are created in the working copy.
1334
if transport is None:
1335
transport = bzrlib.transport.local.ScratchTransport()
1336
Branch.initialize(transport.base)
1337
super(ScratchBranch, self).__init__(transport)
1339
super(ScratchBranch, self).__init__(transport)
1342
self._transport.mkdir(d)
1345
self._transport.put(f, 'content of %s' % f)
1350
>>> orig = ScratchBranch(files=["file1", "file2"])
1351
>>> clone = orig.clone()
1352
>>> if os.name != 'nt':
1353
... os.path.samefile(orig.base, clone.base)
1355
... orig.base == clone.base
1358
>>> os.path.isfile(pathjoin(clone.base, "file1"))
1361
from shutil import copytree
1362
from bzrlib.osutils import mkdtemp
1365
copytree(self.base, base, symlinks=True)
1366
return ScratchBranch(
1367
transport=bzrlib.transport.local.ScratchTransport(base))
1370
######################################################################
1374
def is_control_file(filename):
1375
## FIXME: better check
1376
filename = normpath(filename)
1377
while filename != '':
1378
head, tail = os.path.split(filename)
1379
## mutter('check %r for control file' % ((head, tail), ))
1380
if tail == bzrlib.BZRDIR:
1382
if filename == head: