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
22
from warnings import warn
23
from cStringIO import StringIO
27
import bzrlib.inventory as inventory
28
from bzrlib.trace import mutter, note
29
from bzrlib.osutils import (isdir, quotefn,
30
rename, splitpath, sha_file, appendpath,
32
import bzrlib.errors as errors
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError, UnlistableStore,
36
UnlistableBranch, NoSuchFile, NotVersionedError,
38
from bzrlib.textui import show_status
39
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
42
from bzrlib.delta import compare_trees
43
from bzrlib.tree import EmptyTree, RevisionTree
44
from bzrlib.inventory import Inventory
45
from bzrlib.store import copy_all
46
from bzrlib.store.text import TextStore
47
from bzrlib.store.weave import WeaveStore
48
from bzrlib.testament import Testament
49
import bzrlib.transactions as transactions
50
from bzrlib.transport import Transport, get_transport
53
from config import TreeConfig
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
57
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
58
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
59
## TODO: Maybe include checks for common corruption of newlines, etc?
62
# TODO: Some operations like log might retrieve the same revisions
63
# repeatedly to calculate deltas. We could perhaps have a weakref
64
# cache in memory to make this faster. In general anything can be
65
# cached in memory between lock and unlock operations.
67
def find_branch(*ignored, **ignored_too):
68
# XXX: leave this here for about one release, then remove it
69
raise NotImplementedError('find_branch() is not supported anymore, '
70
'please use one of the new branch constructors')
73
def needs_read_lock(unbound):
74
"""Decorate unbound to take out and release a read lock."""
75
def decorated(self, *args, **kwargs):
78
return unbound(self, *args, **kwargs)
84
def needs_write_lock(unbound):
85
"""Decorate unbound to take out and release a write lock."""
86
def decorated(self, *args, **kwargs):
89
return unbound(self, *args, **kwargs)
94
######################################################################
98
"""Branch holding a history of revisions.
101
Base directory/url of the branch.
105
def __init__(self, *ignored, **ignored_too):
106
raise NotImplementedError('The Branch class is abstract')
109
def open_downlevel(base):
110
"""Open a branch which may be of an old format.
112
Only local branches are supported."""
113
return BzrBranch(get_transport(base), relax_version_check=True)
117
"""Open an existing branch, rooted at 'base' (url)"""
118
t = get_transport(base)
119
mutter("trying to open %r with transport %r", base, t)
123
def open_containing(url):
124
"""Open an existing branch which contains url.
126
This probes for a branch at url, and searches upwards from there.
128
Basically we keep looking up until we find the control directory or
129
run into the root. If there isn't one, raises NotBranchError.
130
If there is one, it is returned, along with the unused portion of url.
132
t = get_transport(url)
135
return BzrBranch(t), t.relpath(url)
136
except NotBranchError:
138
new_t = t.clone('..')
139
if new_t.base == t.base:
140
# reached the root, whatever that may be
141
raise NotBranchError(path=url)
145
def initialize(base):
146
"""Create a new branch, rooted at 'base' (url)"""
147
t = get_transport(base)
148
return BzrBranch(t, init=True)
150
def setup_caching(self, cache_root):
151
"""Subclasses that care about caching should override this, and set
152
up cached stores located under cache_root.
154
self.cache_root = cache_root
157
cfg = self.tree_config()
158
return cfg.get_option(u"nickname", default=self.base.split('/')[-1])
160
def _set_nick(self, nick):
161
cfg = self.tree_config()
162
cfg.set_option(nick, "nickname")
163
assert cfg.get_option("nickname") == nick
165
nick = property(_get_nick, _set_nick)
167
def push_stores(self, branch_to):
168
"""Copy the content of this branches store to branch_to."""
169
raise NotImplementedError('push_stores is abstract')
171
def get_transaction(self):
172
"""Return the current active transaction.
174
If no transaction is active, this returns a passthrough object
175
for which all data is immediately flushed and no caching happens.
177
raise NotImplementedError('get_transaction is abstract')
179
def lock_write(self):
180
raise NotImplementedError('lock_write is abstract')
183
raise NotImplementedError('lock_read is abstract')
186
raise NotImplementedError('unlock is abstract')
188
def abspath(self, name):
189
"""Return absolute filename for something in the branch
191
XXX: Robert Collins 20051017 what is this used for? why is it a branch
192
method and not a tree method.
194
raise NotImplementedError('abspath is abstract')
196
def controlfilename(self, file_or_path):
197
"""Return location relative to branch."""
198
raise NotImplementedError('controlfilename is abstract')
200
def controlfile(self, file_or_path, mode='r'):
201
"""Open a control file for this branch.
203
There are two classes of file in the control directory: text
204
and binary. binary files are untranslated byte streams. Text
205
control files are stored with Unix newlines and in UTF-8, even
206
if the platform or locale defaults are different.
208
Controlfiles should almost never be opened in write mode but
209
rather should be atomically copied and replaced using atomicfile.
211
raise NotImplementedError('controlfile is abstract')
213
def put_controlfile(self, path, f, encode=True):
214
"""Write an entry as a controlfile.
216
:param path: The path to put the file, relative to the .bzr control
218
:param f: A file-like or string object whose contents should be copied.
219
:param encode: If true, encode the contents as utf-8
221
raise NotImplementedError('put_controlfile is abstract')
223
def put_controlfiles(self, files, encode=True):
224
"""Write several entries as controlfiles.
226
:param files: A list of [(path, file)] pairs, where the path is the directory
227
underneath the bzr control directory
228
:param encode: If true, encode the contents as utf-8
230
raise NotImplementedError('put_controlfiles is abstract')
232
def get_root_id(self):
233
"""Return the id of this branches root"""
234
raise NotImplementedError('get_root_id is abstract')
236
def print_file(self, file, revno):
237
"""Print `file` to stdout."""
238
raise NotImplementedError('print_file is abstract')
240
def append_revision(self, *revision_ids):
241
raise NotImplementedError('append_revision is abstract')
243
def set_revision_history(self, rev_history):
244
raise NotImplementedError('set_revision_history is abstract')
246
def has_revision(self, revision_id):
247
"""True if this branch has a copy of the revision.
249
This does not necessarily imply the revision is merge
250
or on the mainline."""
251
raise NotImplementedError('has_revision is abstract')
253
def get_revision_xml_file(self, revision_id):
254
"""Return XML file object for revision object."""
255
raise NotImplementedError('get_revision_xml_file is abstract')
257
def get_revision_xml(self, revision_id):
258
raise NotImplementedError('get_revision_xml is abstract')
260
def get_revision(self, revision_id):
261
"""Return the Revision object for a named revision"""
262
raise NotImplementedError('get_revision is abstract')
264
def get_revision_delta(self, revno):
265
"""Return the delta for one revision.
267
The delta is relative to its mainline predecessor, or the
268
empty tree for revision 1.
270
assert isinstance(revno, int)
271
rh = self.revision_history()
272
if not (1 <= revno <= len(rh)):
273
raise InvalidRevisionNumber(revno)
275
# revno is 1-based; list is 0-based
277
new_tree = self.revision_tree(rh[revno-1])
279
old_tree = EmptyTree()
281
old_tree = self.revision_tree(rh[revno-2])
283
return compare_trees(old_tree, new_tree)
285
def get_revision_sha1(self, revision_id):
286
"""Hash the stored value of a revision, and return it."""
287
raise NotImplementedError('get_revision_sha1 is abstract')
289
def get_ancestry(self, revision_id):
290
"""Return a list of revision-ids integrated by a revision.
292
This currently returns a list, but the ordering is not guaranteed:
295
raise NotImplementedError('get_ancestry is abstract')
297
def get_inventory(self, revision_id):
298
"""Get Inventory object by hash."""
299
raise NotImplementedError('get_inventory is abstract')
301
def get_inventory_xml(self, revision_id):
302
"""Get inventory XML as a file object."""
303
raise NotImplementedError('get_inventory_xml is abstract')
305
def get_inventory_sha1(self, revision_id):
306
"""Return the sha1 hash of the inventory entry."""
307
raise NotImplementedError('get_inventory_sha1 is abstract')
309
def get_revision_inventory(self, revision_id):
310
"""Return inventory of a past revision."""
311
raise NotImplementedError('get_revision_inventory is abstract')
313
def revision_history(self):
314
"""Return sequence of revision hashes on to this branch."""
315
raise NotImplementedError('revision_history is abstract')
318
"""Return current revision number for this branch.
320
That is equivalent to the number of revisions committed to
323
return len(self.revision_history())
325
def last_revision(self):
326
"""Return last patch hash, or None if no history."""
327
ph = self.revision_history()
333
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
334
"""Return a list of new revisions that would perfectly fit.
336
If self and other have not diverged, return a list of the revisions
337
present in other, but missing from self.
339
>>> from bzrlib.commit import commit
340
>>> bzrlib.trace.silent = True
341
>>> br1 = ScratchBranch()
342
>>> br2 = ScratchBranch()
343
>>> br1.missing_revisions(br2)
345
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
346
>>> br1.missing_revisions(br2)
348
>>> br2.missing_revisions(br1)
350
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
351
>>> br1.missing_revisions(br2)
353
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
354
>>> br1.missing_revisions(br2)
356
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
357
>>> br1.missing_revisions(br2)
358
Traceback (most recent call last):
359
DivergedBranches: These branches have diverged.
361
self_history = self.revision_history()
362
self_len = len(self_history)
363
other_history = other.revision_history()
364
other_len = len(other_history)
365
common_index = min(self_len, other_len) -1
366
if common_index >= 0 and \
367
self_history[common_index] != other_history[common_index]:
368
raise DivergedBranches(self, other)
370
if stop_revision is None:
371
stop_revision = other_len
373
assert isinstance(stop_revision, int)
374
if stop_revision > other_len:
375
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
376
return other_history[self_len:stop_revision]
378
def update_revisions(self, other, stop_revision=None):
379
"""Pull in new perfect-fit revisions."""
380
raise NotImplementedError('update_revisions is abstract')
382
def pullable_revisions(self, other, stop_revision):
383
raise NotImplementedError('pullable_revisions is abstract')
385
def revision_id_to_revno(self, revision_id):
386
"""Given a revision id, return its revno"""
387
if revision_id is None:
389
history = self.revision_history()
391
return history.index(revision_id) + 1
393
raise bzrlib.errors.NoSuchRevision(self, revision_id)
395
def get_rev_id(self, revno, history=None):
396
"""Find the revision id of the specified revno."""
400
history = self.revision_history()
401
elif revno <= 0 or revno > len(history):
402
raise bzrlib.errors.NoSuchRevision(self, revno)
403
return history[revno - 1]
405
def revision_tree(self, revision_id):
406
"""Return Tree for a revision on this branch.
408
`revision_id` may be None for the null revision, in which case
409
an `EmptyTree` is returned."""
410
raise NotImplementedError('revision_tree is abstract')
412
def working_tree(self):
413
"""Return a `Tree` for the working copy if this is a local branch."""
414
raise NotImplementedError('working_tree is abstract')
416
def pull(self, source, overwrite=False):
417
raise NotImplementedError('pull is abstract')
419
def basis_tree(self):
420
"""Return `Tree` object for last revision.
422
If there are no revisions yet, return an `EmptyTree`.
424
return self.revision_tree(self.last_revision())
426
def rename_one(self, from_rel, to_rel):
429
This can change the directory or the filename or both.
431
raise NotImplementedError('rename_one is abstract')
433
def move(self, from_paths, to_name):
436
to_name must exist as a versioned directory.
438
If to_name exists and is a directory, the files are moved into
439
it, keeping their old names. If it is a directory,
441
Note that to_name is only the last component of the new name;
442
this doesn't change the directory.
444
This returns a list of (from_path, to_path) pairs for each
447
raise NotImplementedError('move is abstract')
449
def revert(self, filenames, old_tree=None, backups=True):
450
"""Restore selected files to the versions from a previous tree.
453
If true (default) backups are made of files before
456
raise NotImplementedError('revert is abstract')
458
def pending_merges(self):
459
"""Return a list of pending merges.
461
These are revisions that have been merged into the working
462
directory but not yet committed.
464
raise NotImplementedError('pending_merges is abstract')
466
def add_pending_merge(self, *revision_ids):
467
# TODO: Perhaps should check at this point that the
468
# history of the revision is actually present?
469
raise NotImplementedError('add_pending_merge is abstract')
471
def set_pending_merges(self, rev_list):
472
raise NotImplementedError('set_pending_merges is abstract')
474
def get_parent(self):
475
"""Return the parent location of the branch.
477
This is the default location for push/pull/missing. The usual
478
pattern is that the user can override it by specifying a
481
raise NotImplementedError('get_parent is abstract')
483
def get_push_location(self):
484
"""Return the None or the location to push this branch to."""
485
raise NotImplementedError('get_push_location is abstract')
487
def set_push_location(self, location):
488
"""Set a new push location for this branch."""
489
raise NotImplementedError('set_push_location is abstract')
491
def set_parent(self, url):
492
raise NotImplementedError('set_parent is abstract')
494
def check_revno(self, revno):
496
Check whether a revno corresponds to any revision.
497
Zero (the NULL revision) is considered valid.
500
self.check_real_revno(revno)
502
def check_real_revno(self, revno):
504
Check whether a revno corresponds to a real revision.
505
Zero (the NULL revision) is considered invalid
507
if revno < 1 or revno > self.revno():
508
raise InvalidRevisionNumber(revno)
510
def sign_revision(self, revision_id, gpg_strategy):
511
raise NotImplementedError('sign_revision is abstract')
513
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
514
raise NotImplementedError('store_revision_signature is abstract')
516
class BzrBranch(Branch):
517
"""A branch stored in the actual filesystem.
519
Note that it's "local" in the context of the filesystem; it doesn't
520
really matter if it's on an nfs/smb/afs/coda/... share, as long as
521
it's writable, and can be accessed via the normal filesystem API.
527
If _lock_mode is true, a positive count of the number of times the
531
Lock object from bzrlib.lock.
533
# We actually expect this class to be somewhat short-lived; part of its
534
# purpose is to try to isolate what bits of the branch logic are tied to
535
# filesystem access, so that in a later step, we can extricate them to
536
# a separarte ("storage") class.
540
_inventory_weave = None
542
# Map some sort of prefix into a namespace
543
# stuff like "revno:10", "revid:", etc.
544
# This should match a prefix with a function which accepts
545
REVISION_NAMESPACES = {}
547
def push_stores(self, branch_to):
548
"""See Branch.push_stores."""
549
if (self._branch_format != branch_to._branch_format
550
or self._branch_format != 4):
551
from bzrlib.fetch import greedy_fetch
552
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
553
self, self._branch_format, branch_to, branch_to._branch_format)
554
greedy_fetch(to_branch=branch_to, from_branch=self,
555
revision=self.last_revision())
558
store_pairs = ((self.text_store, branch_to.text_store),
559
(self.inventory_store, branch_to.inventory_store),
560
(self.revision_store, branch_to.revision_store))
562
for from_store, to_store in store_pairs:
563
copy_all(from_store, to_store)
564
except UnlistableStore:
565
raise UnlistableBranch(from_store)
567
def __init__(self, transport, init=False,
568
relax_version_check=False):
569
"""Create new branch object at a particular location.
571
transport -- A Transport object, defining how to access files.
573
init -- If True, create new control files in a previously
574
unversioned directory. If False, the branch must already
577
relax_version_check -- If true, the usual check for the branch
578
version is not applied. This is intended only for
579
upgrade/recovery type use; it's not guaranteed that
580
all operations will work on old format branches.
582
In the test suite, creation of new trees is tested using the
583
`ScratchBranch` class.
585
assert isinstance(transport, Transport), \
586
"%r is not a Transport" % transport
587
self._transport = transport
590
self._check_format(relax_version_check)
592
def get_store(name, compressed=True, prefixed=False):
593
# FIXME: This approach of assuming stores are all entirely compressed
594
# or entirely uncompressed is tidy, but breaks upgrade from
595
# some existing branches where there's a mixture; we probably
596
# still want the option to look for both.
597
relpath = self._rel_controlfilename(name)
598
store = TextStore(self._transport.clone(relpath),
600
compressed=compressed)
601
#if self._transport.should_cache():
602
# cache_path = os.path.join(self.cache_root, name)
603
# os.mkdir(cache_path)
604
# store = bzrlib.store.CachedStore(store, cache_path)
606
def get_weave(name, prefixed=False):
607
relpath = self._rel_controlfilename(name)
608
ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
609
if self._transport.should_cache():
610
ws.enable_cache = True
613
if self._branch_format == 4:
614
self.inventory_store = get_store('inventory-store')
615
self.text_store = get_store('text-store')
616
self.revision_store = get_store('revision-store')
617
elif self._branch_format == 5:
618
self.control_weaves = get_weave('')
619
self.weave_store = get_weave('weaves')
620
self.revision_store = get_store('revision-store', compressed=False)
621
elif self._branch_format == 6:
622
self.control_weaves = get_weave('')
623
self.weave_store = get_weave('weaves', prefixed=True)
624
self.revision_store = get_store('revision-store', compressed=False,
626
self.revision_store.register_suffix('sig')
627
self._transaction = None
630
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
635
if self._lock_mode or self._lock:
636
# XXX: This should show something every time, and be suitable for
637
# headless operation and embedding
638
warn("branch %r was not explicitly unlocked" % self)
641
# TODO: It might be best to do this somewhere else,
642
# but it is nice for a Branch object to automatically
643
# cache it's information.
644
# Alternatively, we could have the Transport objects cache requests
645
# See the earlier discussion about how major objects (like Branch)
646
# should never expect their __del__ function to run.
647
if hasattr(self, 'cache_root') and self.cache_root is not None:
649
shutil.rmtree(self.cache_root)
652
self.cache_root = None
656
return self._transport.base
659
base = property(_get_base, doc="The URL for the root of this branch.")
661
def _finish_transaction(self):
662
"""Exit the current transaction."""
663
if self._transaction is None:
664
raise errors.LockError('Branch %s is not in a transaction' %
666
transaction = self._transaction
667
self._transaction = None
670
def get_transaction(self):
671
"""See Branch.get_transaction."""
672
if self._transaction is None:
673
return transactions.PassThroughTransaction()
675
return self._transaction
677
def _set_transaction(self, new_transaction):
678
"""Set a new active transaction."""
679
if self._transaction is not None:
680
raise errors.LockError('Branch %s is in a transaction already.' %
682
self._transaction = new_transaction
684
def lock_write(self):
685
mutter("lock write: %s (%s)", self, self._lock_count)
686
# TODO: Upgrade locking to support using a Transport,
687
# and potentially a remote locking protocol
689
if self._lock_mode != 'w':
690
raise LockError("can't upgrade to a write lock from %r" %
692
self._lock_count += 1
694
self._lock = self._transport.lock_write(
695
self._rel_controlfilename('branch-lock'))
696
self._lock_mode = 'w'
698
self._set_transaction(transactions.PassThroughTransaction())
701
mutter("lock read: %s (%s)", self, self._lock_count)
703
assert self._lock_mode in ('r', 'w'), \
704
"invalid lock mode %r" % self._lock_mode
705
self._lock_count += 1
707
self._lock = self._transport.lock_read(
708
self._rel_controlfilename('branch-lock'))
709
self._lock_mode = 'r'
711
self._set_transaction(transactions.ReadOnlyTransaction())
712
# 5K may be excessive, but hey, its a knob.
713
self.get_transaction().set_cache_size(5000)
716
mutter("unlock: %s (%s)", self, self._lock_count)
717
if not self._lock_mode:
718
raise LockError('branch %r is not locked' % (self))
720
if self._lock_count > 1:
721
self._lock_count -= 1
723
self._finish_transaction()
726
self._lock_mode = self._lock_count = None
728
def abspath(self, name):
729
"""See Branch.abspath."""
730
return self._transport.abspath(name)
732
def _rel_controlfilename(self, file_or_path):
733
if not isinstance(file_or_path, basestring):
734
file_or_path = '/'.join(file_or_path)
735
if file_or_path == '':
737
return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
739
def controlfilename(self, file_or_path):
740
"""See Branch.controlfilename."""
741
return self._transport.abspath(self._rel_controlfilename(file_or_path))
743
def controlfile(self, file_or_path, mode='r'):
744
"""See Branch.controlfile."""
747
relpath = self._rel_controlfilename(file_or_path)
748
#TODO: codecs.open() buffers linewise, so it was overloaded with
749
# a much larger buffer, do we need to do the same for getreader/getwriter?
751
return self._transport.get(relpath)
753
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
755
# XXX: Do we really want errors='replace'? Perhaps it should be
756
# an error, or at least reported, if there's incorrectly-encoded
757
# data inside a file.
758
# <https://launchpad.net/products/bzr/+bug/3823>
759
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
761
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
763
raise BzrError("invalid controlfile mode %r" % mode)
765
def put_controlfile(self, path, f, encode=True):
766
"""See Branch.put_controlfile."""
767
self.put_controlfiles([(path, f)], encode=encode)
769
def put_controlfiles(self, files, encode=True):
770
"""See Branch.put_controlfiles."""
773
for path, f in files:
775
if isinstance(f, basestring):
776
f = f.encode('utf-8', 'replace')
778
f = codecs.getwriter('utf-8')(f, errors='replace')
779
path = self._rel_controlfilename(path)
780
ctrl_files.append((path, f))
781
self._transport.put_multi(ctrl_files)
783
def _make_control(self):
784
from bzrlib.inventory import Inventory
785
from bzrlib.weavefile import write_weave_v5
786
from bzrlib.weave import Weave
788
# Create an empty inventory
790
# if we want per-tree root ids then this is the place to set
791
# them; they're not needed for now and so ommitted for
793
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
794
empty_inv = sio.getvalue()
796
bzrlib.weavefile.write_weave_v5(Weave(), sio)
797
empty_weave = sio.getvalue()
799
dirs = [[], 'revision-store', 'weaves']
801
"This is a Bazaar-NG control directory.\n"
802
"Do not change any files in this directory.\n"),
803
('branch-format', BZR_BRANCH_FORMAT_6),
804
('revision-history', ''),
807
('pending-merges', ''),
808
('inventory', empty_inv),
809
('inventory.weave', empty_weave),
810
('ancestry.weave', empty_weave)
812
cfn = self._rel_controlfilename
813
self._transport.mkdir_multi([cfn(d) for d in dirs])
814
self.put_controlfiles(files)
815
mutter('created control directory in ' + self._transport.base)
817
def _check_format(self, relax_version_check):
818
"""Check this branch format is supported.
820
The format level is stored, as an integer, in
821
self._branch_format for code that needs to check it later.
823
In the future, we might need different in-memory Branch
824
classes to support downlevel branches. But not yet.
827
fmt = self.controlfile('branch-format', 'r').read()
829
raise NotBranchError(path=self.base)
830
mutter("got branch format %r", fmt)
831
if fmt == BZR_BRANCH_FORMAT_6:
832
self._branch_format = 6
833
elif fmt == BZR_BRANCH_FORMAT_5:
834
self._branch_format = 5
835
elif fmt == BZR_BRANCH_FORMAT_4:
836
self._branch_format = 4
838
if (not relax_version_check
839
and self._branch_format not in (5, 6)):
840
raise errors.UnsupportedFormatError(
841
'sorry, branch format %r not supported' % fmt,
842
['use a different bzr version',
843
'or remove the .bzr directory'
844
' and "bzr init" again'])
847
def get_root_id(self):
848
"""See Branch.get_root_id."""
849
inv = self.get_inventory(self.last_revision())
850
return inv.root.file_id
853
def print_file(self, file, revno):
854
"""See Branch.print_file."""
855
tree = self.revision_tree(self.get_rev_id(revno))
856
# use inventory as it was in that revision
857
file_id = tree.inventory.path2id(file)
859
raise BzrError("%r is not present in revision %s" % (file, revno))
860
tree.print_file(file_id)
863
def append_revision(self, *revision_ids):
864
"""See Branch.append_revision."""
865
for revision_id in revision_ids:
866
mutter("add {%s} to revision-history" % revision_id)
867
rev_history = self.revision_history()
868
rev_history.extend(revision_ids)
869
self.set_revision_history(rev_history)
872
def set_revision_history(self, rev_history):
873
"""See Branch.set_revision_history."""
874
self.put_controlfile('revision-history', '\n'.join(rev_history))
876
def has_revision(self, revision_id):
877
"""See Branch.has_revision."""
878
return (revision_id is None
879
or self.revision_store.has_id(revision_id))
882
def get_revision_xml_file(self, revision_id):
883
"""See Branch.get_revision_xml_file."""
884
if not revision_id or not isinstance(revision_id, basestring):
885
raise InvalidRevisionId(revision_id=revision_id, branch=self)
887
return self.revision_store.get(revision_id)
888
except (IndexError, KeyError):
889
raise bzrlib.errors.NoSuchRevision(self, revision_id)
892
get_revision_xml = get_revision_xml_file
894
def get_revision_xml(self, revision_id):
895
"""See Branch.get_revision_xml."""
896
return self.get_revision_xml_file(revision_id).read()
899
def get_revision(self, revision_id):
900
"""See Branch.get_revision."""
901
xml_file = self.get_revision_xml_file(revision_id)
904
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
905
except SyntaxError, e:
906
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
910
assert r.revision_id == revision_id
913
def get_revision_sha1(self, revision_id):
914
"""See Branch.get_revision_sha1."""
915
# In the future, revision entries will be signed. At that
916
# point, it is probably best *not* to include the signature
917
# in the revision hash. Because that lets you re-sign
918
# the revision, (add signatures/remove signatures) and still
919
# have all hash pointers stay consistent.
920
# But for now, just hash the contents.
921
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
923
def get_ancestry(self, revision_id):
924
"""See Branch.get_ancestry."""
925
if revision_id is None:
927
w = self._get_inventory_weave()
928
return [None] + map(w.idx_to_name,
929
w.inclusions([w.lookup(revision_id)]))
931
def _get_inventory_weave(self):
932
return self.control_weaves.get_weave('inventory',
933
self.get_transaction())
935
def get_inventory(self, revision_id):
936
"""See Branch.get_inventory."""
937
xml = self.get_inventory_xml(revision_id)
938
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
940
def get_inventory_xml(self, revision_id):
941
"""See Branch.get_inventory_xml."""
943
assert isinstance(revision_id, basestring), type(revision_id)
944
iw = self._get_inventory_weave()
945
return iw.get_text(iw.lookup(revision_id))
947
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
949
def get_inventory_sha1(self, revision_id):
950
"""See Branch.get_inventory_sha1."""
951
return self.get_revision(revision_id).inventory_sha1
953
def get_revision_inventory(self, revision_id):
954
"""See Branch.get_revision_inventory."""
955
# TODO: Unify this with get_inventory()
956
# bzr 0.0.6 and later imposes the constraint that the inventory_id
957
# must be the same as its revision, so this is trivial.
958
if revision_id == None:
959
# This does not make sense: if there is no revision,
960
# then it is the current tree inventory surely ?!
961
# and thus get_root_id() is something that looks at the last
962
# commit on the branch, and the get_root_id is an inventory check.
963
raise NotImplementedError
964
# return Inventory(self.get_root_id())
966
return self.get_inventory(revision_id)
969
def revision_history(self):
970
"""See Branch.revision_history."""
971
transaction = self.get_transaction()
972
history = transaction.map.find_revision_history()
973
if history is not None:
974
mutter("cache hit for revision-history in %s", self)
976
history = [l.rstrip('\r\n') for l in
977
self.controlfile('revision-history', 'r').readlines()]
978
transaction.map.add_revision_history(history)
979
# this call is disabled because revision_history is
980
# not really an object yet, and the transaction is for objects.
981
# transaction.register_clean(history, precious=True)
984
def update_revisions(self, other, stop_revision=None):
985
"""See Branch.update_revisions."""
986
from bzrlib.fetch import greedy_fetch
987
if stop_revision is None:
988
stop_revision = other.last_revision()
989
### Should this be checking is_ancestor instead of revision_history?
990
if (stop_revision is not None and
991
stop_revision in self.revision_history()):
993
greedy_fetch(to_branch=self, from_branch=other,
994
revision=stop_revision)
995
pullable_revs = self.pullable_revisions(other, stop_revision)
996
if len(pullable_revs) > 0:
997
self.append_revision(*pullable_revs)
999
def pullable_revisions(self, other, stop_revision):
1000
"""See Branch.pullable_revisions."""
1001
other_revno = other.revision_id_to_revno(stop_revision)
1003
return self.missing_revisions(other, other_revno)
1004
except DivergedBranches, e:
1006
pullable_revs = get_intervening_revisions(self.last_revision(),
1007
stop_revision, self)
1008
assert self.last_revision() not in pullable_revs
1009
return pullable_revs
1010
except bzrlib.errors.NotAncestor:
1011
if is_ancestor(self.last_revision(), stop_revision, self):
1016
def revision_id_to_revno(self, revision_id):
1017
"""Given a revision id, return its revno"""
1018
if revision_id is None:
1020
history = self.revision_history()
1022
return history.index(revision_id) + 1
1024
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1026
def get_rev_id(self, revno, history=None):
1027
"""Find the revision id of the specified revno."""
1031
history = self.revision_history()
1032
elif revno <= 0 or revno > len(history):
1033
raise bzrlib.errors.NoSuchRevision(self, revno)
1034
return history[revno - 1]
1036
def revision_tree(self, revision_id):
1037
"""See Branch.revision_tree."""
1038
# TODO: refactor this to use an existing revision object
1039
# so we don't need to read it in twice.
1040
if revision_id == None or revision_id == NULL_REVISION:
1043
inv = self.get_revision_inventory(revision_id)
1044
return RevisionTree(self.weave_store, inv, revision_id)
1046
def working_tree(self):
1047
"""See Branch.working_tree."""
1048
from bzrlib.workingtree import WorkingTree
1049
if self._transport.base.find('://') != -1:
1050
raise NoWorkingTree(self.base)
1051
return WorkingTree(self.base, branch=self)
1054
def pull(self, source, overwrite=False):
1055
"""See Branch.pull."""
1059
self.update_revisions(source)
1060
except DivergedBranches:
1063
self.set_revision_history(source.revision_history())
1067
def get_parent(self):
1068
"""See Branch.get_parent."""
1070
_locs = ['parent', 'pull', 'x-pull']
1073
return self.controlfile(l, 'r').read().strip('\n')
1075
if e.errno != errno.ENOENT:
1079
def get_push_location(self):
1080
"""See Branch.get_push_location."""
1081
config = bzrlib.config.BranchConfig(self)
1082
push_loc = config.get_user_option('push_location')
1085
def set_push_location(self, location):
1086
"""See Branch.set_push_location."""
1087
config = bzrlib.config.LocationConfig(self.base)
1088
config.set_user_option('push_location', location)
1091
def set_parent(self, url):
1092
"""See Branch.set_parent."""
1093
# TODO: Maybe delete old location files?
1094
from bzrlib.atomicfile import AtomicFile
1095
f = AtomicFile(self.controlfilename('parent'))
1102
def tree_config(self):
1103
return TreeConfig(self)
1105
def check_revno(self, revno):
1107
Check whether a revno corresponds to any revision.
1108
Zero (the NULL revision) is considered valid.
1111
self.check_real_revno(revno)
1113
def check_real_revno(self, revno):
1115
Check whether a revno corresponds to a real revision.
1116
Zero (the NULL revision) is considered invalid
1118
if revno < 1 or revno > self.revno():
1119
raise InvalidRevisionNumber(revno)
1121
def sign_revision(self, revision_id, gpg_strategy):
1122
"""See Branch.sign_revision."""
1123
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1124
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1127
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1128
"""See Branch.store_revision_signature."""
1129
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1133
class ScratchBranch(BzrBranch):
1134
"""Special test class: a branch that cleans up after itself.
1136
>>> b = ScratchBranch()
1140
>>> b._transport.__del__()
1145
def __init__(self, files=[], dirs=[], transport=None):
1146
"""Make a test branch.
1148
This creates a temporary directory and runs init-tree in it.
1150
If any files are listed, they are created in the working copy.
1152
if transport is None:
1153
transport = bzrlib.transport.local.ScratchTransport()
1154
super(ScratchBranch, self).__init__(transport, init=True)
1156
super(ScratchBranch, self).__init__(transport)
1159
self._transport.mkdir(d)
1162
self._transport.put(f, 'content of %s' % f)
1167
>>> orig = ScratchBranch(files=["file1", "file2"])
1168
>>> clone = orig.clone()
1169
>>> if os.name != 'nt':
1170
... os.path.samefile(orig.base, clone.base)
1172
... orig.base == clone.base
1175
>>> os.path.isfile(os.path.join(clone.base, "file1"))
1178
from shutil import copytree
1179
from tempfile import mkdtemp
1182
copytree(self.base, base, symlinks=True)
1183
return ScratchBranch(
1184
transport=bzrlib.transport.local.ScratchTransport(base))
1187
######################################################################
1191
def is_control_file(filename):
1192
## FIXME: better check
1193
filename = os.path.normpath(filename)
1194
while filename != '':
1195
head, tail = os.path.split(filename)
1196
## mutter('check %r for control file' % ((head, tail), ))
1197
if tail == bzrlib.BZRDIR:
1199
if filename == head: