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, \
27
29
import bzrlib.errors
28
30
from bzrlib.textui import show_status
29
31
from bzrlib.revision import Revision
30
from bzrlib.xml import unpack_xml
31
32
from bzrlib.delta import compare_trees
32
33
from bzrlib.tree import EmptyTree, RevisionTree
34
39
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
40
## TODO: Maybe include checks for common corruption of newlines, etc?
120
129
head, tail = os.path.split(f)
122
131
# reached the root, whatever that may be
123
raise BzrError('%r is not in a branch' % orig_f)
132
raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
126
class DivergedBranches(Exception):
127
def __init__(self, branch1, branch2):
128
self.branch1 = branch1
129
self.branch2 = branch2
130
Exception.__init__(self, "These branches have diverged.")
133
138
######################################################################
251
252
self._lock = None
252
253
self._lock_mode = self._lock_count = None
255
255
def abspath(self, name):
256
256
"""Return absolute filename for something in the branch"""
257
257
return os.path.join(self.base, name)
260
259
def relpath(self, path):
261
260
"""Return path relative to this branch of something inside it.
263
262
Raises an error if path is not in this branch."""
264
263
return _relpath(self.base, path)
267
265
def controlfilename(self, file_or_path):
268
266
"""Return location relative to branch."""
269
267
if isinstance(file_or_path, basestring):
316
311
self.controlfile(f, 'w').write('')
317
312
mutter('created control directory in ' + self.base)
319
pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
314
# if we want per-tree root ids then this is the place to set
315
# them; they're not needed for now and so ommitted for
317
f = self.controlfile('inventory','w')
318
bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
322
321
def _check_format(self):
331
330
# on Windows from Linux and so on. I think it might be better
332
331
# to always make all internal files in unix format.
333
332
fmt = self.controlfile('branch-format', 'r').read()
334
fmt.replace('\r\n', '')
333
fmt = fmt.replace('\r\n', '\n')
335
334
if fmt != BZR_BRANCH_FORMAT:
336
335
raise BzrError('sorry, branch format %r not supported' % fmt,
337
336
['use a different bzr version',
357
356
def read_working_inventory(self):
358
357
"""Read the working inventory."""
359
358
from bzrlib.inventory import Inventory
360
from bzrlib.xml import unpack_xml
361
from time import time
365
361
# 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))
363
f = self.controlfile('inventory', 'rb')
364
return bzrlib.xml.serializer_v4.read_inventory(f)
400
392
"""Inventory for the working copy.""")
403
def add(self, files, verbose=False, ids=None):
395
def add(self, files, ids=None):
404
396
"""Make files versioned.
406
Note that the command line normally calls smart_add instead.
398
Note that the command line normally calls smart_add instead,
399
which can automatically recurse.
408
401
This puts the files in the Added state, so that they will be
409
402
recorded by the next commit.
419
412
TODO: Perhaps have an option to add the ids even if the files do
422
TODO: Perhaps return the ids of the files? But then again it
423
is easy to retrieve them if they're needed.
425
TODO: Adding a directory should optionally recurse down and
426
add all non-ignored children. Perhaps do that in a
415
TODO: Perhaps yield the ids and paths as they're added.
429
417
# TODO: Re-adding a file that is removed in the working copy
430
418
# should probably put it back with the previous ID.
595
580
return self.revision_store[revision_id]
596
581
except IndexError:
597
raise bzrlib.errors.NoSuchRevision(revision_id)
582
raise bzrlib.errors.NoSuchRevision(self, revision_id)
588
get_revision_xml = get_revision_xml_file
602
591
def get_revision(self, revision_id):
603
592
"""Return the Revision object for a named revision"""
604
xml_file = self.get_revision_xml(revision_id)
593
xml_file = self.get_revision_xml_file(revision_id)
607
r = unpack_xml(Revision, xml_file)
596
r = bzrlib.xml.serializer_v4.read_revision(xml_file)
608
597
except SyntaxError, e:
609
598
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
655
644
parameter which can be either an integer revno or a
657
646
from bzrlib.inventory import Inventory
658
from bzrlib.xml import unpack_xml
660
return unpack_xml(Inventory, self.inventory_store[inventory_id])
648
f = self.get_inventory_xml_file(inventory_id)
649
return bzrlib.xml.serializer_v4.read_inventory(f)
652
def get_inventory_xml(self, inventory_id):
653
"""Get inventory XML as a file object."""
654
return self.inventory_store[inventory_id]
656
get_inventory_xml_file = get_inventory_xml
663
659
def get_inventory_sha1(self, inventory_id):
664
660
"""Return the sha1 hash of the inventory entry
666
return sha_file(self.inventory_store[inventory_id])
662
return sha_file(self.get_inventory_xml(inventory_id))
669
665
def get_revision_inventory(self, revision_id):
758
def missing_revisions(self, other, stop_revision=None):
754
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
760
756
If self and other have not diverged, return a list of the revisions
761
757
present in other, but missing from self.
794
790
if stop_revision is None:
795
791
stop_revision = other_len
796
792
elif stop_revision > other_len:
797
raise NoSuchRevision(self, stop_revision)
793
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
799
795
return other_history[self_len:stop_revision]
802
798
def update_revisions(self, other, stop_revision=None):
803
799
"""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
801
from bzrlib.fetch import greedy_fetch
803
pb = bzrlib.ui.ui_factory.progress_bar()
829
804
pb.update('comparing histories')
830
806
revision_ids = self.missing_revisions(other, stop_revision)
808
if len(revision_ids) > 0:
809
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
812
self.append_revision(*revision_ids)
813
## note("Added %d revisions." % count)
816
def install_revisions(self, other, revision_ids, pb):
832
817
if hasattr(other.revision_store, "prefetch"):
833
818
other.revision_store.prefetch(revision_ids)
834
819
if hasattr(other.inventory_store, "prefetch"):
835
820
inventory_ids = [other.get_revision(r).inventory_id
836
821
for r in revision_ids]
837
822
other.inventory_store.prefetch(inventory_ids)
825
pb = bzrlib.ui.ui_factory.progress_bar()
840
828
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)
832
for i, rev_id in enumerate(revision_ids):
833
pb.update('fetching revision', i+1, len(revision_ids))
835
rev = other.get_revision(rev_id)
836
except bzrlib.errors.NoSuchRevision:
846
840
revisions.append(rev)
847
841
inv = other.get_inventory(str(rev.inventory_id))
848
842
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
850
count, cp_fail = self.text_store.copy_multi(other.text_store,
852
#print "Added %d texts." % count
858
853
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
854
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
856
#print "Added %d inventories." % count
862
857
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
859
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
862
assert len(cp_fail) == 0
863
return count, failures
870
866
def commit(self, *args, **kw):
871
867
from bzrlib.commit import commit
872
868
commit(self, *args, **kw)
875
871
def lookup_revision(self, revision):
876
872
"""Return the revision identifier for a given revision information."""
877
revno, info = self.get_revision_info(revision)
873
revno, info = self._get_revision_info(revision)
877
def revision_id_to_revno(self, revision_id):
878
"""Given a revision id, return its revno"""
879
history = self.revision_history()
881
return history.index(revision_id) + 1
883
raise bzrlib.errors.NoSuchRevision(self, revision_id)
880
886
def get_revision_info(self, revision):
881
887
"""Return (revno, revision id) for revision identifier.
885
891
revision can also be a string, in which case it is parsed for something like
886
892
'date:' or 'revid:' etc.
894
revno, rev_id = self._get_revision_info(revision)
896
raise bzrlib.errors.NoSuchRevision(self, revision)
899
def get_rev_id(self, revno, history=None):
900
"""Find the revision id of the specified revno."""
904
history = self.revision_history()
905
elif revno <= 0 or revno > len(history):
906
raise bzrlib.errors.NoSuchRevision(self, revno)
907
return history[revno - 1]
909
def _get_revision_info(self, revision):
910
"""Return (revno, revision id) for revision specifier.
912
revision can be an integer, in which case it is assumed to be revno
913
(though this will translate negative values into positive ones)
914
revision can also be a string, in which case it is parsed for something
915
like 'date:' or 'revid:' etc.
917
A revid is always returned. If it is None, the specifier referred to
918
the null revision. If the revid does not occur in the revision
919
history, revno will be None.
888
922
if revision is None:
895
929
revs = self.revision_history()
896
930
if isinstance(revision, int):
899
# Mabye we should do this first, but we don't need it if revision == 0
901
932
revno = len(revs) + revision + 1
935
rev_id = self.get_rev_id(revno, revs)
904
936
elif isinstance(revision, basestring):
905
937
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
906
938
if revision.startswith(prefix):
907
revno = func(self, revs, revision)
939
result = func(self, revs, revision)
941
revno, rev_id = result
944
rev_id = self.get_rev_id(revno, revs)
910
raise BzrError('No namespace registered for string: %r' % revision)
947
raise BzrError('No namespace registered for string: %r' %
950
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]
954
raise bzrlib.errors.NoSuchRevision(self, revision)
916
957
def _namespace_revno(self, revs, revision):
917
958
"""Lookup a revision by revision number"""
918
959
assert revision.startswith('revno:')
920
return int(revision[6:])
961
return (int(revision[6:]),)
921
962
except ValueError:
923
964
REVISION_NAMESPACES['revno:'] = _namespace_revno
925
966
def _namespace_revid(self, revs, revision):
926
967
assert revision.startswith('revid:')
968
rev_id = revision[len('revid:'):]
928
return revs.index(revision[6:]) + 1
970
return revs.index(rev_id) + 1, rev_id
929
971
except ValueError:
931
973
REVISION_NAMESPACES['revid:'] = _namespace_revid
933
975
def _namespace_last(self, revs, revision):
936
978
offset = int(revision[5:])
937
979
except ValueError:
941
983
raise BzrError('You must supply a positive value for --revision last:XXX')
942
return len(revs) - offset + 1
984
return (len(revs) - offset + 1,)
943
985
REVISION_NAMESPACES['last:'] = _namespace_last
945
987
def _namespace_tag(self, revs, revision):
1020
1062
# TODO: Handle timezone.
1021
1063
dt = datetime.datetime.fromtimestamp(r.timestamp)
1022
1064
if first >= dt and (last is None or dt >= last):
1025
1067
for i in range(len(revs)):
1026
1068
r = self.get_revision(revs[i])
1027
1069
# TODO: Handle timezone.
1028
1070
dt = datetime.datetime.fromtimestamp(r.timestamp)
1029
1071
if first <= dt and (last is None or dt <= last):
1031
1073
REVISION_NAMESPACES['date:'] = _namespace_date
1033
1075
def revision_tree(self, revision_id):
1165
1209
for f in from_paths:
1166
1210
name_tail = splitpath(f)[-1]
1167
1211
dest_path = appendpath(to_name, name_tail)
1168
print "%s => %s" % (f, dest_path)
1212
result.append((f, dest_path))
1169
1213
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1171
1215
os.rename(self.abspath(f), self.abspath(dest_path))
1313
def get_parent(self):
1314
"""Return the parent location of the branch.
1316
This is the default location for push/pull/missing. The usual
1317
pattern is that the user can override it by specifying a
1321
_locs = ['parent', 'pull', 'x-pull']
1324
return self.controlfile(l, 'r').read().strip('\n')
1326
if e.errno != errno.ENOENT:
1331
def set_parent(self, url):
1332
# TODO: Maybe delete old location files?
1333
from bzrlib.atomicfile import AtomicFile
1336
f = AtomicFile(self.controlfilename('parent'))
1345
def check_revno(self, revno):
1347
Check whether a revno corresponds to any revision.
1348
Zero (the NULL revision) is considered valid.
1351
self.check_real_revno(revno)
1353
def check_real_revno(self, revno):
1355
Check whether a revno corresponds to a real revision.
1356
Zero (the NULL revision) is considered invalid
1358
if revno < 1 or revno > self.revno():
1359
raise InvalidRevisionNumber(revno)
1268
1364
class ScratchBranch(Branch):
1269
1365
"""Special test class: a branch that cleans up after itself.
1386
1484
"""Return a new tree-root file id."""
1387
1485
return gen_file_id('TREE_ROOT')
1488
def pull_loc(branch):
1489
# TODO: Should perhaps just make attribute be 'base' in
1490
# RemoteBranch and Branch?
1491
if hasattr(branch, "baseurl"):
1492
return branch.baseurl
1497
def copy_branch(branch_from, to_location, revision=None):
1498
"""Copy branch_from into the existing directory to_location.
1501
If not None, only revisions up to this point will be copied.
1502
The head of the new branch will be that revision.
1505
The name of a local directory that exists but is empty.
1507
from bzrlib.merge import merge
1508
from bzrlib.branch import Branch
1510
assert isinstance(branch_from, Branch)
1511
assert isinstance(to_location, basestring)
1513
br_to = Branch(to_location, init=True)
1514
br_to.set_root_id(branch_from.get_root_id())
1515
if revision is None:
1516
revno = branch_from.revno()
1518
revno, rev_id = branch_from.get_revision_info(revision)
1519
br_to.update_revisions(branch_from, stop_revision=revno)
1520
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1521
check_clean=False, ignore_zero=True)
1523
from_location = pull_loc(branch_from)
1524
br_to.set_parent(pull_loc(branch_from))