25
26
sha_file, appendpath, file_kind
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
28
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
29
NoSuchRevision, HistoryMissing, NotBranchError,
29
31
from bzrlib.textui import show_status
30
from bzrlib.revision import Revision
31
from bzrlib.xml import unpack_xml
32
from bzrlib.revision import Revision, validate_revision_id
32
33
from bzrlib.delta import compare_trees
33
34
from bzrlib.tree import EmptyTree, RevisionTree
35
from bzrlib.inventory import Inventory
36
from bzrlib.weavestore import WeaveStore
37
from bzrlib.store import ImmutableStore
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
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"
39
48
## TODO: Maybe include checks for common corruption of newlines, etc?
42
51
# TODO: Some operations like log might retrieve the same revisions
43
52
# repeatedly to calculate deltas. We could perhaps have a weakref
44
# 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.
46
56
# TODO: please move the revision-string syntax stuff out of the branch
47
57
# object; it's clutter
164
173
_lock_mode = None
165
174
_lock_count = None
176
_inventory_weave = None
168
178
# Map some sort of prefix into a namespace
169
179
# stuff like "revno:10", "revid:", etc.
170
180
# This should match a prefix with a function which accepts
171
181
REVISION_NAMESPACES = {}
173
def __init__(self, base, init=False, find_root=True):
183
def __init__(self, base, init=False, find_root=True,
184
relax_version_check=False):
174
185
"""Create new branch object at a particular location.
176
187
base -- Base directory for the branch.
195
210
self.base = os.path.realpath(base)
196
211
if not isdir(self.controlfilename('.')):
197
from errors import NotBranchError
198
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
199
['use "bzr init" to initialize a new working tree',
200
'current bzr can only operate from top-of-tree'])
203
self.text_store = ImmutableStore(self.controlfilename('text-store'))
204
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
205
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'))
208
227
def __str__(self):
301
318
raise BzrError("invalid controlfile mode %r" % mode)
303
320
def _make_control(self):
304
from bzrlib.inventory import Inventory
305
from bzrlib.xml import pack_xml
307
321
os.mkdir(self.controlfilename([]))
308
322
self.controlfile('README', 'w').write(
309
323
"This is a Bazaar-NG control directory.\n"
310
324
"Do not change any files in this directory.\n")
311
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
312
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',
313
328
os.mkdir(self.controlfilename(d))
314
329
for f in ('revision-history', 'merged-patches',
315
330
'pending-merged-patches', 'branch-name',
321
336
# if we want per-tree root ids then this is the place to set
322
337
# them; they're not needed for now and so ommitted for
324
pack_xml(Inventory(), self.controlfile('inventory','w'))
326
def _check_format(self):
339
f = self.controlfile('inventory','w')
340
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
344
def _check_format(self, relax_version_check):
327
345
"""Check this branch format is supported.
329
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.
331
350
In the future, we might need different in-memory Branch
332
351
classes to support downlevel branches. But not yet.
334
# This ignores newlines so that we can open branches created
335
# on Windows from Linux and so on. I think it might be better
336
# to always make all internal files in unix format.
337
353
fmt = self.controlfile('branch-format', 'r').read()
338
fmt.replace('\r\n', '')
339
if fmt != BZR_BRANCH_FORMAT:
340
raise BzrError('sorry, branch format %r not supported' % fmt,
341
['use a different bzr version',
342
'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'))
344
367
def get_root_id(self):
345
368
"""Return the id of this branches root"""
361
384
def read_working_inventory(self):
362
385
"""Read the working inventory."""
363
from bzrlib.inventory import Inventory
364
from bzrlib.xml import unpack_xml
365
from time import time
369
388
# ElementTree does its own conversion from UTF-8, so open in
371
inv = unpack_xml(Inventory,
372
self.controlfile('inventory', 'rb'))
373
mutter("loaded inventory of %d items in %f"
374
% (len(inv), time() - before))
390
f = self.controlfile('inventory', 'rb')
391
return bzrlib.xml5.serializer_v5.read_inventory(f)
584
def get_revision_xml(self, revision_id):
599
def has_revision(self, revision_id):
600
"""True if this branch has a copy of the revision.
602
This does not necessarily imply the revision is merge
603
or on the mainline."""
604
return revision_id in self.revision_store
607
def get_revision_xml_file(self, revision_id):
585
608
"""Return XML file object for revision object."""
586
609
if not revision_id or not isinstance(revision_id, basestring):
587
610
raise InvalidRevisionId(revision_id)
622
def get_revision_xml(self, revision_id):
623
return self.get_revision_xml_file(revision_id).read()
599
626
def get_revision(self, revision_id):
600
627
"""Return the Revision object for a named revision"""
601
xml_file = self.get_revision_xml(revision_id)
628
xml_file = self.get_revision_xml_file(revision_id)
604
r = unpack_xml(Revision, xml_file)
631
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
605
632
except SyntaxError, e:
606
633
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
637
664
def get_revision_sha1(self, revision_id):
638
665
"""Hash the stored value of a revision, and return it."""
639
# In the future, revision entries will be signed. At that
640
# point, it is probably best *not* to include the signature
641
# in the revision hash. Because that lets you re-sign
642
# the revision, (add signatures/remove signatures) and still
643
# have all hash pointers stay consistent.
644
# But for now, just hash the contents.
645
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
648
def get_inventory(self, inventory_id):
649
"""Get Inventory object by hash.
651
TODO: Perhaps for this and similar methods, take a revision
652
parameter which can be either an integer revno or a
654
from bzrlib.inventory import Inventory
655
from bzrlib.xml import unpack_xml
657
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
660
def get_inventory_xml(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):
661
689
"""Get inventory XML as a file object."""
662
return self.inventory_store[inventory_id]
665
def get_inventory_sha1(self, inventory_id):
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):
666
699
"""Return the sha1 hash of the inventory entry
668
return sha_file(self.get_inventory_xml(inventory_id))
701
return self.get_revision(revision_id).inventory_sha1
671
704
def get_revision_inventory(self, revision_id):
672
705
"""Return inventory of a past revision."""
673
# 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
674
707
# must be the same as its revision, so this is trivial.
675
708
if revision_id == None:
676
from bzrlib.inventory import Inventory
677
709
return Inventory(self.get_root_id())
679
711
return self.get_inventory(revision_id)
682
714
def revision_history(self):
683
"""Return sequence of revision hashes on to this branch.
685
>>> ScratchBranch().revision_history()
715
"""Return sequence of revision hashes on to this branch."""
690
718
return [l.rstrip('\r\n') for l in
699
727
>>> sb = ScratchBranch(files=['foo', 'foo~'])
700
728
>>> sb.common_ancestor(sb) == (None, None)
702
>>> commit.commit(sb, "Committing first revision", verbose=False)
730
>>> commit.commit(sb, "Committing first revision")
703
731
>>> sb.common_ancestor(sb)[0]
705
733
>>> clone = sb.clone()
706
>>> commit.commit(sb, "Committing second revision", verbose=False)
734
>>> commit.commit(sb, "Committing second revision")
707
735
>>> sb.common_ancestor(sb)[0]
709
737
>>> sb.common_ancestor(clone)[0]
711
>>> commit.commit(clone, "Committing divergent second revision",
739
>>> commit.commit(clone, "Committing divergent second revision")
713
740
>>> sb.common_ancestor(clone)[0]
715
742
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
796
827
if stop_revision is None:
797
828
stop_revision = other_len
798
elif stop_revision > other_len:
799
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
830
assert isinstance(stop_revision, int)
831
if stop_revision > other_len:
832
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
801
834
return other_history[self_len:stop_revision]
804
def update_revisions(self, other, stop_revision=None):
805
"""Pull in all new revisions from other branch.
837
def update_revisions(self, other, stop_revno=None):
838
"""Pull in new perfect-fit revisions.
807
840
from bzrlib.fetch import greedy_fetch
809
pb = bzrlib.ui.ui_factory.progress_bar()
810
pb.update('comparing histories')
812
revision_ids = self.missing_revisions(other, stop_revision)
814
if len(revision_ids) > 0:
815
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
843
stop_revision = other.lookup_revision(stop_revno)
818
self.append_revision(*revision_ids)
819
## note("Added %d revisions." % count)
822
def install_revisions(self, other, revision_ids, pb):
823
if hasattr(other.revision_store, "prefetch"):
824
other.revision_store.prefetch(revision_ids)
825
if hasattr(other.inventory_store, "prefetch"):
826
inventory_ids = [other.get_revision(r).inventory_id
827
for r in revision_ids]
828
other.inventory_store.prefetch(inventory_ids)
831
pb = bzrlib.ui.ui_factory.progress_bar()
838
for i, rev_id in enumerate(revision_ids):
839
pb.update('fetching revision', i+1, len(revision_ids))
841
rev = other.get_revision(rev_id)
842
except bzrlib.errors.NoSuchRevision:
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, cp_fail = self.text_store.copy_multi(other.text_store,
858
#print "Added %d texts." % count
859
inventory_ids = [ f.inventory_id for f in revisions ]
860
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
862
#print "Added %d inventories." % count
863
revision_ids = [ f.revision_id for f in revisions]
865
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
868
assert len(cp_fail) == 0
869
return count, failures
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)
872
858
def commit(self, *args, **kw):
873
from bzrlib.commit import commit
874
commit(self, *args, **kw)
859
from bzrlib.commit import Commit
860
Commit().commit(self, *args, **kw)
877
863
def lookup_revision(self, revision):
1519
1503
br_to = Branch(to_location, init=True)
1520
1504
br_to.set_root_id(branch_from.get_root_id())
1521
1505
if revision is None:
1522
revno = branch_from.revno()
1524
1508
revno, rev_id = branch_from.get_revision_info(revision)
1525
br_to.update_revisions(branch_from, stop_revision=revno)
1509
br_to.update_revisions(branch_from, stop_revno=revno)
1526
1510
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1527
1511
check_clean=False, ignore_zero=True)