~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Lalo Martins
  • Date: 2005-09-09 10:58:51 UTC
  • mto: (1185.1.22)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: lalo@exoweb.net-20050909105851-25aa36ea27f4ce7b
creating the new branch constructors

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
        return tuple(self) == tuple(other)
 
104
 
 
105
    def __repr__(self):
 
106
        return '<bzrlib.revisionspec.RevisionSpec object %s, %s for %r>' % (
 
107
            self.revno, self.rev_id, self.branch)
 
108
 
 
109
 
 
110
# private API
 
111
 
 
112
def _namespace_revno(branch, revs, spec):
 
113
    """Lookup a revision by revision number"""
 
114
    assert spec.startswith('revno:')
 
115
    try:
 
116
        return (int(spec[len('revno:'):]),)
 
117
    except ValueError:
 
118
        return (None,)
 
119
REVISION_NAMESPACES['revno:'] = _namespace_revno
 
120
 
 
121
 
 
122
def _namespace_revid(branch, revs, spec):
 
123
    assert spec.startswith('revid:')
 
124
    rev_id = spec[len('revid:'):]
 
125
    try:
 
126
        return revs.index(rev_id) + 1, rev_id
 
127
    except ValueError:
 
128
        return (None,)
 
129
REVISION_NAMESPACES['revid:'] = _namespace_revid
 
130
 
 
131
 
 
132
def _namespace_last(branch, revs, spec):
 
133
    assert spec.startswith('last:')
 
134
    try:
 
135
        offset = int(spec[5:])
 
136
    except ValueError:
 
137
        return (None,)
 
138
    else:
 
139
        if offset <= 0:
 
140
            raise BzrError('You must supply a positive value for --revision last:XXX')
 
141
        return (len(revs) - offset + 1,)
 
142
REVISION_NAMESPACES['last:'] = _namespace_last
 
143
 
 
144
 
 
145
def _namespace_tag(branch, revs, spec):
 
146
    assert spec.startswith('tag:')
 
147
    raise BzrError('tag: namespace registered, but not implemented.')
 
148
REVISION_NAMESPACES['tag:'] = _namespace_tag
 
149
 
 
150
 
 
151
_date_re = re.compile(
 
152
        r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
153
        r'(,|T)?\s*'
 
154
        r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
155
    )
 
156
 
 
157
def _namespace_date(branch, revs, spec):
 
158
    """
 
159
    Spec for date revisions:
 
160
      date:value
 
161
      value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
162
      it can also start with a '+/-/='. '+' says match the first
 
163
      entry after the given date. '-' is match the first entry before the date
 
164
      '=' is match the first entry after, but still on the given date.
 
165
    
 
166
      +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
 
167
      -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
 
168
      =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
 
169
          May 13th, 2005 at 0:00
 
170
    
 
171
      So the proper way of saying 'give me all entries for today' is:
 
172
          -r {date:+today}:{date:-tomorrow}
 
173
      The default is '=' when not supplied
 
174
    """
 
175
    assert spec.startswith('date:')
 
176
    val = spec[5:]
 
177
    match_style = '='
 
178
    if val[:1] in ('+', '-', '='):
 
179
        match_style = val[:1]
 
180
        val = val[1:]
 
181
 
 
182
    # XXX: this should probably be using datetime.date instead
 
183
    today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
 
184
                                              microsecond=0)
 
185
    if val.lower() == 'yesterday':
 
186
        dt = today - datetime.timedelta(days=1)
 
187
    elif val.lower() == 'today':
 
188
        dt = today
 
189
    elif val.lower() == 'tomorrow':
 
190
        dt = today + datetime.timedelta(days=1)
 
191
    else:
 
192
        m = _date_re.match(val)
 
193
        if not m or (not m.group('date') and not m.group('time')):
 
194
            raise BzrError('Invalid revision date %r' % spec)
 
195
 
 
196
        if m.group('date'):
 
197
            year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
198
        else:
 
199
            year, month, day = today.year, today.month, today.day
 
200
        if m.group('time'):
 
201
            hour = int(m.group('hour'))
 
202
            minute = int(m.group('minute'))
 
203
            if m.group('second'):
 
204
                second = int(m.group('second'))
 
205
            else:
 
206
                second = 0
 
207
        else:
 
208
            hour, minute, second = 0,0,0
 
209
 
 
210
        dt = datetime.datetime(year=year, month=month, day=day,
 
211
                hour=hour, minute=minute, second=second)
 
212
    first = dt
 
213
    last = None
 
214
    reversed = False
 
215
    if match_style == '-':
 
216
        reversed = True
 
217
    elif match_style == '=':
 
218
        last = dt + datetime.timedelta(days=1)
 
219
 
 
220
    if reversed:
 
221
        for i in range(len(revs)-1, -1, -1):
 
222
            r = branch.get_revision(revs[i])
 
223
            # TODO: Handle timezone.
 
224
            dt = datetime.datetime.fromtimestamp(r.timestamp)
 
225
            if first >= dt and (last is None or dt >= last):
 
226
                return (i+1,)
 
227
    else:
 
228
        for i in range(len(revs)):
 
229
            r = branch.get_revision(revs[i])
 
230
            # TODO: Handle timezone.
 
231
            dt = datetime.datetime.fromtimestamp(r.timestamp)
 
232
            if first <= dt and (last is None or dt <= last):
 
233
                return (i+1,)
 
234
REVISION_NAMESPACES['date:'] = _namespace_date