35
35
DivergedBranches, LockError, UnlistableStore,
36
36
UnlistableBranch, NoSuchFile)
37
37
from bzrlib.textui import show_status
38
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
38
from bzrlib.revision import Revision
40
39
from bzrlib.delta import compare_trees
41
40
from bzrlib.tree import EmptyTree, RevisionTree
42
41
from bzrlib.inventory import Inventory
44
43
from bzrlib.store.compressed_text import CompressedTextStore
45
44
from bzrlib.store.text import TextStore
46
45
from bzrlib.store.weave import WeaveStore
47
from bzrlib.testament import Testament
48
46
import bzrlib.transactions as transactions
49
47
from bzrlib.transport import Transport, get_transport
66
64
# XXX: leave this here for about one release, then remove it
67
65
raise NotImplementedError('find_branch() is not supported anymore, '
68
66
'please use one of the new branch constructors')
67
def _relpath(base, path):
68
"""Return path relative to base, or raise exception.
70
The path may be either an absolute path or a path relative to the
71
current working directory.
73
Lifted out of Branch.relpath for ease of testing.
75
os.path.commonprefix (python2.4) has a bad bug that it works just
76
on string prefixes, assuming that '/u' is a prefix of '/u2'. This
77
avoids that problem."""
78
rp = os.path.abspath(path)
82
while len(head) >= len(base):
85
head, tail = os.path.split(head)
89
raise NotBranchError("path %r is not within branch %r" % (rp, base))
70
94
######################################################################
280
303
return self._transport.base
283
base = property(_get_base, doc="The URL for the root of this branch.")
306
base = property(_get_base)
285
308
def _finish_transaction(self):
286
309
"""Exit the current transaction."""
354
377
self._lock_mode = self._lock_count = None
356
379
def abspath(self, name):
357
"""Return absolute filename for something in the branch
359
XXX: Robert Collins 20051017 what is this used for? why is it a branch
360
method and not a tree method.
380
"""Return absolute filename for something in the branch"""
362
381
return self._transport.abspath(name)
383
def relpath(self, path):
384
"""Return path relative to this branch of something inside it.
386
Raises an error if path is not in this branch."""
387
return self._transport.relpath(path)
364
390
def _rel_controlfilename(self, file_or_path):
365
391
if isinstance(file_or_path, basestring):
366
392
file_or_path = [file_or_path]
724
750
This does not necessarily imply the revision is merge
725
751
or on the mainline."""
726
752
return (revision_id is None
727
or self.revision_store.has_id(revision_id))
753
or revision_id in self.revision_store)
729
755
def get_revision_xml_file(self, revision_id):
730
756
"""Return XML file object for revision object."""
737
return self.revision_store.get(revision_id)
763
return self.revision_store[revision_id]
738
764
except (IndexError, KeyError):
739
765
raise bzrlib.errors.NoSuchRevision(self, revision_id)
885
def common_ancestor(self, other, self_revno=None, other_revno=None):
887
>>> from bzrlib.commit import commit
888
>>> sb = ScratchBranch(files=['foo', 'foo~'])
889
>>> sb.common_ancestor(sb) == (None, None)
891
>>> commit(sb, "Committing first revision", verbose=False)
892
>>> sb.common_ancestor(sb)[0]
894
>>> clone = sb.clone()
895
>>> commit(sb, "Committing second revision", verbose=False)
896
>>> sb.common_ancestor(sb)[0]
898
>>> sb.common_ancestor(clone)[0]
900
>>> commit(clone, "Committing divergent second revision",
902
>>> sb.common_ancestor(clone)[0]
904
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
906
>>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
908
>>> clone2 = sb.clone()
909
>>> sb.common_ancestor(clone2)[0]
911
>>> sb.common_ancestor(clone2, self_revno=1)[0]
913
>>> sb.common_ancestor(clone2, other_revno=1)[0]
916
my_history = self.revision_history()
917
other_history = other.revision_history()
918
if self_revno is None:
919
self_revno = len(my_history)
920
if other_revno is None:
921
other_revno = len(other_history)
922
indices = range(min((self_revno, other_revno)))
925
if my_history[r] == other_history[r]:
926
return r+1, my_history[r]
860
931
"""Return current revision number for this branch.
903
974
Traceback (most recent call last):
904
975
DivergedBranches: These branches have diverged.
977
# FIXME: If the branches have diverged, but the latest
978
# revision in this branch is completely merged into the other,
979
# then we should still be able to pull.
906
980
self_history = self.revision_history()
907
981
self_len = len(self_history)
908
982
other_history = other.revision_history()
923
997
def update_revisions(self, other, stop_revision=None):
924
998
"""Pull in new perfect-fit revisions."""
925
# FIXME: If the branches have diverged, but the latest
926
# revision in this branch is completely merged into the other,
927
# then we should still be able to pull.
928
999
from bzrlib.fetch import greedy_fetch
1000
from bzrlib.revision import get_intervening_revisions
929
1001
if stop_revision is None:
930
1002
stop_revision = other.last_revision()
931
### Should this be checking is_ancestor instead of revision_history?
932
1003
if (stop_revision is not None and
933
1004
stop_revision in self.revision_history()):
935
1006
greedy_fetch(to_branch=self, from_branch=other,
936
1007
revision=stop_revision)
937
pullable_revs = self.pullable_revisions(other, stop_revision)
938
if len(pullable_revs) > 0:
1008
pullable_revs = self.missing_revisions(
1009
other, other.revision_id_to_revno(stop_revision))
1011
greedy_fetch(to_branch=self,
1013
revision=pullable_revs[-1])
939
1014
self.append_revision(*pullable_revs)
941
def pullable_revisions(self, other, stop_revision):
942
other_revno = other.revision_id_to_revno(stop_revision)
944
return self.missing_revisions(other, other_revno)
945
except DivergedBranches, e:
947
pullable_revs = get_intervening_revisions(self.last_revision(),
949
assert self.last_revision() not in pullable_revs
951
except bzrlib.errors.NotAncestor:
952
if is_ancestor(self.last_revision(), stop_revision, self):
957
1017
def commit(self, *args, **kw):
958
1018
from bzrlib.commit import Commit
959
1019
Commit().commit(self, *args, **kw)
991
1051
inv = self.get_revision_inventory(revision_id)
992
1052
return RevisionTree(self.weave_store, inv, revision_id)
994
1055
def working_tree(self):
995
1056
"""Return a `Tree` for the working copy."""
996
1057
from bzrlib.workingtree import WorkingTree
999
1060
# much more complex to keep consistent than our careful .bzr subset.
1000
1061
# instead, we should say that working trees are local only, and optimise
1002
return WorkingTree(self.base, branch=self)
1063
return WorkingTree(self._transport.base, self.read_working_inventory())
1005
1066
def basis_tree(self):
1257
1318
if revno < 1 or revno > self.revno():
1258
1319
raise InvalidRevisionNumber(revno)
1260
def sign_revision(self, revision_id, gpg_strategy):
1261
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1262
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1264
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1267
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1273
1325
class ScratchBranch(_Branch):
1277
1329
>>> isdir(b.base)
1279
1331
>>> bd = b.base
1280
>>> b._transport.__del__()
1285
def __init__(self, files=[], dirs=[], transport=None):
1336
def __init__(self, files=[], dirs=[], base=None):
1286
1337
"""Make a test branch.
1288
1339
This creates a temporary directory and runs init-tree in it.
1290
1341
If any files are listed, they are created in the working copy.
1292
if transport is None:
1293
transport = bzrlib.transport.local.ScratchTransport()
1294
super(ScratchBranch, self).__init__(transport, init=True)
1296
super(ScratchBranch, self).__init__(transport)
1343
from tempfile import mkdtemp
1348
if isinstance(base, basestring):
1349
base = get_transport(base)
1350
_Branch.__init__(self, base, init=init)
1299
1352
self._transport.mkdir(d)
1320
1373
base = mkdtemp()
1322
1375
copytree(self.base, base, symlinks=True)
1323
return ScratchBranch(
1324
transport=bzrlib.transport.local.ScratchTransport(base))
1376
return ScratchBranch(base=base)
1382
"""Destroy the test branch, removing the scratch directory."""
1383
from shutil import rmtree
1386
mutter("delete ScratchBranch %s" % self.base)
1389
# Work around for shutil.rmtree failing on Windows when
1390
# readonly files are encountered
1391
mutter("hit exception in destroying ScratchBranch: %s" % e)
1392
for root, dirs, files in os.walk(self.base, topdown=False):
1394
os.chmod(os.path.join(root, name), 0700)
1396
self._transport = None
1327
1400
######################################################################