~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Robert Collins
  • Date: 2005-08-25 06:08:36 UTC
  • mto: (974.1.50) (1185.1.10) (1092.3.1)
  • mto: This revision was merged to the branch mainline in revision 1139.
  • Revision ID: robertc@robertcollins.net-20050825060836-40b430abb9d341d9
unbreak cmd_branch now that something tests the core of it..

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