/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
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
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
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.
1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
41
    """
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
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)
1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
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