~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

add a clean target

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