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://')):
594
def get_revision_xml(self, revision_id):
595
"""Return XML file object for revision object."""
596
if not revision_id or not isinstance(revision_id, basestring):
597
raise InvalidRevisionId(revision_id)
602
return self.revision_store[revision_id]
604
raise bzrlib.errors.NoSuchRevision(self, revision_id)
609
562
def get_revision(self, revision_id):
610
563
"""Return the Revision object for a named revision"""
611
xml_file = self.get_revision_xml(revision_id)
564
from bzrlib.revision import Revision
565
from bzrlib.xml import unpack_xml
614
r = unpack_xml(Revision, xml_file)
615
except SyntaxError, e:
616
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])
620
575
assert r.revision_id == revision_id
624
def get_revision_delta(self, revno):
625
"""Return the delta for one revision.
627
The delta is relative to its mainline predecessor, or the
628
empty tree for revision 1.
630
assert isinstance(revno, int)
631
rh = self.revision_history()
632
if not (1 <= revno <= len(rh)):
633
raise InvalidRevisionNumber(revno)
635
# revno is 1-based; list is 0-based
637
new_tree = self.revision_tree(rh[revno-1])
639
old_tree = EmptyTree()
641
old_tree = self.revision_tree(rh[revno-2])
643
return compare_trees(old_tree, new_tree)
647
579
def get_revision_sha1(self, revision_id):
806
754
if stop_revision is None:
807
755
stop_revision = other_len
808
756
elif stop_revision > other_len:
809
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
757
raise NoSuchRevision(self, stop_revision)
811
759
return other_history[self_len:stop_revision]
814
762
def update_revisions(self, other, stop_revision=None):
815
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()
817
from bzrlib.fetch import greedy_fetch
819
pb = bzrlib.ui.ui_factory.progress_bar()
785
from bzrlib.progress import ProgressBar
789
from sets import Set as set
820
793
pb.update('comparing histories')
822
794
revision_ids = self.missing_revisions(other, stop_revision)
824
if len(revision_ids) > 0:
825
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
828
self.append_revision(*revision_ids)
829
## note("Added %d revisions." % count)
834
def install_revisions(self, other, revision_ids, pb):
835
796
if hasattr(other.revision_store, "prefetch"):
836
797
other.revision_store.prefetch(revision_ids)
837
798
if hasattr(other.inventory_store, "prefetch"):
838
799
inventory_ids = [other.get_revision(r).inventory_id
839
800
for r in revision_ids]
840
801
other.inventory_store.prefetch(inventory_ids)
843
pb = bzrlib.ui.ui_factory.progress_bar()
846
804
needed_texts = set()
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:
806
for rev_id in revision_ids:
808
pb.update('fetching revision', i, len(revision_ids))
809
rev = other.get_revision(rev_id)
858
810
revisions.append(rev)
859
811
inv = other.get_inventory(str(rev.inventory_id))
860
812
for key, entry in inv.iter_entries():
868
count, cp_fail = self.text_store.copy_multi(other.text_store,
870
#print "Added %d texts." % count
820
count = self.text_store.copy_multi(other.text_store, needed_texts)
821
print "Added %d texts." % count
871
822
inventory_ids = [ f.inventory_id for f in revisions ]
872
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
874
#print "Added %d inventories." % count
823
count = self.inventory_store.copy_multi(other.inventory_store,
825
print "Added %d inventories." % count
875
826
revision_ids = [ f.revision_id for f in revisions]
877
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
880
assert len(cp_fail) == 0
881
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
884
834
def commit(self, *args, **kw):
885
835
from bzrlib.commit import commit
886
836
commit(self, *args, **kw)
889
def lookup_revision(self, revision):
890
"""Return the revision identifier for a given revision information."""
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)
904
def get_revision_info(self, revision):
905
"""Return (revno, revision id) for revision identifier.
907
revision can be an integer, in which case it is assumed to be revno (though
908
this will translate negative values into positive ones)
909
revision can also be a string, in which case it is parsed for something like
910
'date:' or 'revid:' etc.
915
try:# Convert to int if possible
916
revision = int(revision)
919
revs = self.revision_history()
920
if isinstance(revision, int):
923
# Mabye we should do this first, but we don't need it if revision == 0
925
revno = len(revs) + revision + 1
928
elif isinstance(revision, basestring):
929
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
930
if revision.startswith(prefix):
931
revno = func(self, revs, revision)
934
raise BzrError('No namespace registered for string: %r' % revision)
936
if revno is None or revno <= 0 or revno > len(revs):
937
raise BzrError("no such revision %s" % revision)
938
return revno, revs[revno-1]
940
def _namespace_revno(self, revs, revision):
941
"""Lookup a revision by revision number"""
942
assert revision.startswith('revno:')
944
return int(revision[6:])
947
REVISION_NAMESPACES['revno:'] = _namespace_revno
949
def _namespace_revid(self, revs, revision):
950
assert revision.startswith('revid:')
952
return revs.index(revision[6:]) + 1
955
REVISION_NAMESPACES['revid:'] = _namespace_revid
957
def _namespace_last(self, revs, revision):
958
assert revision.startswith('last:')
960
offset = int(revision[5:])
965
raise BzrError('You must supply a positive value for --revision last:XXX')
966
return len(revs) - offset + 1
967
REVISION_NAMESPACES['last:'] = _namespace_last
969
def _namespace_tag(self, revs, revision):
970
assert revision.startswith('tag:')
971
raise BzrError('tag: namespace registered, but not implemented.')
972
REVISION_NAMESPACES['tag:'] = _namespace_tag
974
def _namespace_date(self, revs, revision):
975
assert revision.startswith('date:')
977
# Spec for date revisions:
979
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
980
# it can also start with a '+/-/='. '+' says match the first
981
# entry after the given date. '-' is match the first entry before the date
982
# '=' is match the first entry after, but still on the given date.
984
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
985
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
986
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
987
# May 13th, 2005 at 0:00
989
# So the proper way of saying 'give me all entries for today' is:
990
# -r {date:+today}:{date:-tomorrow}
991
# The default is '=' when not supplied
994
if val[:1] in ('+', '-', '='):
995
match_style = val[:1]
998
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
999
if val.lower() == 'yesterday':
1000
dt = today - datetime.timedelta(days=1)
1001
elif val.lower() == 'today':
1003
elif val.lower() == 'tomorrow':
1004
dt = today + datetime.timedelta(days=1)
1007
# This should be done outside the function to avoid recompiling it.
1008
_date_re = re.compile(
1009
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1011
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1013
m = _date_re.match(val)
1014
if not m or (not m.group('date') and not m.group('time')):
1015
raise BzrError('Invalid revision date %r' % revision)
1018
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1020
year, month, day = today.year, today.month, today.day
1022
hour = int(m.group('hour'))
1023
minute = int(m.group('minute'))
1024
if m.group('second'):
1025
second = int(m.group('second'))
1029
hour, minute, second = 0,0,0
1031
dt = datetime.datetime(year=year, month=month, day=day,
1032
hour=hour, minute=minute, second=second)
1036
if match_style == '-':
1038
elif match_style == '=':
1039
last = dt + datetime.timedelta(days=1)
1042
for i in range(len(revs)-1, -1, -1):
1043
r = self.get_revision(revs[i])
1044
# TODO: Handle timezone.
1045
dt = datetime.datetime.fromtimestamp(r.timestamp)
1046
if first >= dt and (last is None or dt >= last):
1049
for i in range(len(revs)):
1050
r = self.get_revision(revs[i])
1051
# TODO: Handle timezone.
1052
dt = datetime.datetime.fromtimestamp(r.timestamp)
1053
if first <= dt and (last is None or dt <= last):
1055
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)
1057
851
def revision_tree(self, revision_id):
1058
852
"""Return Tree for a revision on this branch.
1060
854
`revision_id` may be None for the null revision, in which case
1061
855
an `EmptyTree` is returned."""
856
from bzrlib.tree import EmptyTree, RevisionTree
1062
857
# TODO: refactor this to use an existing revision object
1063
858
# so we don't need to read it in twice.
1064
859
if revision_id == None: