403
396
if revno < 1 or revno > self.revno():
404
397
raise InvalidRevisionNumber(revno)
406
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
407
"""Copy this branch into the existing directory to_location.
409
Returns the newly created branch object.
412
If not None, only revisions up to this point will be copied.
413
The head of the new branch will be that revision. Must be a
416
to_location -- The destination directory; must either exist and be
417
empty, or not exist, in which case it is created.
420
A local branch to copy revisions from, related to this branch.
421
This is used when branching from a remote (slow) branch, and we have
422
a local branch that might contain some relevant revisions.
425
Branch type of destination branch
427
from bzrlib.workingtree import WorkingTree
428
assert isinstance(to_location, basestring)
429
if not bzrlib.osutils.lexists(to_location):
430
os.mkdir(to_location)
431
if to_branch_type is None:
432
to_branch_type = BzrBranch
433
print "FIXME use a branch format here"
434
br_to = to_branch_type.initialize(to_location)
435
mutter("copy branch from %s to %s", self, br_to)
436
if basis_branch is not None:
437
basis_branch.push_stores(br_to)
439
revision = self.last_revision()
440
br_to.update_revisions(self, stop_revision=revision)
441
br_to.set_parent(self.base)
442
WorkingTree.create(br_to, to_location).set_root_id(self.get_root_id())
446
def fileid_involved_between_revs(self, from_revid, to_revid):
447
""" This function returns the file_id(s) involved in the
448
changes between the from_revid revision and the to_revid
451
raise NotImplementedError('fileid_involved_between_revs is abstract')
453
def fileid_involved(self, last_revid=None):
454
""" This function returns the file_id(s) involved in the
455
changes up to the revision last_revid
456
If no parametr is passed, then all file_id[s] present in the
457
repository are returned
459
raise NotImplementedError('fileid_involved is abstract')
461
def fileid_involved_by_set(self, changes):
462
""" This function returns the file_id(s) involved in the
463
changes present in the set 'changes'
465
raise NotImplementedError('fileid_involved_by_set is abstract')
467
def fileid_involved_between_revs(self, from_revid, to_revid):
468
""" This function returns the file_id(s) involved in the
469
changes between the from_revid revision and the to_revid
472
raise NotImplementedError('fileid_involved_between_revs is abstract')
474
def fileid_involved(self, last_revid=None):
475
""" This function returns the file_id(s) involved in the
476
changes up to the revision last_revid
477
If no parametr is passed, then all file_id[s] present in the
478
repository are returned
480
raise NotImplementedError('fileid_involved is abstract')
482
def fileid_involved_by_set(self, changes):
483
""" This function returns the file_id(s) involved in the
484
changes present in the set 'changes'
486
raise NotImplementedError('fileid_involved_by_set is abstract')
488
class BzrBranchFormat(object):
400
def clone(self, *args, **kwargs):
401
"""Clone this branch into to_bzrdir preserving all semantic values.
403
revision_id: if not None, the revision history in the new branch will
404
be truncated to end with revision_id.
406
# for API compatability, until 0.8 releases we provide the old api:
407
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
408
# after 0.8 releases, the *args and **kwargs should be changed:
409
# def clone(self, to_bzrdir, revision_id=None):
410
if (kwargs.get('to_location', None) or
411
kwargs.get('revision', None) or
412
kwargs.get('basis_branch', None) or
413
(len(args) and isinstance(args[0], basestring))):
414
# backwards compatability api:
415
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
416
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
419
basis_branch = args[2]
421
basis_branch = kwargs.get('basis_branch', None)
423
basis = basis_branch.bzrdir
428
revision_id = args[1]
430
revision_id = kwargs.get('revision', None)
435
# no default to raise if not provided.
436
url = kwargs.get('to_location')
437
return self.bzrdir.clone(url,
438
revision_id=revision_id,
439
basis=basis).open_branch()
441
# generate args by hand
443
revision_id = args[1]
445
revision_id = kwargs.get('revision_id', None)
449
# no default to raise if not provided.
450
to_bzrdir = kwargs.get('to_bzrdir')
451
result = self._format.initialize(to_bzrdir)
452
self.copy_content_into(result, revision_id=revision_id)
456
def sprout(self, to_bzrdir, revision_id=None):
457
"""Create a new line of development from the branch, into to_bzrdir.
459
revision_id: if not None, the revision history in the new branch will
460
be truncated to end with revision_id.
462
result = self._format.initialize(to_bzrdir)
463
self.copy_content_into(result, revision_id=revision_id)
464
result.set_parent(self.bzrdir.root_transport.base)
468
def copy_content_into(self, destination, revision_id=None):
469
"""Copy the content of self into destination.
471
revision_id: if not None, the revision history in the new branch will
472
be truncated to end with revision_id.
474
new_history = self.revision_history()
475
if revision_id is not None:
477
new_history = new_history[:new_history.index(revision_id) + 1]
479
rev = self.repository.get_revision(revision_id)
480
new_history = rev.get_history(self.repository)[1:]
481
destination.set_revision_history(new_history)
482
parent = self.get_parent()
484
destination.set_parent(parent)
487
class BranchFormat(object):
489
488
"""An encapsulation of the initialization and open routines for a format.
491
490
Formats provide three things:
503
502
object will be created every time regardless.
505
_default_format = None
506
"""The default format used for new branches."""
507
509
"""The known formats."""
510
def find_format(klass, transport):
511
"""Return the format registered for URL."""
512
def find_format(klass, a_bzrdir):
513
"""Return the format for the branch object in a_bzrdir."""
513
format_string = transport.get(".bzr/branch-format").read()
515
transport = a_bzrdir.get_branch_transport(None)
516
format_string = transport.get("format").read()
514
517
return klass._formats[format_string]
515
518
except NoSuchFile:
516
519
raise NotBranchError(path=transport.base)
518
521
raise errors.UnknownFormatError(format_string)
524
def get_default_format(klass):
525
"""Return the current default format."""
526
return klass._default_format
520
528
def get_format_string(self):
521
529
"""Return the ASCII format string that identifies this format."""
522
530
raise NotImplementedError(self.get_format_string)
524
def _find_modes(self, t):
525
"""Determine the appropriate modes for files and directories.
527
FIXME: When this merges into, or from storage,
528
this code becomes delgatable to a LockableFiles instance.
530
For now its cribbed and returns (dir_mode, file_mode)
534
except errors.TransportNotPossible:
538
dir_mode = st.st_mode & 07777
539
# Remove the sticky and execute bits for files
540
file_mode = dir_mode & ~07111
541
if not BzrBranch._set_dir_mode:
543
if not BzrBranch._set_file_mode:
545
return dir_mode, file_mode
547
def initialize(self, url):
548
"""Create a branch of this format at url and return an open branch."""
549
t = get_transport(url)
550
from bzrlib.weavefile import write_weave_v5
551
from bzrlib.weave import Weave
553
# Create an empty weave
555
bzrlib.weavefile.write_weave_v5(Weave(), sio)
556
empty_weave = sio.getvalue()
558
# Since we don't have a .bzr directory, inherit the
559
# mode from the root directory
560
temp_control = LockableFiles(t, '')
561
temp_control._transport.mkdir('.bzr',
562
mode=temp_control._dir_mode)
563
file_mode = temp_control._file_mode
565
mutter('created control directory in ' + t.base)
566
control = t.clone('.bzr')
567
dirs = ['revision-store', 'weaves']
568
lock_file = 'branch-lock'
569
utf8_files = [('README',
570
"This is a Bazaar-NG control directory.\n"
571
"Do not change any files in this directory.\n"),
572
('branch-format', self.get_format_string()),
573
('revision-history', ''),
576
files = [('inventory.weave', StringIO(empty_weave)),
579
# FIXME: RBC 20060125 dont peek under the covers
580
# NB: no need to escape relative paths that are url safe.
581
control.put(lock_file, StringIO(), mode=file_mode)
582
control_files = LockableFiles(control, lock_file)
583
control_files.lock_write()
584
control_files._transport.mkdir_multi(dirs,
585
mode=control_files._dir_mode)
587
for file, content in utf8_files:
588
control_files.put_utf8(file, content)
589
for file, content in files:
590
control_files.put(file, content)
592
control_files.unlock()
593
return BzrBranch(t, _format=self, _control_files=control_files)
532
def initialize(self, a_bzrdir):
533
"""Create a branch of this format in a_bzrdir."""
534
raise NotImplementedError(self.initialized)
595
536
def is_supported(self):
596
537
"""Is this format supported?
604
def open(self, transport):
605
"""Fill out the data in branch for the branch at url."""
606
return BzrBranch(transport, _format=self)
545
def open(self, a_bzrdir, _found=False):
546
"""Return the branch object for a_bzrdir
548
_found is a private parameter, do not use it. It is used to indicate
549
if format probing has already be done.
551
raise NotImplementedError(self.open)
609
554
def register_format(klass, format):
610
555
klass._formats[format.get_format_string()] = format
558
def set_default_format(klass, format):
559
klass._default_format = format
613
562
def unregister_format(klass, format):
614
563
assert klass._formats[format.get_format_string()] is format
615
564
del klass._formats[format.get_format_string()]
618
class BzrBranchFormat4(BzrBranchFormat):
567
class BzrBranchFormat4(BranchFormat):
619
568
"""Bzr branch format 4.
623
- TextStores for texts, inventories,revisions.
625
This format is deprecated: it indexes texts using a text it which is
626
removed in format 5; write support for this format has been removed.
571
- a revision-history file.
572
- a branch-lock lock file [ to be shared with the bzrdir ]
629
def get_format_string(self):
630
"""See BzrBranchFormat.get_format_string()."""
631
return BZR_BRANCH_FORMAT_4
633
def initialize(self, url):
634
"""Format 4 branches cannot be created."""
635
raise UninitializableFormat(self)
637
def is_supported(self):
638
"""Format 4 is not supported.
640
It is not supported because the model changed from 4 to 5 and the
641
conversion logic is expensive - so doing it on the fly was not
575
def initialize(self, a_bzrdir):
576
"""Create a branch of this format in a_bzrdir."""
577
mutter('creating branch in %s', a_bzrdir.transport.base)
578
branch_transport = a_bzrdir.get_branch_transport(self)
579
utf8_files = [('revision-history', ''),
582
control_files = LockableFiles(branch_transport, 'branch-lock')
583
control_files.lock_write()
585
for file, content in utf8_files:
586
control_files.put_utf8(file, content)
588
control_files.unlock()
589
return self.open(a_bzrdir, _found=True)
592
super(BzrBranchFormat4, self).__init__()
593
self._matchingbzrdir = bzrdir.BzrDirFormat6()
595
def open(self, a_bzrdir, _found=False):
596
"""Return the branch object for a_bzrdir
598
_found is a private parameter, do not use it. It is used to indicate
599
if format probing has already be done.
647
class BzrBranchFormat5(BzrBranchFormat):
602
# we are being called directly and must probe.
603
raise NotImplementedError
604
return BzrBranch(_format=self,
605
_control_files=a_bzrdir._control_files,
607
_repository=a_bzrdir.open_repository())
610
class BzrBranchFormat5(BranchFormat):
648
611
"""Bzr branch format 5.
651
- weaves for file texts and inventory
653
- TextStores for revisions and signatures.
614
- a revision-history file.
617
- works with shared repositories.
656
620
def get_format_string(self):
657
"""See BzrBranchFormat.get_format_string()."""
658
return BZR_BRANCH_FORMAT_5
661
class BzrBranchFormat6(BzrBranchFormat):
662
"""Bzr branch format 6.
621
"""See BranchFormat.get_format_string()."""
622
return "Bazaar-NG branch format 5\n"
624
def initialize(self, a_bzrdir):
625
"""Create a branch of this format in a_bzrdir."""
626
mutter('creating branch in %s', a_bzrdir.transport.base)
627
branch_transport = a_bzrdir.get_branch_transport(self)
629
utf8_files = [('revision-history', ''),
633
branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
634
control_files = LockableFiles(branch_transport, 'lock')
635
control_files.lock_write()
636
control_files.put_utf8('format', self.get_format_string())
638
for file, content in utf8_files:
639
control_files.put_utf8(file, content)
641
control_files.unlock()
642
return self.open(a_bzrdir, _found=True, )
645
super(BzrBranchFormat5, self).__init__()
646
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
648
def open(self, a_bzrdir, _found=False):
649
"""Return the branch object for a_bzrdir
651
_found is a private parameter, do not use it. It is used to indicate
652
if format probing has already be done.
655
format = BranchFormat.find_format(a_bzrdir)
656
assert format.__class__ == self.__class__
657
transport = a_bzrdir.get_branch_transport(None)
658
control_files = LockableFiles(transport, 'lock')
659
return BzrBranch(_format=self,
660
_control_files=control_files,
662
_repository=a_bzrdir.find_repository())
665
class BranchReferenceFormat(BranchFormat):
666
"""Bzr branch reference format.
668
Branch references are used in implementing checkouts, they
669
act as an alias to the real branch which is at some other url.
665
- weaves for file texts and inventory
666
- hash subdirectory based stores.
667
- TextStores for revisions and signatures.
670
676
def get_format_string(self):
671
"""See BzrBranchFormat.get_format_string()."""
672
return BZR_BRANCH_FORMAT_6
675
BzrBranchFormat.register_format(BzrBranchFormat4())
676
BzrBranchFormat.register_format(BzrBranchFormat5())
677
BzrBranchFormat.register_format(BzrBranchFormat6())
679
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
680
# make sure that ancestry.weave is deleted (it is never used, but
681
# used to be created)
677
"""See BranchFormat.get_format_string()."""
678
return "Bazaar-NG Branch Reference Format 1\n"
680
def initialize(self, a_bzrdir, target_branch=None):
681
"""Create a branch of this format in a_bzrdir."""
682
if target_branch is None:
683
# this format does not implement branch itself, thus the implicit
684
# creation contract must see it as uninitializable
685
raise errors.UninitializableFormat(self)
686
mutter('creating branch reference in %s', a_bzrdir.transport.base)
687
branch_transport = a_bzrdir.get_branch_transport(self)
688
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
689
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
690
branch_transport.put('format', StringIO(self.get_format_string()))
691
return self.open(a_bzrdir, _found=True)
694
super(BranchReferenceFormat, self).__init__()
695
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
697
def _make_reference_clone_function(format, a_branch):
698
"""Create a clone() routine for a branch dynamically."""
699
def clone(to_bzrdir, revision_id=None):
700
"""See Branch.clone()."""
701
return format.initialize(to_bzrdir, a_branch)
702
# cannot obey revision_id limits when cloning a reference ...
703
# FIXME RBC 20060210 either nuke revision_id for clone, or
704
# emit some sort of warning/error to the caller ?!
707
def open(self, a_bzrdir, _found=False):
708
"""Return the branch that the branch reference in a_bzrdir points at.
710
_found is a private parameter, do not use it. It is used to indicate
711
if format probing has already be done.
714
format = BranchFormat.find_format(a_bzrdir)
715
assert format.__class__ == self.__class__
716
transport = a_bzrdir.get_branch_transport(None)
717
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
718
result = real_bzrdir.open_branch()
719
# this changes the behaviour of result.clone to create a new reference
720
# rather than a copy of the content of the branch.
721
# I did not use a proxy object because that needs much more extensive
722
# testing, and we are only changing one behaviour at the moment.
723
# If we decide to alter more behaviours - i.e. the implicit nickname
724
# then this should be refactored to introduce a tested proxy branch
725
# and a subclass of that for use in overriding clone() and ....
727
result.clone = self._make_reference_clone_function(result)
731
# formats which have no format string are not discoverable
732
# and not independently creatable, so are not registered.
733
__default_format = BzrBranchFormat5()
734
BranchFormat.register_format(__default_format)
735
BranchFormat.register_format(BranchReferenceFormat())
736
BranchFormat.set_default_format(__default_format)
737
_legacy_formats = [BzrBranchFormat4(),
684
740
class BzrBranch(Branch):
685
741
"""A branch stored in the actual filesystem.
700
755
# This should match a prefix with a function which accepts
701
756
REVISION_NAMESPACES = {}
703
def push_stores(self, branch_to):
704
"""See Branch.push_stores."""
705
if (not isinstance(self._branch_format, BzrBranchFormat4) or
706
self._branch_format != branch_to._branch_format):
707
from bzrlib.fetch import greedy_fetch
708
mutter("Using fetch logic to push between %s(%s) and %s(%s)",
709
self, self._branch_format, branch_to, branch_to._branch_format)
710
greedy_fetch(to_branch=branch_to, from_branch=self,
711
revision=self.last_revision())
714
# format 4 to format 4 logic only.
715
store_pairs = ((self.text_store, branch_to.text_store),
716
(self.inventory_store, branch_to.inventory_store),
717
(self.revision_store, branch_to.revision_store))
719
for from_store, to_store in store_pairs:
720
copy_all(from_store, to_store)
721
except UnlistableStore:
722
raise UnlistableBranch(from_store)
724
def __init__(self, transport, init=DEPRECATED_PARAMETER,
758
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
725
759
relax_version_check=DEPRECATED_PARAMETER, _format=None,
726
_control_files=None):
760
_control_files=None, a_bzrdir=None, _repository=None):
727
761
"""Create new branch object at a particular location.
729
763
transport -- A Transport object, defining how to access files.
966
999
def basis_tree(self):
967
1000
"""See Branch.basis_tree."""
969
revision_id = self.revision_history()[-1]
970
# FIXME: This is an abstraction violation, the basis tree
971
# here as defined is on the working tree, the method should
972
# be too. The basis tree for a branch can be different than
973
# that for a working tree. RBC 20051207
974
xml = self.working_tree().read_basis_inventory(revision_id)
975
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
976
return RevisionTree(self.repository, inv, revision_id)
977
except (IndexError, NoSuchFile, NoWorkingTree), e:
978
return self.repository.revision_tree(self.last_revision())
1001
return self.repository.revision_tree(self.last_revision())
1003
@deprecated_method(zero_eight)
980
1004
def working_tree(self):
981
"""See Branch.working_tree."""
1005
"""Create a Working tree object for this branch."""
982
1006
from bzrlib.workingtree import WorkingTree
983
1007
from bzrlib.transport.local import LocalTransport
984
1008
if (self.base.find('://') != -1 or
985
1009
not isinstance(self._transport, LocalTransport)):
986
1010
raise NoWorkingTree(self.base)
987
return WorkingTree(self.base, branch=self)
1011
return self.bzrdir.open_workingtree()
989
1013
@needs_write_lock
990
def pull(self, source, overwrite=False):
1014
def pull(self, source, overwrite=False, stop_revision=None):
991
1015
"""See Branch.pull."""
992
1016
source.lock_read()
994
1018
old_count = len(self.revision_history())
996
self.update_revisions(source)
1020
self.update_revisions(source,stop_revision)
997
1021
except DivergedBranches:
998
1022
if not overwrite:
1060
1084
history = self._get_truncated_history(revision)
1061
1085
if not bzrlib.osutils.lexists(to_location):
1062
1086
os.mkdir(to_location)
1063
branch_to = Branch.initialize(to_location)
1087
bzrdir_to = self.bzrdir._format.initialize(to_location)
1088
self.repository.clone(bzrdir_to)
1089
branch_to = bzrdir_to.create_branch()
1064
1090
mutter("copy branch from %s to %s", self, branch_to)
1066
self.repository.copy(branch_to.repository)
1068
# must be done *after* history is copied across
1069
1092
# FIXME duplicate code with base .clone().
1070
1093
# .. would template method be useful here? RBC 20051207
1071
1094
branch_to.set_parent(self.base)
1072
1095
branch_to.append_revision(*history)
1073
# FIXME: this should be in workingtree.clone
1074
WorkingTree.create(branch_to, to_location).set_root_id(self.get_root_id())
1096
WorkingTree.create(branch_to, branch_to.base)
1075
1097
mutter("copied")
1076
1098
return branch_to
1078
def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
1079
print "FIXME: clone via create and fetch is probably faster when versioned file comes in."
1080
if to_branch_type is None:
1081
to_branch_type = BzrBranch
1083
if to_branch_type == BzrBranch \
1084
and self.repository.weave_store.listable() \
1085
and self.repository.revision_store.listable():
1086
return self._clone_weave(to_location, revision, basis_branch)
1088
return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
1090
def fileid_involved_between_revs(self, from_revid, to_revid):
1091
"""Find file_id(s) which are involved in the changes between revisions.
1093
This determines the set of revisions which are involved, and then
1094
finds all file ids affected by those revisions.
1096
# TODO: jam 20060119 This code assumes that w.inclusions will
1097
# always be correct. But because of the presence of ghosts
1098
# it is possible to be wrong.
1099
# One specific example from Robert Collins:
1100
# Two branches, with revisions ABC, and AD
1101
# C is a ghost merge of D.
1102
# Inclusions doesn't recognize D as an ancestor.
1103
# If D is ever merged in the future, the weave
1104
# won't be fixed, because AD never saw revision C
1105
# to cause a conflict which would force a reweave.
1106
w = self.repository.get_inventory_weave()
1107
from_set = set(w.inclusions([w.lookup(from_revid)]))
1108
to_set = set(w.inclusions([w.lookup(to_revid)]))
1109
included = to_set.difference(from_set)
1110
changed = map(w.idx_to_name, included)
1111
return self._fileid_involved_by_set(changed)
1113
def fileid_involved(self, last_revid=None):
1114
"""Find all file_ids modified in the ancestry of last_revid.
1116
:param last_revid: If None, last_revision() will be used.
1118
w = self.repository.get_inventory_weave()
1120
changed = set(w._names)
1122
included = w.inclusions([w.lookup(last_revid)])
1123
changed = map(w.idx_to_name, included)
1124
return self._fileid_involved_by_set(changed)
1126
def fileid_involved_by_set(self, changes):
1127
"""Find all file_ids modified by the set of revisions passed in.
1129
:param changes: A set() of revision ids
1131
# TODO: jam 20060119 This line does *nothing*, remove it.
1132
# or better yet, change _fileid_involved_by_set so
1133
# that it takes the inventory weave, rather than
1134
# pulling it out by itself.
1135
w = self.repository.get_inventory_weave()
1136
return self._fileid_involved_by_set(changes)
1138
def _fileid_involved_by_set(self, changes):
1139
"""Find the set of file-ids affected by the set of revisions.
1141
:param changes: A set() of revision ids.
1142
:return: A set() of file ids.
1144
This peaks at the Weave, interpreting each line, looking to
1145
see if it mentions one of the revisions. And if so, includes
1146
the file id mentioned.
1147
This expects both the Weave format, and the serialization
1148
to have a single line per file/directory, and to have
1149
fileid="" and revision="" on that line.
1151
assert (isinstance(self._branch_format, BzrBranchFormat5) or
1152
isinstance(self._branch_format, BzrBranchFormat6)), \
1153
"fileid_involved only supported for branches which store inventory as xml"
1155
w = self.repository.get_inventory_weave()
1157
for line in w._weave:
1159
# it is ugly, but it is due to the weave structure
1160
if not isinstance(line, basestring): continue
1162
start = line.find('file_id="')+9
1163
if start < 9: continue
1164
end = line.find('"', start)
1166
file_id = xml.sax.saxutils.unescape(line[start:end])
1168
# check if file_id is already present
1169
if file_id in file_ids: continue
1171
start = line.find('revision="')+10
1172
if start < 10: continue
1173
end = line.find('"', start)
1175
revision_id = xml.sax.saxutils.unescape(line[start:end])
1177
if revision_id in changes:
1178
file_ids.add(file_id)
1183
Branch.set_default_initializer(BzrBranch._initialize)
1186
1101
class BranchTestProviderAdapter(object):
1187
1102
"""A tool to generate a suite testing multiple branch formats at once.
1200
1115
def adapt(self, test):
1201
1116
result = TestSuite()
1202
for format in self._formats:
1117
for branch_format, bzrdir_format in self._formats:
1203
1118
new_test = deepcopy(test)
1204
1119
new_test.transport_server = self._transport_server
1205
1120
new_test.transport_readonly_server = self._transport_readonly_server
1206
new_test.branch_format = format
1121
new_test.bzrdir_format = bzrdir_format
1122
new_test.branch_format = branch_format
1207
1123
def make_new_test_id():
1208
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1124
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1209
1125
return lambda: new_id
1210
1126
new_test.id = make_new_test_id()
1211
1127
result.addTest(new_test)
1215
class ScratchBranch(BzrBranch):
1216
"""Special test class: a branch that cleans up after itself.
1218
>>> b = ScratchBranch()
1222
>>> b._transport.__del__()
1227
def __init__(self, files=[], dirs=[], transport=None):
1228
"""Make a test branch.
1230
This creates a temporary directory and runs init-tree in it.
1232
If any files are listed, they are created in the working copy.
1234
if transport is None:
1235
transport = bzrlib.transport.local.ScratchTransport()
1236
# local import for scope restriction
1237
from bzrlib.workingtree import WorkingTree
1238
WorkingTree.create_standalone(transport.base)
1239
super(ScratchBranch, self).__init__(transport)
1241
super(ScratchBranch, self).__init__(transport)
1243
# BzrBranch creates a clone to .bzr and then forgets about the
1244
# original transport. A ScratchTransport() deletes itself and
1245
# everything underneath it when it goes away, so we need to
1246
# grab a local copy to prevent that from happening
1247
self._transport = transport
1250
self._transport.mkdir(d)
1253
self._transport.put(f, 'content of %s' % f)
1257
>>> orig = ScratchBranch(files=["file1", "file2"])
1258
>>> os.listdir(orig.base)
1259
[u'.bzr', u'file1', u'file2']
1260
>>> clone = orig.clone()
1261
>>> if os.name != 'nt':
1262
... os.path.samefile(orig.base, clone.base)
1264
... orig.base == clone.base
1267
>>> os.listdir(clone.base)
1268
[u'.bzr', u'file1', u'file2']
1270
from shutil import copytree
1271
from bzrlib.osutils import mkdtemp
1274
copytree(self.base, base, symlinks=True)
1275
return ScratchBranch(
1276
transport=bzrlib.transport.local.ScratchTransport(base))
1279
1131
######################################################################
1283
def is_control_file(filename):
1284
## FIXME: better check
1285
filename = normpath(filename)
1286
while filename != '':
1287
head, tail = os.path.split(filename)
1288
## mutter('check %r for control file' % ((head, tail),))
1289
if tail == bzrlib.BZRDIR:
1291
if filename == head:
1135
@deprecated_function(zero_eight)
1136
def ScratchBranch(*args, **kwargs):
1137
"""See bzrlib.bzrdir.ScratchDir."""
1138
d = ScratchDir(*args, **kwargs)
1139
return d.open_branch()
1142
@deprecated_function(zero_eight)
1143
def is_control_file(*args, **kwargs):
1144
"""See bzrlib.workingtree.is_control_file."""
1145
return bzrlib.workingtree.is_control_file(*args, **kwargs)