/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: John Arbash Meinel
  • Date: 2006-07-18 18:57:54 UTC
  • mto: This revision was merged to the branch mainline in revision 1868.
  • Revision ID: john@arbash-meinel.com-20060718185754-4007745748e28db9
Commit timestamp restricted to 1ms precision.

The old code would restrict to 1s resolution if the timestamp was
supplied, while it preserved full resolution if the timestamp was
auto generated. Now both paths preserve only 1ms resolution.

Show diffs side-by-side

added added

removed removed

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