43
43
# repeatedly to calculate deltas. We could perhaps have a weakref
44
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
def find_branch(f, **args):
51
if f and (f.startswith('http://') or f.startswith('https://')):
53
return remotebranch.RemoteBranch(f, **args)
55
return Branch(f, **args)
58
def find_cached_branch(f, cache_root, **args):
59
from remotebranch import RemoteBranch
60
br = find_branch(f, **args)
61
def cacheify(br, store_name):
62
from meta_store import CachedStore
63
cache_path = os.path.join(cache_root, store_name)
65
new_store = CachedStore(getattr(br, store_name), cache_path)
66
setattr(br, store_name, new_store)
68
if isinstance(br, RemoteBranch):
69
cacheify(br, 'inventory_store')
70
cacheify(br, 'text_store')
71
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')
75
51
def _relpath(base, path):
76
52
"""Return path relative to base, or raise exception.
148
116
"""Branch holding a history of revisions.
151
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.
154
168
None, or 'r' or 'w'
926
919
raise bzrlib.errors.NoSuchRevision(self, revno)
927
920
return history[revno - 1]
929
def _get_revision_info(self, revision):
930
"""Return (revno, revision id) for revision specifier.
932
revision can be an integer, in which case it is assumed to be revno
933
(though this will translate negative values into positive ones)
934
revision can also be a string, in which case it is parsed for something
935
like 'date:' or 'revid:' etc.
937
A revid is always returned. If it is None, the specifier referred to
938
the null revision. If the revid does not occur in the revision
939
history, revno will be None.
945
try:# Convert to int if possible
946
revision = int(revision)
949
revs = self.revision_history()
950
if isinstance(revision, int):
952
revno = len(revs) + revision + 1
955
rev_id = self.get_rev_id(revno, revs)
956
elif isinstance(revision, basestring):
957
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
958
if revision.startswith(prefix):
959
result = func(self, revs, revision)
961
revno, rev_id = result
964
rev_id = self.get_rev_id(revno, revs)
967
raise BzrError('No namespace registered for string: %r' %
970
raise TypeError('Unhandled revision type %s' % revision)
974
raise bzrlib.errors.NoSuchRevision(self, revision)
977
def _namespace_revno(self, revs, revision):
978
"""Lookup a revision by revision number"""
979
assert revision.startswith('revno:')
981
return (int(revision[6:]),)
984
REVISION_NAMESPACES['revno:'] = _namespace_revno
986
def _namespace_revid(self, revs, revision):
987
assert revision.startswith('revid:')
988
rev_id = revision[len('revid:'):]
990
return revs.index(rev_id) + 1, rev_id
993
REVISION_NAMESPACES['revid:'] = _namespace_revid
995
def _namespace_last(self, revs, revision):
996
assert revision.startswith('last:')
998
offset = int(revision[5:])
1003
raise BzrError('You must supply a positive value for --revision last:XXX')
1004
return (len(revs) - offset + 1,)
1005
REVISION_NAMESPACES['last:'] = _namespace_last
1007
def _namespace_tag(self, revs, revision):
1008
assert revision.startswith('tag:')
1009
raise BzrError('tag: namespace registered, but not implemented.')
1010
REVISION_NAMESPACES['tag:'] = _namespace_tag
1012
def _namespace_date(self, revs, revision):
1013
assert revision.startswith('date:')
1015
# Spec for date revisions:
1017
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
1018
# it can also start with a '+/-/='. '+' says match the first
1019
# entry after the given date. '-' is match the first entry before the date
1020
# '=' is match the first entry after, but still on the given date.
1022
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1023
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1024
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1025
# May 13th, 2005 at 0:00
1027
# So the proper way of saying 'give me all entries for today' is:
1028
# -r {date:+today}:{date:-tomorrow}
1029
# The default is '=' when not supplied
1032
if val[:1] in ('+', '-', '='):
1033
match_style = val[:1]
1036
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1037
if val.lower() == 'yesterday':
1038
dt = today - datetime.timedelta(days=1)
1039
elif val.lower() == 'today':
1041
elif val.lower() == 'tomorrow':
1042
dt = today + datetime.timedelta(days=1)
1045
# This should be done outside the function to avoid recompiling it.
1046
_date_re = re.compile(
1047
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1049
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1051
m = _date_re.match(val)
1052
if not m or (not m.group('date') and not m.group('time')):
1053
raise BzrError('Invalid revision date %r' % revision)
1056
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1058
year, month, day = today.year, today.month, today.day
1060
hour = int(m.group('hour'))
1061
minute = int(m.group('minute'))
1062
if m.group('second'):
1063
second = int(m.group('second'))
1067
hour, minute, second = 0,0,0
1069
dt = datetime.datetime(year=year, month=month, day=day,
1070
hour=hour, minute=minute, second=second)
1074
if match_style == '-':
1076
elif match_style == '=':
1077
last = dt + datetime.timedelta(days=1)
1080
for i in range(len(revs)-1, -1, -1):
1081
r = self.get_revision(revs[i])
1082
# TODO: Handle timezone.
1083
dt = datetime.datetime.fromtimestamp(r.timestamp)
1084
if first >= dt and (last is None or dt >= last):
1087
for i in range(len(revs)):
1088
r = self.get_revision(revs[i])
1089
# TODO: Handle timezone.
1090
dt = datetime.datetime.fromtimestamp(r.timestamp)
1091
if first <= dt and (last is None or dt <= last):
1093
REVISION_NAMESPACES['date:'] = _namespace_date
1096
def _namespace_ancestor(self, revs, revision):
1097
from revision import common_ancestor, MultipleRevisionSources
1098
other_branch = find_branch(_trim_namespace('ancestor', revision))
1099
revision_a = self.last_patch()
1100
revision_b = other_branch.last_patch()
1101
for r, b in ((revision_a, self), (revision_b, other_branch)):
1103
raise bzrlib.errors.NoCommits(b)
1104
revision_source = MultipleRevisionSources(self, other_branch)
1105
result = common_ancestor(revision_a, revision_b, revision_source)
1107
revno = self.revision_id_to_revno(result)
1108
except bzrlib.errors.NoSuchRevision:
1113
REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1115
923
def revision_tree(self, revision_id):
1116
924
"""Return Tree for a revision on this branch.
1545
1345
The name of a local directory that exists but is empty.
1547
1347
from bzrlib.merge import merge
1548
from bzrlib.branch import Branch
1348
from bzrlib.revisionspec import RevisionSpec
1550
1350
assert isinstance(branch_from, Branch)
1551
1351
assert isinstance(to_location, basestring)
1553
br_to = Branch(to_location, init=True)
1353
br_to = Branch.initialize(to_location)
1554
1354
br_to.set_root_id(branch_from.get_root_id())
1555
1355
if revision is None:
1556
1356
revno = branch_from.revno()
1558
revno, rev_id = branch_from.get_revision_info(revision)
1358
revno, rev_id = RevisionSpec(revision).in_history(branch_from)
1559
1359
br_to.update_revisions(branch_from, stop_revision=revno)
1560
1360
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1561
1361
check_clean=False, ignore_zero=True)
1563
from_location = pull_loc(branch_from)
1564
br_to.set_parent(pull_loc(branch_from))
1362
br_to.set_parent(branch_from.base)
1567
def _trim_namespace(namespace, spec):
1568
full_namespace = namespace + ':'
1569
assert spec.startswith(full_namespace)
1570
return spec[len(full_namespace):]