1
# Copyright (C) 2005, 2006 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
import bzrlib.bzrdir as bzrdir
30
from bzrlib.config import TreeConfig
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
32
from bzrlib.delta import compare_trees
33
import bzrlib.errors as errors
34
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
35
NoSuchRevision, HistoryMissing, NotBranchError,
36
DivergedBranches, LockError,
37
UninitializableFormat,
39
UnlistableBranch, NoSuchFile, NotVersionedError,
41
import bzrlib.inventory as inventory
42
from bzrlib.inventory import Inventory
43
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.osutils import (isdir, quotefn,
45
rename, splitpath, sha_file,
46
file_kind, abspath, normpath, pathjoin,
49
from bzrlib.textui import show_status
50
from bzrlib.trace import mutter, note
51
from bzrlib.tree import EmptyTree, RevisionTree
52
from bzrlib.repository import Repository
53
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
54
from bzrlib.store import copy_all
55
from bzrlib.symbol_versioning import *
56
import bzrlib.transactions as transactions
57
from bzrlib.transport import Transport, get_transport
58
from bzrlib.tree import EmptyTree, RevisionTree
63
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
64
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
65
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
68
# TODO: Maybe include checks for common corruption of newlines, etc?
70
# TODO: Some operations like log might retrieve the same revisions
71
# repeatedly to calculate deltas. We could perhaps have a weakref
72
# cache in memory to make this faster. In general anything can be
73
# cached in memory between lock and unlock operations. .. nb thats
74
# what the transaction identity map provides
77
######################################################################
81
"""Branch holding a history of revisions.
84
Base directory/url of the branch.
86
# this is really an instance variable - FIXME move it there
92
"""Construct the current default format branch in a_bzrdir.
94
This creates the current default BzrDir format, and if that
95
supports multiple Branch formats, then the default Branch format
98
print "not usable until we have repositories"
99
raise NotImplementedError("not usable right now")
100
return bzrdir.BzrDir.create(base)
102
def __init__(self, *ignored, **ignored_too):
103
raise NotImplementedError('The Branch class is abstract')
106
def open_downlevel(base):
107
"""Open a branch which may be of an old format."""
108
return Branch.open(base, _unsupported=True)
111
def open(base, _unsupported=False):
112
"""Open the repository rooted at base.
114
For instance, if the repository is at URL/.bzr/repository,
115
Repository.open(URL) -> a Repository instance.
117
control = bzrdir.BzrDir.open(base, _unsupported)
118
return control.open_branch()
121
def open_containing(url):
122
"""Open an existing branch which contains url.
124
This probes for a branch at url, and searches upwards from there.
126
Basically we keep looking up until we find the control directory or
127
run into the root. If there isn't one, raises NotBranchError.
128
If there is one and it is either an unrecognised format or an unsupported
129
format, UnknownFormatError or UnsupportedFormatError are raised.
130
If there is one, it is returned, along with the unused portion of url.
132
control, relpath = bzrdir.BzrDir.open_containing(url)
133
return control.open_branch(), relpath
136
@deprecated_function(zero_eight)
137
def initialize(base):
138
"""Create a new working tree and branch, rooted at 'base' (url)
140
# imported here to prevent scope creep as this is going.
141
from bzrlib.workingtree import WorkingTree
142
return WorkingTree.create_standalone(safe_unicode(base)).branch
144
def setup_caching(self, cache_root):
145
"""Subclasses that care about caching should override this, and set
146
up cached stores located under cache_root.
148
# seems to be unused, 2006-01-13 mbp
149
warn('%s is deprecated' % self.setup_caching)
150
self.cache_root = cache_root
153
cfg = self.tree_config()
154
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
156
def _set_nick(self, nick):
157
cfg = self.tree_config()
158
cfg.set_option(nick, "nickname")
159
assert cfg.get_option("nickname") == nick
161
nick = property(_get_nick, _set_nick)
163
def lock_write(self):
164
raise NotImplementedError('lock_write is abstract')
167
raise NotImplementedError('lock_read is abstract')
170
raise NotImplementedError('unlock is abstract')
172
def peek_lock_mode(self):
173
"""Return lock mode for the Branch: 'r', 'w' or None"""
174
raise NotImplementedError(self.peek_lock_mode)
176
def abspath(self, name):
177
"""Return absolute filename for something in the branch
179
XXX: Robert Collins 20051017 what is this used for? why is it a branch
180
method and not a tree method.
182
raise NotImplementedError('abspath is abstract')
184
def get_root_id(self):
185
"""Return the id of this branches root"""
186
raise NotImplementedError('get_root_id is abstract')
188
def print_file(self, file, revision_id):
189
"""Print `file` to stdout."""
190
raise NotImplementedError('print_file is abstract')
192
def append_revision(self, *revision_ids):
193
raise NotImplementedError('append_revision is abstract')
195
def set_revision_history(self, rev_history):
196
raise NotImplementedError('set_revision_history is abstract')
198
def revision_history(self):
199
"""Return sequence of revision hashes on to this branch."""
200
raise NotImplementedError('revision_history is abstract')
203
"""Return current revision number for this branch.
205
That is equivalent to the number of revisions committed to
208
return len(self.revision_history())
210
def last_revision(self):
211
"""Return last patch hash, or None if no history."""
212
ph = self.revision_history()
218
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
219
"""Return a list of new revisions that would perfectly fit.
221
If self and other have not diverged, return a list of the revisions
222
present in other, but missing from self.
224
>>> from bzrlib.workingtree import WorkingTree
225
>>> bzrlib.trace.silent = True
226
>>> d1 = bzrdir.ScratchDir()
227
>>> br1 = d1.open_branch()
228
>>> wt1 = WorkingTree(br1.base, br1)
229
>>> d2 = bzrdir.ScratchDir()
230
>>> br2 = d2.open_branch()
231
>>> wt2 = WorkingTree(br2.base, br2)
232
>>> br1.missing_revisions(br2)
234
>>> wt2.commit("lala!", rev_id="REVISION-ID-1")
235
>>> br1.missing_revisions(br2)
237
>>> br2.missing_revisions(br1)
239
>>> wt1.commit("lala!", rev_id="REVISION-ID-1")
240
>>> br1.missing_revisions(br2)
242
>>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
243
>>> br1.missing_revisions(br2)
245
>>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
246
>>> br1.missing_revisions(br2)
247
Traceback (most recent call last):
248
DivergedBranches: These branches have diverged. Try merge.
250
self_history = self.revision_history()
251
self_len = len(self_history)
252
other_history = other.revision_history()
253
other_len = len(other_history)
254
common_index = min(self_len, other_len) -1
255
if common_index >= 0 and \
256
self_history[common_index] != other_history[common_index]:
257
raise DivergedBranches(self, other)
259
if stop_revision is None:
260
stop_revision = other_len
262
assert isinstance(stop_revision, int)
263
if stop_revision > other_len:
264
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
265
return other_history[self_len:stop_revision]
267
def update_revisions(self, other, stop_revision=None):
268
"""Pull in new perfect-fit revisions."""
269
raise NotImplementedError('update_revisions is abstract')
271
def pullable_revisions(self, other, stop_revision):
272
raise NotImplementedError('pullable_revisions is abstract')
274
def revision_id_to_revno(self, revision_id):
275
"""Given a revision id, return its revno"""
276
if revision_id is None:
278
history = self.revision_history()
280
return history.index(revision_id) + 1
282
raise bzrlib.errors.NoSuchRevision(self, revision_id)
284
def get_rev_id(self, revno, history=None):
285
"""Find the revision id of the specified revno."""
289
history = self.revision_history()
290
elif revno <= 0 or revno > len(history):
291
raise bzrlib.errors.NoSuchRevision(self, revno)
292
return history[revno - 1]
294
def pull(self, source, overwrite=False):
295
raise NotImplementedError('pull is abstract')
297
def basis_tree(self):
298
"""Return `Tree` object for last revision.
300
If there are no revisions yet, return an `EmptyTree`.
302
return self.repository.revision_tree(self.last_revision())
304
def rename_one(self, from_rel, to_rel):
307
This can change the directory or the filename or both.
309
raise NotImplementedError('rename_one is abstract')
311
def move(self, from_paths, to_name):
314
to_name must exist as a versioned directory.
316
If to_name exists and is a directory, the files are moved into
317
it, keeping their old names. If it is a directory,
319
Note that to_name is only the last component of the new name;
320
this doesn't change the directory.
322
This returns a list of (from_path, to_path) pairs for each
325
raise NotImplementedError('move is abstract')
327
def get_parent(self):
328
"""Return the parent location of the branch.
330
This is the default location for push/pull/missing. The usual
331
pattern is that the user can override it by specifying a
334
raise NotImplementedError('get_parent is abstract')
336
def get_push_location(self):
337
"""Return the None or the location to push this branch to."""
338
raise NotImplementedError('get_push_location is abstract')
340
def set_push_location(self, location):
341
"""Set a new push location for this branch."""
342
raise NotImplementedError('set_push_location is abstract')
344
def set_parent(self, url):
345
raise NotImplementedError('set_parent is abstract')
347
def check_revno(self, revno):
349
Check whether a revno corresponds to any revision.
350
Zero (the NULL revision) is considered valid.
353
self.check_real_revno(revno)
355
def check_real_revno(self, revno):
357
Check whether a revno corresponds to a real revision.
358
Zero (the NULL revision) is considered invalid
360
if revno < 1 or revno > self.revno():
361
raise InvalidRevisionNumber(revno)
363
def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
364
"""Copy this branch into the existing directory to_location.
366
Returns the newly created branch object.
369
If not None, only revisions up to this point will be copied.
370
The head of the new branch will be that revision. Must be a
373
to_location -- The destination directory; must either exist and be
374
empty, or not exist, in which case it is created.
377
A local branch to copy revisions from, related to this branch.
378
This is used when branching from a remote (slow) branch, and we have
379
a local branch that might contain some relevant revisions.
382
Branch type of destination branch
384
from bzrlib.workingtree import WorkingTree
385
assert isinstance(to_location, basestring)
386
segments = to_location.split('/')
387
if segments and segments[-1] not in ('', '.'):
388
parent = '/'.join(segments[:-1])
389
t = get_transport(parent)
391
t.mkdir(segments[-1])
392
except errors.FileExists:
394
if to_branch_format is None:
396
br_to = bzrdir.BzrDir.create_branch_and_repo(to_location)
398
br_to = to_branch_format.initialize(to_location)
399
mutter("copy branch from %s to %s", self, br_to)
401
revision = self.last_revision()
402
if basis_branch is not None:
403
basis_branch.repository.push_stores(br_to.repository,
405
br_to.update_revisions(self, stop_revision=revision)
406
br_to.set_parent(self.base)
411
class BranchFormat(object):
412
"""An encapsulation of the initialization and open routines for a format.
414
Formats provide three things:
415
* An initialization routine,
419
Formats are placed in an dict by their format string for reference
420
during branch opening. Its not required that these be instances, they
421
can be classes themselves with class methods - it simply depends on
422
whether state is needed for a given format or not.
424
Once a format is deprecated, just deprecate the initialize and open
425
methods on the format class. Do not deprecate the object, as the
426
object will be created every time regardless.
429
_default_format = None
430
"""The default format used for new branches."""
433
"""The known formats."""
436
def get_default_format(klass):
437
"""Return the current default format."""
438
return klass._default_format
440
def get_format_string(self):
441
"""Return the ASCII format string that identifies this format."""
442
raise NotImplementedError(self.get_format_string)
444
def _find_modes(self, t):
445
"""Determine the appropriate modes for files and directories.
447
FIXME: When this merges into, or from storage,
448
this code becomes delgatable to a LockableFiles instance.
450
For now its cribbed and returns (dir_mode, file_mode)
454
except errors.TransportNotPossible:
458
dir_mode = st.st_mode & 07777
459
# Remove the sticky and execute bits for files
460
file_mode = dir_mode & ~07111
461
if not BzrBranch._set_dir_mode:
463
if not BzrBranch._set_file_mode:
465
return dir_mode, file_mode
467
def initialize(self, a_bzrdir):
468
"""Create a branch of this format in a_bzrdir."""
469
mutter('creating branch in %s', a_bzrdir.transport.base)
470
utf8_files = [('revision-history', ''),
474
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
475
control_files.lock_write()
477
for file, content in utf8_files:
478
control_files.put_utf8(file, content)
480
control_files.unlock()
481
return self.open(a_bzrdir, _found=True)
483
def is_supported(self):
484
"""Is this format supported?
486
Supported formats can be initialized and opened.
487
Unsupported formats may not support initialization or committing or
488
some other features depending on the reason for not being supported.
492
def open(self, a_bzrdir, _found=False):
493
"""Return the branch object for a_bzrdir
495
_found is a private parameter, do not use it. It is used to indicate
496
if format probing has already be done.
499
# we are being called directly and must probe.
500
raise NotImplementedError
501
return BzrBranch(a_bzrdir.transport.clone('..'), _format=self, a_bzrdir=a_bzrdir)
504
def register_format(klass, format):
505
klass._formats[format.get_format_string()] = format
508
def set_default_format(klass, format):
509
klass._default_format = format
512
def unregister_format(klass, format):
513
assert klass._formats[format.get_format_string()] is format
514
del klass._formats[format.get_format_string()]
517
class BzrBranchFormat4(BranchFormat):
518
"""Bzr branch format 4.
521
- a revision-history file.
525
super(BzrBranchFormat4, self).__init__()
526
self._matchingbzrdir = bzrdir.BzrDirFormat6()
528
# formats which have no format string are not discoverable
529
# and not independently creatable, so are not registered.
530
# __default_format = BranchFormatXXX()
531
# BranchFormat.register_format(__default_format)
532
# BranchFormat.set_default_format(__default_format)
533
_legacy_formats = [BzrBranchFormat4(),
536
class BzrBranch(Branch):
537
"""A branch stored in the actual filesystem.
539
Note that it's "local" in the context of the filesystem; it doesn't
540
really matter if it's on an nfs/smb/afs/coda/... share, as long as
541
it's writable, and can be accessed via the normal filesystem API.
544
# We actually expect this class to be somewhat short-lived; part of its
545
# purpose is to try to isolate what bits of the branch logic are tied to
546
# filesystem access, so that in a later step, we can extricate them to
547
# a separarte ("storage") class.
548
_inventory_weave = None
550
# Map some sort of prefix into a namespace
551
# stuff like "revno:10", "revid:", etc.
552
# This should match a prefix with a function which accepts
553
REVISION_NAMESPACES = {}
555
def __init__(self, transport, init=DEPRECATED_PARAMETER,
556
relax_version_check=DEPRECATED_PARAMETER, _format=None,
557
_control_files=None, a_bzrdir=None):
558
"""Create new branch object at a particular location.
560
transport -- A Transport object, defining how to access files.
562
init -- If True, create new control files in a previously
563
unversioned directory. If False, the branch must already
566
relax_version_check -- If true, the usual check for the branch
567
version is not applied. This is intended only for
568
upgrade/recovery type use; it's not guaranteed that
569
all operations will work on old format branches.
571
self.bzrdir = a_bzrdir
572
self._transport = transport
573
self._base = self._transport.base
574
self._format = _format
575
if _control_files is None:
576
_control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
578
self.control_files = _control_files
579
if deprecated_passed(init):
580
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
581
"deprecated as of bzr 0.8. Please use Branch.create().",
585
# this is slower than before deprecation, oh well never mind.
587
self._initialize(transport.base)
588
self._check_format(_format)
589
if deprecated_passed(relax_version_check):
590
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
591
"relax_version_check parameter is deprecated as of bzr 0.8. "
592
"Please use Branch.open_downlevel, or a BzrBranchFormat's "
596
if (not relax_version_check
597
and not self._branch_format.is_supported()):
598
raise errors.UnsupportedFormatError(
599
'sorry, branch format %r not supported' % fmt,
600
['use a different bzr version',
601
'or remove the .bzr directory'
602
' and "bzr init" again'])
603
self.repository = self.bzrdir.open_repository()
606
return '%s(%r)' % (self.__class__.__name__, self.base)
611
# TODO: It might be best to do this somewhere else,
612
# but it is nice for a Branch object to automatically
613
# cache it's information.
614
# Alternatively, we could have the Transport objects cache requests
615
# See the earlier discussion about how major objects (like Branch)
616
# should never expect their __del__ function to run.
617
# XXX: cache_root seems to be unused, 2006-01-13 mbp
618
if hasattr(self, 'cache_root') and self.cache_root is not None:
620
shutil.rmtree(self.cache_root)
623
self.cache_root = None
628
base = property(_get_base, doc="The URL for the root of this branch.")
630
def _finish_transaction(self):
631
"""Exit the current transaction."""
632
return self.control_files._finish_transaction()
634
def get_transaction(self):
635
"""Return the current active transaction.
637
If no transaction is active, this returns a passthrough object
638
for which all data is immediately flushed and no caching happens.
640
# this is an explicit function so that we can do tricky stuff
641
# when the storage in rev_storage is elsewhere.
642
# we probably need to hook the two 'lock a location' and
643
# 'have a transaction' together more delicately, so that
644
# we can have two locks (branch and storage) and one transaction
645
# ... and finishing the transaction unlocks both, but unlocking
646
# does not. - RBC 20051121
647
return self.control_files.get_transaction()
649
def _set_transaction(self, transaction):
650
"""Set a new active transaction."""
651
return self.control_files._set_transaction(transaction)
653
def abspath(self, name):
654
"""See Branch.abspath."""
655
return self.control_files._transport.abspath(name)
657
def _check_format(self, format):
658
"""Identify the branch format if needed.
660
The format is stored as a reference to the format object in
661
self._branch_format for code that needs to check it later.
663
The format parameter is either None or the branch format class
664
used to open this branch.
667
format = BzrBranchFormat.find_format(self._transport)
668
self._branch_format = format
669
mutter("got branch format %s", self._branch_format)
672
def get_root_id(self):
673
"""See Branch.get_root_id."""
674
tree = self.repository.revision_tree(self.last_revision())
675
return tree.inventory.root.file_id
677
def lock_write(self):
678
# TODO: test for failed two phase locks. This is known broken.
679
self.control_files.lock_write()
680
self.repository.lock_write()
683
# TODO: test for failed two phase locks. This is known broken.
684
self.control_files.lock_read()
685
self.repository.lock_read()
688
# TODO: test for failed two phase locks. This is known broken.
689
self.repository.unlock()
690
self.control_files.unlock()
692
def peek_lock_mode(self):
693
if self.control_files._lock_count == 0:
696
return self.control_files._lock_mode
699
def print_file(self, file, revision_id):
700
"""See Branch.print_file."""
701
return self.repository.print_file(file, revision_id)
704
def append_revision(self, *revision_ids):
705
"""See Branch.append_revision."""
706
for revision_id in revision_ids:
707
mutter("add {%s} to revision-history" % revision_id)
708
rev_history = self.revision_history()
709
rev_history.extend(revision_ids)
710
self.set_revision_history(rev_history)
713
def set_revision_history(self, rev_history):
714
"""See Branch.set_revision_history."""
715
self.control_files.put_utf8(
716
'revision-history', '\n'.join(rev_history))
718
def get_revision_delta(self, revno):
719
"""Return the delta for one revision.
721
The delta is relative to its mainline predecessor, or the
722
empty tree for revision 1.
724
assert isinstance(revno, int)
725
rh = self.revision_history()
726
if not (1 <= revno <= len(rh)):
727
raise InvalidRevisionNumber(revno)
729
# revno is 1-based; list is 0-based
731
new_tree = self.repository.revision_tree(rh[revno-1])
733
old_tree = EmptyTree()
735
old_tree = self.repository.revision_tree(rh[revno-2])
736
return compare_trees(old_tree, new_tree)
739
def revision_history(self):
740
"""See Branch.revision_history."""
741
# FIXME are transactions bound to control files ? RBC 20051121
742
transaction = self.get_transaction()
743
history = transaction.map.find_revision_history()
744
if history is not None:
745
mutter("cache hit for revision-history in %s", self)
747
history = [l.rstrip('\r\n') for l in
748
self.control_files.get_utf8('revision-history').readlines()]
749
transaction.map.add_revision_history(history)
750
# this call is disabled because revision_history is
751
# not really an object yet, and the transaction is for objects.
752
# transaction.register_clean(history, precious=True)
755
def update_revisions(self, other, stop_revision=None):
756
"""See Branch.update_revisions."""
757
from bzrlib.fetch import greedy_fetch
758
if stop_revision is None:
759
stop_revision = other.last_revision()
760
### Should this be checking is_ancestor instead of revision_history?
761
if (stop_revision is not None and
762
stop_revision in self.revision_history()):
764
greedy_fetch(to_branch=self, from_branch=other,
765
revision=stop_revision)
766
pullable_revs = self.pullable_revisions(other, stop_revision)
767
if len(pullable_revs) > 0:
768
self.append_revision(*pullable_revs)
770
def pullable_revisions(self, other, stop_revision):
771
"""See Branch.pullable_revisions."""
772
other_revno = other.revision_id_to_revno(stop_revision)
774
return self.missing_revisions(other, other_revno)
775
except DivergedBranches, e:
777
pullable_revs = get_intervening_revisions(self.last_revision(),
780
assert self.last_revision() not in pullable_revs
782
except bzrlib.errors.NotAncestor:
783
if is_ancestor(self.last_revision(), stop_revision, self):
788
def basis_tree(self):
789
"""See Branch.basis_tree."""
790
return self.repository.revision_tree(self.last_revision())
792
@deprecated_method(zero_eight)
793
def working_tree(self):
794
"""Create a Working tree object for this branch."""
795
from bzrlib.workingtree import WorkingTree
796
from bzrlib.transport.local import LocalTransport
797
if (self.base.find('://') != -1 or
798
not isinstance(self._transport, LocalTransport)):
799
raise NoWorkingTree(self.base)
800
return WorkingTree(self.base, branch=self)
803
def pull(self, source, overwrite=False):
804
"""See Branch.pull."""
807
old_count = len(self.revision_history())
809
self.update_revisions(source)
810
except DivergedBranches:
814
self.set_revision_history(source.revision_history())
815
new_count = len(self.revision_history())
816
return new_count - old_count
820
def get_parent(self):
821
"""See Branch.get_parent."""
823
_locs = ['parent', 'pull', 'x-pull']
826
return self.control_files.get_utf8(l).read().strip('\n')
831
def get_push_location(self):
832
"""See Branch.get_push_location."""
833
config = bzrlib.config.BranchConfig(self)
834
push_loc = config.get_user_option('push_location')
837
def set_push_location(self, location):
838
"""See Branch.set_push_location."""
839
config = bzrlib.config.LocationConfig(self.base)
840
config.set_user_option('push_location', location)
843
def set_parent(self, url):
844
"""See Branch.set_parent."""
845
# TODO: Maybe delete old location files?
846
# URLs should never be unicode, even on the local fs,
847
# FIXUP this and get_parent in a future branch format bump:
848
# read and rewrite the file, and have the new format code read
849
# using .get not .get_utf8. RBC 20060125
850
self.control_files.put_utf8('parent', url + '\n')
852
def tree_config(self):
853
return TreeConfig(self)
855
def _get_truncated_history(self, revision_id):
856
history = self.revision_history()
857
if revision_id is None:
860
idx = history.index(revision_id)
862
raise InvalidRevisionId(revision_id=revision, branch=self)
863
return history[:idx+1]
866
def _clone_weave(self, to_location, revision=None, basis_branch=None):
868
from bzrlib.workingtree import WorkingTree
869
assert isinstance(to_location, basestring)
870
if basis_branch is not None:
871
note("basis_branch is not supported for fast weave copy yet.")
873
history = self._get_truncated_history(revision)
874
if not bzrlib.osutils.lexists(to_location):
875
os.mkdir(to_location)
876
bzrdir_to = self.bzrdir._format.initialize(to_location)
877
self.repository.clone(bzrdir_to)
878
branch_to = bzrdir_to.create_branch()
879
mutter("copy branch from %s to %s", self, branch_to)
881
# FIXME duplicate code with base .clone().
882
# .. would template method be useful here? RBC 20051207
883
branch_to.set_parent(self.base)
884
branch_to.append_revision(*history)
885
WorkingTree.create(branch_to, branch_to.base)
889
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
890
print "FIXME: clone via create and fetch is probably faster when versioned file comes in."
891
if (to_branch_type is None
892
and self.repository.weave_store.listable()
893
and self.repository.revision_store.listable()):
894
return self._clone_weave(to_location, revision, basis_branch)
896
return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
899
class BranchTestProviderAdapter(object):
900
"""A tool to generate a suite testing multiple branch formats at once.
902
This is done by copying the test once for each transport and injecting
903
the transport_server, transport_readonly_server, and branch_format
904
classes into each copy. Each copy is also given a new id() to make it
908
def __init__(self, transport_server, transport_readonly_server, formats):
909
self._transport_server = transport_server
910
self._transport_readonly_server = transport_readonly_server
911
self._formats = formats
913
def adapt(self, test):
915
for branch_format, bzrdir_format in self._formats:
916
new_test = deepcopy(test)
917
new_test.transport_server = self._transport_server
918
new_test.transport_readonly_server = self._transport_readonly_server
919
new_test.bzrdir_format = bzrdir_format
920
new_test.branch_format = branch_format
921
def make_new_test_id():
922
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
923
return lambda: new_id
924
new_test.id = make_new_test_id()
925
result.addTest(new_test)
929
######################################################################
933
@deprecated_function(zero_eight)
934
def ScratchBranch(*args, **kwargs):
935
"""See bzrlib.bzrdir.ScratchDir."""
936
d = ScratchDir(*args, **kwargs)
937
return d.open_branch()
940
@deprecated_function(zero_eight)
941
def is_control_file(*args, **kwargs):
942
"""See bzrlib.workingtree.is_control_file."""
943
return bzrlib.workingtree.is_control_file(*args, **kwargs)