26
25
sha_file, appendpath, file_kind
28
27
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
29
NoSuchRevision, HistoryMissing, NotBranchError,
31
29
from bzrlib.textui import show_status
32
from bzrlib.revision import Revision, validate_revision_id
30
from bzrlib.revision import Revision
33
31
from bzrlib.delta import compare_trees
34
32
from bzrlib.tree import EmptyTree, RevisionTree
35
33
from bzrlib.inventory import Inventory
36
34
from bzrlib.weavestore import WeaveStore
37
from bzrlib.store import ImmutableStore
42
INVENTORY_FILEID = '__inventory'
43
ANCESTRY_FILEID = '__ancestry'
46
40
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
47
41
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
51
45
# TODO: Some operations like log might retrieve the same revisions
52
46
# repeatedly to calculate deltas. We could perhaps have a weakref
53
# cache in memory to make this faster. In general anything can be
54
# cached in memory between lock and unlock operations.
47
# cache in memory to make this faster.
56
49
# TODO: please move the revision-string syntax stuff out of the branch
57
50
# object; it's clutter
137
131
head, tail = os.path.split(f)
139
133
# reached the root, whatever that may be
140
raise NotBranchError('%s is not in a branch' % orig_f)
134
raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
173
167
_lock_mode = None
174
168
_lock_count = None
176
_inventory_weave = None
178
171
# Map some sort of prefix into a namespace
179
172
# stuff like "revno:10", "revid:", etc.
180
173
# This should match a prefix with a function which accepts
181
174
REVISION_NAMESPACES = {}
183
def __init__(self, base, init=False, find_root=True,
184
relax_version_check=False):
176
def __init__(self, base, init=False, find_root=True):
185
177
"""Create new branch object at a particular location.
187
179
base -- Base directory for the branch.
193
185
find_root -- If true and init is false, find the root of the
194
186
existing branch containing base.
196
relax_version_check -- If true, the usual check for the branch
197
version is not applied. This is intended only for
198
upgrade/recovery type use; it's not guaranteed that
199
all operations will work on old format branches.
201
188
In the test suite, creation of new trees is tested using the
202
189
`ScratchBranch` class.
191
from bzrlib.store import ImmutableStore
205
193
self.base = os.path.realpath(base)
206
194
self._make_control()
210
198
self.base = os.path.realpath(base)
211
199
if not isdir(self.controlfilename('.')):
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'))
200
from errors import NotBranchError
201
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
202
['use "bzr init" to initialize a new working tree',
203
'current bzr can only operate from top-of-tree'])
222
206
self.weave_store = WeaveStore(self.controlfilename('weaves'))
223
self.revision_store = \
224
ImmutableStore(self.controlfilename('revision-store'))
207
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
208
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
227
211
def __str__(self):
323
309
"This is a Bazaar-NG control directory.\n"
324
310
"Do not change any files in this directory.\n")
325
311
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
326
for d in ('text-store', 'revision-store',
312
for d in ('text-store', 'inventory-store', 'revision-store',
328
314
os.mkdir(self.controlfilename(d))
329
315
for f in ('revision-history', 'merged-patches',
339
325
f = self.controlfile('inventory','w')
340
326
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
344
def _check_format(self, relax_version_check):
329
def _check_format(self):
345
330
"""Check this branch format is supported.
347
332
The format level is stored, as an integer, in
353
338
fmt = self.controlfile('branch-format', 'r').read()
354
339
if fmt == BZR_BRANCH_FORMAT_5:
355
340
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
342
raise BzrError('sorry, branch format "%s" not supported; '
362
343
'use a different bzr version, '
363
'or run "bzr upgrade"'
344
'or run "bzr upgrade", '
345
'or remove the .bzr directory and "bzr init" again'
364
346
% fmt.rstrip('\n\r'))
367
348
def get_root_id(self):
368
349
"""Return the id of this branches root"""
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
580
def get_revision_xml_file(self, revision_id):
608
581
"""Return XML file object for revision object."""
609
582
if not revision_id or not isinstance(revision_id, basestring):
622
def get_revision_xml(self, revision_id):
623
return self.get_revision_xml_file(revision_id).read()
596
get_revision_xml = get_revision_xml_file
626
599
def get_revision(self, revision_id):
664
637
def get_revision_sha1(self, revision_id):
665
638
"""Hash the stored value of a revision, and return it."""
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)
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))
681
648
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))
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
f = self.get_inventory_xml_file(revision_id)
685
655
return bzrlib.xml5.serializer_v5.read_inventory(f)
689
659
"""Get inventory XML as a file object."""
691
661
assert isinstance(revision_id, basestring), type(revision_id)
692
iw = self.get_inventory_weave()
693
return iw.get_text(iw.lookup(revision_id))
662
return self.inventory_store[revision_id]
694
663
except IndexError:
695
664
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
666
get_inventory_xml_file = get_inventory_xml
698
669
def get_inventory_sha1(self, revision_id):
699
670
"""Return the sha1 hash of the inventory entry
701
return self.get_revision(revision_id).inventory_sha1
672
return sha_file(self.get_inventory_xml_file(revision_id))
704
675
def get_revision_inventory(self, revision_id):
714
685
def revision_history(self):
715
"""Return sequence of revision hashes on to this branch."""
686
"""Return sequence of revision hashes on to this branch.
688
>>> ScratchBranch().revision_history()
718
693
return [l.rstrip('\r\n') for l in
727
702
>>> sb = ScratchBranch(files=['foo', 'foo~'])
728
703
>>> sb.common_ancestor(sb) == (None, None)
730
>>> commit.commit(sb, "Committing first revision")
705
>>> commit.commit(sb, "Committing first revision", verbose=False)
731
706
>>> sb.common_ancestor(sb)[0]
733
708
>>> clone = sb.clone()
734
>>> commit.commit(sb, "Committing second revision")
709
>>> commit.commit(sb, "Committing second revision", verbose=False)
735
710
>>> sb.common_ancestor(sb)[0]
737
712
>>> sb.common_ancestor(clone)[0]
739
>>> commit.commit(clone, "Committing divergent second revision")
714
>>> commit.commit(clone, "Committing divergent second revision",
740
716
>>> sb.common_ancestor(clone)[0]
742
718
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
787
763
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
788
"""Return a list of new revisions that would perfectly fit.
790
765
If self and other have not diverged, return a list of the revisions
791
766
present in other, but missing from self.
812
787
Traceback (most recent call last):
813
788
DivergedBranches: These branches have diverged.
815
# FIXME: If the branches have diverged, but the latest
816
# revision in this branch is completely merged into the other,
817
# then we should still be able to pull.
818
790
self_history = self.revision_history()
819
791
self_len = len(self_history)
820
792
other_history = other.revision_history()
827
799
if stop_revision is None:
828
800
stop_revision = other_len
830
assert isinstance(stop_revision, int)
831
if stop_revision > other_len:
832
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
801
elif stop_revision > other_len:
802
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
834
804
return other_history[self_len:stop_revision]
837
def update_revisions(self, other, stop_revno=None):
838
"""Pull in new perfect-fit revisions.
807
def update_revisions(self, other, stop_revision=None):
808
"""Pull in all new revisions from other branch.
840
810
from bzrlib.fetch import greedy_fetch
843
stop_revision = other.lookup_revision(stop_revno)
812
pb = bzrlib.ui.ui_factory.progress_bar()
813
pb.update('comparing histories')
815
revision_ids = self.missing_revisions(other, stop_revision)
817
if len(revision_ids) > 0:
818
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
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)
821
self.append_revision(*revision_ids)
822
## note("Added %d revisions." % count)
858
826
def commit(self, *args, **kw):
1090
1058
If there are no revisions yet, return an `EmptyTree`.
1092
return self.revision_tree(self.last_revision())
1060
return self.revision_tree(self.last_patch())
1095
1063
def rename_one(self, from_rel, to_rel):
1273
1241
def add_pending_merge(self, revision_id):
1242
from bzrlib.revision import validate_revision_id
1274
1244
validate_revision_id(revision_id)
1275
# TODO: Perhaps should check at this point that the
1276
# history of the revision is actually present?
1277
1246
p = self.pending_merges()
1278
1247
if revision_id in p:
1487
1456
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
1457
The head of the new branch will be that revision.
1492
1460
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
1462
from bzrlib.merge import merge
1498
1463
from bzrlib.branch import Branch
1503
1468
br_to = Branch(to_location, init=True)
1504
1469
br_to.set_root_id(branch_from.get_root_id())
1505
1470
if revision is None:
1471
revno = branch_from.revno()
1508
1473
revno, rev_id = branch_from.get_revision_info(revision)
1509
br_to.update_revisions(branch_from, stop_revno=revno)
1474
br_to.update_revisions(branch_from, stop_revision=revno)
1510
1475
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1511
1476
check_clean=False, ignore_zero=True)