~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Lalo Martins
  • Date: 2005-09-08 00:40:15 UTC
  • mto: (1185.1.22)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: lalo@exoweb.net-20050908004014-bb63b3378ac8ff58
turned get_revision_info into a RevisionSpec class

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
from bzrlib.errors import BzrError, NoSuchRevision
 
21
 
 
22
# Map some sort of prefix into a namespace
 
23
# stuff like "revno:10", "revid:", etc.
 
24
# This should match a prefix with a function which accepts it
 
25
REVISION_NAMESPACES = {}
 
26
 
 
27
class RevisionSpec(object):
 
28
    """Equivalent to the old get_revision_info().
 
29
    An instance has two useful attributes: revno, and rev_id.
 
30
 
 
31
    They can also be accessed as spec[0] and spec[1] respectively,
 
32
    so that you can write code like:
 
33
    revno, rev_id = RevisionSpec(branch, spec)
 
34
    although this is probably going to be deprecated later.
 
35
 
 
36
    Revision specs are an UI element, and they have been moved out
 
37
    of the branch class to leave "back-end" classes unaware of such
 
38
    details.  Code that gets a revno or rev_id from other code should
 
39
    not be using revision specs - revnos and revision ids are the
 
40
    accepted ways to refer to revisions internally.
 
41
    """
 
42
    def __init__(self, branch, spec):
 
43
        """Parse a revision specifier.
 
44
 
 
45
        spec can be an integer, in which case it is assumed to be revno
 
46
        (though this will translate negative values into positive ones)
 
47
        spec can also be a string, in which case it is parsed for something
 
48
        like 'date:' or 'revid:' etc.
 
49
        """
 
50
        self.branch = branch
 
51
 
 
52
        if spec is None:
 
53
            self.revno = 0
 
54
            self.rev_id = None
 
55
            return
 
56
        self.revno = None
 
57
        try:# Convert to int if possible
 
58
            spec = int(spec)
 
59
        except ValueError:
 
60
            pass
 
61
        revs = branch.revision_history()
 
62
        if isinstance(spec, int):
 
63
            if spec < 0:
 
64
                self.revno = len(revs) + spec + 1
 
65
            else:
 
66
                self.revno = spec
 
67
            self.rev_id = branch.get_rev_id(self.revno, revs)
 
68
        elif isinstance(spec, basestring):
 
69
            for prefix, func in REVISION_NAMESPACES.iteritems():
 
70
                if spec.startswith(prefix):
 
71
                    result = func(branch, revs, spec)
 
72
                    if len(result) > 1:
 
73
                        self.revno, self.rev_id = result
 
74
                    else:
 
75
                        self.revno = result[0]
 
76
                        self.rev_id = branch.get_rev_id(self.revno, revs)
 
77
                    break
 
78
            else:
 
79
                raise BzrError('No namespace registered for string: %r' %
 
80
                               spec)
 
81
        else:
 
82
            raise TypeError('Unhandled revision type %s' % spec)
 
83
 
 
84
        if self.revno is None or self.rev_id is None:
 
85
            raise NoSuchRevision(branch, spec)
 
86
 
 
87
    def __len__(self):
 
88
        return 2
 
89
 
 
90
    def __getitem__(self, index):
 
91
        if index == 0: return self.revno
 
92
        if index == 1: return self.rev_id
 
93
        raise IndexError(index)
 
94
 
 
95
    def get(self):
 
96
        return self.branch.get_revision(self.rev_id)
 
97
 
 
98
    def __eq__(self, other):
 
99
        if type(other) not in (tuple, list, type(self)):
 
100
            return False
 
101
        if type(other) is type(self) and self.branch is not other.branch:
 
102
            return False
 
103
        print 'comparing', tuple(self), tuple(other)
 
104
        return tuple(self) == tuple(other)
 
105
 
 
106
    def __repr__(self):
 
107
        return '<bzrlib.revisionspec.RevisionSpec object %s, %s for %r>' % (
 
108
            self.revno, self.rev_id, self.branch)
 
109
 
 
110
 
 
111
# private API
 
112
 
 
113
def _namespace_revno(branch, revs, spec):
 
114
    """Lookup a revision by revision number"""
 
115
    assert spec.startswith('revno:')
 
116
    try:
 
117
        return (int(spec[len('revno:'):]),)
 
118
    except ValueError:
 
119
        return (None,)
 
120
REVISION_NAMESPACES['revno:'] = _namespace_revno
 
121
 
 
122
 
 
123
def _namespace_revid(branch, revs, spec):
 
124
    assert spec.startswith('revid:')
 
125
    rev_id = spec[len('revid:'):]
 
126
    try:
 
127
        return revs.index(rev_id) + 1, rev_id
 
128
    except ValueError:
 
129
        return (None,)
 
130
REVISION_NAMESPACES['revid:'] = _namespace_revid
 
131
 
 
132
 
 
133
def _namespace_last(branch, revs, spec):
 
134
    assert spec.startswith('last:')
 
135
    try:
 
136
        offset = int(spec[5:])
 
137
    except ValueError:
 
138
        return (None,)
 
139
    else:
 
140
        if offset <= 0:
 
141
            raise BzrError('You must supply a positive value for --revision last:XXX')
 
142
        return (len(revs) - offset + 1,)
 
143
REVISION_NAMESPACES['last:'] = _namespace_last
 
144
 
 
145
 
 
146
def _namespace_tag(branch, revs, spec):
 
147
    assert spec.startswith('tag:')
 
148
    raise BzrError('tag: namespace registered, but not implemented.')
 
149
REVISION_NAMESPACES['tag:'] = _namespace_tag
 
150
 
 
151
 
 
152
_date_re = re.compile(
 
153
        r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
154
        r'(,|T)?\s*'
 
155
        r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
156
    )
 
157
 
 
158
def _namespace_date(branch, revs, spec):
 
159
    """
 
160
    Spec for date revisions:
 
161
      date:value
 
162
      value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
163
      it can also start with a '+/-/='. '+' says match the first
 
164
      entry after the given date. '-' is match the first entry before the date
 
165
      '=' is match the first entry after, but still on the given date.
 
166
    
 
167
      +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
 
168
      -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
 
169
      =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
 
170
          May 13th, 2005 at 0:00
 
171
    
 
172
      So the proper way of saying 'give me all entries for today' is:
 
173
          -r {date:+today}:{date:-tomorrow}
 
174
      The default is '=' when not supplied
 
175
    """
 
176
    assert spec.startswith('date:')
 
177
    val = spec[5:]
 
178
    match_style = '='
 
179
    if val[:1] in ('+', '-', '='):
 
180
        match_style = val[:1]
 
181
        val = val[1:]
 
182
 
 
183
    # XXX: this should probably be using datetime.date instead
 
184
    today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
 
185
                                              microsecond=0)
 
186
    if val.lower() == 'yesterday':
 
187
        dt = today - datetime.timedelta(days=1)
 
188
    elif val.lower() == 'today':
 
189
        dt = today
 
190
    elif val.lower() == 'tomorrow':
 
191
        dt = today + datetime.timedelta(days=1)
 
192
    else:
 
193
        m = _date_re.match(val)
 
194
        if not m or (not m.group('date') and not m.group('time')):
 
195
            raise BzrError('Invalid revision date %r' % spec)
 
196
 
 
197
        if m.group('date'):
 
198
            year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
199
        else:
 
200
            year, month, day = today.year, today.month, today.day
 
201
        if m.group('time'):
 
202
            hour = int(m.group('hour'))
 
203
            minute = int(m.group('minute'))
 
204
            if m.group('second'):
 
205
                second = int(m.group('second'))
 
206
            else:
 
207
                second = 0
 
208
        else:
 
209
            hour, minute, second = 0,0,0
 
210
 
 
211
        dt = datetime.datetime(year=year, month=month, day=day,
 
212
                hour=hour, minute=minute, second=second)
 
213
    first = dt
 
214
    last = None
 
215
    reversed = False
 
216
    if match_style == '-':
 
217
        reversed = True
 
218
    elif match_style == '=':
 
219
        last = dt + datetime.timedelta(days=1)
 
220
 
 
221
    if reversed:
 
222
        for i in range(len(revs)-1, -1, -1):
 
223
            r = branch.get_revision(revs[i])
 
224
            # TODO: Handle timezone.
 
225
            dt = datetime.datetime.fromtimestamp(r.timestamp)
 
226
            if first >= dt and (last is None or dt >= last):
 
227
                return (i+1,)
 
228
    else:
 
229
        for i in range(len(revs)):
 
230
            r = branch.get_revision(revs[i])
 
231
            # TODO: Handle timezone.
 
232
            dt = datetime.datetime.fromtimestamp(r.timestamp)
 
233
            if first <= dt and (last is None or dt <= last):
 
234
                return (i+1,)
 
235
REVISION_NAMESPACES['date:'] = _namespace_date