129
102
"""Branch holding a history of revisions.
132
Base directory of the branch.
105
Base directory/url of the branch.
109
def __init__(self, *ignored, **ignored_too):
110
raise NotImplementedError('The Branch class is abstract')
114
"""Open an existing branch, rooted at 'base' (url)"""
115
t = bzrlib.transport.transport(base)
116
return LocalBranch(t)
119
def open_containing(url):
120
"""Open an existing branch, containing url (search upwards for the root)
122
t = bzrlib.transport.transport(base)
123
found_t = find_branch_root(t)
124
return LocalBranch(t)
127
def initialize(base):
128
"""Create a new branch, rooted at 'base' (url)"""
129
t = bzrlib.transport.transport(base)
130
return LocalBranch(t, init=True)
132
def setup_caching(self, cache_root):
133
"""Subclasses that care about caching should override this, and set
134
up cached stores located under cache_root.
138
class LocalBranch(Branch):
139
"""A branch stored in the actual filesystem.
141
Note that it's "local" in the context of the filesystem; it doesn't
142
really matter if it's on an nfs/smb/afs/coda/... share, as long as
143
it's writable, and can be accessed via the normal filesystem API.
135
146
None, or 'r' or 'w'
1032
1021
raise bzrlib.errors.NoSuchRevision(self, revno)
1033
1022
return history[revno - 1]
1035
def _get_revision_info(self, revision):
1036
"""Return (revno, revision id) for revision specifier.
1038
revision can be an integer, in which case it is assumed to be revno
1039
(though this will translate negative values into positive ones)
1040
revision can also be a string, in which case it is parsed for something
1041
like 'date:' or 'revid:' etc.
1043
A revid is always returned. If it is None, the specifier referred to
1044
the null revision. If the revid does not occur in the revision
1045
history, revno will be None.
1048
if revision is None:
1051
try:# Convert to int if possible
1052
revision = int(revision)
1055
revs = self.revision_history()
1056
if isinstance(revision, int):
1058
revno = len(revs) + revision + 1
1061
rev_id = self.get_rev_id(revno, revs)
1062
elif isinstance(revision, basestring):
1063
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
1064
if revision.startswith(prefix):
1065
result = func(self, revs, revision)
1067
revno, rev_id = result
1070
rev_id = self.get_rev_id(revno, revs)
1073
raise BzrError('No namespace registered for string: %r' %
1076
raise TypeError('Unhandled revision type %s' % revision)
1080
raise bzrlib.errors.NoSuchRevision(self, revision)
1081
return revno, rev_id
1083
def _namespace_revno(self, revs, revision):
1084
"""Lookup a revision by revision number"""
1085
assert revision.startswith('revno:')
1087
return (int(revision[6:]),)
1090
REVISION_NAMESPACES['revno:'] = _namespace_revno
1092
def _namespace_revid(self, revs, revision):
1093
assert revision.startswith('revid:')
1094
rev_id = revision[len('revid:'):]
1096
return revs.index(rev_id) + 1, rev_id
1099
REVISION_NAMESPACES['revid:'] = _namespace_revid
1101
def _namespace_last(self, revs, revision):
1102
assert revision.startswith('last:')
1104
offset = int(revision[5:])
1109
raise BzrError('You must supply a positive value for --revision last:XXX')
1110
return (len(revs) - offset + 1,)
1111
REVISION_NAMESPACES['last:'] = _namespace_last
1113
def _namespace_tag(self, revs, revision):
1114
assert revision.startswith('tag:')
1115
raise BzrError('tag: namespace registered, but not implemented.')
1116
REVISION_NAMESPACES['tag:'] = _namespace_tag
1118
def _namespace_date(self, revs, revision):
1119
assert revision.startswith('date:')
1121
# Spec for date revisions:
1123
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
1124
# it can also start with a '+/-/='. '+' says match the first
1125
# entry after the given date. '-' is match the first entry before the date
1126
# '=' is match the first entry after, but still on the given date.
1128
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1129
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1130
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1131
# May 13th, 2005 at 0:00
1133
# So the proper way of saying 'give me all entries for today' is:
1134
# -r {date:+today}:{date:-tomorrow}
1135
# The default is '=' when not supplied
1138
if val[:1] in ('+', '-', '='):
1139
match_style = val[:1]
1142
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1143
if val.lower() == 'yesterday':
1144
dt = today - datetime.timedelta(days=1)
1145
elif val.lower() == 'today':
1147
elif val.lower() == 'tomorrow':
1148
dt = today + datetime.timedelta(days=1)
1151
# This should be done outside the function to avoid recompiling it.
1152
_date_re = re.compile(
1153
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1155
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1157
m = _date_re.match(val)
1158
if not m or (not m.group('date') and not m.group('time')):
1159
raise BzrError('Invalid revision date %r' % revision)
1162
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1164
year, month, day = today.year, today.month, today.day
1166
hour = int(m.group('hour'))
1167
minute = int(m.group('minute'))
1168
if m.group('second'):
1169
second = int(m.group('second'))
1173
hour, minute, second = 0,0,0
1175
dt = datetime.datetime(year=year, month=month, day=day,
1176
hour=hour, minute=minute, second=second)
1180
if match_style == '-':
1182
elif match_style == '=':
1183
last = dt + datetime.timedelta(days=1)
1186
for i in range(len(revs)-1, -1, -1):
1187
r = self.get_revision(revs[i])
1188
# TODO: Handle timezone.
1189
dt = datetime.datetime.fromtimestamp(r.timestamp)
1190
if first >= dt and (last is None or dt >= last):
1193
for i in range(len(revs)):
1194
r = self.get_revision(revs[i])
1195
# TODO: Handle timezone.
1196
dt = datetime.datetime.fromtimestamp(r.timestamp)
1197
if first <= dt and (last is None or dt <= last):
1199
REVISION_NAMESPACES['date:'] = _namespace_date
1202
def _namespace_ancestor(self, revs, revision):
1203
from revision import common_ancestor, MultipleRevisionSources
1204
other_branch = find_branch(_trim_namespace('ancestor', revision))
1205
revision_a = self.last_patch()
1206
revision_b = other_branch.last_patch()
1207
for r, b in ((revision_a, self), (revision_b, other_branch)):
1209
raise bzrlib.errors.NoCommits(b)
1210
revision_source = MultipleRevisionSources(self, other_branch)
1211
result = common_ancestor(revision_a, revision_b, revision_source)
1213
revno = self.revision_id_to_revno(result)
1214
except bzrlib.errors.NoSuchRevision:
1219
REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1221
1025
def revision_tree(self, revision_id):
1222
1026
"""Return Tree for a revision on this branch.
1640
1445
The name of a local directory that exists but is empty.
1642
1447
from bzrlib.merge import merge
1448
from bzrlib.revisionspec import RevisionSpec
1644
1450
assert isinstance(branch_from, Branch)
1645
1451
assert isinstance(to_location, basestring)
1647
br_to = Branch(to_location, init=True)
1453
br_to = Branch.initialize(to_location)
1648
1454
br_to.set_root_id(branch_from.get_root_id())
1649
1455
if revision is None:
1650
1456
revno = branch_from.revno()
1652
revno, rev_id = branch_from.get_revision_info(revision)
1458
revno, rev_id = RevisionSpec(revision).in_history(branch_from)
1653
1459
br_to.update_revisions(branch_from, stop_revision=revno)
1654
1460
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1655
1461
check_clean=False, ignore_zero=True)
1656
1462
br_to.set_parent(branch_from.base)
1659
def _trim_namespace(namespace, spec):
1660
full_namespace = namespace + ':'
1661
assert spec.startswith(full_namespace)
1662
return spec[len(full_namespace):]