15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
21
from bzrlib.trace import mutter, note
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
25
23
sha_file, appendpath, file_kind
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
29
from bzrlib.textui import show_status
30
from bzrlib.revision import Revision
31
from bzrlib.xml import unpack_xml
32
from bzrlib.delta import compare_trees
33
from bzrlib.tree import EmptyTree, RevisionTree
24
from bzrlib.errors import BzrError
38
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
39
27
## TODO: Maybe include checks for common corruption of newlines, etc?
42
# TODO: Some operations like log might retrieve the same revisions
43
# repeatedly to calculate deltas. We could perhaps have a weakref
44
# cache in memory to make this faster.
46
# TODO: please move the revision-string syntax stuff out of the branch
47
# object; it's clutter
50
31
def find_branch(f, **args):
51
32
if f and (f.startswith('http://') or f.startswith('https://')):
601
def get_revision_xml(self, revision_id):
602
"""Return XML file object for revision object."""
603
if not revision_id or not isinstance(revision_id, basestring):
604
raise InvalidRevisionId(revision_id)
609
return self.revision_store[revision_id]
611
raise bzrlib.errors.NoSuchRevision(self, revision_id)
616
562
def get_revision(self, revision_id):
617
563
"""Return the Revision object for a named revision"""
618
xml_file = self.get_revision_xml(revision_id)
564
from bzrlib.revision import Revision
565
from bzrlib.xml import unpack_xml
621
r = unpack_xml(Revision, xml_file)
622
except SyntaxError, e:
623
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
569
if not revision_id or not isinstance(revision_id, basestring):
570
raise ValueError('invalid revision-id: %r' % revision_id)
571
r = unpack_xml(Revision, self.revision_store[revision_id])
627
575
assert r.revision_id == revision_id
631
def get_revision_delta(self, revno):
632
"""Return the delta for one revision.
634
The delta is relative to its mainline predecessor, or the
635
empty tree for revision 1.
637
assert isinstance(revno, int)
638
rh = self.revision_history()
639
if not (1 <= revno <= len(rh)):
640
raise InvalidRevisionNumber(revno)
642
# revno is 1-based; list is 0-based
644
new_tree = self.revision_tree(rh[revno-1])
646
old_tree = EmptyTree()
648
old_tree = self.revision_tree(rh[revno-2])
650
return compare_trees(old_tree, new_tree)
654
579
def get_revision_sha1(self, revision_id):
813
754
if stop_revision is None:
814
755
stop_revision = other_len
815
756
elif stop_revision > other_len:
816
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
757
raise NoSuchRevision(self, stop_revision)
818
759
return other_history[self_len:stop_revision]
821
762
def update_revisions(self, other, stop_revision=None):
822
763
"""Pull in all new revisions from other branch.
765
>>> from bzrlib.commit import commit
766
>>> bzrlib.trace.silent = True
767
>>> br1 = ScratchBranch(files=['foo', 'bar'])
770
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
771
>>> br2 = ScratchBranch()
772
>>> br2.update_revisions(br1)
776
>>> br2.revision_history()
778
>>> br2.update_revisions(br1)
782
>>> br1.text_store.total_size() == br2.text_store.total_size()
824
from bzrlib.fetch import greedy_fetch
826
pb = bzrlib.ui.ui_factory.progress_bar()
785
from bzrlib.progress import ProgressBar
789
from sets import Set as set
827
793
pb.update('comparing histories')
829
794
revision_ids = self.missing_revisions(other, stop_revision)
831
if len(revision_ids) > 0:
832
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
835
self.append_revision(*revision_ids)
836
## note("Added %d revisions." % count)
839
def install_revisions(self, other, revision_ids, pb):
840
796
if hasattr(other.revision_store, "prefetch"):
841
797
other.revision_store.prefetch(revision_ids)
842
798
if hasattr(other.inventory_store, "prefetch"):
843
799
inventory_ids = [other.get_revision(r).inventory_id
844
800
for r in revision_ids]
845
801
other.inventory_store.prefetch(inventory_ids)
848
pb = bzrlib.ui.ui_factory.progress_bar()
851
804
needed_texts = set()
855
for i, rev_id in enumerate(revision_ids):
856
pb.update('fetching revision', i+1, len(revision_ids))
858
rev = other.get_revision(rev_id)
859
except bzrlib.errors.NoSuchRevision:
806
for rev_id in revision_ids:
808
pb.update('fetching revision', i, len(revision_ids))
809
rev = other.get_revision(rev_id)
863
810
revisions.append(rev)
864
811
inv = other.get_inventory(str(rev.inventory_id))
865
812
for key, entry in inv.iter_entries():
873
count, cp_fail = self.text_store.copy_multi(other.text_store,
820
count = self.text_store.copy_multi(other.text_store, needed_texts)
875
821
print "Added %d texts." % count
876
822
inventory_ids = [ f.inventory_id for f in revisions ]
877
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
823
count = self.inventory_store.copy_multi(other.inventory_store,
879
825
print "Added %d inventories." % count
880
826
revision_ids = [ f.revision_id for f in revisions]
882
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
885
assert len(cp_fail) == 0
886
return count, failures
827
count = self.revision_store.copy_multi(other.revision_store,
829
for revision_id in revision_ids:
830
self.append_revision(revision_id)
831
print "Added %d revisions." % count
889
834
def commit(self, *args, **kw):
890
835
from bzrlib.commit import commit
891
836
commit(self, *args, **kw)
894
def lookup_revision(self, revision):
895
"""Return the revision identifier for a given revision information."""
896
revno, info = self.get_revision_info(revision)
900
def revision_id_to_revno(self, revision_id):
901
"""Given a revision id, return its revno"""
902
history = self.revision_history()
904
return history.index(revision_id) + 1
906
raise bzrlib.errors.NoSuchRevision(self, revision_id)
909
def get_revision_info(self, revision):
910
"""Return (revno, revision id) for revision identifier.
912
revision can be an integer, in which case it is assumed to be revno (though
913
this will translate negative values into positive ones)
914
revision can also be a string, in which case it is parsed for something like
915
'date:' or 'revid:' etc.
920
try:# Convert to int if possible
921
revision = int(revision)
924
revs = self.revision_history()
925
if isinstance(revision, int):
928
# Mabye we should do this first, but we don't need it if revision == 0
930
revno = len(revs) + revision + 1
933
elif isinstance(revision, basestring):
934
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
935
if revision.startswith(prefix):
936
revno = func(self, revs, revision)
939
raise BzrError('No namespace registered for string: %r' % revision)
941
if revno is None or revno <= 0 or revno > len(revs):
942
raise BzrError("no such revision %s" % revision)
943
return revno, revs[revno-1]
945
def _namespace_revno(self, revs, revision):
946
"""Lookup a revision by revision number"""
947
assert revision.startswith('revno:')
949
return int(revision[6:])
952
REVISION_NAMESPACES['revno:'] = _namespace_revno
954
def _namespace_revid(self, revs, revision):
955
assert revision.startswith('revid:')
957
return revs.index(revision[6:]) + 1
960
REVISION_NAMESPACES['revid:'] = _namespace_revid
962
def _namespace_last(self, revs, revision):
963
assert revision.startswith('last:')
965
offset = int(revision[5:])
970
raise BzrError('You must supply a positive value for --revision last:XXX')
971
return len(revs) - offset + 1
972
REVISION_NAMESPACES['last:'] = _namespace_last
974
def _namespace_tag(self, revs, revision):
975
assert revision.startswith('tag:')
976
raise BzrError('tag: namespace registered, but not implemented.')
977
REVISION_NAMESPACES['tag:'] = _namespace_tag
979
def _namespace_date(self, revs, revision):
980
assert revision.startswith('date:')
982
# Spec for date revisions:
984
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
985
# it can also start with a '+/-/='. '+' says match the first
986
# entry after the given date. '-' is match the first entry before the date
987
# '=' is match the first entry after, but still on the given date.
989
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
990
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
991
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
992
# May 13th, 2005 at 0:00
994
# So the proper way of saying 'give me all entries for today' is:
995
# -r {date:+today}:{date:-tomorrow}
996
# The default is '=' when not supplied
999
if val[:1] in ('+', '-', '='):
1000
match_style = val[:1]
1003
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1004
if val.lower() == 'yesterday':
1005
dt = today - datetime.timedelta(days=1)
1006
elif val.lower() == 'today':
1008
elif val.lower() == 'tomorrow':
1009
dt = today + datetime.timedelta(days=1)
1012
# This should be done outside the function to avoid recompiling it.
1013
_date_re = re.compile(
1014
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1016
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1018
m = _date_re.match(val)
1019
if not m or (not m.group('date') and not m.group('time')):
1020
raise BzrError('Invalid revision date %r' % revision)
1023
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1025
year, month, day = today.year, today.month, today.day
1027
hour = int(m.group('hour'))
1028
minute = int(m.group('minute'))
1029
if m.group('second'):
1030
second = int(m.group('second'))
1034
hour, minute, second = 0,0,0
1036
dt = datetime.datetime(year=year, month=month, day=day,
1037
hour=hour, minute=minute, second=second)
1041
if match_style == '-':
1043
elif match_style == '=':
1044
last = dt + datetime.timedelta(days=1)
1047
for i in range(len(revs)-1, -1, -1):
1048
r = self.get_revision(revs[i])
1049
# TODO: Handle timezone.
1050
dt = datetime.datetime.fromtimestamp(r.timestamp)
1051
if first >= dt and (last is None or dt >= last):
1054
for i in range(len(revs)):
1055
r = self.get_revision(revs[i])
1056
# TODO: Handle timezone.
1057
dt = datetime.datetime.fromtimestamp(r.timestamp)
1058
if first <= dt and (last is None or dt <= last):
1060
REVISION_NAMESPACES['date:'] = _namespace_date
839
def lookup_revision(self, revno):
840
"""Return revision hash for revision number."""
845
# list is 0-based; revisions are 1-based
846
return self.revision_history()[revno-1]
848
raise BzrError("no such revision %s" % revno)
1062
851
def revision_tree(self, revision_id):
1063
852
"""Return Tree for a revision on this branch.
1065
854
`revision_id` may be None for the null revision, in which case
1066
855
an `EmptyTree` is returned."""
856
from bzrlib.tree import EmptyTree, RevisionTree
1067
857
# TODO: refactor this to use an existing revision object
1068
858
# so we don't need to read it in twice.
1069
859
if revision_id == None: