~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Lalo Martins
  • Date: 2005-09-07 15:28:14 UTC
  • mto: (1185.1.22)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: lalo@exoweb.net-20050907152813-da20c1411bc3be7f
moving the 'revision spec' stuff out of the Branch class and into a new
module (obeying a XXX comment).

This is the last XXX in branch.py, except for the ones I added :-)

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
def get_revision_info(branch, spec):
 
28
    """Return (revno, revision id) for revision specifier.
 
29
 
 
30
    spec can be an integer, in which case it is assumed to be revno
 
31
    (though this will translate negative values into positive ones)
 
32
    spec can also be a string, in which case it is parsed for something
 
33
    like 'date:' or 'revid:' etc.
 
34
 
 
35
    A revid is always returned.  If it is None, the specifier referred to
 
36
    the null revision.  If the revid does not occur in the revision
 
37
    history, revno will be None.
 
38
    """
 
39
 
 
40
    if spec is None:
 
41
        return 0, None
 
42
    revno = None
 
43
    try:# Convert to int if possible
 
44
        spec = int(spec)
 
45
    except ValueError:
 
46
        pass
 
47
    revs = branch.revision_history()
 
48
    if isinstance(spec, int):
 
49
        if spec < 0:
 
50
            revno = len(revs) + spec + 1
 
51
        else:
 
52
            revno = spec
 
53
        rev_id = branch.get_rev_id(revno, revs)
 
54
    elif isinstance(spec, basestring):
 
55
        for prefix, func in REVISION_NAMESPACES.iteritems():
 
56
            if spec.startswith(prefix):
 
57
                result = func(branch, revs, spec)
 
58
                if len(result) > 1:
 
59
                    revno, rev_id = result
 
60
                else:
 
61
                    revno = result[0]
 
62
                    rev_id = branch.get_rev_id(revno, revs)
 
63
                break
 
64
        else:
 
65
            raise BzrError('No namespace registered for string: %r' %
 
66
                           spec)
 
67
    else:
 
68
        raise TypeError('Unhandled revision type %s' % spec)
 
69
 
 
70
    if revno is None or rev_id is None:
 
71
        raise NoSuchRevision(branch, spec)
 
72
    return revno, rev_id
 
73
 
 
74
 
 
75
# private API
 
76
 
 
77
def _namespace_revno(branch, revs, spec):
 
78
    """Lookup a revision by revision number"""
 
79
    assert spec.startswith('revno:')
 
80
    try:
 
81
        return (int(spec[len('revno:'):]),)
 
82
    except ValueError:
 
83
        return (None,)
 
84
REVISION_NAMESPACES['revno:'] = _namespace_revno
 
85
 
 
86
 
 
87
def _namespace_revid(branch, revs, spec):
 
88
    assert spec.startswith('revid:')
 
89
    rev_id = spec[len('revid:'):]
 
90
    try:
 
91
        return revs.index(rev_id) + 1, rev_id
 
92
    except ValueError:
 
93
        return (None,)
 
94
REVISION_NAMESPACES['revid:'] = _namespace_revid
 
95
 
 
96
 
 
97
def _namespace_last(branch, revs, spec):
 
98
    assert spec.startswith('last:')
 
99
    try:
 
100
        offset = int(spec[5:])
 
101
    except ValueError:
 
102
        return (None,)
 
103
    else:
 
104
        if offset <= 0:
 
105
            raise BzrError('You must supply a positive value for --revision last:XXX')
 
106
        return (len(revs) - offset + 1,)
 
107
REVISION_NAMESPACES['last:'] = _namespace_last
 
108
 
 
109
 
 
110
def _namespace_tag(branch, revs, spec):
 
111
    assert spec.startswith('tag:')
 
112
    raise BzrError('tag: namespace registered, but not implemented.')
 
113
REVISION_NAMESPACES['tag:'] = _namespace_tag
 
114
 
 
115
 
 
116
_date_re = re.compile(
 
117
        r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
118
        r'(,|T)?\s*'
 
119
        r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
120
    )
 
121
 
 
122
def _namespace_date(branch, revs, spec):
 
123
    """
 
124
    Spec for date revisions:
 
125
      date:value
 
126
      value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
127
      it can also start with a '+/-/='. '+' says match the first
 
128
      entry after the given date. '-' is match the first entry before the date
 
129
      '=' is match the first entry after, but still on the given date.
 
130
    
 
131
      +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
 
132
      -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
 
133
      =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
 
134
          May 13th, 2005 at 0:00
 
135
    
 
136
      So the proper way of saying 'give me all entries for today' is:
 
137
          -r {date:+today}:{date:-tomorrow}
 
138
      The default is '=' when not supplied
 
139
    """
 
140
    assert spec.startswith('date:')
 
141
    val = spec[5:]
 
142
    match_style = '='
 
143
    if val[:1] in ('+', '-', '='):
 
144
        match_style = val[:1]
 
145
        val = val[1:]
 
146
 
 
147
    # XXX: this should probably be using datetime.date instead
 
148
    today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
 
149
                                              microsecond=0)
 
150
    if val.lower() == 'yesterday':
 
151
        dt = today - datetime.timedelta(days=1)
 
152
    elif val.lower() == 'today':
 
153
        dt = today
 
154
    elif val.lower() == 'tomorrow':
 
155
        dt = today + datetime.timedelta(days=1)
 
156
    else:
 
157
        m = _date_re.match(val)
 
158
        if not m or (not m.group('date') and not m.group('time')):
 
159
            raise BzrError('Invalid revision date %r' % spec)
 
160
 
 
161
        if m.group('date'):
 
162
            year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
163
        else:
 
164
            year, month, day = today.year, today.month, today.day
 
165
        if m.group('time'):
 
166
            hour = int(m.group('hour'))
 
167
            minute = int(m.group('minute'))
 
168
            if m.group('second'):
 
169
                second = int(m.group('second'))
 
170
            else:
 
171
                second = 0
 
172
        else:
 
173
            hour, minute, second = 0,0,0
 
174
 
 
175
        dt = datetime.datetime(year=year, month=month, day=day,
 
176
                hour=hour, minute=minute, second=second)
 
177
    first = dt
 
178
    last = None
 
179
    reversed = False
 
180
    if match_style == '-':
 
181
        reversed = True
 
182
    elif match_style == '=':
 
183
        last = dt + datetime.timedelta(days=1)
 
184
 
 
185
    if reversed:
 
186
        for i in range(len(revs)-1, -1, -1):
 
187
            r = branch.get_revision(revs[i])
 
188
            # TODO: Handle timezone.
 
189
            dt = datetime.datetime.fromtimestamp(r.timestamp)
 
190
            if first >= dt and (last is None or dt >= last):
 
191
                return (i+1,)
 
192
    else:
 
193
        for i in range(len(revs)):
 
194
            r = branch.get_revision(revs[i])
 
195
            # TODO: Handle timezone.
 
196
            dt = datetime.datetime.fromtimestamp(r.timestamp)
 
197
            if first <= dt and (last is None or dt <= last):
 
198
                return (i+1,)
 
199
REVISION_NAMESPACES['date:'] = _namespace_date