39
42
# repeatedly to calculate deltas. We could perhaps have a weakref
40
43
# 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')
45
def find_branch(*ignored, **ignored_too):
46
# XXX: leave this here for about one release, then remove it
47
raise NotImplementedError('find_branch() is not supported anymore, '
48
'please use one of the new branch constructors')
68
50
def _relpath(base, path):
69
51
"""Return path relative to base, or raise exception.
137
115
"""Branch holding a history of revisions.
140
Base directory of the branch.
118
Base directory/url of the branch.
122
def __init__(self, *ignored, **ignored_too):
123
raise NotImplementedError('The Branch class is abstract')
127
"""Open an existing branch, rooted at 'base' (url)"""
128
if base and (base.startswith('http://') or base.startswith('https://')):
129
from bzrlib.remotebranch import RemoteBranch
130
return RemoteBranch(base, find_root=False)
132
return LocalBranch(base, find_root=False)
135
def open_containing(url):
136
"""Open an existing branch which contains url.
138
This probes for a branch at url, and searches upwards from there.
140
if url and (url.startswith('http://') or url.startswith('https://')):
141
from bzrlib.remotebranch import RemoteBranch
142
return RemoteBranch(url)
144
return LocalBranch(url)
147
def initialize(base):
148
"""Create a new branch, rooted at 'base' (url)"""
149
if base and (base.startswith('http://') or base.startswith('https://')):
150
from bzrlib.remotebranch import RemoteBranch
151
return RemoteBranch(base, init=True)
153
return LocalBranch(base, init=True)
155
def setup_caching(self, cache_root):
156
"""Subclasses that care about caching should override this, and set
157
up cached stores located under cache_root.
161
class LocalBranch(Branch):
162
"""A branch stored in the actual filesystem.
164
Note that it's "local" in the context of the filesystem; it doesn't
165
really matter if it's on an nfs/smb/afs/coda/... share, as long as
166
it's writable, and can be accessed via the normal filesystem API.
143
169
None, or 'r' or 'w'
794
809
if stop_revision is None:
795
810
stop_revision = other_len
796
811
elif stop_revision > other_len:
797
raise NoSuchRevision(self, stop_revision)
812
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
799
814
return other_history[self_len:stop_revision]
802
817
def update_revisions(self, other, stop_revision=None):
803
818
"""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
820
from bzrlib.fetch import greedy_fetch
821
from bzrlib.revision import get_intervening_revisions
823
pb = bzrlib.ui.ui_factory.progress_bar()
829
824
pb.update('comparing histories')
830
revision_ids = self.missing_revisions(other, stop_revision)
825
if stop_revision is None:
826
other_revision = other.last_patch()
828
other_revision = other.get_rev_id(stop_revision)
829
count = greedy_fetch(self, other, other_revision, pb)[0]
831
revision_ids = self.missing_revisions(other, stop_revision)
832
except DivergedBranches, e:
834
revision_ids = get_intervening_revisions(self.last_patch(),
835
other_revision, self)
836
assert self.last_patch() not in revision_ids
837
except bzrlib.errors.NotAncestor:
840
self.append_revision(*revision_ids)
843
def install_revisions(self, other, revision_ids, pb):
832
844
if hasattr(other.revision_store, "prefetch"):
833
845
other.revision_store.prefetch(revision_ids)
834
846
if hasattr(other.inventory_store, "prefetch"):
835
inventory_ids = [other.get_revision(r).inventory_id
836
for r in revision_ids]
848
for rev_id in revision_ids:
850
revision = other.get_revision(rev_id).inventory_id
851
inventory_ids.append(revision)
852
except bzrlib.errors.NoSuchRevision:
837
854
other.inventory_store.prefetch(inventory_ids)
857
pb = bzrlib.ui.ui_factory.progress_bar()
840
860
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)
864
for i, rev_id in enumerate(revision_ids):
865
pb.update('fetching revision', i+1, len(revision_ids))
867
rev = other.get_revision(rev_id)
868
except bzrlib.errors.NoSuchRevision:
846
872
revisions.append(rev)
847
873
inv = other.get_inventory(str(rev.inventory_id))
848
874
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
882
count, cp_fail = self.text_store.copy_multi(other.text_store,
884
#print "Added %d texts." % count
858
885
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
886
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
888
#print "Added %d inventories." % count
862
889
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
891
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
894
assert len(cp_fail) == 0
895
return count, failures
870
898
def commit(self, *args, **kw):
871
899
from bzrlib.commit import commit
872
900
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
902
def revision_id_to_revno(self, revision_id):
903
"""Given a revision id, return its revno"""
904
history = self.revision_history()
906
return history.index(revision_id) + 1
908
raise bzrlib.errors.NoSuchRevision(self, revision_id)
910
def get_rev_id(self, revno, history=None):
911
"""Find the revision id of the specified revno."""
915
history = self.revision_history()
916
elif revno <= 0 or revno > len(history):
917
raise bzrlib.errors.NoSuchRevision(self, revno)
918
return history[revno - 1]
1033
921
def revision_tree(self, revision_id):
1034
922
"""Return Tree for a revision on this branch.