20
from cStringIO import StringIO
22
23
from bzrlib.trace import mutter, note
23
24
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
25
26
sha_file, appendpath, file_kind
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
28
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
29
NoSuchRevision, HistoryMissing, NotBranchError,
28
31
from bzrlib.textui import show_status
29
from bzrlib.revision import Revision
30
from bzrlib.xml import unpack_xml
32
from bzrlib.revision import Revision, validate_revision_id
31
33
from bzrlib.delta import compare_trees
32
34
from bzrlib.tree import EmptyTree, RevisionTree
34
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
from bzrlib.inventory import Inventory
36
from bzrlib.weavestore import WeaveStore
37
from bzrlib.store import ImmutableStore
42
INVENTORY_FILEID = '__inventory'
43
ANCESTRY_FILEID = '__ancestry'
46
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
47
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
35
48
## TODO: Maybe include checks for common corruption of newlines, etc?
38
51
# TODO: Some operations like log might retrieve the same revisions
39
52
# repeatedly to calculate deltas. We could perhaps have a weakref
40
# cache in memory to make this faster.
53
# cache in memory to make this faster. In general anything can be
54
# cached in memory between lock and unlock operations.
56
# TODO: please move the revision-string syntax stuff out of the branch
57
# object; it's clutter
43
60
def find_branch(f, **args):
184
210
self.base = os.path.realpath(base)
185
211
if not isdir(self.controlfilename('.')):
186
from errors import NotBranchError
187
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
188
['use "bzr init" to initialize a new working tree',
189
'current bzr can only operate from top-of-tree'])
192
self.text_store = ImmutableStore(self.controlfilename('text-store'))
193
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
194
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
212
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
213
['use "bzr init" to initialize a '
216
self._check_format(relax_version_check)
217
if self._branch_format == 4:
218
self.inventory_store = \
219
ImmutableStore(self.controlfilename('inventory-store'))
221
ImmutableStore(self.controlfilename('text-store'))
222
self.weave_store = WeaveStore(self.controlfilename('weaves'))
223
self.revision_store = \
224
ImmutableStore(self.controlfilename('revision-store'))
197
227
def __str__(self):
297
318
raise BzrError("invalid controlfile mode %r" % mode)
301
320
def _make_control(self):
302
from bzrlib.inventory import Inventory
303
from bzrlib.xml import pack_xml
305
321
os.mkdir(self.controlfilename([]))
306
322
self.controlfile('README', 'w').write(
307
323
"This is a Bazaar-NG control directory.\n"
308
324
"Do not change any files in this directory.\n")
309
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
310
for d in ('text-store', 'inventory-store', 'revision-store'):
325
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
326
for d in ('text-store', 'revision-store',
311
328
os.mkdir(self.controlfilename(d))
312
329
for f in ('revision-history', 'merged-patches',
313
330
'pending-merged-patches', 'branch-name',
316
333
self.controlfile(f, 'w').write('')
317
334
mutter('created control directory in ' + self.base)
319
pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
322
def _check_format(self):
336
# if we want per-tree root ids then this is the place to set
337
# them; they're not needed for now and so ommitted for
339
f = self.controlfile('inventory','w')
340
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
344
def _check_format(self, relax_version_check):
323
345
"""Check this branch format is supported.
325
The current tool only supports the current unstable format.
347
The format level is stored, as an integer, in
348
self._branch_format for code that needs to check it later.
327
350
In the future, we might need different in-memory Branch
328
351
classes to support downlevel branches. But not yet.
330
# This ignores newlines so that we can open branches created
331
# on Windows from Linux and so on. I think it might be better
332
# to always make all internal files in unix format.
333
353
fmt = self.controlfile('branch-format', 'r').read()
334
fmt.replace('\r\n', '')
335
if fmt != BZR_BRANCH_FORMAT:
336
raise BzrError('sorry, branch format %r not supported' % fmt,
337
['use a different bzr version',
338
'or remove the .bzr directory and "bzr init" again'])
354
if fmt == BZR_BRANCH_FORMAT_5:
355
self._branch_format = 5
356
elif fmt == BZR_BRANCH_FORMAT_4:
357
self._branch_format = 4
359
if (not relax_version_check
360
and self._branch_format != 5):
361
raise BzrError('sorry, branch format "%s" not supported; '
362
'use a different bzr version, '
363
'or run "bzr upgrade"'
364
% fmt.rstrip('\n\r'))
340
367
def get_root_id(self):
341
368
"""Return the id of this branches root"""
595
615
return self.revision_store[revision_id]
596
616
except IndexError:
597
raise bzrlib.errors.NoSuchRevision(revision_id)
617
raise bzrlib.errors.NoSuchRevision(self, revision_id)
622
def get_revision_xml(self, revision_id):
623
return self.get_revision_xml_file(revision_id).read()
602
626
def get_revision(self, revision_id):
603
627
"""Return the Revision object for a named revision"""
604
xml_file = self.get_revision_xml(revision_id)
628
xml_file = self.get_revision_xml_file(revision_id)
607
r = unpack_xml(Revision, xml_file)
631
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
608
632
except SyntaxError, e:
609
633
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
640
664
def get_revision_sha1(self, revision_id):
641
665
"""Hash the stored value of a revision, and return it."""
642
# In the future, revision entries will be signed. At that
643
# point, it is probably best *not* to include the signature
644
# in the revision hash. Because that lets you re-sign
645
# the revision, (add signatures/remove signatures) and still
646
# have all hash pointers stay consistent.
647
# But for now, just hash the contents.
648
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
651
def get_inventory(self, inventory_id):
652
"""Get Inventory object by hash.
654
TODO: Perhaps for this and similar methods, take a revision
655
parameter which can be either an integer revno or a
657
from bzrlib.inventory import Inventory
658
from bzrlib.xml import unpack_xml
660
return unpack_xml(Inventory, self.inventory_store[inventory_id])
663
def get_inventory_sha1(self, inventory_id):
666
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
669
def get_ancestry(self, revision_id):
670
"""Return a list of revision-ids integrated by a revision.
672
w = self.weave_store.get_weave(ANCESTRY_FILEID)
674
return [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
677
def get_inventory_weave(self):
678
return self.weave_store.get_weave(INVENTORY_FILEID)
681
def get_inventory(self, revision_id):
682
"""Get Inventory object by hash."""
683
# FIXME: The text gets passed around a lot coming from the weave.
684
f = StringIO(self.get_inventory_xml(revision_id))
685
return bzrlib.xml5.serializer_v5.read_inventory(f)
688
def get_inventory_xml(self, revision_id):
689
"""Get inventory XML as a file object."""
691
assert isinstance(revision_id, basestring), type(revision_id)
692
iw = self.get_inventory_weave()
693
return iw.get_text(iw.lookup(revision_id))
695
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
698
def get_inventory_sha1(self, revision_id):
664
699
"""Return the sha1 hash of the inventory entry
666
return sha_file(self.inventory_store[inventory_id])
701
return self.get_revision(revision_id).inventory_sha1
669
704
def get_revision_inventory(self, revision_id):
670
705
"""Return inventory of a past revision."""
671
# bzr 0.0.6 imposes the constraint that the inventory_id
706
# bzr 0.0.6 and later imposes the constraint that the inventory_id
672
707
# must be the same as its revision, so this is trivial.
673
708
if revision_id == None:
674
from bzrlib.inventory import Inventory
675
709
return Inventory(self.get_root_id())
677
711
return self.get_inventory(revision_id)
680
714
def revision_history(self):
681
"""Return sequence of revision hashes on to this branch.
683
>>> ScratchBranch().revision_history()
715
"""Return sequence of revision hashes on to this branch."""
688
718
return [l.rstrip('\r\n') for l in
697
727
>>> sb = ScratchBranch(files=['foo', 'foo~'])
698
728
>>> sb.common_ancestor(sb) == (None, None)
700
>>> commit.commit(sb, "Committing first revision", verbose=False)
730
>>> commit.commit(sb, "Committing first revision")
701
731
>>> sb.common_ancestor(sb)[0]
703
733
>>> clone = sb.clone()
704
>>> commit.commit(sb, "Committing second revision", verbose=False)
734
>>> commit.commit(sb, "Committing second revision")
705
735
>>> sb.common_ancestor(sb)[0]
707
737
>>> sb.common_ancestor(clone)[0]
709
>>> commit.commit(clone, "Committing divergent second revision",
739
>>> commit.commit(clone, "Committing divergent second revision")
711
740
>>> sb.common_ancestor(clone)[0]
713
742
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
794
827
if stop_revision is None:
795
828
stop_revision = other_len
796
elif stop_revision > other_len:
797
raise NoSuchRevision(self, stop_revision)
830
assert isinstance(stop_revision, int)
831
if stop_revision > other_len:
832
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
799
834
return other_history[self_len:stop_revision]
802
def update_revisions(self, other, stop_revision=None):
803
"""Pull in all new revisions from other branch.
805
>>> from bzrlib.commit import commit
806
>>> bzrlib.trace.silent = True
807
>>> br1 = ScratchBranch(files=['foo', 'bar'])
810
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
811
>>> br2 = ScratchBranch()
812
>>> br2.update_revisions(br1)
816
>>> br2.revision_history()
818
>>> br2.update_revisions(br1)
822
>>> br1.text_store.total_size() == br2.text_store.total_size()
837
def update_revisions(self, other, stop_revno=None):
838
"""Pull in new perfect-fit revisions.
825
from bzrlib.progress import ProgressBar
829
pb.update('comparing histories')
830
revision_ids = self.missing_revisions(other, stop_revision)
832
if hasattr(other.revision_store, "prefetch"):
833
other.revision_store.prefetch(revision_ids)
834
if hasattr(other.inventory_store, "prefetch"):
835
inventory_ids = [other.get_revision(r).inventory_id
836
for r in revision_ids]
837
other.inventory_store.prefetch(inventory_ids)
842
for rev_id in revision_ids:
844
pb.update('fetching revision', i, len(revision_ids))
845
rev = other.get_revision(rev_id)
846
revisions.append(rev)
847
inv = other.get_inventory(str(rev.inventory_id))
848
for key, entry in inv.iter_entries():
849
if entry.text_id is None:
851
if entry.text_id not in self.text_store:
852
needed_texts.add(entry.text_id)
856
count = self.text_store.copy_multi(other.text_store, needed_texts)
857
print "Added %d texts." % count
858
inventory_ids = [ f.inventory_id for f in revisions ]
859
count = self.inventory_store.copy_multi(other.inventory_store,
861
print "Added %d inventories." % count
862
revision_ids = [ f.revision_id for f in revisions]
863
count = self.revision_store.copy_multi(other.revision_store,
865
for revision_id in revision_ids:
866
self.append_revision(revision_id)
867
print "Added %d revisions." % count
840
from bzrlib.fetch import greedy_fetch
843
stop_revision = other.lookup_revision(stop_revno)
846
greedy_fetch(to_branch=self, from_branch=other,
847
revision=stop_revision)
849
pullable_revs = self.missing_revisions(other, stop_revision)
852
greedy_fetch(to_branch=self,
854
revision=pullable_revs[-1])
855
self.append_revision(*pullable_revs)
870
858
def commit(self, *args, **kw):
871
from bzrlib.commit import commit
872
commit(self, *args, **kw)
859
from bzrlib.commit import Commit
860
Commit().commit(self, *args, **kw)
875
863
def lookup_revision(self, revision):
876
864
"""Return the revision identifier for a given revision information."""
877
revno, info = self.get_revision_info(revision)
865
revno, info = self._get_revision_info(revision)
869
def revision_id_to_revno(self, revision_id):
870
"""Given a revision id, return its revno"""
871
history = self.revision_history()
873
return history.index(revision_id) + 1
875
raise bzrlib.errors.NoSuchRevision(self, revision_id)
880
878
def get_revision_info(self, revision):
881
879
"""Return (revno, revision id) for revision identifier.
885
883
revision can also be a string, in which case it is parsed for something like
886
884
'date:' or 'revid:' etc.
886
revno, rev_id = self._get_revision_info(revision)
888
raise bzrlib.errors.NoSuchRevision(self, revision)
891
def get_rev_id(self, revno, history=None):
892
"""Find the revision id of the specified revno."""
896
history = self.revision_history()
897
elif revno <= 0 or revno > len(history):
898
raise bzrlib.errors.NoSuchRevision(self, revno)
899
return history[revno - 1]
901
def _get_revision_info(self, revision):
902
"""Return (revno, revision id) for revision specifier.
904
revision can be an integer, in which case it is assumed to be revno
905
(though this will translate negative values into positive ones)
906
revision can also be a string, in which case it is parsed for something
907
like 'date:' or 'revid:' etc.
909
A revid is always returned. If it is None, the specifier referred to
910
the null revision. If the revid does not occur in the revision
911
history, revno will be None.
888
914
if revision is None:
895
921
revs = self.revision_history()
896
922
if isinstance(revision, int):
899
# Mabye we should do this first, but we don't need it if revision == 0
901
924
revno = len(revs) + revision + 1
927
rev_id = self.get_rev_id(revno, revs)
904
928
elif isinstance(revision, basestring):
905
929
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
906
930
if revision.startswith(prefix):
907
revno = func(self, revs, revision)
931
result = func(self, revs, revision)
933
revno, rev_id = result
936
rev_id = self.get_rev_id(revno, revs)
910
raise BzrError('No namespace registered for string: %r' % revision)
939
raise BzrError('No namespace registered for string: %r' %
942
raise TypeError('Unhandled revision type %s' % revision)
912
if revno is None or revno <= 0 or revno > len(revs):
913
raise BzrError("no such revision %s" % revision)
914
return revno, revs[revno-1]
946
raise bzrlib.errors.NoSuchRevision(self, revision)
916
949
def _namespace_revno(self, revs, revision):
917
950
"""Lookup a revision by revision number"""
918
951
assert revision.startswith('revno:')
920
return int(revision[6:])
953
return (int(revision[6:]),)
921
954
except ValueError:
923
956
REVISION_NAMESPACES['revno:'] = _namespace_revno
925
958
def _namespace_revid(self, revs, revision):
926
959
assert revision.startswith('revid:')
960
rev_id = revision[len('revid:'):]
928
return revs.index(revision[6:]) + 1
962
return revs.index(rev_id) + 1, rev_id
929
963
except ValueError:
931
965
REVISION_NAMESPACES['revid:'] = _namespace_revid
933
967
def _namespace_last(self, revs, revision):
1299
def get_parent(self):
1300
"""Return the parent location of the branch.
1302
This is the default location for push/pull/missing. The usual
1303
pattern is that the user can override it by specifying a
1307
_locs = ['parent', 'pull', 'x-pull']
1310
return self.controlfile(l, 'r').read().strip('\n')
1312
if e.errno != errno.ENOENT:
1317
def set_parent(self, url):
1318
# TODO: Maybe delete old location files?
1319
from bzrlib.atomicfile import AtomicFile
1322
f = AtomicFile(self.controlfilename('parent'))
1331
def check_revno(self, revno):
1333
Check whether a revno corresponds to any revision.
1334
Zero (the NULL revision) is considered valid.
1337
self.check_real_revno(revno)
1339
def check_real_revno(self, revno):
1341
Check whether a revno corresponds to a real revision.
1342
Zero (the NULL revision) is considered invalid
1344
if revno < 1 or revno > self.revno():
1345
raise InvalidRevisionNumber(revno)
1268
1350
class ScratchBranch(Branch):
1269
1351
"""Special test class: a branch that cleans up after itself.
1386
1470
"""Return a new tree-root file id."""
1387
1471
return gen_file_id('TREE_ROOT')
1474
def pull_loc(branch):
1475
# TODO: Should perhaps just make attribute be 'base' in
1476
# RemoteBranch and Branch?
1477
if hasattr(branch, "baseurl"):
1478
return branch.baseurl
1483
def copy_branch(branch_from, to_location, revision=None):
1484
"""Copy branch_from into the existing directory to_location.
1487
If not None, only revisions up to this point will be copied.
1488
The head of the new branch will be that revision. Can be a
1492
The name of a local directory that exists but is empty.
1494
# TODO: This could be done *much* more efficiently by just copying
1495
# all the whole weaves and revisions, rather than getting one
1496
# revision at a time.
1497
from bzrlib.merge import merge
1498
from bzrlib.branch import Branch
1500
assert isinstance(branch_from, Branch)
1501
assert isinstance(to_location, basestring)
1503
br_to = Branch(to_location, init=True)
1504
br_to.set_root_id(branch_from.get_root_id())
1505
if revision is None:
1508
revno, rev_id = branch_from.get_revision_info(revision)
1509
br_to.update_revisions(branch_from, stop_revno=revno)
1510
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1511
check_clean=False, ignore_zero=True)
1513
from_location = pull_loc(branch_from)
1514
br_to.set_parent(pull_loc(branch_from))