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
from bzrlib.trace import mutter, note
28
from bzrlib.osutils import (isdir, quotefn,
29
rename, splitpath, sha_file,
30
file_kind, abspath, normpath, pathjoin)
31
import bzrlib.errors as errors
32
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
33
NoSuchRevision, HistoryMissing, NotBranchError,
34
DivergedBranches, LockError, UnlistableStore,
35
UnlistableBranch, NoSuchFile, NotVersionedError,
37
from bzrlib.textui import show_status
38
from bzrlib.config import TreeConfig
39
from bzrlib.delta import compare_trees
40
import bzrlib.inventory as inventory
41
from bzrlib.inventory import Inventory
42
from bzrlib.lockable_files import LockableFiles
43
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
44
from bzrlib.repository import Repository
45
from bzrlib.store import copy_all
46
import bzrlib.transactions as transactions
47
from bzrlib.transport import Transport, get_transport
48
from bzrlib.tree import EmptyTree, RevisionTree
51
from bzrlib.decorators import needs_read_lock, needs_write_lock
54
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
55
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
56
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
59
# TODO: Maybe include checks for common corruption of newlines, etc?
61
# TODO: Some operations like log might retrieve the same revisions
62
# repeatedly to calculate deltas. We could perhaps have a weakref
63
# cache in memory to make this faster. In general anything can be
64
# cached in memory between lock and unlock operations.
66
# FIXME: At the moment locking the Branch locks both the repository and the
67
# control files, representing the two aspects currently controlled by one
68
# object. However, they currently both map to the same lockfile.
70
def find_branch(*ignored, **ignored_too):
71
# XXX: leave this here for about one release, then remove it
72
raise NotImplementedError('find_branch() is not supported anymore, '
73
'please use one of the new branch constructors')
76
######################################################################
80
"""Branch holding a history of revisions.
83
Base directory/url of the branch.
87
def __init__(self, *ignored, **ignored_too):
88
raise NotImplementedError('The Branch class is abstract')
91
def open_downlevel(base):
92
"""Open a branch which may be of an old format.
94
Only local branches are supported."""
95
return BzrBranch(get_transport(base), relax_version_check=True)
99
"""Open an existing branch, rooted at 'base' (url)"""
100
t = get_transport(base)
101
mutter("trying to open %r with transport %r", base, t)
105
def open_containing(url):
106
"""Open an existing branch which contains url.
108
This probes for a branch at url, and searches upwards from there.
110
Basically we keep looking up until we find the control directory or
111
run into the root. If there isn't one, raises NotBranchError.
112
If there is one, it is returned, along with the unused portion of url.
114
t = get_transport(url)
117
return BzrBranch(t), t.relpath(url)
118
except NotBranchError, e:
119
mutter('not a branch in: %r %s', t.base, e)
120
new_t = t.clone('..')
121
if new_t.base == t.base:
122
# reached the root, whatever that may be
123
raise NotBranchError(path=url)
127
def initialize(base):
128
"""Create a new branch, rooted at 'base' (url)"""
129
t = get_transport(unicode(base))
130
return BzrBranch(t, init=True)
132
def setup_caching(self, cache_root):
133
"""Subclasses that care about caching should override this, and set
134
up cached stores located under cache_root.
136
self.cache_root = cache_root
139
cfg = self.tree_config()
140
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
142
def _set_nick(self, nick):
143
cfg = self.tree_config()
144
cfg.set_option(nick, "nickname")
145
assert cfg.get_option("nickname") == nick
147
nick = property(_get_nick, _set_nick)
149
def push_stores(self, branch_to):
150
"""Copy the content of this branches store to branch_to."""
151
raise NotImplementedError('push_stores is abstract')
153
def lock_write(self):
154
raise NotImplementedError('lock_write is abstract')
157
raise NotImplementedError('lock_read is abstract')
160
raise NotImplementedError('unlock is abstract')
162
def peek_lock_mode(self):
163
"""Return lock mode for the Branch: 'r', 'w' or None"""
164
raise NotImplementedError(self.is_locked)
166
def abspath(self, name):
167
"""Return absolute filename for something in the branch
169
XXX: Robert Collins 20051017 what is this used for? why is it a branch
170
method and not a tree method.
172
raise NotImplementedError('abspath is abstract')
174
def get_root_id(self):
175
"""Return the id of this branches root"""
176
raise NotImplementedError('get_root_id is abstract')
178
def print_file(self, file, revision_id):
179
"""Print `file` to stdout."""
180
raise NotImplementedError('print_file is abstract')
182
def append_revision(self, *revision_ids):
183
raise NotImplementedError('append_revision is abstract')
185
def set_revision_history(self, rev_history):
186
raise NotImplementedError('set_revision_history is abstract')
188
def revision_history(self):
189
"""Return sequence of revision hashes on to this branch."""
190
raise NotImplementedError('revision_history is abstract')
193
"""Return current revision number for this branch.
195
That is equivalent to the number of revisions committed to
198
return len(self.revision_history())
200
def last_revision(self):
201
"""Return last patch hash, or None if no history."""
202
ph = self.revision_history()
208
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
209
"""Return a list of new revisions that would perfectly fit.
211
If self and other have not diverged, return a list of the revisions
212
present in other, but missing from self.
214
>>> from bzrlib.commit import commit
215
>>> bzrlib.trace.silent = True
216
>>> br1 = ScratchBranch()
217
>>> br2 = ScratchBranch()
218
>>> br1.missing_revisions(br2)
220
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
221
>>> br1.missing_revisions(br2)
223
>>> br2.missing_revisions(br1)
225
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
226
>>> br1.missing_revisions(br2)
228
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
229
>>> br1.missing_revisions(br2)
231
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
232
>>> br1.missing_revisions(br2)
233
Traceback (most recent call last):
234
DivergedBranches: These branches have diverged. Try merge.
236
self_history = self.revision_history()
237
self_len = len(self_history)
238
other_history = other.revision_history()
239
other_len = len(other_history)
240
common_index = min(self_len, other_len) -1
241
if common_index >= 0 and \
242
self_history[common_index] != other_history[common_index]:
243
raise DivergedBranches(self, other)
245
if stop_revision is None:
246
stop_revision = other_len
248
assert isinstance(stop_revision, int)
249
if stop_revision > other_len:
250
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
251
return other_history[self_len:stop_revision]
254
def update_revisions(self, other, stop_revision=None):
255
"""Pull in new perfect-fit revisions."""
256
raise NotImplementedError('update_revisions is abstract')
258
def pullable_revisions(self, other, stop_revision):
259
raise NotImplementedError('pullable_revisions is abstract')
261
def revision_id_to_revno(self, revision_id):
262
"""Given a revision id, return its revno"""
263
if revision_id is None:
265
history = self.revision_history()
267
return history.index(revision_id) + 1
269
raise bzrlib.errors.NoSuchRevision(self, revision_id)
271
def get_rev_id(self, revno, history=None):
272
"""Find the revision id of the specified revno."""
276
history = self.revision_history()
277
elif revno <= 0 or revno > len(history):
278
raise bzrlib.errors.NoSuchRevision(self, revno)
279
return history[revno - 1]
281
def working_tree(self):
282
"""Return a `Tree` for the working copy if this is a local branch."""
283
raise NotImplementedError('working_tree is abstract')
285
def pull(self, source, overwrite=False):
286
raise NotImplementedError('pull is abstract')
288
def basis_tree(self):
289
"""Return `Tree` object for last revision.
291
If there are no revisions yet, return an `EmptyTree`.
293
return self.repository.revision_tree(self.last_revision())
295
def rename_one(self, from_rel, to_rel):
298
This can change the directory or the filename or both.
300
raise NotImplementedError('rename_one is abstract')
302
def move(self, from_paths, to_name):
305
to_name must exist as a versioned directory.
307
If to_name exists and is a directory, the files are moved into
308
it, keeping their old names. If it is a directory,
310
Note that to_name is only the last component of the new name;
311
this doesn't change the directory.
313
This returns a list of (from_path, to_path) pairs for each
316
raise NotImplementedError('move is abstract')
318
def get_parent(self):
319
"""Return the parent location of the branch.
321
This is the default location for push/pull/missing. The usual
322
pattern is that the user can override it by specifying a
325
raise NotImplementedError('get_parent is abstract')
327
def get_push_location(self):
328
"""Return the None or the location to push this branch to."""
329
raise NotImplementedError('get_push_location is abstract')
331
def set_push_location(self, location):
332
"""Set a new push location for this branch."""
333
raise NotImplementedError('set_push_location is abstract')
335
def set_parent(self, url):
336
raise NotImplementedError('set_parent is abstract')
338
def check_revno(self, revno):
340
Check whether a revno corresponds to any revision.
341
Zero (the NULL revision) is considered valid.
344
self.check_real_revno(revno)
346
def check_real_revno(self, revno):
348
Check whether a revno corresponds to a real revision.
349
Zero (the NULL revision) is considered invalid
351
if revno < 1 or revno > self.revno():
352
raise InvalidRevisionNumber(revno)
354
def sign_revision(self, revision_id, gpg_strategy):
355
raise NotImplementedError('sign_revision is abstract')
357
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
358
raise NotImplementedError('store_revision_signature is abstract')
360
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
361
"""Copy this branch into the existing directory to_location.
363
Returns the newly created branch object.
366
If not None, only revisions up to this point will be copied.
367
The head of the new branch will be that revision. Must be a
370
to_location -- The destination directory; must either exist and be
371
empty, or not exist, in which case it is created.
374
A local branch to copy revisions from, related to this branch.
375
This is used when branching from a remote (slow) branch, and we have
376
a local branch that might contain some relevant revisions.
379
Branch type of destination branch
381
assert isinstance(to_location, basestring)
382
if not bzrlib.osutils.lexists(to_location):
383
os.mkdir(to_location)
384
if to_branch_type is None:
385
to_branch_type = BzrBranch
386
br_to = to_branch_type.initialize(to_location)
387
mutter("copy branch from %s to %s", self, br_to)
388
if basis_branch is not None:
389
basis_branch.push_stores(br_to)
390
br_to.working_tree().set_root_id(self.get_root_id())
392
revision = self.last_revision()
393
br_to.update_revisions(self, stop_revision=revision)
394
br_to.set_parent(self.base)
395
# circular import protection
396
from bzrlib.merge import build_working_dir
397
build_working_dir(to_location)
401
class BzrBranch(Branch):
402
"""A branch stored in the actual filesystem.
404
Note that it's "local" in the context of the filesystem; it doesn't
405
really matter if it's on an nfs/smb/afs/coda/... share, as long as
406
it's writable, and can be accessed via the normal filesystem API.
409
# We actually expect this class to be somewhat short-lived; part of its
410
# purpose is to try to isolate what bits of the branch logic are tied to
411
# filesystem access, so that in a later step, we can extricate them to
412
# a separarte ("storage") class.
413
_inventory_weave = None
415
# Map some sort of prefix into a namespace
416
# stuff like "revno:10", "revid:", etc.
417
# This should match a prefix with a function which accepts
418
REVISION_NAMESPACES = {}
420
def push_stores(self, branch_to):
421
"""See Branch.push_stores."""
422
if (self._branch_format != branch_to._branch_format
423
or self._branch_format != 4):
424
from bzrlib.fetch import greedy_fetch
425
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
426
self, self._branch_format, branch_to, branch_to._branch_format)
427
greedy_fetch(to_branch=branch_to, from_branch=self,
428
revision=self.last_revision())
431
store_pairs = ((self.text_store, branch_to.text_store),
432
(self.inventory_store, branch_to.inventory_store),
433
(self.revision_store, branch_to.revision_store))
435
for from_store, to_store in store_pairs:
436
copy_all(from_store, to_store)
437
except UnlistableStore:
438
raise UnlistableBranch(from_store)
440
def __init__(self, transport, init=False,
441
relax_version_check=False):
442
"""Create new branch object at a particular location.
444
transport -- A Transport object, defining how to access files.
446
init -- If True, create new control files in a previously
447
unversioned directory. If False, the branch must already
450
relax_version_check -- If true, the usual check for the branch
451
version is not applied. This is intended only for
452
upgrade/recovery type use; it's not guaranteed that
453
all operations will work on old format branches.
455
In the test suite, creation of new trees is tested using the
456
`ScratchBranch` class.
458
assert isinstance(transport, Transport), \
459
"%r is not a Transport" % transport
460
# TODO: jam 20060103 We create a clone of this transport at .bzr/
461
# and then we forget about it, should we keep a handle to it?
462
self._base = transport.base
463
self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR),
467
self._check_format(relax_version_check)
468
self.repository = Repository(transport, self._branch_format)
471
return '%s(%r)' % (self.__class__.__name__, self.base)
476
# TODO: It might be best to do this somewhere else,
477
# but it is nice for a Branch object to automatically
478
# cache it's information.
479
# Alternatively, we could have the Transport objects cache requests
480
# See the earlier discussion about how major objects (like Branch)
481
# should never expect their __del__ function to run.
482
if hasattr(self, 'cache_root') and self.cache_root is not None:
484
shutil.rmtree(self.cache_root)
487
self.cache_root = None
492
base = property(_get_base, doc="The URL for the root of this branch.")
494
def _finish_transaction(self):
495
"""Exit the current transaction."""
496
return self.control_files._finish_transaction()
498
def get_transaction(self):
499
"""Return the current active transaction.
501
If no transaction is active, this returns a passthrough object
502
for which all data is immediately flushed and no caching happens.
504
# this is an explicit function so that we can do tricky stuff
505
# when the storage in rev_storage is elsewhere.
506
# we probably need to hook the two 'lock a location' and
507
# 'have a transaction' together more delicately, so that
508
# we can have two locks (branch and storage) and one transaction
509
# ... and finishing the transaction unlocks both, but unlocking
510
# does not. - RBC 20051121
511
return self.control_files.get_transaction()
513
def _set_transaction(self, transaction):
514
"""Set a new active transaction."""
515
return self.control_files._set_transaction(transaction)
517
def abspath(self, name):
518
"""See Branch.abspath."""
519
return self.control_files._transport.abspath(name)
521
def _make_control(self):
522
from bzrlib.inventory import Inventory
523
from bzrlib.weavefile import write_weave_v5
524
from bzrlib.weave import Weave
526
# Create an empty inventory
528
# if we want per-tree root ids then this is the place to set
529
# them; they're not needed for now and so ommitted for
531
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
532
empty_inv = sio.getvalue()
534
bzrlib.weavefile.write_weave_v5(Weave(), sio)
535
empty_weave = sio.getvalue()
537
dirs = ['', 'revision-store', 'weaves']
539
"This is a Bazaar-NG control directory.\n"
540
"Do not change any files in this directory.\n"),
541
('branch-format', BZR_BRANCH_FORMAT_6),
542
('revision-history', ''),
545
('pending-merges', ''),
546
('inventory', empty_inv),
547
('inventory.weave', empty_weave),
548
('ancestry.weave', empty_weave)
550
cfe = self.control_files._escape
551
self.control_files._transport.mkdir_multi([cfe(d) for d in dirs],
552
mode=self.control_files._dir_mode)
553
self.control_files.lock_write()
555
for file, content in files:
556
self.control_files.put_utf8(file, content)
557
mutter('created control directory in ' + self.base)
559
self.control_files.unlock()
561
def _check_format(self, relax_version_check):
562
"""Check this branch format is supported.
564
The format level is stored, as an integer, in
565
self._branch_format for code that needs to check it later.
567
In the future, we might need different in-memory Branch
568
classes to support downlevel branches. But not yet.
571
fmt = self.control_files.controlfile('branch-format', 'r').read()
573
raise NotBranchError(path=self.base)
574
mutter("got branch format %r", fmt)
575
if fmt == BZR_BRANCH_FORMAT_6:
576
self._branch_format = 6
577
elif fmt == BZR_BRANCH_FORMAT_5:
578
self._branch_format = 5
579
elif fmt == BZR_BRANCH_FORMAT_4:
580
self._branch_format = 4
582
if (not relax_version_check
583
and self._branch_format not in (5, 6)):
584
raise errors.UnsupportedFormatError(
585
'sorry, branch format %r not supported' % fmt,
586
['use a different bzr version',
587
'or remove the .bzr directory'
588
' and "bzr init" again'])
591
def get_root_id(self):
592
"""See Branch.get_root_id."""
593
inv = self.repository.get_inventory(self.last_revision())
594
return inv.root.file_id
596
def lock_write(self):
597
# TODO: test for failed two phase locks. This is known broken.
598
self.control_files.lock_write()
599
self.repository.lock_write()
602
# TODO: test for failed two phase locks. This is known broken.
603
self.control_files.lock_read()
604
self.repository.lock_read()
607
# TODO: test for failed two phase locks. This is known broken.
608
self.repository.unlock()
609
self.control_files.unlock()
611
def peek_lock_mode(self):
612
if self.control_files._lock_count == 0:
615
return self.control_files._lock_mode
618
def print_file(self, file, revision_id):
619
"""See Branch.print_file."""
620
return self.repository.print_file(file, revision_id)
623
def append_revision(self, *revision_ids):
624
"""See Branch.append_revision."""
625
for revision_id in revision_ids:
626
mutter("add {%s} to revision-history" % revision_id)
627
rev_history = self.revision_history()
628
rev_history.extend(revision_ids)
629
self.set_revision_history(rev_history)
632
def set_revision_history(self, rev_history):
633
"""See Branch.set_revision_history."""
634
old_revision = self.last_revision()
635
new_revision = rev_history[-1]
636
self.control_files.put_utf8(
637
'revision-history', '\n'.join(rev_history))
639
# FIXME: RBC 20051207 this smells wrong, last_revision in the
640
# working tree may be != to last_revision in the branch - so
641
# why is this passing in the branches last_revision ?
642
self.working_tree().set_last_revision(new_revision, old_revision)
643
except NoWorkingTree:
644
mutter('Unable to set_last_revision without a working tree.')
646
def get_revision_delta(self, revno):
647
"""Return the delta for one revision.
649
The delta is relative to its mainline predecessor, or the
650
empty tree for revision 1.
652
assert isinstance(revno, int)
653
rh = self.revision_history()
654
if not (1 <= revno <= len(rh)):
655
raise InvalidRevisionNumber(revno)
657
# revno is 1-based; list is 0-based
659
new_tree = self.repository.revision_tree(rh[revno-1])
661
old_tree = EmptyTree()
663
old_tree = self.repository.revision_tree(rh[revno-2])
664
return compare_trees(old_tree, new_tree)
667
def revision_history(self):
668
"""See Branch.revision_history."""
669
# FIXME are transactions bound to control files ? RBC 20051121
670
transaction = self.get_transaction()
671
history = transaction.map.find_revision_history()
672
if history is not None:
673
mutter("cache hit for revision-history in %s", self)
675
history = [l.rstrip('\r\n') for l in
676
self.control_files.controlfile('revision-history', 'r').readlines()]
677
transaction.map.add_revision_history(history)
678
# this call is disabled because revision_history is
679
# not really an object yet, and the transaction is for objects.
680
# transaction.register_clean(history, precious=True)
683
def update_revisions(self, other, stop_revision=None):
684
"""See Branch.update_revisions."""
685
from bzrlib.fetch import greedy_fetch
686
if stop_revision is None:
687
stop_revision = other.last_revision()
688
### Should this be checking is_ancestor instead of revision_history?
689
if (stop_revision is not None and
690
stop_revision in self.revision_history()):
692
greedy_fetch(to_branch=self, from_branch=other,
693
revision=stop_revision)
694
pullable_revs = self.pullable_revisions(other, stop_revision)
695
if len(pullable_revs) > 0:
696
self.append_revision(*pullable_revs)
698
def pullable_revisions(self, other, stop_revision):
699
"""See Branch.pullable_revisions."""
700
other_revno = other.revision_id_to_revno(stop_revision)
702
return self.missing_revisions(other, other_revno)
703
except DivergedBranches, e:
705
pullable_revs = get_intervening_revisions(self.last_revision(),
708
assert self.last_revision() not in pullable_revs
710
except bzrlib.errors.NotAncestor:
711
if is_ancestor(self.last_revision(), stop_revision, self):
716
def basis_tree(self):
717
"""See Branch.basis_tree."""
719
revision_id = self.revision_history()[-1]
720
# FIXME: This is an abstraction violation, the basis tree
721
# here as defined is on the working tree, the method should
722
# be too. The basis tree for a branch can be different than
723
# that for a working tree. RBC 20051207
724
xml = self.working_tree().read_basis_inventory(revision_id)
725
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
726
return RevisionTree(self.repository, inv, revision_id)
727
except (IndexError, NoSuchFile, NoWorkingTree), e:
728
return self.repository.revision_tree(self.last_revision())
730
def working_tree(self):
731
"""See Branch.working_tree."""
732
from bzrlib.workingtree import WorkingTree
733
if self.base.find('://') != -1:
734
raise NoWorkingTree(self.base)
735
return WorkingTree(self.base, branch=self)
738
def pull(self, source, overwrite=False):
739
"""See Branch.pull."""
742
old_count = len(self.revision_history())
744
self.update_revisions(source)
745
except DivergedBranches:
749
self.set_revision_history(source.revision_history())
750
new_count = len(self.revision_history())
751
return new_count - old_count
755
def get_parent(self):
756
"""See Branch.get_parent."""
758
_locs = ['parent', 'pull', 'x-pull']
761
return self.control_files.controlfile(l, 'r').read().strip('\n')
766
def get_push_location(self):
767
"""See Branch.get_push_location."""
768
config = bzrlib.config.BranchConfig(self)
769
push_loc = config.get_user_option('push_location')
772
def set_push_location(self, location):
773
"""See Branch.set_push_location."""
774
config = bzrlib.config.LocationConfig(self.base)
775
config.set_user_option('push_location', location)
778
def set_parent(self, url):
779
"""See Branch.set_parent."""
780
# TODO: Maybe delete old location files?
781
from bzrlib.atomicfile import AtomicFile
782
f = AtomicFile(self.control_files.controlfilename('parent'))
789
def tree_config(self):
790
return TreeConfig(self)
792
def _get_truncated_history(self, revision_id):
793
history = self.revision_history()
794
if revision_id is None:
797
idx = history.index(revision_id)
799
raise InvalidRevisionId(revision_id=revision, branch=self)
800
return history[:idx+1]
803
def _clone_weave(self, to_location, revision=None, basis_branch=None):
804
assert isinstance(to_location, basestring)
805
if basis_branch is not None:
806
note("basis_branch is not supported for fast weave copy yet.")
808
history = self._get_truncated_history(revision)
809
if not bzrlib.osutils.lexists(to_location):
810
os.mkdir(to_location)
811
branch_to = Branch.initialize(to_location)
812
mutter("copy branch from %s to %s", self, branch_to)
813
branch_to.working_tree().set_root_id(self.get_root_id())
815
self.repository.copy(branch_to.repository)
817
# must be done *after* history is copied across
818
# FIXME duplicate code with base .clone().
819
# .. would template method be useful here. RBC 20051207
820
branch_to.set_parent(self.base)
821
branch_to.append_revision(*history)
822
# circular import protection
823
from bzrlib.merge import build_working_dir
824
build_working_dir(to_location)
828
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
829
if to_branch_type is None:
830
to_branch_type = BzrBranch
832
if to_branch_type == BzrBranch \
833
and self.repository.weave_store.listable() \
834
and self.repository.revision_store.listable():
835
return self._clone_weave(to_location, revision, basis_branch)
837
return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
840
class ScratchBranch(BzrBranch):
841
"""Special test class: a branch that cleans up after itself.
843
>>> b = ScratchBranch()
847
>>> b._transport.__del__()
852
def __init__(self, files=[], dirs=[], transport=None):
853
"""Make a test branch.
855
This creates a temporary directory and runs init-tree in it.
857
If any files are listed, they are created in the working copy.
859
if transport is None:
860
transport = bzrlib.transport.local.ScratchTransport()
861
super(ScratchBranch, self).__init__(transport, init=True)
863
super(ScratchBranch, self).__init__(transport)
865
# BzrBranch creates a clone to .bzr and then forgets about the
866
# original transport. A ScratchTransport() deletes itself and
867
# everything underneath it when it goes away, so we need to
868
# grab a local copy to prevent that from happening
869
self._transport = transport
872
self._transport.mkdir(d)
875
self._transport.put(f, 'content of %s' % f)
879
>>> orig = ScratchBranch(files=["file1", "file2"])
880
>>> os.listdir(orig.base)
881
[u'.bzr', u'file1', u'file2']
882
>>> clone = orig.clone()
883
>>> if os.name != 'nt':
884
... os.path.samefile(orig.base, clone.base)
886
... orig.base == clone.base
889
>>> os.listdir(clone.base)
890
[u'.bzr', u'file1', u'file2']
892
from shutil import copytree
893
from bzrlib.osutils import mkdtemp
896
copytree(self.base, base, symlinks=True)
897
return ScratchBranch(
898
transport=bzrlib.transport.local.ScratchTransport(base))
901
######################################################################
905
def is_control_file(filename):
906
## FIXME: better check
907
filename = normpath(filename)
908
while filename != '':
909
head, tail = os.path.split(filename)
910
## mutter('check %r for control file' % ((head, tail), ))
911
if tail == bzrlib.BZRDIR: