/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-08-18 22:17:03 UTC
  • mto: This revision was merged to the branch mainline in revision 1989.
  • Revision ID: john@arbash-meinel.com-20060818221703-958786fafe340fd9
2 changes to knits. Delay creating the .knit or .kndx file until we have actually tried to write data. Because of this, we must allow the Knit to create the prefix directories

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
    def needs_branch(self):
 
175
        """Whether this revision spec needs a branch.
 
176
 
 
177
        Set this to False the branch argument of _match_on is not used.
 
178
        """
 
179
        return True
 
180
 
 
181
# private API
 
182
 
 
183
class RevisionSpec_int(RevisionSpec):
 
184
    """Spec is a number.  Special case."""
 
185
    def __init__(self, spec):
 
186
        self.spec = int(spec)
 
187
 
 
188
    def _match_on(self, branch, revs):
 
189
        if self.spec < 0:
 
190
            revno = len(revs) + self.spec + 1
 
191
        else:
 
192
            revno = self.spec
 
193
        rev_id = branch.get_rev_id(revno, revs)
 
194
        return RevisionInfo(branch, revno, rev_id)
 
195
 
 
196
 
 
197
class RevisionSpec_revno(RevisionSpec):
 
198
    prefix = 'revno:'
 
199
 
 
200
    def _match_on(self, branch, revs):
 
201
        """Lookup a revision by revision number"""
 
202
        if self.spec.find(':') == -1:
 
203
            try:
 
204
                return RevisionInfo(branch, int(self.spec))
 
205
            except ValueError:
 
206
                return RevisionInfo(branch, None)
 
207
        else:
 
208
            from branch import Branch
 
209
            revname = self.spec[self.spec.find(':')+1:]
 
210
            other_branch = Branch.open_containing(revname)[0]
 
211
            try:
 
212
                revno = int(self.spec[:self.spec.find(':')])
 
213
            except ValueError:
 
214
                return RevisionInfo(other_branch, None)
 
215
            revid = other_branch.get_rev_id(revno)
 
216
            return RevisionInfo(other_branch, revno)
 
217
        
 
218
    def needs_branch(self):
 
219
        return self.spec.find(':') == -1
 
220
 
 
221
SPEC_TYPES.append(RevisionSpec_revno)
 
222
 
 
223
 
 
224
class RevisionSpec_revid(RevisionSpec):
 
225
    prefix = 'revid:'
 
226
 
 
227
    def _match_on(self, branch, revs):
 
228
        try:
 
229
            return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
 
230
        except ValueError:
 
231
            return RevisionInfo(branch, None, self.spec)
 
232
 
 
233
SPEC_TYPES.append(RevisionSpec_revid)
 
234
 
 
235
 
 
236
class RevisionSpec_last(RevisionSpec):
 
237
 
 
238
    prefix = 'last:'
 
239
 
 
240
    def _match_on(self, branch, revs):
 
241
        try:
 
242
            offset = int(self.spec)
 
243
        except ValueError:
 
244
            return RevisionInfo(branch, None)
 
245
        else:
 
246
            if offset <= 0:
 
247
                raise BzrError('You must supply a positive value for --revision last:XXX')
 
248
            return RevisionInfo(branch, len(revs) - offset + 1)
 
249
 
 
250
SPEC_TYPES.append(RevisionSpec_last)
 
251
 
 
252
 
 
253
class RevisionSpec_before(RevisionSpec):
 
254
 
 
255
    prefix = 'before:'
 
256
    
 
257
    def _match_on(self, branch, revs):
 
258
        r = RevisionSpec(self.spec)._match_on(branch, revs)
 
259
        if (r.revno is None) or (r.revno == 0):
 
260
            return r
 
261
        return RevisionInfo(branch, r.revno - 1)
 
262
 
 
263
SPEC_TYPES.append(RevisionSpec_before)
 
264
 
 
265
 
 
266
class RevisionSpec_tag(RevisionSpec):
 
267
    prefix = 'tag:'
 
268
 
 
269
    def _match_on(self, branch, revs):
 
270
        raise BzrError('tag: namespace registered, but not implemented.')
 
271
 
 
272
SPEC_TYPES.append(RevisionSpec_tag)
 
273
 
 
274
 
 
275
class RevisionSpec_revs:
 
276
    def __init__(self, revs, branch):
 
277
        self.revs = revs
 
278
        self.branch = branch
 
279
    def __getitem__(self, index):
 
280
        r = self.branch.repository.get_revision(self.revs[index])
 
281
        # TODO: Handle timezone.
 
282
        return datetime.datetime.fromtimestamp(r.timestamp)
 
283
    def __len__(self):
 
284
        return len(self.revs)
 
285
 
 
286
 
 
287
class RevisionSpec_date(RevisionSpec):
 
288
    prefix = 'date:'
 
289
    _date_re = re.compile(
 
290
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
291
            r'(,|T)?\s*'
 
292
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
293
        )
 
294
 
 
295
    def _match_on(self, branch, revs):
 
296
        """
 
297
        Spec for date revisions:
 
298
          date:value
 
299
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
300
          matches the first entry after a given date (either at midnight or
 
301
          at a specified time).
 
302
 
 
303
          So the proper way of saying 'give me all entries for today' is:
 
304
              -r date:yesterday..date:today
 
305
        """
 
306
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
 
307
        if self.spec.lower() == 'yesterday':
 
308
            dt = today - datetime.timedelta(days=1)
 
309
        elif self.spec.lower() == 'today':
 
310
            dt = today
 
311
        elif self.spec.lower() == 'tomorrow':
 
312
            dt = today + datetime.timedelta(days=1)
 
313
        else:
 
314
            m = self._date_re.match(self.spec)
 
315
            if not m or (not m.group('date') and not m.group('time')):
 
316
                raise BzrError('Invalid revision date %r' % self.spec)
 
317
 
 
318
            if m.group('date'):
 
319
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
320
            else:
 
321
                year, month, day = today.year, today.month, today.day
 
322
            if m.group('time'):
 
323
                hour = int(m.group('hour'))
 
324
                minute = int(m.group('minute'))
 
325
                if m.group('second'):
 
326
                    second = int(m.group('second'))
 
327
                else:
 
328
                    second = 0
 
329
            else:
 
330
                hour, minute, second = 0,0,0
 
331
 
 
332
            dt = datetime.datetime(year=year, month=month, day=day,
 
333
                    hour=hour, minute=minute, second=second)
 
334
        branch.lock_read()
 
335
        try:
 
336
            rev = bisect.bisect(RevisionSpec_revs(revs, branch), dt)
 
337
        finally:
 
338
            branch.unlock()
 
339
        if rev == len(revs):
 
340
            return RevisionInfo(branch, None)
 
341
        else:
 
342
            return RevisionInfo(branch, rev + 1)
 
343
 
 
344
SPEC_TYPES.append(RevisionSpec_date)
 
345
 
 
346
 
 
347
class RevisionSpec_ancestor(RevisionSpec):
 
348
    prefix = 'ancestor:'
 
349
 
 
350
    def _match_on(self, branch, revs):
 
351
        from branch import Branch
 
352
        from revision import common_ancestor, MultipleRevisionSources
 
353
        other_branch = Branch.open_containing(self.spec)[0]
 
354
        revision_a = branch.last_revision()
 
355
        revision_b = other_branch.last_revision()
 
356
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
 
357
            if r is None:
 
358
                raise NoCommits(b)
 
359
        revision_source = MultipleRevisionSources(branch.repository,
 
360
                                                  other_branch.repository)
 
361
        rev_id = common_ancestor(revision_a, revision_b, revision_source)
 
362
        try:
 
363
            revno = branch.revision_id_to_revno(rev_id)
 
364
        except NoSuchRevision:
 
365
            revno = None
 
366
        return RevisionInfo(branch, revno, rev_id)
 
367
        
 
368
SPEC_TYPES.append(RevisionSpec_ancestor)
 
369
 
 
370
class RevisionSpec_branch(RevisionSpec):
 
371
    """A branch: revision specifier.
 
372
 
 
373
    This takes the path to a branch and returns its tip revision id.
 
374
    """
 
375
    prefix = 'branch:'
 
376
 
 
377
    def _match_on(self, branch, revs):
 
378
        from branch import Branch
 
379
        other_branch = Branch.open_containing(self.spec)[0]
 
380
        revision_b = other_branch.last_revision()
 
381
        if revision_b is None:
 
382
            raise NoCommits(other_branch)
 
383
        # pull in the remote revisions so we can diff
 
384
        branch.fetch(other_branch, revision_b)
 
385
        try:
 
386
            revno = branch.revision_id_to_revno(revision_b)
 
387
        except NoSuchRevision:
 
388
            revno = None
 
389
        return RevisionInfo(branch, revno, revision_b)
 
390
        
 
391
SPEC_TYPES.append(RevisionSpec_branch)