23
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
25
25
sha_file, appendpath, file_kind
26
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
27
28
import bzrlib.errors
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
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
39
## TODO: Maybe include checks for common corruption of newlines, etc?
251
259
self._lock = None
252
260
self._lock_mode = self._lock_count = None
255
262
def abspath(self, name):
256
263
"""Return absolute filename for something in the branch"""
257
264
return os.path.join(self.base, name)
260
266
def relpath(self, path):
261
267
"""Return path relative to this branch of something inside it.
263
269
Raises an error if path is not in this branch."""
264
270
return _relpath(self.base, path)
267
272
def controlfilename(self, file_or_path):
268
273
"""Return location relative to branch."""
269
274
if isinstance(file_or_path, basestring):
316
318
self.controlfile(f, 'w').write('')
317
319
mutter('created control directory in ' + self.base)
319
pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
321
# if we want per-tree root ids then this is the place to set
322
# them; they're not needed for now and so ommitted for
324
f = self.controlfile('inventory','w')
325
bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
322
328
def _check_format(self):
331
337
# on Windows from Linux and so on. I think it might be better
332
338
# to always make all internal files in unix format.
333
339
fmt = self.controlfile('branch-format', 'r').read()
334
fmt.replace('\r\n', '')
340
fmt = fmt.replace('\r\n', '\n')
335
341
if fmt != BZR_BRANCH_FORMAT:
336
342
raise BzrError('sorry, branch format %r not supported' % fmt,
337
343
['use a different bzr version',
357
363
def read_working_inventory(self):
358
364
"""Read the working inventory."""
359
365
from bzrlib.inventory import Inventory
360
from bzrlib.xml import unpack_xml
361
from time import time
365
368
# 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))
370
f = self.controlfile('inventory', 'rb')
371
return bzrlib.xml.serializer_v4.read_inventory(f)
400
399
"""Inventory for the working copy.""")
403
def add(self, files, verbose=False, ids=None):
402
def add(self, files, ids=None):
404
403
"""Make files versioned.
406
Note that the command line normally calls smart_add instead.
405
Note that the command line normally calls smart_add instead,
406
which can automatically recurse.
408
408
This puts the files in the Added state, so that they will be
409
409
recorded by the next commit.
595
587
return self.revision_store[revision_id]
596
588
except IndexError:
597
raise bzrlib.errors.NoSuchRevision(revision_id)
589
raise bzrlib.errors.NoSuchRevision(self, revision_id)
595
get_revision_xml = get_revision_xml_file
602
598
def get_revision(self, revision_id):
603
599
"""Return the Revision object for a named revision"""
604
xml_file = self.get_revision_xml(revision_id)
600
xml_file = self.get_revision_xml_file(revision_id)
607
r = unpack_xml(Revision, xml_file)
603
r = bzrlib.xml.serializer_v4.read_revision(xml_file)
608
604
except SyntaxError, e:
609
605
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
655
651
parameter which can be either an integer revno or a
657
653
from bzrlib.inventory import Inventory
658
from bzrlib.xml import unpack_xml
660
return unpack_xml(Inventory, self.inventory_store[inventory_id])
655
f = self.get_inventory_xml_file(inventory_id)
656
return bzrlib.xml.serializer_v4.read_inventory(f)
659
def get_inventory_xml(self, inventory_id):
660
"""Get inventory XML as a file object."""
661
return self.inventory_store[inventory_id]
663
get_inventory_xml_file = get_inventory_xml
663
666
def get_inventory_sha1(self, inventory_id):
664
667
"""Return the sha1 hash of the inventory entry
666
return sha_file(self.inventory_store[inventory_id])
669
return sha_file(self.get_inventory_xml(inventory_id))
669
672
def get_revision_inventory(self, revision_id):
794
797
if stop_revision is None:
795
798
stop_revision = other_len
796
799
elif stop_revision > other_len:
797
raise NoSuchRevision(self, stop_revision)
800
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
799
802
return other_history[self_len:stop_revision]
802
805
def update_revisions(self, other, stop_revision=None):
803
806
"""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
808
from bzrlib.fetch import greedy_fetch
809
from bzrlib.revision import get_intervening_revisions
811
pb = bzrlib.ui.ui_factory.progress_bar()
829
812
pb.update('comparing histories')
830
revision_ids = self.missing_revisions(other, stop_revision)
815
revision_ids = self.missing_revisions(other, stop_revision)
816
except DivergedBranches, e:
818
if stop_revision is None:
819
end_revision = other.last_patch()
820
revision_ids = get_intervening_revisions(self.last_patch(),
822
assert self.last_patch() not in revision_ids
823
except bzrlib.errors.NotAncestor:
826
if len(revision_ids) > 0:
827
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
830
self.append_revision(*revision_ids)
831
## note("Added %d revisions." % count)
834
def install_revisions(self, other, revision_ids, pb):
832
835
if hasattr(other.revision_store, "prefetch"):
833
836
other.revision_store.prefetch(revision_ids)
834
837
if hasattr(other.inventory_store, "prefetch"):
835
838
inventory_ids = [other.get_revision(r).inventory_id
836
839
for r in revision_ids]
837
840
other.inventory_store.prefetch(inventory_ids)
843
pb = bzrlib.ui.ui_factory.progress_bar()
840
846
needed_texts = set()
842
for rev_id in revision_ids:
844
pb.update('fetching revision', i, len(revision_ids))
845
rev = other.get_revision(rev_id)
850
for i, rev_id in enumerate(revision_ids):
851
pb.update('fetching revision', i+1, len(revision_ids))
853
rev = other.get_revision(rev_id)
854
except bzrlib.errors.NoSuchRevision:
846
858
revisions.append(rev)
847
859
inv = other.get_inventory(str(rev.inventory_id))
848
860
for key, entry in inv.iter_entries():
856
count = self.text_store.copy_multi(other.text_store, needed_texts)
857
print "Added %d texts." % count
868
count, cp_fail = self.text_store.copy_multi(other.text_store,
870
#print "Added %d texts." % count
858
871
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
872
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
874
#print "Added %d inventories." % count
862
875
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
877
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
880
assert len(cp_fail) == 0
881
return count, failures
870
884
def commit(self, *args, **kw):
871
885
from bzrlib.commit import commit
872
886
commit(self, *args, **kw)
875
889
def lookup_revision(self, revision):
876
890
"""Return the revision identifier for a given revision information."""
877
revno, info = self.get_revision_info(revision)
891
revno, info = self._get_revision_info(revision)
895
def revision_id_to_revno(self, revision_id):
896
"""Given a revision id, return its revno"""
897
history = self.revision_history()
899
return history.index(revision_id) + 1
901
raise bzrlib.errors.NoSuchRevision(self, revision_id)
880
904
def get_revision_info(self, revision):
881
905
"""Return (revno, revision id) for revision identifier.
885
909
revision can also be a string, in which case it is parsed for something like
886
910
'date:' or 'revid:' etc.
912
revno, rev_id = self._get_revision_info(revision)
914
raise bzrlib.errors.NoSuchRevision(self, revision)
917
def get_rev_id(self, revno, history=None):
918
"""Find the revision id of the specified revno."""
922
history = self.revision_history()
923
elif revno <= 0 or revno > len(history):
924
raise bzrlib.errors.NoSuchRevision(self, revno)
925
return history[revno - 1]
927
def _get_revision_info(self, revision):
928
"""Return (revno, revision id) for revision specifier.
930
revision can be an integer, in which case it is assumed to be revno
931
(though this will translate negative values into positive ones)
932
revision can also be a string, in which case it is parsed for something
933
like 'date:' or 'revid:' etc.
935
A revid is always returned. If it is None, the specifier referred to
936
the null revision. If the revid does not occur in the revision
937
history, revno will be None.
888
940
if revision is None:
895
947
revs = self.revision_history()
896
948
if isinstance(revision, int):
899
# Mabye we should do this first, but we don't need it if revision == 0
901
950
revno = len(revs) + revision + 1
953
rev_id = self.get_rev_id(revno, revs)
904
954
elif isinstance(revision, basestring):
905
955
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
906
956
if revision.startswith(prefix):
907
revno = func(self, revs, revision)
957
result = func(self, revs, revision)
959
revno, rev_id = result
962
rev_id = self.get_rev_id(revno, revs)
910
raise BzrError('No namespace registered for string: %r' % revision)
965
raise BzrError('No namespace registered for string: %r' %
968
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]
972
raise bzrlib.errors.NoSuchRevision(self, revision)
916
975
def _namespace_revno(self, revs, revision):
917
976
"""Lookup a revision by revision number"""
918
977
assert revision.startswith('revno:')
920
return int(revision[6:])
979
return (int(revision[6:]),)
921
980
except ValueError:
923
982
REVISION_NAMESPACES['revno:'] = _namespace_revno
925
984
def _namespace_revid(self, revs, revision):
926
985
assert revision.startswith('revid:')
986
rev_id = revision[len('revid:'):]
928
return revs.index(revision[6:]) + 1
988
return revs.index(rev_id) + 1, rev_id
929
989
except ValueError:
931
991
REVISION_NAMESPACES['revid:'] = _namespace_revid
933
993
def _namespace_last(self, revs, revision):
1331
def get_parent(self):
1332
"""Return the parent location of the branch.
1334
This is the default location for push/pull/missing. The usual
1335
pattern is that the user can override it by specifying a
1339
_locs = ['parent', 'pull', 'x-pull']
1342
return self.controlfile(l, 'r').read().strip('\n')
1344
if e.errno != errno.ENOENT:
1349
def set_parent(self, url):
1350
# TODO: Maybe delete old location files?
1351
from bzrlib.atomicfile import AtomicFile
1354
f = AtomicFile(self.controlfilename('parent'))
1363
def check_revno(self, revno):
1365
Check whether a revno corresponds to any revision.
1366
Zero (the NULL revision) is considered valid.
1369
self.check_real_revno(revno)
1371
def check_real_revno(self, revno):
1373
Check whether a revno corresponds to a real revision.
1374
Zero (the NULL revision) is considered invalid
1376
if revno < 1 or revno > self.revno():
1377
raise InvalidRevisionNumber(revno)
1268
1382
class ScratchBranch(Branch):
1269
1383
"""Special test class: a branch that cleans up after itself.
1386
1502
"""Return a new tree-root file id."""
1387
1503
return gen_file_id('TREE_ROOT')
1506
def pull_loc(branch):
1507
# TODO: Should perhaps just make attribute be 'base' in
1508
# RemoteBranch and Branch?
1509
if hasattr(branch, "baseurl"):
1510
return branch.baseurl
1515
def copy_branch(branch_from, to_location, revision=None):
1516
"""Copy branch_from into the existing directory to_location.
1519
If not None, only revisions up to this point will be copied.
1520
The head of the new branch will be that revision.
1523
The name of a local directory that exists but is empty.
1525
from bzrlib.merge import merge
1526
from bzrlib.branch import Branch
1528
assert isinstance(branch_from, Branch)
1529
assert isinstance(to_location, basestring)
1531
br_to = Branch(to_location, init=True)
1532
br_to.set_root_id(branch_from.get_root_id())
1533
if revision is None:
1534
revno = branch_from.revno()
1536
revno, rev_id = branch_from.get_revision_info(revision)
1537
br_to.update_revisions(branch_from, stop_revision=revno)
1538
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1539
check_clean=False, ignore_zero=True)
1541
from_location = pull_loc(branch_from)
1542
br_to.set_parent(pull_loc(branch_from))