bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 1 | # Copyright (C) 2005 Canonical Ltd
 | 
| 2 | ||
| 3 | # This program is free software; you can redistribute it and/or modify
 | |
| 4 | # it under the terms of the GNU General Public License as published by
 | |
| 5 | # the Free Software Foundation; either version 2 of the License, or
 | |
| 6 | # (at your option) any later version.
 | |
| 7 | ||
| 8 | # This program is distributed in the hope that it will be useful,
 | |
| 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| 11 | # GNU General Public License for more details.
 | |
| 12 | ||
| 13 | # You should have received a copy of the GNU General Public License
 | |
| 14 | # along with this program; if not, write to the Free Software
 | |
| 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | |
| 16 | ||
| 17 | ||
| 18 | import datetime | |
| 19 | import re | |
| 20 | from bzrlib.errors import BzrError, NoSuchRevision, NoCommits | |
| 21 | ||
| 22 | _marker = [] | |
| 23 | ||
| 24 | class RevisionInfo(object): | |
| 25 | """The results of applying a revision specification to a branch. | |
| 26 | ||
| 27 |     An instance has two useful attributes: revno, and rev_id.
 | |
| 28 | ||
| 29 |     They can also be accessed as spec[0] and spec[1] respectively,
 | |
| 30 |     so that you can write code like:
 | |
| 31 |     revno, rev_id = RevisionSpec(branch, spec)
 | |
| 32 |     although this is probably going to be deprecated later.
 | |
| 33 | ||
| 34 |     This class exists mostly to be the return value of a RevisionSpec,
 | |
| 35 |     so that you can access the member you're interested in (number or id)
 | |
| 36 |     or treat the result as a tuple.
 | |
| 37 |     """
 | |
| 38 | ||
| 39 | def __init__(self, branch, revno, rev_id=_marker): | |
| 40 | self.branch = branch | |
| 41 | self.revno = revno | |
| 42 | if rev_id is _marker: | |
| 43 |             # allow caller to be lazy
 | |
| 44 | if self.revno is None: | |
| 45 | self.rev_id = None | |
| 46 | else: | |
| 47 | self.rev_id = branch.get_rev_id(self.revno) | |
| 48 | else: | |
| 49 | self.rev_id = rev_id | |
| 50 | ||
| 51 | def __nonzero__(self): | |
| 52 |         # first the easy ones...
 | |
| 53 | if self.rev_id is None: | |
| 54 | return False | |
| 55 | if self.revno is not None: | |
| 56 | return True | |
| 57 |         # TODO: otherwise, it should depend on how I was built -
 | |
| 58 |         # if it's in_history(branch), then check revision_history(),
 | |
| 59 |         # if it's in_store(branch), do the check below
 | |
| 1442.1.45
by Robert Collins replace __contains__ calls in stores with has_id | 60 | return self.branch.revision_store.has_id(self.rev_id) | 
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 61 | |
| 62 | def __len__(self): | |
| 63 | return 2 | |
| 64 | ||
| 65 | def __getitem__(self, index): | |
| 66 | if index == 0: return self.revno | |
| 67 | if index == 1: return self.rev_id | |
| 68 | raise IndexError(index) | |
| 69 | ||
| 70 | def get(self): | |
| 71 | return self.branch.get_revision(self.rev_id) | |
| 72 | ||
| 73 | def __eq__(self, other): | |
| 74 | if type(other) not in (tuple, list, type(self)): | |
| 75 | return False | |
| 76 | if type(other) is type(self) and self.branch is not other.branch: | |
| 77 | return False | |
| 78 | return tuple(self) == tuple(other) | |
| 79 | ||
| 80 | def __repr__(self): | |
| 81 | return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % ( | |
| 82 | self.revno, self.rev_id, self.branch) | |
| 83 | ||
| 84 | # classes in this list should have a "prefix" attribute, against which
 | |
| 85 | # string specs are matched
 | |
| 86 | SPEC_TYPES = [] | |
| 87 | ||
| 88 | class RevisionSpec(object): | |
| 89 | """A parsed revision specification. | |
| 90 | ||
| 91 |     A revision specification can be an integer, in which case it is
 | |
| 92 |     assumed to be a revno (though this will translate negative values
 | |
| 93 |     into positive ones); or it can be a string, in which case it is
 | |
| 94 |     parsed for something like 'date:' or 'revid:' etc.
 | |
| 95 | ||
| 96 |     Revision specs are an UI element, and they have been moved out
 | |
| 97 |     of the branch class to leave "back-end" classes unaware of such
 | |
| 98 |     details.  Code that gets a revno or rev_id from other code should
 | |
| 99 |     not be using revision specs - revnos and revision ids are the
 | |
| 100 |     accepted ways to refer to revisions internally.
 | |
| 101 | ||
| 102 |     (Equivalent to the old Branch method get_revision_info())
 | |
| 103 |     """
 | |
| 104 | ||
| 105 | prefix = None | |
| 106 | ||
| 107 | def __new__(cls, spec, foo=_marker): | |
| 108 | """Parse a revision specifier. | |
| 109 |         """
 | |
| 110 | if spec is None: | |
| 111 | return object.__new__(RevisionSpec, spec) | |
| 112 | ||
| 113 | try: | |
| 114 | spec = int(spec) | |
| 115 | except ValueError: | |
| 116 |             pass
 | |
| 117 | ||
| 118 | if isinstance(spec, int): | |
| 119 | return object.__new__(RevisionSpec_int, spec) | |
| 120 | elif isinstance(spec, basestring): | |
| 121 | for spectype in SPEC_TYPES: | |
| 122 | if spec.startswith(spectype.prefix): | |
| 123 | return object.__new__(spectype, spec) | |
| 124 | else: | |
| 125 | raise BzrError('No namespace registered for string: %r' % | |
| 126 | spec) | |
| 127 | else: | |
| 128 | raise TypeError('Unhandled revision type %s' % spec) | |
| 129 | ||
| 130 | def __init__(self, spec): | |
| 131 | if self.prefix and spec.startswith(self.prefix): | |
| 132 | spec = spec[len(self.prefix):] | |
| 133 | self.spec = spec | |
| 134 | ||
| 135 | def _match_on(self, branch, revs): | |
| 136 | return RevisionInfo(branch, 0, None) | |
| 137 | ||
| 138 | def _match_on_and_check(self, branch, revs): | |
| 139 | info = self._match_on(branch, revs) | |
| 140 | if info: | |
| 141 | return info | |
| 142 | elif info == (0, None): | |
| 143 |             # special case - the empty tree
 | |
| 144 | return info | |
| 145 | elif self.prefix: | |
| 146 | raise NoSuchRevision(branch, self.prefix + str(self.spec)) | |
| 147 | else: | |
| 148 | raise NoSuchRevision(branch, str(self.spec)) | |
| 149 | ||
| 150 | def in_history(self, branch): | |
| 151 | revs = branch.revision_history() | |
| 152 | return self._match_on_and_check(branch, revs) | |
| 153 | ||
| 1432
by Robert Collins branch: namespace | 154 |         # FIXME: in_history is somewhat broken,
 | 
| 155 |         # it will return non-history revisions in many
 | |
| 156 |         # circumstances. The expected facility is that
 | |
| 157 |         # in_history only returns revision-history revs,
 | |
| 158 |         # in_store returns any rev. RBC 20051010
 | |
| 159 |     # aliases for now, when we fix the core logic, then they
 | |
| 160 |     # will do what you expect.
 | |
| 161 | in_store = in_history | |
| 162 | in_branch = in_store | |
| 163 | ||
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 164 | def __repr__(self): | 
| 165 |         # this is mostly for helping with testing
 | |
| 166 | return '<%s %s%s>' % (self.__class__.__name__, | |
| 167 | self.prefix or '', | |
| 168 | self.spec) | |
| 169 | ||
| 170 | ||
| 171 | # private API
 | |
| 172 | ||
| 173 | class RevisionSpec_int(RevisionSpec): | |
| 174 | """Spec is a number. Special case.""" | |
| 175 | def __init__(self, spec): | |
| 176 | self.spec = int(spec) | |
| 177 | ||
| 178 | def _match_on(self, branch, revs): | |
| 179 | if self.spec < 0: | |
| 180 | revno = len(revs) + self.spec + 1 | |
| 181 | else: | |
| 182 | revno = self.spec | |
| 183 | rev_id = branch.get_rev_id(revno, revs) | |
| 184 | return RevisionInfo(branch, revno, rev_id) | |
| 185 | ||
| 186 | ||
| 187 | class RevisionSpec_revno(RevisionSpec): | |
| 188 | prefix = 'revno:' | |
| 189 | ||
| 190 | def _match_on(self, branch, revs): | |
| 191 | """Lookup a revision by revision number""" | |
| 192 | try: | |
| 193 | return RevisionInfo(branch, int(self.spec)) | |
| 194 | except ValueError: | |
| 195 | return RevisionInfo(branch, None) | |
| 196 | ||
| 197 | SPEC_TYPES.append(RevisionSpec_revno) | |
| 198 | ||
| 199 | ||
| 200 | class RevisionSpec_revid(RevisionSpec): | |
| 201 | prefix = 'revid:' | |
| 202 | ||
| 203 | def _match_on(self, branch, revs): | |
| 204 | try: | |
| 205 | return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec) | |
| 206 | except ValueError: | |
| 207 | return RevisionInfo(branch, None) | |
| 208 | ||
| 209 | SPEC_TYPES.append(RevisionSpec_revid) | |
| 210 | ||
| 211 | ||
| 212 | class RevisionSpec_last(RevisionSpec): | |
| 1185.1.39
by Robert Collins Robey Pointers before: namespace to clear up usage of dates in revision parameters | 213 | |
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 214 | prefix = 'last:' | 
| 215 | ||
| 216 | def _match_on(self, branch, revs): | |
| 217 | try: | |
| 218 | offset = int(self.spec) | |
| 219 | except ValueError: | |
| 220 | return RevisionInfo(branch, None) | |
| 221 | else: | |
| 222 | if offset <= 0: | |
| 223 | raise BzrError('You must supply a positive value for --revision last:XXX') | |
| 224 | return RevisionInfo(branch, len(revs) - offset + 1) | |
| 225 | ||
| 226 | SPEC_TYPES.append(RevisionSpec_last) | |
| 227 | ||
| 228 | ||
| 1185.1.39
by Robert Collins Robey Pointers before: namespace to clear up usage of dates in revision parameters | 229 | class RevisionSpec_before(RevisionSpec): | 
| 230 | ||
| 231 | prefix = 'before:' | |
| 232 | ||
| 233 | def _match_on(self, branch, revs): | |
| 234 | r = RevisionSpec(self.spec)._match_on(branch, revs) | |
| 235 | if (r.revno is None) or (r.revno == 0): | |
| 236 | return r | |
| 237 | return RevisionInfo(branch, r.revno - 1) | |
| 238 | ||
| 239 | SPEC_TYPES.append(RevisionSpec_before) | |
| 240 | ||
| 241 | ||
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 242 | class RevisionSpec_tag(RevisionSpec): | 
| 243 | prefix = 'tag:' | |
| 244 | ||
| 245 | def _match_on(self, branch, revs): | |
| 246 | raise BzrError('tag: namespace registered, but not implemented.') | |
| 247 | ||
| 248 | SPEC_TYPES.append(RevisionSpec_tag) | |
| 249 | ||
| 250 | ||
| 251 | class RevisionSpec_date(RevisionSpec): | |
| 252 | prefix = 'date:' | |
| 253 | _date_re = re.compile( | |
| 254 | r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?' | |
| 255 | r'(,|T)?\s*' | |
| 256 | r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?' | |
| 257 |         )
 | |
| 258 | ||
| 259 | def _match_on(self, branch, revs): | |
| 260 | """ | |
| 261 |         Spec for date revisions:
 | |
| 262 |           date:value
 | |
| 263 |           value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 | |
| 1185.1.39
by Robert Collins Robey Pointers before: namespace to clear up usage of dates in revision parameters | 264 |           matches the first entry after a given date (either at midnight or
 | 
| 265 |           at a specified time).
 | |
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 266 | |
| 267 |           So the proper way of saying 'give me all entries for today' is:
 | |
| 1185.1.39
by Robert Collins Robey Pointers before: namespace to clear up usage of dates in revision parameters | 268 |               -r date:today..date:tomorrow
 | 
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 269 |         """
 | 
| 1185.1.39
by Robert Collins Robey Pointers before: namespace to clear up usage of dates in revision parameters | 270 | today = datetime.datetime.fromordinal(datetime.date.today().toordinal()) | 
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 271 | if self.spec.lower() == 'yesterday': | 
| 272 | dt = today - datetime.timedelta(days=1) | |
| 273 | elif self.spec.lower() == 'today': | |
| 274 | dt = today | |
| 275 | elif self.spec.lower() == 'tomorrow': | |
| 276 | dt = today + datetime.timedelta(days=1) | |
| 277 | else: | |
| 278 | m = self._date_re.match(self.spec) | |
| 279 | if not m or (not m.group('date') and not m.group('time')): | |
| 280 | raise BzrError('Invalid revision date %r' % self.spec) | |
| 281 | ||
| 282 | if m.group('date'): | |
| 283 | year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day')) | |
| 284 | else: | |
| 285 | year, month, day = today.year, today.month, today.day | |
| 286 | if m.group('time'): | |
| 287 | hour = int(m.group('hour')) | |
| 288 | minute = int(m.group('minute')) | |
| 289 | if m.group('second'): | |
| 290 | second = int(m.group('second')) | |
| 291 | else: | |
| 292 | second = 0 | |
| 293 | else: | |
| 294 | hour, minute, second = 0,0,0 | |
| 295 | ||
| 296 | dt = datetime.datetime(year=year, month=month, day=day, | |
| 297 | hour=hour, minute=minute, second=second) | |
| 298 | first = dt | |
| 1185.1.39
by Robert Collins Robey Pointers before: namespace to clear up usage of dates in revision parameters | 299 | for i in range(len(revs)): | 
| 300 | r = branch.get_revision(revs[i]) | |
| 301 |             # TODO: Handle timezone.
 | |
| 302 | dt = datetime.datetime.fromtimestamp(r.timestamp) | |
| 303 | if first <= dt: | |
| 304 | return RevisionInfo(branch, i+1) | |
| 305 | return RevisionInfo(branch, None) | |
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 306 | |
| 307 | SPEC_TYPES.append(RevisionSpec_date) | |
| 308 | ||
| 309 | ||
| 310 | class RevisionSpec_ancestor(RevisionSpec): | |
| 311 | prefix = 'ancestor:' | |
| 312 | ||
| 313 | def _match_on(self, branch, revs): | |
| 314 | from branch import Branch | |
| 315 | from revision import common_ancestor, MultipleRevisionSources | |
| 1442.1.64
by Robert Collins Branch.open_containing now returns a tuple (Branch, relative-path). | 316 | other_branch = Branch.open_containing(self.spec)[0] | 
| 1390
by Robert Collins pair programming worx... merge integration and weave | 317 | revision_a = branch.last_revision() | 
| 318 | revision_b = other_branch.last_revision() | |
| 1185.11.5
by John Arbash Meinel Merged up-to-date against mainline, still broken. | 319 | for r, b in ((revision_a, branch), (revision_b, other_branch)): | 
| 320 | if r is None: | |
| 321 | raise NoCommits(b) | |
| 322 | revision_source = MultipleRevisionSources(branch, other_branch) | |
| 323 | rev_id = common_ancestor(revision_a, revision_b, revision_source) | |
| 324 | try: | |
| 325 | revno = branch.revision_id_to_revno(rev_id) | |
| 326 | except NoSuchRevision: | |
| 327 | revno = None | |
| 328 | return RevisionInfo(branch, revno, rev_id) | |
| 329 | ||
| 330 | SPEC_TYPES.append(RevisionSpec_ancestor) | |
| 1432
by Robert Collins branch: namespace | 331 | |
| 332 | class RevisionSpec_branch(RevisionSpec): | |
| 333 | """A branch: revision specifier. | |
| 334 | ||
| 335 |     This takes the path to a branch and returns its tip revision id.
 | |
| 336 |     """
 | |
| 337 | prefix = 'branch:' | |
| 338 | ||
| 339 | def _match_on(self, branch, revs): | |
| 340 | from branch import Branch | |
| 341 | from fetch import greedy_fetch | |
| 1442.1.64
by Robert Collins Branch.open_containing now returns a tuple (Branch, relative-path). | 342 | other_branch = Branch.open_containing(self.spec)[0] | 
| 1432
by Robert Collins branch: namespace | 343 | revision_b = other_branch.last_revision() | 
| 344 | if revision_b is None: | |
| 345 | raise NoCommits(other_branch) | |
| 346 |         # pull in the remote revisions so we can diff
 | |
| 347 | greedy_fetch(branch, other_branch, revision=revision_b) | |
| 348 | try: | |
| 349 | revno = branch.revision_id_to_revno(revision_b) | |
| 350 | except NoSuchRevision: | |
| 351 | revno = None | |
| 352 | return RevisionInfo(branch, revno, revision_b) | |
| 353 | ||
| 354 | SPEC_TYPES.append(RevisionSpec_branch) |