35
35
DivergedBranches, LockError, UnlistableStore,
36
36
UnlistableBranch, NoSuchFile)
37
37
from bzrlib.textui import show_status
38
from bzrlib.revision import Revision
38
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
39
40
from bzrlib.delta import compare_trees
40
41
from bzrlib.tree import EmptyTree, RevisionTree
41
42
from bzrlib.inventory import Inventory
43
44
from bzrlib.store.compressed_text import CompressedTextStore
44
45
from bzrlib.store.text import TextStore
45
46
from bzrlib.store.weave import WeaveStore
47
from bzrlib.testament import Testament
46
48
import bzrlib.transactions as transactions
47
49
from bzrlib.transport import Transport, get_transport
64
66
# XXX: leave this here for about one release, then remove it
65
67
raise NotImplementedError('find_branch() is not supported anymore, '
66
68
'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))
94
70
######################################################################
303
280
return self._transport.base
306
base = property(_get_base)
283
base = property(_get_base, doc="The URL for the root of this branch.")
308
285
def _finish_transaction(self):
309
286
"""Exit the current transaction."""
377
354
self._lock_mode = self._lock_count = None
379
356
def abspath(self, name):
380
"""Return absolute filename for something in the branch"""
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.
381
362
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)
390
364
def _rel_controlfilename(self, file_or_path):
391
365
if isinstance(file_or_path, basestring):
392
366
file_or_path = [file_or_path]
750
724
This does not necessarily imply the revision is merge
751
725
or on the mainline."""
752
726
return (revision_id is None
753
or revision_id in self.revision_store)
727
or self.revision_store.has_id(revision_id))
755
729
def get_revision_xml_file(self, revision_id):
756
730
"""Return XML file object for revision object."""
763
return self.revision_store[revision_id]
737
return self.revision_store.get(revision_id)
764
738
except (IndexError, KeyError):
765
739
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]
931
860
"""Return current revision number for this branch.
974
903
Traceback (most recent call last):
975
904
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.
980
906
self_history = self.revision_history()
981
907
self_len = len(self_history)
982
908
other_history = other.revision_history()
997
923
def update_revisions(self, other, stop_revision=None):
998
924
"""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.
999
928
from bzrlib.fetch import greedy_fetch
1000
from bzrlib.revision import get_intervening_revisions
1001
929
if stop_revision is None:
1002
930
stop_revision = other.last_revision()
931
### Should this be checking is_ancestor instead of revision_history?
1003
932
if (stop_revision is not None and
1004
933
stop_revision in self.revision_history()):
1006
935
greedy_fetch(to_branch=self, from_branch=other,
1007
936
revision=stop_revision)
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])
937
pullable_revs = self.pullable_revisions(other, stop_revision)
938
if len(pullable_revs) > 0:
1014
939
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):
1017
957
def commit(self, *args, **kw):
1018
958
from bzrlib.commit import Commit
1019
959
Commit().commit(self, *args, **kw)
1051
991
inv = self.get_revision_inventory(revision_id)
1052
992
return RevisionTree(self.weave_store, inv, revision_id)
1055
994
def working_tree(self):
1056
995
"""Return a `Tree` for the working copy."""
1057
996
from bzrlib.workingtree import WorkingTree
1060
999
# much more complex to keep consistent than our careful .bzr subset.
1061
1000
# instead, we should say that working trees are local only, and optimise
1063
return WorkingTree(self._transport.base, self.read_working_inventory())
1002
return WorkingTree(self.base, branch=self)
1066
1005
def basis_tree(self):
1318
1257
if revno < 1 or revno > self.revno():
1319
1258
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)),
1325
1273
class ScratchBranch(_Branch):
1329
1277
>>> isdir(b.base)
1331
1279
>>> bd = b.base
1280
>>> b._transport.__del__()
1336
def __init__(self, files=[], dirs=[], base=None):
1285
def __init__(self, files=[], dirs=[], transport=None):
1337
1286
"""Make a test branch.
1339
1288
This creates a temporary directory and runs init-tree in it.
1341
1290
If any files are listed, they are created in the working copy.
1343
from tempfile import mkdtemp
1348
if isinstance(base, basestring):
1349
base = get_transport(base)
1350
_Branch.__init__(self, base, init=init)
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)
1352
1299
self._transport.mkdir(d)
1373
1320
base = mkdtemp()
1375
1322
copytree(self.base, base, symlinks=True)
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
1323
return ScratchBranch(
1324
transport=bzrlib.transport.local.ScratchTransport(base))
1400
1327
######################################################################