39
43
# repeatedly to calculate deltas. We could perhaps have a weakref
40
44
# cache in memory to make this faster.
43
def find_branch(f, **args):
44
if f and (f.startswith('http://') or f.startswith('https://')):
46
return remotebranch.RemoteBranch(f, **args)
48
return Branch(f, **args)
51
def find_cached_branch(f, cache_root, **args):
52
from remotebranch import RemoteBranch
53
br = find_branch(f, **args)
54
def cacheify(br, store_name):
55
from meta_store import CachedStore
56
cache_path = os.path.join(cache_root, store_name)
58
new_store = CachedStore(getattr(br, store_name), cache_path)
59
setattr(br, store_name, new_store)
61
if isinstance(br, RemoteBranch):
62
cacheify(br, 'inventory_store')
63
cacheify(br, 'text_store')
64
cacheify(br, 'revision_store')
46
def find_branch(*ignored, **ignored_too):
47
# XXX: leave this here for about one release, then remove it
48
raise NotImplementedError('find_branch() is not supported anymore, '
49
'please use one of the new branch constructors')
68
51
def _relpath(base, path):
69
52
"""Return path relative to base, or raise exception.
137
116
"""Branch holding a history of revisions.
140
Base directory of the branch.
119
Base directory/url of the branch.
123
def __init__(self, *ignored, **ignored_too):
124
raise NotImplementedError('The Branch class is abstract')
128
"""Open an existing branch, rooted at 'base' (url)"""
129
if base and (base.startswith('http://') or base.startswith('https://')):
130
from bzrlib.remotebranch import RemoteBranch
131
return RemoteBranch(base, find_root=False)
133
return LocalBranch(base, find_root=False)
136
def open_containing(url):
137
"""Open an existing branch, containing url (search upwards for the root)
139
if url and (url.startswith('http://') or url.startswith('https://')):
140
from bzrlib.remotebranch import RemoteBranch
141
return RemoteBranch(url)
143
return LocalBranch(url)
146
def initialize(base):
147
"""Create a new branch, rooted at 'base' (url)"""
148
if base and (base.startswith('http://') or base.startswith('https://')):
149
from bzrlib.remotebranch import RemoteBranch
150
return RemoteBranch(base, init=True)
152
return LocalBranch(base, init=True)
154
def setup_caching(self, cache_root):
155
"""Subclasses that care about caching should override this, and set
156
up cached stores located under cache_root.
160
class LocalBranch(Branch):
161
"""A branch stored in the actual filesystem.
163
Note that it's "local" in the context of the filesystem; it doesn't
164
really matter if it's on an nfs/smb/afs/coda/... share, as long as
165
it's writable, and can be accessed via the normal filesystem API.
143
168
None, or 'r' or 'w'
794
808
if stop_revision is None:
795
809
stop_revision = other_len
796
810
elif stop_revision > other_len:
797
raise NoSuchRevision(self, stop_revision)
811
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
799
813
return other_history[self_len:stop_revision]
802
816
def update_revisions(self, other, stop_revision=None):
803
817
"""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
819
from bzrlib.fetch import greedy_fetch
820
from bzrlib.revision import get_intervening_revisions
822
pb = bzrlib.ui.ui_factory.progress_bar()
829
823
pb.update('comparing histories')
830
revision_ids = self.missing_revisions(other, stop_revision)
824
if stop_revision is None:
825
other_revision = other.last_patch()
827
other_revision = other.get_rev_id(stop_revision)
828
count = greedy_fetch(self, other, other_revision, pb)[0]
830
revision_ids = self.missing_revisions(other, stop_revision)
831
except DivergedBranches, e:
833
revision_ids = get_intervening_revisions(self.last_patch(),
834
other_revision, self)
835
assert self.last_patch() not in revision_ids
836
except bzrlib.errors.NotAncestor:
839
self.append_revision(*revision_ids)
842
def install_revisions(self, other, revision_ids, pb):
832
843
if hasattr(other.revision_store, "prefetch"):
833
844
other.revision_store.prefetch(revision_ids)
834
845
if hasattr(other.inventory_store, "prefetch"):
835
inventory_ids = [other.get_revision(r).inventory_id
836
for r in revision_ids]
847
for rev_id in revision_ids:
849
revision = other.get_revision(rev_id).inventory_id
850
inventory_ids.append(revision)
851
except bzrlib.errors.NoSuchRevision:
837
853
other.inventory_store.prefetch(inventory_ids)
856
pb = bzrlib.ui.ui_factory.progress_bar()
840
859
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)
863
for i, rev_id in enumerate(revision_ids):
864
pb.update('fetching revision', i+1, len(revision_ids))
866
rev = other.get_revision(rev_id)
867
except bzrlib.errors.NoSuchRevision:
846
871
revisions.append(rev)
847
872
inv = other.get_inventory(str(rev.inventory_id))
848
873
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
881
count, cp_fail = self.text_store.copy_multi(other.text_store,
883
#print "Added %d texts." % count
858
884
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
885
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
887
#print "Added %d inventories." % count
862
888
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
890
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
893
assert len(cp_fail) == 0
894
return count, failures
870
897
def commit(self, *args, **kw):
871
898
from bzrlib.commit import commit
872
899
commit(self, *args, **kw)
875
def lookup_revision(self, revision):
876
"""Return the revision identifier for a given revision information."""
877
revno, info = self.get_revision_info(revision)
880
def get_revision_info(self, revision):
881
"""Return (revno, revision id) for revision identifier.
883
revision can be an integer, in which case it is assumed to be revno (though
884
this will translate negative values into positive ones)
885
revision can also be a string, in which case it is parsed for something like
886
'date:' or 'revid:' etc.
891
try:# Convert to int if possible
892
revision = int(revision)
895
revs = self.revision_history()
896
if isinstance(revision, int):
899
# Mabye we should do this first, but we don't need it if revision == 0
901
revno = len(revs) + revision + 1
904
elif isinstance(revision, basestring):
905
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
906
if revision.startswith(prefix):
907
revno = func(self, revs, revision)
910
raise BzrError('No namespace registered for string: %r' % 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]
916
def _namespace_revno(self, revs, revision):
917
"""Lookup a revision by revision number"""
918
assert revision.startswith('revno:')
920
return int(revision[6:])
923
REVISION_NAMESPACES['revno:'] = _namespace_revno
925
def _namespace_revid(self, revs, revision):
926
assert revision.startswith('revid:')
928
return revs.index(revision[6:]) + 1
931
REVISION_NAMESPACES['revid:'] = _namespace_revid
933
def _namespace_last(self, revs, revision):
934
assert revision.startswith('last:')
936
offset = int(revision[5:])
941
raise BzrError('You must supply a positive value for --revision last:XXX')
942
return len(revs) - offset + 1
943
REVISION_NAMESPACES['last:'] = _namespace_last
945
def _namespace_tag(self, revs, revision):
946
assert revision.startswith('tag:')
947
raise BzrError('tag: namespace registered, but not implemented.')
948
REVISION_NAMESPACES['tag:'] = _namespace_tag
950
def _namespace_date(self, revs, revision):
951
assert revision.startswith('date:')
953
# Spec for date revisions:
955
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
956
# it can also start with a '+/-/='. '+' says match the first
957
# entry after the given date. '-' is match the first entry before the date
958
# '=' is match the first entry after, but still on the given date.
960
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
961
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
962
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
963
# May 13th, 2005 at 0:00
965
# So the proper way of saying 'give me all entries for today' is:
966
# -r {date:+today}:{date:-tomorrow}
967
# The default is '=' when not supplied
970
if val[:1] in ('+', '-', '='):
971
match_style = val[:1]
974
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
975
if val.lower() == 'yesterday':
976
dt = today - datetime.timedelta(days=1)
977
elif val.lower() == 'today':
979
elif val.lower() == 'tomorrow':
980
dt = today + datetime.timedelta(days=1)
983
# This should be done outside the function to avoid recompiling it.
984
_date_re = re.compile(
985
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
987
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
989
m = _date_re.match(val)
990
if not m or (not m.group('date') and not m.group('time')):
991
raise BzrError('Invalid revision date %r' % revision)
994
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
996
year, month, day = today.year, today.month, today.day
998
hour = int(m.group('hour'))
999
minute = int(m.group('minute'))
1000
if m.group('second'):
1001
second = int(m.group('second'))
1005
hour, minute, second = 0,0,0
1007
dt = datetime.datetime(year=year, month=month, day=day,
1008
hour=hour, minute=minute, second=second)
1012
if match_style == '-':
1014
elif match_style == '=':
1015
last = dt + datetime.timedelta(days=1)
1018
for i in range(len(revs)-1, -1, -1):
1019
r = self.get_revision(revs[i])
1020
# TODO: Handle timezone.
1021
dt = datetime.datetime.fromtimestamp(r.timestamp)
1022
if first >= dt and (last is None or dt >= last):
1025
for i in range(len(revs)):
1026
r = self.get_revision(revs[i])
1027
# TODO: Handle timezone.
1028
dt = datetime.datetime.fromtimestamp(r.timestamp)
1029
if first <= dt and (last is None or dt <= last):
1031
REVISION_NAMESPACES['date:'] = _namespace_date
903
def revision_id_to_revno(self, revision_id):
904
"""Given a revision id, return its revno"""
905
history = self.revision_history()
907
return history.index(revision_id) + 1
909
raise bzrlib.errors.NoSuchRevision(self, revision_id)
912
def get_rev_id(self, revno, history=None):
913
"""Find the revision id of the specified revno."""
917
history = self.revision_history()
918
elif revno <= 0 or revno > len(history):
919
raise bzrlib.errors.NoSuchRevision(self, revno)
920
return history[revno - 1]
1033
923
def revision_tree(self, revision_id):
1034
924
"""Return Tree for a revision on this branch.