23
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
25
25
sha_file, appendpath, file_kind
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
27
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
28
29
from bzrlib.textui import show_status
29
30
from bzrlib.revision import Revision
30
from bzrlib.xml import unpack_xml
31
31
from bzrlib.delta import compare_trees
32
32
from bzrlib.tree import EmptyTree, RevisionTree
34
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
33
from bzrlib.inventory import Inventory
34
from bzrlib.weavestore import WeaveStore
40
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
41
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
35
42
## TODO: Maybe include checks for common corruption of newlines, etc?
297
304
raise BzrError("invalid controlfile mode %r" % mode)
301
306
def _make_control(self):
302
from bzrlib.inventory import Inventory
303
from bzrlib.xml import pack_xml
305
307
os.mkdir(self.controlfilename([]))
306
308
self.controlfile('README', 'w').write(
307
309
"This is a Bazaar-NG control directory.\n"
308
310
"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'):
311
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
312
for d in ('text-store', 'inventory-store', 'revision-store',
311
314
os.mkdir(self.controlfilename(d))
312
315
for f in ('revision-history', 'merged-patches',
313
316
'pending-merged-patches', 'branch-name',
316
319
self.controlfile(f, 'w').write('')
317
320
mutter('created control directory in ' + self.base)
319
pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
322
# if we want per-tree root ids then this is the place to set
323
# them; they're not needed for now and so ommitted for
325
f = self.controlfile('inventory','w')
326
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
322
329
def _check_format(self):
323
330
"""Check this branch format is supported.
325
The current tool only supports the current unstable format.
332
The format level is stored, as an integer, in
333
self._branch_format for code that needs to check it later.
327
335
In the future, we might need different in-memory Branch
328
336
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
338
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'])
339
if fmt == BZR_BRANCH_FORMAT_5:
340
self._branch_format = 5
342
raise BzrError('sorry, branch format "%s" not supported; '
343
'use a different bzr version, '
344
'or run "bzr upgrade", '
345
'or remove the .bzr directory and "bzr init" again'
346
% fmt.rstrip('\n\r'))
340
348
def get_root_id(self):
341
349
"""Return the id of this branches root"""
357
365
def read_working_inventory(self):
358
366
"""Read the working inventory."""
359
from bzrlib.inventory import Inventory
360
from bzrlib.xml import unpack_xml
361
from time import time
365
369
# ElementTree does its own conversion from UTF-8, so open in
367
inv = unpack_xml(Inventory,
368
self.controlfile('inventory', 'rb'))
369
mutter("loaded inventory of %d items in %f"
370
% (len(inv), time() - before))
371
f = self.controlfile('inventory', 'rb')
372
return bzrlib.xml5.serializer_v5.read_inventory(f)
595
588
return self.revision_store[revision_id]
596
589
except IndexError:
597
raise bzrlib.errors.NoSuchRevision(revision_id)
590
raise bzrlib.errors.NoSuchRevision(self, revision_id)
596
get_revision_xml = get_revision_xml_file
602
599
def get_revision(self, revision_id):
603
600
"""Return the Revision object for a named revision"""
604
xml_file = self.get_revision_xml(revision_id)
601
xml_file = self.get_revision_xml_file(revision_id)
607
r = unpack_xml(Revision, xml_file)
604
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
608
605
except SyntaxError, e:
609
606
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
648
645
return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
651
def get_inventory(self, inventory_id):
648
def get_inventory(self, revision_id):
652
649
"""Get Inventory object by hash.
654
651
TODO: Perhaps for this and similar methods, take a revision
655
652
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])
654
f = self.get_inventory_xml_file(revision_id)
655
return bzrlib.xml5.serializer_v5.read_inventory(f)
658
def get_inventory_xml(self, revision_id):
659
"""Get inventory XML as a file object."""
661
assert isinstance(revision_id, basestring), type(revision_id)
662
return self.inventory_store[revision_id]
664
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
666
get_inventory_xml_file = get_inventory_xml
663
def get_inventory_sha1(self, inventory_id):
669
def get_inventory_sha1(self, revision_id):
664
670
"""Return the sha1 hash of the inventory entry
666
return sha_file(self.inventory_store[inventory_id])
672
return sha_file(self.get_inventory_xml_file(revision_id))
669
675
def get_revision_inventory(self, revision_id):
670
676
"""Return inventory of a past revision."""
671
# bzr 0.0.6 imposes the constraint that the inventory_id
677
# bzr 0.0.6 and later imposes the constraint that the inventory_id
672
678
# must be the same as its revision, so this is trivial.
673
679
if revision_id == None:
674
from bzrlib.inventory import Inventory
675
680
return Inventory(self.get_root_id())
677
682
return self.get_inventory(revision_id)
697
702
>>> sb = ScratchBranch(files=['foo', 'foo~'])
698
703
>>> sb.common_ancestor(sb) == (None, None)
700
>>> commit.commit(sb, "Committing first revision", verbose=False)
705
>>> commit.commit(sb, "Committing first revision")
701
706
>>> sb.common_ancestor(sb)[0]
703
708
>>> clone = sb.clone()
704
>>> commit.commit(sb, "Committing second revision", verbose=False)
709
>>> commit.commit(sb, "Committing second revision")
705
710
>>> sb.common_ancestor(sb)[0]
707
712
>>> sb.common_ancestor(clone)[0]
709
>>> commit.commit(clone, "Committing divergent second revision",
714
>>> commit.commit(clone, "Committing divergent second revision")
711
715
>>> sb.common_ancestor(clone)[0]
713
717
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
794
798
if stop_revision is None:
795
799
stop_revision = other_len
796
800
elif stop_revision > other_len:
797
raise NoSuchRevision(self, stop_revision)
801
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
799
803
return other_history[self_len:stop_revision]
802
806
def update_revisions(self, other, stop_revision=None):
803
807
"""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()
825
from bzrlib.progress import ProgressBar
809
from bzrlib.fetch import greedy_fetch
811
pb = bzrlib.ui.ui_factory.progress_bar()
829
812
pb.update('comparing histories')
830
814
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)
816
if len(revision_ids) > 0:
817
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
820
self.append_revision(*revision_ids)
821
## note("Added %d revisions." % count)
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
870
825
def commit(self, *args, **kw):
871
from bzrlib.commit import commit
872
commit(self, *args, **kw)
826
from bzrlib.commit import Commit
827
Commit().commit(self, *args, **kw)
875
830
def lookup_revision(self, revision):
876
831
"""Return the revision identifier for a given revision information."""
877
revno, info = self.get_revision_info(revision)
832
revno, info = self._get_revision_info(revision)
836
def revision_id_to_revno(self, revision_id):
837
"""Given a revision id, return its revno"""
838
history = self.revision_history()
840
return history.index(revision_id) + 1
842
raise bzrlib.errors.NoSuchRevision(self, revision_id)
880
845
def get_revision_info(self, revision):
881
846
"""Return (revno, revision id) for revision identifier.
885
850
revision can also be a string, in which case it is parsed for something like
886
851
'date:' or 'revid:' etc.
853
revno, rev_id = self._get_revision_info(revision)
855
raise bzrlib.errors.NoSuchRevision(self, revision)
858
def get_rev_id(self, revno, history=None):
859
"""Find the revision id of the specified revno."""
863
history = self.revision_history()
864
elif revno <= 0 or revno > len(history):
865
raise bzrlib.errors.NoSuchRevision(self, revno)
866
return history[revno - 1]
868
def _get_revision_info(self, revision):
869
"""Return (revno, revision id) for revision specifier.
871
revision can be an integer, in which case it is assumed to be revno
872
(though this will translate negative values into positive ones)
873
revision can also be a string, in which case it is parsed for something
874
like 'date:' or 'revid:' etc.
876
A revid is always returned. If it is None, the specifier referred to
877
the null revision. If the revid does not occur in the revision
878
history, revno will be None.
888
881
if revision is None:
895
888
revs = self.revision_history()
896
889
if isinstance(revision, int):
899
# Mabye we should do this first, but we don't need it if revision == 0
901
891
revno = len(revs) + revision + 1
894
rev_id = self.get_rev_id(revno, revs)
904
895
elif isinstance(revision, basestring):
905
896
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
906
897
if revision.startswith(prefix):
907
revno = func(self, revs, revision)
898
result = func(self, revs, revision)
900
revno, rev_id = result
903
rev_id = self.get_rev_id(revno, revs)
910
raise BzrError('No namespace registered for string: %r' % revision)
906
raise BzrError('No namespace registered for string: %r' %
909
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]
913
raise bzrlib.errors.NoSuchRevision(self, revision)
916
916
def _namespace_revno(self, revs, revision):
917
917
"""Lookup a revision by revision number"""
918
918
assert revision.startswith('revno:')
920
return int(revision[6:])
920
return (int(revision[6:]),)
921
921
except ValueError:
923
923
REVISION_NAMESPACES['revno:'] = _namespace_revno
925
925
def _namespace_revid(self, revs, revision):
926
926
assert revision.startswith('revid:')
927
rev_id = revision[len('revid:'):]
928
return revs.index(revision[6:]) + 1
929
return revs.index(rev_id) + 1, rev_id
929
930
except ValueError:
931
932
REVISION_NAMESPACES['revid:'] = _namespace_revid
933
934
def _namespace_last(self, revs, revision):
1267
def get_parent(self):
1268
"""Return the parent location of the branch.
1270
This is the default location for push/pull/missing. The usual
1271
pattern is that the user can override it by specifying a
1275
_locs = ['parent', 'pull', 'x-pull']
1278
return self.controlfile(l, 'r').read().strip('\n')
1280
if e.errno != errno.ENOENT:
1285
def set_parent(self, url):
1286
# TODO: Maybe delete old location files?
1287
from bzrlib.atomicfile import AtomicFile
1290
f = AtomicFile(self.controlfilename('parent'))
1299
def check_revno(self, revno):
1301
Check whether a revno corresponds to any revision.
1302
Zero (the NULL revision) is considered valid.
1305
self.check_real_revno(revno)
1307
def check_real_revno(self, revno):
1309
Check whether a revno corresponds to a real revision.
1310
Zero (the NULL revision) is considered invalid
1312
if revno < 1 or revno > self.revno():
1313
raise InvalidRevisionNumber(revno)
1268
1318
class ScratchBranch(Branch):
1269
1319
"""Special test class: a branch that cleans up after itself.
1386
1438
"""Return a new tree-root file id."""
1387
1439
return gen_file_id('TREE_ROOT')
1442
def pull_loc(branch):
1443
# TODO: Should perhaps just make attribute be 'base' in
1444
# RemoteBranch and Branch?
1445
if hasattr(branch, "baseurl"):
1446
return branch.baseurl
1451
def copy_branch(branch_from, to_location, revision=None):
1452
"""Copy branch_from into the existing directory to_location.
1455
If not None, only revisions up to this point will be copied.
1456
The head of the new branch will be that revision.
1459
The name of a local directory that exists but is empty.
1461
from bzrlib.merge import merge
1462
from bzrlib.branch import Branch
1464
assert isinstance(branch_from, Branch)
1465
assert isinstance(to_location, basestring)
1467
br_to = Branch(to_location, init=True)
1468
br_to.set_root_id(branch_from.get_root_id())
1469
if revision is None:
1470
revno = branch_from.revno()
1472
revno, rev_id = branch_from.get_revision_info(revision)
1473
br_to.update_revisions(branch_from, stop_revision=revno)
1474
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1475
check_clean=False, ignore_zero=True)
1477
from_location = pull_loc(branch_from)
1478
br_to.set_parent(pull_loc(branch_from))