262
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
281
def missing_revisions(self, other, stop_revision=None):
263
282
"""Return a list of new revisions that would perfectly fit.
265
284
If self and other have not diverged, return a list of the revisions
266
285
present in other, but missing from self.
287
>>> from bzrlib.workingtree import WorkingTree
268
288
>>> bzrlib.trace.silent = True
269
>>> br1 = ScratchBranch()
270
>>> br2 = ScratchBranch()
289
>>> d1 = bzrdir.ScratchDir()
290
>>> br1 = d1.open_branch()
291
>>> wt1 = d1.open_workingtree()
292
>>> d2 = bzrdir.ScratchDir()
293
>>> br2 = d2.open_branch()
294
>>> wt2 = d2.open_workingtree()
271
295
>>> br1.missing_revisions(br2)
273
>>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-1")
297
>>> wt2.commit("lala!", rev_id="REVISION-ID-1")
274
298
>>> br1.missing_revisions(br2)
275
299
[u'REVISION-ID-1']
276
300
>>> br2.missing_revisions(br1)
278
>>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-1")
302
>>> wt1.commit("lala!", rev_id="REVISION-ID-1")
279
303
>>> br1.missing_revisions(br2)
281
>>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-2A")
305
>>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
282
306
>>> br1.missing_revisions(br2)
283
307
[u'REVISION-ID-2A']
284
>>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-2B")
308
>>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
285
309
>>> br1.missing_revisions(br2)
286
310
Traceback (most recent call last):
287
311
DivergedBranches: These branches have diverged. Try merge.
403
436
if revno < 1 or revno > self.revno():
404
437
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):
440
def clone(self, *args, **kwargs):
441
"""Clone this branch into to_bzrdir preserving all semantic values.
443
revision_id: if not None, the revision history in the new branch will
444
be truncated to end with revision_id.
446
# for API compatability, until 0.8 releases we provide the old api:
447
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
448
# after 0.8 releases, the *args and **kwargs should be changed:
449
# def clone(self, to_bzrdir, revision_id=None):
450
if (kwargs.get('to_location', None) or
451
kwargs.get('revision', None) or
452
kwargs.get('basis_branch', None) or
453
(len(args) and isinstance(args[0], basestring))):
454
# backwards compatability api:
455
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
456
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
459
basis_branch = args[2]
461
basis_branch = kwargs.get('basis_branch', None)
463
basis = basis_branch.bzrdir
468
revision_id = args[1]
470
revision_id = kwargs.get('revision', None)
475
# no default to raise if not provided.
476
url = kwargs.get('to_location')
477
return self.bzrdir.clone(url,
478
revision_id=revision_id,
479
basis=basis).open_branch()
481
# generate args by hand
483
revision_id = args[1]
485
revision_id = kwargs.get('revision_id', None)
489
# no default to raise if not provided.
490
to_bzrdir = kwargs.get('to_bzrdir')
491
result = self._format.initialize(to_bzrdir)
492
self.copy_content_into(result, revision_id=revision_id)
496
def sprout(self, to_bzrdir, revision_id=None):
497
"""Create a new line of development from the branch, into to_bzrdir.
499
revision_id: if not None, the revision history in the new branch will
500
be truncated to end with revision_id.
502
result = self._format.initialize(to_bzrdir)
503
self.copy_content_into(result, revision_id=revision_id)
504
result.set_parent(self.bzrdir.root_transport.base)
508
def copy_content_into(self, destination, revision_id=None):
509
"""Copy the content of self into destination.
511
revision_id: if not None, the revision history in the new branch will
512
be truncated to end with revision_id.
514
new_history = self.revision_history()
515
if revision_id is not None:
517
new_history = new_history[:new_history.index(revision_id) + 1]
519
rev = self.repository.get_revision(revision_id)
520
new_history = rev.get_history(self.repository)[1:]
521
destination.set_revision_history(new_history)
522
parent = self.get_parent()
524
destination.set_parent(parent)
527
class BranchFormat(object):
489
528
"""An encapsulation of the initialization and open routines for a format.
491
530
Formats provide three things:
503
542
object will be created every time regardless.
545
_default_format = None
546
"""The default format used for new branches."""
507
549
"""The known formats."""
510
def find_format(klass, transport):
511
"""Return the format registered for URL."""
552
def find_format(klass, a_bzrdir):
553
"""Return the format for the branch object in a_bzrdir."""
513
format_string = transport.get(".bzr/branch-format").read()
555
transport = a_bzrdir.get_branch_transport(None)
556
format_string = transport.get("format").read()
514
557
return klass._formats[format_string]
515
558
except NoSuchFile:
516
559
raise NotBranchError(path=transport.base)
518
561
raise errors.UnknownFormatError(format_string)
564
def get_default_format(klass):
565
"""Return the current default format."""
566
return klass._default_format
520
568
def get_format_string(self):
521
569
"""Return the ASCII format string that identifies this format."""
522
570
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)
572
def initialize(self, a_bzrdir):
573
"""Create a branch of this format in a_bzrdir."""
574
raise NotImplementedError(self.initialized)
595
576
def is_supported(self):
596
577
"""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)
585
def open(self, a_bzrdir, _found=False):
586
"""Return the branch object for a_bzrdir
588
_found is a private parameter, do not use it. It is used to indicate
589
if format probing has already be done.
591
raise NotImplementedError(self.open)
609
594
def register_format(klass, format):
610
595
klass._formats[format.get_format_string()] = format
598
def set_default_format(klass, format):
599
klass._default_format = format
613
602
def unregister_format(klass, format):
614
603
assert klass._formats[format.get_format_string()] is format
615
604
del klass._formats[format.get_format_string()]
618
class BzrBranchFormat4(BzrBranchFormat):
607
class BzrBranchFormat4(BranchFormat):
619
608
"""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.
611
- a revision-history file.
612
- 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
615
def initialize(self, a_bzrdir):
616
"""Create a branch of this format in a_bzrdir."""
617
mutter('creating branch in %s', a_bzrdir.transport.base)
618
branch_transport = a_bzrdir.get_branch_transport(self)
619
utf8_files = [('revision-history', ''),
622
control_files = LockableFiles(branch_transport, 'branch-lock',
624
control_files.create_lock()
625
control_files.lock_write()
627
for file, content in utf8_files:
628
control_files.put_utf8(file, content)
630
control_files.unlock()
631
return self.open(a_bzrdir, _found=True)
634
super(BzrBranchFormat4, self).__init__()
635
self._matchingbzrdir = bzrdir.BzrDirFormat6()
637
def open(self, a_bzrdir, _found=False):
638
"""Return the branch object for a_bzrdir
640
_found is a private parameter, do not use it. It is used to indicate
641
if format probing has already be done.
647
class BzrBranchFormat5(BzrBranchFormat):
644
# we are being called directly and must probe.
645
raise NotImplementedError
646
return BzrBranch(_format=self,
647
_control_files=a_bzrdir._control_files,
649
_repository=a_bzrdir.open_repository())
652
class BzrBranchFormat5(BranchFormat):
648
653
"""Bzr branch format 5.
651
- weaves for file texts and inventory
653
- TextStores for revisions and signatures.
656
- a revision-history file.
659
- works with shared repositories.
656
662
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.
663
"""See BranchFormat.get_format_string()."""
664
return "Bazaar-NG branch format 5\n"
666
def initialize(self, a_bzrdir):
667
"""Create a branch of this format in a_bzrdir."""
668
mutter('creating branch in %s', a_bzrdir.transport.base)
669
branch_transport = a_bzrdir.get_branch_transport(self)
671
utf8_files = [('revision-history', ''),
674
control_files = LockableFiles(branch_transport, 'lock', TransportLock)
675
control_files.create_lock()
676
control_files.lock_write()
677
control_files.put_utf8('format', self.get_format_string())
679
for file, content in utf8_files:
680
control_files.put_utf8(file, content)
682
control_files.unlock()
683
return self.open(a_bzrdir, _found=True, )
686
super(BzrBranchFormat5, self).__init__()
687
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
689
def open(self, a_bzrdir, _found=False):
690
"""Return the branch object for a_bzrdir
692
_found is a private parameter, do not use it. It is used to indicate
693
if format probing has already be done.
696
format = BranchFormat.find_format(a_bzrdir)
697
assert format.__class__ == self.__class__
698
transport = a_bzrdir.get_branch_transport(None)
699
control_files = LockableFiles(transport, 'lock', TransportLock)
700
return BzrBranch5(_format=self,
701
_control_files=control_files,
703
_repository=a_bzrdir.find_repository())
706
return "Bazaar-NG Metadir branch format 5"
709
class BranchReferenceFormat(BranchFormat):
710
"""Bzr branch reference format.
712
Branch references are used in implementing checkouts, they
713
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
720
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)
721
"""See BranchFormat.get_format_string()."""
722
return "Bazaar-NG Branch Reference Format 1\n"
724
def initialize(self, a_bzrdir, target_branch=None):
725
"""Create a branch of this format in a_bzrdir."""
726
if target_branch is None:
727
# this format does not implement branch itself, thus the implicit
728
# creation contract must see it as uninitializable
729
raise errors.UninitializableFormat(self)
730
mutter('creating branch reference in %s', a_bzrdir.transport.base)
731
branch_transport = a_bzrdir.get_branch_transport(self)
732
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
733
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
734
branch_transport.put('format', StringIO(self.get_format_string()))
735
return self.open(a_bzrdir, _found=True)
738
super(BranchReferenceFormat, self).__init__()
739
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
741
def _make_reference_clone_function(format, a_branch):
742
"""Create a clone() routine for a branch dynamically."""
743
def clone(to_bzrdir, revision_id=None):
744
"""See Branch.clone()."""
745
return format.initialize(to_bzrdir, a_branch)
746
# cannot obey revision_id limits when cloning a reference ...
747
# FIXME RBC 20060210 either nuke revision_id for clone, or
748
# emit some sort of warning/error to the caller ?!
751
def open(self, a_bzrdir, _found=False):
752
"""Return the branch that the branch reference in a_bzrdir points at.
754
_found is a private parameter, do not use it. It is used to indicate
755
if format probing has already be done.
758
format = BranchFormat.find_format(a_bzrdir)
759
assert format.__class__ == self.__class__
760
transport = a_bzrdir.get_branch_transport(None)
761
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
762
result = real_bzrdir.open_branch()
763
# this changes the behaviour of result.clone to create a new reference
764
# rather than a copy of the content of the branch.
765
# I did not use a proxy object because that needs much more extensive
766
# testing, and we are only changing one behaviour at the moment.
767
# If we decide to alter more behaviours - i.e. the implicit nickname
768
# then this should be refactored to introduce a tested proxy branch
769
# and a subclass of that for use in overriding clone() and ....
771
result.clone = self._make_reference_clone_function(result)
775
# formats which have no format string are not discoverable
776
# and not independently creatable, so are not registered.
777
__default_format = BzrBranchFormat5()
778
BranchFormat.register_format(__default_format)
779
BranchFormat.register_format(BranchReferenceFormat())
780
BranchFormat.set_default_format(__default_format)
781
_legacy_formats = [BzrBranchFormat4(),
684
784
class BzrBranch(Branch):
685
785
"""A branch stored in the actual filesystem.
687
787
Note that it's "local" in the context of the filesystem; it doesn't
688
788
really matter if it's on an nfs/smb/afs/coda/... share, as long as
689
789
it's writable, and can be accessed via the normal filesystem API.
692
# We actually expect this class to be somewhat short-lived; part of its
693
# purpose is to try to isolate what bits of the branch logic are tied to
694
# filesystem access, so that in a later step, we can extricate them to
695
# a separarte ("storage") class.
696
_inventory_weave = None
698
# Map some sort of prefix into a namespace
699
# stuff like "revno:10", "revid:", etc.
700
# This should match a prefix with a function which accepts
701
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,
792
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
725
793
relax_version_check=DEPRECATED_PARAMETER, _format=None,
726
_control_files=None):
794
_control_files=None, a_bzrdir=None, _repository=None):
727
795
"""Create new branch object at a particular location.
729
797
transport -- A Transport object, defining how to access files.
1039
1096
def tree_config(self):
1040
1097
return TreeConfig(self)
1042
def _get_truncated_history(self, revision_id):
1043
history = self.revision_history()
1044
if revision_id is None:
1100
class BzrBranch5(BzrBranch):
1101
"""A format 5 branch. This supports new features over plan branches.
1103
It has support for a master_branch which is the data for bound branches.
1111
super(BzrBranch5, self).__init__(_format=_format,
1112
_control_files=_control_files,
1114
_repository=_repository)
1117
def pull(self, source, overwrite=False, stop_revision=None):
1118
"""Updates branch.pull to be bound branch aware."""
1119
bound_location = self.get_bound_location()
1120
if source.base != bound_location:
1121
# not pulling from master, so we need to update master.
1122
master_branch = self.get_master_branch()
1124
master_branch.pull(source)
1125
source = master_branch
1126
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1128
def get_bound_location(self):
1047
idx = history.index(revision_id)
1049
raise InvalidRevisionId(revision_id=revision, branch=self)
1050
return history[:idx+1]
1130
return self.control_files.get_utf8('bound').read()[:-1]
1131
except errors.NoSuchFile:
1052
1134
@needs_read_lock
1053
def _clone_weave(self, to_location, revision=None, basis_branch=None):
1055
from bzrlib.workingtree import WorkingTree
1056
assert isinstance(to_location, basestring)
1057
if basis_branch is not None:
1058
note("basis_branch is not supported for fast weave copy yet.")
1060
history = self._get_truncated_history(revision)
1061
if not bzrlib.osutils.lexists(to_location):
1062
os.mkdir(to_location)
1063
branch_to = Branch.initialize(to_location)
1064
mutter("copy branch from %s to %s", self, branch_to)
1066
self.repository.copy(branch_to.repository)
1135
def get_master_branch(self):
1136
"""Return the branch we are bound to.
1068
# must be done *after* history is copied across
1069
# FIXME duplicate code with base .clone().
1070
# .. would template method be useful here? RBC 20051207
1071
branch_to.set_parent(self.base)
1072
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())
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)
1138
:return: Either a Branch, or None
1140
This could memoise the branch, but if thats done
1141
it must be revalidated on each new lock.
1142
So for now we just dont memoise it.
1143
# RBC 20060304 review this decision.
1145
bound_loc = self.get_bound_location()
1149
return Branch.open(bound_loc)
1150
except (errors.NotBranchError, errors.ConnectionError), e:
1151
raise errors.BoundBranchConnectionFailure(
1155
def set_bound_location(self, location):
1156
"""Set the target where this branch is bound to.
1158
:param location: URL to the target branch
1161
self.control_files.put_utf8('bound', location+'\n')
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
1164
self.control_files._transport.delete('bound')
1170
def bind(self, other):
1171
"""Bind the local branch the other branch.
1173
:param other: The branch to bind to
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.
1176
# TODO: jam 20051230 Consider checking if the target is bound
1177
# It is debatable whether you should be able to bind to
1178
# a branch which is itself bound.
1179
# Committing is obviously forbidden,
1180
# but binding itself may not be.
1181
# Since we *have* to check at commit time, we don't
1182
# *need* to check here
1185
# we are now equal to or a suffix of other.
1187
# Since we have 'pulled' from the remote location,
1188
# now we should try to pull in the opposite direction
1189
# in case the local tree has more revisions than the
1191
# There may be a different check you could do here
1192
# rather than actually trying to install revisions remotely.
1193
# TODO: capture an exception which indicates the remote branch
1195
# If it is up-to-date, this probably should not be a failure
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.
1197
# lock other for write so the revision-history syncing cannot race
1201
# if this does not error, other now has the same last rev we do
1202
# it can only error if the pull from other was concurrent with
1203
# a commit to other from someone else.
1205
# until we ditch revision-history, we need to sync them up:
1206
self.set_revision_history(other.revision_history())
1207
# now other and self are up to date with each other and have the
1208
# same revision-history.
1212
self.set_bound_location(other.base)
1216
"""If bound, unbind"""
1217
return self.set_bound_location(None)
1221
"""Synchronise this branch with the master branch if any.
1223
:return: None or the last_revision that was pivoted out during the
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)
1226
master = self.get_master_branch()
1227
if master is not None:
1228
old_tip = self.last_revision()
1229
self.pull(master, overwrite=True)
1230
if old_tip in self.repository.get_ancestry(self.last_revision()):
1186
1236
class BranchTestProviderAdapter(object):
1200
1250
def adapt(self, test):
1201
1251
result = TestSuite()
1202
for format in self._formats:
1252
for branch_format, bzrdir_format in self._formats:
1203
1253
new_test = deepcopy(test)
1204
1254
new_test.transport_server = self._transport_server
1205
1255
new_test.transport_readonly_server = self._transport_readonly_server
1206
new_test.branch_format = format
1256
new_test.bzrdir_format = bzrdir_format
1257
new_test.branch_format = branch_format
1207
1258
def make_new_test_id():
1208
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1259
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1209
1260
return lambda: new_id
1210
1261
new_test.id = make_new_test_id()
1211
1262
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
1266
######################################################################
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:
1270
@deprecated_function(zero_eight)
1271
def ScratchBranch(*args, **kwargs):
1272
"""See bzrlib.bzrdir.ScratchDir."""
1273
d = ScratchDir(*args, **kwargs)
1274
return d.open_branch()
1277
@deprecated_function(zero_eight)
1278
def is_control_file(*args, **kwargs):
1279
"""See bzrlib.workingtree.is_control_file."""
1280
return bzrlib.workingtree.is_control_file(*args, **kwargs)