bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 
1185.11.5
by John Arbash Meinel
 Merged up-to-date against mainline, still broken.  | 
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, NoCommits  | 
|
21  | 
||
22  | 
_marker = []  | 
|
23  | 
||
24  | 
class RevisionInfo(object):  | 
|
25  | 
"""The results of applying a revision specification to a branch.  | 
|
26  | 
||
27  | 
    An instance has two useful attributes: revno, and rev_id.
 | 
|
28  | 
||
29  | 
    They can also be accessed as spec[0] and spec[1] respectively,
 | 
|
30  | 
    so that you can write code like:
 | 
|
31  | 
    revno, rev_id = RevisionSpec(branch, spec)
 | 
|
32  | 
    although this is probably going to be deprecated later.
 | 
|
33  | 
||
34  | 
    This class exists mostly to be the return value of a RevisionSpec,
 | 
|
35  | 
    so that you can access the member you're interested in (number or id)
 | 
|
36  | 
    or treat the result as a tuple.
 | 
|
37  | 
    """
 | 
|
38  | 
||
39  | 
def __init__(self, branch, revno, rev_id=_marker):  | 
|
40  | 
self.branch = branch  | 
|
41  | 
self.revno = revno  | 
|
42  | 
if rev_id is _marker:  | 
|
43  | 
            # allow caller to be lazy
 | 
|
44  | 
if self.revno is None:  | 
|
45  | 
self.rev_id = None  | 
|
46  | 
else:  | 
|
47  | 
self.rev_id = branch.get_rev_id(self.revno)  | 
|
48  | 
else:  | 
|
49  | 
self.rev_id = rev_id  | 
|
50  | 
||
51  | 
def __nonzero__(self):  | 
|
52  | 
        # first the easy ones...
 | 
|
53  | 
if self.rev_id is None:  | 
|
54  | 
return False  | 
|
55  | 
if self.revno is not None:  | 
|
56  | 
return True  | 
|
57  | 
        # TODO: otherwise, it should depend on how I was built -
 | 
|
58  | 
        # if it's in_history(branch), then check revision_history(),
 | 
|
59  | 
        # if it's in_store(branch), do the check below
 | 
|
60  | 
return self.rev_id in self.branch.revision_store  | 
|
61  | 
||
62  | 
def __len__(self):  | 
|
63  | 
return 2  | 
|
64  | 
||
65  | 
def __getitem__(self, index):  | 
|
66  | 
if index == 0: return self.revno  | 
|
67  | 
if index == 1: return self.rev_id  | 
|
68  | 
raise IndexError(index)  | 
|
69  | 
||
70  | 
def get(self):  | 
|
71  | 
return self.branch.get_revision(self.rev_id)  | 
|
72  | 
||
73  | 
def __eq__(self, other):  | 
|
74  | 
if type(other) not in (tuple, list, type(self)):  | 
|
75  | 
return False  | 
|
76  | 
if type(other) is type(self) and self.branch is not other.branch:  | 
|
77  | 
return False  | 
|
78  | 
return tuple(self) == tuple(other)  | 
|
79  | 
||
80  | 
def __repr__(self):  | 
|
81  | 
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (  | 
|
82  | 
self.revno, self.rev_id, self.branch)  | 
|
83  | 
||
84  | 
# classes in this list should have a "prefix" attribute, against which
 | 
|
85  | 
# string specs are matched
 | 
|
86  | 
SPEC_TYPES = []  | 
|
87  | 
||
88  | 
class RevisionSpec(object):  | 
|
89  | 
"""A parsed revision specification.  | 
|
90  | 
||
91  | 
    A revision specification can be an integer, in which case it is
 | 
|
92  | 
    assumed to be a revno (though this will translate negative values
 | 
|
93  | 
    into positive ones); or it can be a string, in which case it is
 | 
|
94  | 
    parsed for something like 'date:' or 'revid:' etc.
 | 
|
95  | 
||
96  | 
    Revision specs are an UI element, and they have been moved out
 | 
|
97  | 
    of the branch class to leave "back-end" classes unaware of such
 | 
|
98  | 
    details.  Code that gets a revno or rev_id from other code should
 | 
|
99  | 
    not be using revision specs - revnos and revision ids are the
 | 
|
100  | 
    accepted ways to refer to revisions internally.
 | 
|
101  | 
||
102  | 
    (Equivalent to the old Branch method get_revision_info())
 | 
|
103  | 
    """
 | 
|
104  | 
||
105  | 
prefix = None  | 
|
106  | 
||
107  | 
def __new__(cls, spec, foo=_marker):  | 
|
108  | 
"""Parse a revision specifier.  | 
|
109  | 
        """
 | 
|
110  | 
if spec is None:  | 
|
111  | 
return object.__new__(RevisionSpec, spec)  | 
|
112  | 
||
113  | 
try:  | 
|
114  | 
spec = int(spec)  | 
|
115  | 
except ValueError:  | 
|
116  | 
            pass
 | 
|
117  | 
||
118  | 
if isinstance(spec, int):  | 
|
119  | 
return object.__new__(RevisionSpec_int, spec)  | 
|
120  | 
elif isinstance(spec, basestring):  | 
|
121  | 
for spectype in SPEC_TYPES:  | 
|
122  | 
if spec.startswith(spectype.prefix):  | 
|
123  | 
return object.__new__(spectype, spec)  | 
|
124  | 
else:  | 
|
125  | 
raise BzrError('No namespace registered for string: %r' %  | 
|
126  | 
spec)  | 
|
127  | 
else:  | 
|
128  | 
raise TypeError('Unhandled revision type %s' % spec)  | 
|
129  | 
||
130  | 
def __init__(self, spec):  | 
|
131  | 
if self.prefix and spec.startswith(self.prefix):  | 
|
132  | 
spec = spec[len(self.prefix):]  | 
|
133  | 
self.spec = spec  | 
|
134  | 
||
135  | 
def _match_on(self, branch, revs):  | 
|
136  | 
return RevisionInfo(branch, 0, None)  | 
|
137  | 
||
138  | 
def _match_on_and_check(self, branch, revs):  | 
|
139  | 
info = self._match_on(branch, revs)  | 
|
140  | 
if info:  | 
|
141  | 
return info  | 
|
142  | 
elif info == (0, None):  | 
|
143  | 
            # special case - the empty tree
 | 
|
144  | 
return info  | 
|
145  | 
elif self.prefix:  | 
|
146  | 
raise NoSuchRevision(branch, self.prefix + str(self.spec))  | 
|
147  | 
else:  | 
|
148  | 
raise NoSuchRevision(branch, str(self.spec))  | 
|
149  | 
||
150  | 
def in_history(self, branch):  | 
|
151  | 
revs = branch.revision_history()  | 
|
152  | 
return self._match_on_and_check(branch, revs)  | 
|
153  | 
||
| 
1432
by Robert Collins
 branch: namespace  | 
154  | 
        # FIXME: in_history is somewhat broken,
 | 
155  | 
        # it will return non-history revisions in many
 | 
|
156  | 
        # circumstances. The expected facility is that
 | 
|
157  | 
        # in_history only returns revision-history revs,
 | 
|
158  | 
        # in_store returns any rev. RBC 20051010
 | 
|
159  | 
    # aliases for now, when we fix the core logic, then they
 | 
|
160  | 
    # will do what you expect.
 | 
|
161  | 
in_store = in_history  | 
|
162  | 
in_branch = in_store  | 
|
163  | 
||
| 
1185.11.5
by John Arbash Meinel
 Merged up-to-date against mainline, still broken.  | 
164  | 
def __repr__(self):  | 
165  | 
        # this is mostly for helping with testing
 | 
|
166  | 
return '<%s %s%s>' % (self.__class__.__name__,  | 
|
167  | 
self.prefix or '',  | 
|
168  | 
self.spec)  | 
|
169  | 
||
170  | 
||
171  | 
# private API
 | 
|
172  | 
||
173  | 
class RevisionSpec_int(RevisionSpec):  | 
|
174  | 
"""Spec is a number. Special case."""  | 
|
175  | 
def __init__(self, spec):  | 
|
176  | 
self.spec = int(spec)  | 
|
177  | 
||
178  | 
def _match_on(self, branch, revs):  | 
|
179  | 
if self.spec < 0:  | 
|
180  | 
revno = len(revs) + self.spec + 1  | 
|
181  | 
else:  | 
|
182  | 
revno = self.spec  | 
|
183  | 
rev_id = branch.get_rev_id(revno, revs)  | 
|
184  | 
return RevisionInfo(branch, revno, rev_id)  | 
|
185  | 
||
186  | 
||
187  | 
class RevisionSpec_revno(RevisionSpec):  | 
|
188  | 
prefix = 'revno:'  | 
|
189  | 
||
190  | 
def _match_on(self, branch, revs):  | 
|
191  | 
"""Lookup a revision by revision number"""  | 
|
192  | 
try:  | 
|
193  | 
return RevisionInfo(branch, int(self.spec))  | 
|
194  | 
except ValueError:  | 
|
195  | 
return RevisionInfo(branch, None)  | 
|
196  | 
||
197  | 
SPEC_TYPES.append(RevisionSpec_revno)  | 
|
198  | 
||
199  | 
||
200  | 
class RevisionSpec_revid(RevisionSpec):  | 
|
201  | 
prefix = 'revid:'  | 
|
202  | 
||
203  | 
def _match_on(self, branch, revs):  | 
|
204  | 
try:  | 
|
205  | 
return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)  | 
|
206  | 
except ValueError:  | 
|
207  | 
return RevisionInfo(branch, None)  | 
|
208  | 
||
209  | 
SPEC_TYPES.append(RevisionSpec_revid)  | 
|
210  | 
||
211  | 
||
212  | 
class RevisionSpec_last(RevisionSpec):  | 
|
| 
1185.1.39
by Robert Collins
 Robey Pointers before: namespace to clear up usage of dates in revision parameters  | 
213  | 
|
| 
1185.11.5
by John Arbash Meinel
 Merged up-to-date against mainline, still broken.  | 
214  | 
prefix = 'last:'  | 
215  | 
||
216  | 
def _match_on(self, branch, revs):  | 
|
217  | 
try:  | 
|
218  | 
offset = int(self.spec)  | 
|
219  | 
except ValueError:  | 
|
220  | 
return RevisionInfo(branch, None)  | 
|
221  | 
else:  | 
|
222  | 
if offset <= 0:  | 
|
223  | 
raise BzrError('You must supply a positive value for --revision last:XXX')  | 
|
224  | 
return RevisionInfo(branch, len(revs) - offset + 1)  | 
|
225  | 
||
226  | 
SPEC_TYPES.append(RevisionSpec_last)  | 
|
227  | 
||
228  | 
||
| 
1185.1.39
by Robert Collins
 Robey Pointers before: namespace to clear up usage of dates in revision parameters  | 
229  | 
class RevisionSpec_before(RevisionSpec):  | 
230  | 
||
231  | 
prefix = 'before:'  | 
|
232  | 
||
233  | 
def _match_on(self, branch, revs):  | 
|
234  | 
r = RevisionSpec(self.spec)._match_on(branch, revs)  | 
|
235  | 
if (r.revno is None) or (r.revno == 0):  | 
|
236  | 
return r  | 
|
237  | 
return RevisionInfo(branch, r.revno - 1)  | 
|
238  | 
||
239  | 
SPEC_TYPES.append(RevisionSpec_before)  | 
|
240  | 
||
241  | 
||
| 
1185.11.5
by John Arbash Meinel
 Merged up-to-date against mainline, still broken.  | 
242  | 
class RevisionSpec_tag(RevisionSpec):  | 
243  | 
prefix = 'tag:'  | 
|
244  | 
||
245  | 
def _match_on(self, branch, revs):  | 
|
246  | 
raise BzrError('tag: namespace registered, but not implemented.')  | 
|
247  | 
||
248  | 
SPEC_TYPES.append(RevisionSpec_tag)  | 
|
249  | 
||
250  | 
||
251  | 
class RevisionSpec_date(RevisionSpec):  | 
|
252  | 
prefix = 'date:'  | 
|
253  | 
_date_re = re.compile(  | 
|
254  | 
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'  | 
|
255  | 
r'(,|T)?\s*'  | 
|
256  | 
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'  | 
|
257  | 
        )
 | 
|
258  | 
||
259  | 
def _match_on(self, branch, revs):  | 
|
260  | 
"""  | 
|
261  | 
        Spec for date revisions:
 | 
|
262  | 
          date:value
 | 
|
263  | 
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 | 
|
| 
1185.1.39
by Robert Collins
 Robey Pointers before: namespace to clear up usage of dates in revision parameters  | 
264  | 
          matches the first entry after a given date (either at midnight or
 | 
265  | 
          at a specified time).
 | 
|
| 
1185.11.5
by John Arbash Meinel
 Merged up-to-date against mainline, still broken.  | 
266  | 
|
267  | 
          So the proper way of saying 'give me all entries for today' is:
 | 
|
| 
1185.1.39
by Robert Collins
 Robey Pointers before: namespace to clear up usage of dates in revision parameters  | 
268  | 
              -r date:today..date:tomorrow
 | 
| 
1185.11.5
by John Arbash Meinel
 Merged up-to-date against mainline, still broken.  | 
269  | 
        """
 | 
| 
1185.1.39
by Robert Collins
 Robey Pointers before: namespace to clear up usage of dates in revision parameters  | 
270  | 
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())  | 
| 
1185.11.5
by John Arbash Meinel
 Merged up-to-date against mainline, still broken.  | 
271  | 
if self.spec.lower() == 'yesterday':  | 
272  | 
dt = today - datetime.timedelta(days=1)  | 
|
273  | 
elif self.spec.lower() == 'today':  | 
|
274  | 
dt = today  | 
|
275  | 
elif self.spec.lower() == 'tomorrow':  | 
|
276  | 
dt = today + datetime.timedelta(days=1)  | 
|
277  | 
else:  | 
|
278  | 
m = self._date_re.match(self.spec)  | 
|
279  | 
if not m or (not m.group('date') and not m.group('time')):  | 
|
280  | 
raise BzrError('Invalid revision date %r' % self.spec)  | 
|
281  | 
||
282  | 
if m.group('date'):  | 
|
283  | 
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))  | 
|
284  | 
else:  | 
|
285  | 
year, month, day = today.year, today.month, today.day  | 
|
286  | 
if m.group('time'):  | 
|
287  | 
hour = int(m.group('hour'))  | 
|
288  | 
minute = int(m.group('minute'))  | 
|
289  | 
if m.group('second'):  | 
|
290  | 
second = int(m.group('second'))  | 
|
291  | 
else:  | 
|
292  | 
second = 0  | 
|
293  | 
else:  | 
|
294  | 
hour, minute, second = 0,0,0  | 
|
295  | 
||
296  | 
dt = datetime.datetime(year=year, month=month, day=day,  | 
|
297  | 
hour=hour, minute=minute, second=second)  | 
|
298  | 
first = dt  | 
|
| 
1185.1.39
by Robert Collins
 Robey Pointers before: namespace to clear up usage of dates in revision parameters  | 
299  | 
for i in range(len(revs)):  | 
300  | 
r = branch.get_revision(revs[i])  | 
|
301  | 
            # TODO: Handle timezone.
 | 
|
302  | 
dt = datetime.datetime.fromtimestamp(r.timestamp)  | 
|
303  | 
if first <= dt:  | 
|
304  | 
return RevisionInfo(branch, i+1)  | 
|
305  | 
return RevisionInfo(branch, None)  | 
|
| 
1185.11.5
by John Arbash Meinel
 Merged up-to-date against mainline, still broken.  | 
306  | 
|
307  | 
SPEC_TYPES.append(RevisionSpec_date)  | 
|
308  | 
||
309  | 
||
310  | 
class RevisionSpec_ancestor(RevisionSpec):  | 
|
311  | 
prefix = 'ancestor:'  | 
|
312  | 
||
313  | 
def _match_on(self, branch, revs):  | 
|
314  | 
from branch import Branch  | 
|
315  | 
from revision import common_ancestor, MultipleRevisionSources  | 
|
316  | 
other_branch = Branch.open_containing(self.spec)  | 
|
| 
1390
by Robert Collins
 pair programming worx... merge integration and weave  | 
317  | 
revision_a = branch.last_revision()  | 
318  | 
revision_b = other_branch.last_revision()  | 
|
| 
1185.11.5
by John Arbash Meinel
 Merged up-to-date against mainline, still broken.  | 
319  | 
for r, b in ((revision_a, branch), (revision_b, other_branch)):  | 
320  | 
if r is None:  | 
|
321  | 
raise NoCommits(b)  | 
|
322  | 
revision_source = MultipleRevisionSources(branch, other_branch)  | 
|
323  | 
rev_id = common_ancestor(revision_a, revision_b, revision_source)  | 
|
324  | 
try:  | 
|
325  | 
revno = branch.revision_id_to_revno(rev_id)  | 
|
326  | 
except NoSuchRevision:  | 
|
327  | 
revno = None  | 
|
328  | 
return RevisionInfo(branch, revno, rev_id)  | 
|
329  | 
||
330  | 
SPEC_TYPES.append(RevisionSpec_ancestor)  | 
|
| 
1432
by Robert Collins
 branch: namespace  | 
331  | 
|
332  | 
class RevisionSpec_branch(RevisionSpec):  | 
|
333  | 
"""A branch: revision specifier.  | 
|
334  | 
||
335  | 
    This takes the path to a branch and returns its tip revision id.
 | 
|
336  | 
    """
 | 
|
337  | 
prefix = 'branch:'  | 
|
338  | 
||
339  | 
def _match_on(self, branch, revs):  | 
|
340  | 
from branch import Branch  | 
|
341  | 
from fetch import greedy_fetch  | 
|
342  | 
other_branch = Branch.open_containing(self.spec)  | 
|
343  | 
revision_b = other_branch.last_revision()  | 
|
344  | 
if revision_b is None:  | 
|
345  | 
raise NoCommits(other_branch)  | 
|
346  | 
        # pull in the remote revisions so we can diff
 | 
|
347  | 
greedy_fetch(branch, other_branch, revision=revision_b)  | 
|
348  | 
try:  | 
|
349  | 
revno = branch.revision_id_to_revno(revision_b)  | 
|
350  | 
except NoSuchRevision:  | 
|
351  | 
revno = None  | 
|
352  | 
return RevisionInfo(branch, revno, revision_b)  | 
|
353  | 
||
354  | 
SPEC_TYPES.append(RevisionSpec_branch)  |