21
from warnings import warn
22
25
from bzrlib.trace import mutter, note
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
rename, splitpath, sha_file, appendpath, file_kind
26
from bzrlib.store import copy_all
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
DivergedBranches, NotBranchError, UnlistableStore, UnlistableBranch
26
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
27
rename, splitpath, sha_file, appendpath,
29
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
30
NoSuchRevision, HistoryMissing, NotBranchError,
31
DivergedBranches, LockError, UnlistableStore,
29
33
from bzrlib.textui import show_status
30
from bzrlib.revision import Revision, is_ancestor
34
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
31
35
from bzrlib.delta import compare_trees
32
36
from bzrlib.tree import EmptyTree, RevisionTree
37
from bzrlib.inventory import Inventory
38
from bzrlib.weavestore import WeaveStore
39
from bzrlib.store import copy_all, ImmutableStore
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
44
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
45
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
39
46
## TODO: Maybe include checks for common corruption of newlines, etc?
42
49
# TODO: Some operations like log might retrieve the same revisions
43
50
# repeatedly to calculate deltas. We could perhaps have a weakref
44
# cache in memory to make this faster.
51
# cache in memory to make this faster. In general anything can be
52
# cached in memory between lock and unlock operations.
46
54
def find_branch(*ignored, **ignored_too):
47
55
# XXX: leave this here for about one release, then remove it
180
188
_lock_mode = None
181
189
_lock_count = None
184
def __init__(self, base, init=False, find_root=True):
191
_inventory_weave = None
193
# Map some sort of prefix into a namespace
194
# stuff like "revno:10", "revid:", etc.
195
# This should match a prefix with a function which accepts
196
REVISION_NAMESPACES = {}
198
def push_stores(self, branch_to):
199
"""Copy the content of this branches store to branch_to."""
200
if (self._branch_format != branch_to._branch_format
201
or self._branch_format != 4):
202
from bzrlib.fetch import greedy_fetch
203
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
204
self, self._branch_format, branch_to, branch_to._branch_format)
205
greedy_fetch(to_branch=branch_to, from_branch=self,
206
revision=self.last_revision())
209
store_pairs = ((self.text_store, branch_to.text_store),
210
(self.inventory_store, branch_to.inventory_store),
211
(self.revision_store, branch_to.revision_store))
213
for from_store, to_store in store_pairs:
214
copy_all(from_store, to_store)
215
except UnlistableStore:
216
raise UnlistableBranch(from_store)
218
def __init__(self, base, init=False, find_root=True,
219
relax_version_check=False):
185
220
"""Create new branch object at a particular location.
187
222
base -- Base directory for the branch. May be a file:// url.
208
247
self.base = os.path.realpath(base)
209
248
if not isdir(self.controlfilename('.')):
210
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
211
['use "bzr init" to initialize a new working tree',
212
'current bzr can only operate from top-of-tree'])
215
self.text_store = ImmutableStore(self.controlfilename('text-store'))
216
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
217
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
249
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
250
['use "bzr init" to initialize a '
252
self._check_format(relax_version_check)
253
cfn = self.controlfilename
254
if self._branch_format == 4:
255
self.inventory_store = ImmutableStore(cfn('inventory-store'))
256
self.text_store = ImmutableStore(cfn('text-store'))
257
elif self._branch_format == 5:
258
self.control_weaves = WeaveStore(cfn([]))
259
self.weave_store = WeaveStore(cfn('weaves'))
261
# FIXME: Unify with make_control_files
262
self.control_weaves.put_empty_weave('inventory')
263
self.control_weaves.put_empty_weave('ancestry')
264
self.revision_store = ImmutableStore(cfn('revision-store'))
220
267
def __str__(self):
312
358
raise BzrError("invalid controlfile mode %r" % mode)
314
360
def _make_control(self):
315
from bzrlib.inventory import Inventory
317
361
os.mkdir(self.controlfilename([]))
318
362
self.controlfile('README', 'w').write(
319
363
"This is a Bazaar-NG control directory.\n"
320
364
"Do not change any files in this directory.\n")
321
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
322
for d in ('text-store', 'inventory-store', 'revision-store'):
365
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
366
for d in ('text-store', 'revision-store',
323
368
os.mkdir(self.controlfilename(d))
324
for f in ('revision-history', 'merged-patches',
325
'pending-merged-patches', 'branch-name',
369
for f in ('revision-history',
327
372
'pending-merges'):
328
373
self.controlfile(f, 'w').write('')
332
377
# them; they're not needed for now and so ommitted for
334
379
f = self.controlfile('inventory','w')
335
bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
338
def _check_format(self):
380
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
383
def _check_format(self, relax_version_check):
339
384
"""Check this branch format is supported.
341
The current tool only supports the current unstable format.
386
The format level is stored, as an integer, in
387
self._branch_format for code that needs to check it later.
343
389
In the future, we might need different in-memory Branch
344
390
classes to support downlevel branches. But not yet.
346
# This ignores newlines so that we can open branches created
347
# on Windows from Linux and so on. I think it might be better
348
# to always make all internal files in unix format.
349
fmt = self.controlfile('branch-format', 'r').read()
350
fmt = fmt.replace('\r\n', '\n')
351
if fmt != BZR_BRANCH_FORMAT:
393
fmt = self.controlfile('branch-format', 'r').read()
395
if hasattr(e, 'errno'):
396
if e.errno == errno.ENOENT:
397
raise NotBranchError(self.base)
403
if fmt == BZR_BRANCH_FORMAT_5:
404
self._branch_format = 5
405
elif fmt == BZR_BRANCH_FORMAT_4:
406
self._branch_format = 4
408
if (not relax_version_check
409
and self._branch_format != 5):
352
410
raise BzrError('sorry, branch format %r not supported' % fmt,
353
411
['use a different bzr version',
354
412
'or remove the .bzr directory and "bzr init" again'])
645
710
# the revision, (add signatures/remove signatures) and still
646
711
# have all hash pointers stay consistent.
647
712
# But for now, just hash the contents.
648
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
650
def get_inventory(self, inventory_id):
651
"""Get Inventory object by hash.
653
TODO: Perhaps for this and similar methods, take a revision
654
parameter which can be either an integer revno or a
656
from bzrlib.inventory import Inventory
657
f = self.get_inventory_xml_file(inventory_id)
658
return bzrlib.xml.serializer_v4.read_inventory(f)
660
def get_inventory_xml(self, inventory_id):
713
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
715
def _get_ancestry_weave(self):
716
return self.control_weaves.get_weave('ancestry')
718
def get_ancestry(self, revision_id):
719
"""Return a list of revision-ids integrated by a revision.
722
if revision_id is None:
724
w = self._get_ancestry_weave()
725
return [None] + [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
727
def get_inventory_weave(self):
728
return self.control_weaves.get_weave('inventory')
730
def get_inventory(self, revision_id):
731
"""Get Inventory object by hash."""
732
xml = self.get_inventory_xml(revision_id)
733
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
735
def get_inventory_xml(self, revision_id):
661
736
"""Get inventory XML as a file object."""
662
return self.inventory_store[inventory_id]
738
assert isinstance(revision_id, basestring), type(revision_id)
739
iw = self.get_inventory_weave()
740
return iw.get_text(iw.lookup(revision_id))
742
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
664
get_inventory_xml_file = get_inventory_xml
666
def get_inventory_sha1(self, inventory_id):
744
def get_inventory_sha1(self, revision_id):
667
745
"""Return the sha1 hash of the inventory entry
669
return sha_file(self.get_inventory_xml(inventory_id))
747
return self.get_revision(revision_id).inventory_sha1
671
749
def get_revision_inventory(self, revision_id):
672
750
"""Return inventory of a past revision."""
673
# bzr 0.0.6 imposes the constraint that the inventory_id
751
# TODO: Unify this with get_inventory()
752
# bzr 0.0.6 and later imposes the constraint that the inventory_id
674
753
# must be the same as its revision, so this is trivial.
675
754
if revision_id == None:
676
from bzrlib.inventory import Inventory
677
755
return Inventory(self.get_root_id())
679
757
return self.get_inventory(revision_id)
681
759
def revision_history(self):
682
"""Return sequence of revision hashes on to this branch.
684
>>> ScratchBranch().revision_history()
760
"""Return sequence of revision hashes on to this branch."""
689
763
return [l.rstrip('\r\n') for l in
794
872
if stop_revision is None:
795
873
stop_revision = other_len
796
elif stop_revision > other_len:
797
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
875
assert isinstance(stop_revision, int)
876
if stop_revision > other_len:
877
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
799
878
return other_history[self_len:stop_revision]
802
880
def update_revisions(self, other, stop_revision=None):
803
"""Pull in all new revisions from other branch.
881
"""Pull in new perfect-fit revisions."""
805
882
from bzrlib.fetch import greedy_fetch
806
883
from bzrlib.revision import get_intervening_revisions
808
pb = bzrlib.ui.ui_factory.progress_bar()
809
pb.update('comparing histories')
810
884
if stop_revision is None:
811
other_revision = other.last_patch()
813
other_revision = other.get_rev_id(stop_revision)
814
count = greedy_fetch(self, other, other_revision, pb)[0]
816
revision_ids = self.missing_revisions(other, stop_revision)
817
except DivergedBranches, e:
819
revision_ids = get_intervening_revisions(self.last_patch(),
820
other_revision, self)
821
assert self.last_patch() not in revision_ids
822
except bzrlib.errors.NotAncestor:
823
if is_ancestor(self.last_patch(), other_revision, self):
827
self.append_revision(*revision_ids)
830
def install_revisions(self, other, revision_ids, pb):
831
if hasattr(other.revision_store, "prefetch"):
832
other.revision_store.prefetch(revision_ids)
833
if hasattr(other.inventory_store, "prefetch"):
835
for rev_id in revision_ids:
837
revision = other.get_revision(rev_id).inventory_id
838
inventory_ids.append(revision)
839
except bzrlib.errors.NoSuchRevision:
841
other.inventory_store.prefetch(inventory_ids)
844
pb = bzrlib.ui.ui_factory.progress_bar()
851
for i, rev_id in enumerate(revision_ids):
852
pb.update('fetching revision', i+1, len(revision_ids))
854
rev = other.get_revision(rev_id)
855
except bzrlib.errors.NoSuchRevision:
859
revisions.append(rev)
860
inv = other.get_inventory(str(rev.inventory_id))
861
for key, entry in inv.iter_entries():
862
if entry.text_id is None:
864
if entry.text_id not in self.text_store:
865
needed_texts.add(entry.text_id)
869
count, cp_fail = self.text_store.copy_multi(other.text_store,
871
#print "Added %d texts." % count
872
inventory_ids = [ f.inventory_id for f in revisions ]
873
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
875
#print "Added %d inventories." % count
876
revision_ids = [ f.revision_id for f in revisions]
878
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
881
assert len(cp_fail) == 0
882
return count, failures
885
stop_revision = other.last_revision()
886
greedy_fetch(to_branch=self, from_branch=other,
887
revision=stop_revision)
888
pullable_revs = self.missing_revisions(
889
other, other.revision_id_to_revno(stop_revision))
891
greedy_fetch(to_branch=self,
893
revision=pullable_revs[-1])
894
self.append_revision(*pullable_revs)
885
897
def commit(self, *args, **kw):
886
from bzrlib.commit import commit
887
commit(self, *args, **kw)
898
from bzrlib.commit import Commit
899
Commit().commit(self, *args, **kw)
889
901
def revision_id_to_revno(self, revision_id):
890
902
"""Given a revision id, return its revno"""
903
if revision_id is None:
891
905
history = self.revision_history()
893
907
return history.index(revision_id) + 1
1337
1346
A local branch to copy revisions from, related to branch_from
1348
# TODO: This could be done *much* more efficiently by just copying
1349
# all the whole weaves and revisions, rather than getting one
1350
# revision at a time.
1339
1351
from bzrlib.merge import merge
1341
1353
assert isinstance(branch_from, Branch)
1342
1354
assert isinstance(to_location, basestring)
1344
1356
br_to = Branch.initialize(to_location)
1357
mutter("copy branch from %s to %s", branch_from, br_to)
1345
1358
if basis_branch is not None:
1346
copy_stores(basis_branch, br_to)
1359
basis_branch.push_stores(br_to)
1347
1360
br_to.set_root_id(branch_from.get_root_id())
1349
revno = branch_from.revno()
1350
br_to.update_revisions(branch_from, stop_revision=revno)
1361
if revision is None:
1362
revision = branch_from.last_revision()
1363
br_to.update_revisions(branch_from, stop_revision=revision)
1351
1364
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1352
1365
check_clean=False, ignore_zero=True)
1353
1366
br_to.set_parent(branch_from.base)
1356
def copy_stores(branch_from, branch_to):
1357
"""Copies all entries from branch stores to another branch's stores.
1359
store_pairs = ((branch_from.text_store, branch_to.text_store),
1360
(branch_from.inventory_store, branch_to.inventory_store),
1361
(branch_from.revision_store, branch_to.revision_store))
1363
for from_store, to_store in store_pairs:
1364
copy_all(from_store, to_store)
1365
except UnlistableStore:
1366
raise UnlistableBranch(from_store)