~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Alexander Belchenko
  • Date: 2006-07-30 16:43:12 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060730164312-b025fd3ff0cee59e
rename  gpl.txt => COPYING.txt

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
import bisect
19
18
import datetime
20
19
import re
21
 
 
22
 
from bzrlib import (
23
 
    errors,
24
 
    osutils,
25
 
    revision,
26
 
    symbol_versioning,
27
 
    trace,
28
 
    tsort,
29
 
    )
30
 
 
 
20
import bisect
 
21
from bzrlib.errors import BzrError, NoSuchRevision, NoCommits
31
22
 
32
23
_marker = []
33
24
 
34
 
 
35
25
class RevisionInfo(object):
36
 
    """The results of applying a revision specification to a branch."""
37
 
 
38
 
    help_txt = """The results of applying a revision specification to a branch.
 
26
    """The results of applying a revision specification to a branch.
39
27
 
40
28
    An instance has two useful attributes: revno, and rev_id.
41
29
 
94
82
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
95
83
            self.revno, self.rev_id, self.branch)
96
84
 
97
 
    @staticmethod
98
 
    def from_revision_id(branch, revision_id, revs):
99
 
        """Construct a RevisionInfo given just the id.
100
 
 
101
 
        Use this if you don't know or care what the revno is.
102
 
        """
103
 
        if revision_id == revision.NULL_REVISION:
104
 
            return RevisionInfo(branch, 0, revision_id)
105
 
        try:
106
 
            revno = revs.index(revision_id) + 1
107
 
        except ValueError:
108
 
            revno = None
109
 
        return RevisionInfo(branch, revno, revision_id)
110
 
 
111
 
 
112
85
# classes in this list should have a "prefix" attribute, against which
113
86
# string specs are matched
114
87
SPEC_TYPES = []
115
 
_revno_regex = None
116
 
 
117
88
 
118
89
class RevisionSpec(object):
119
 
    """A parsed revision specification."""
120
 
 
121
 
    help_txt = """A parsed revision specification.
 
90
    """A parsed revision specification.
122
91
 
123
92
    A revision specification can be an integer, in which case it is
124
93
    assumed to be a revno (though this will translate negative values
136
105
 
137
106
    prefix = None
138
107
 
139
 
    def __new__(cls, spec, _internal=False):
140
 
        if _internal:
141
 
            return object.__new__(cls, spec, _internal=_internal)
142
 
 
143
 
        symbol_versioning.warn('Creating a RevisionSpec directly has'
144
 
                               ' been deprecated in version 0.11. Use'
145
 
                               ' RevisionSpec.from_string()'
146
 
                               ' instead.',
147
 
                               DeprecationWarning, stacklevel=2)
148
 
        return RevisionSpec.from_string(spec)
149
 
 
150
 
    @staticmethod
151
 
    def from_string(spec):
152
 
        """Parse a revision spec string into a RevisionSpec object.
153
 
 
154
 
        :param spec: A string specified by the user
155
 
        :return: A RevisionSpec object that understands how to parse the
156
 
            supplied notation.
 
108
    def __new__(cls, spec, foo=_marker):
 
109
        """Parse a revision specifier.
157
110
        """
158
 
        if not isinstance(spec, (type(None), basestring)):
159
 
            raise TypeError('error')
160
 
 
161
111
        if spec is None:
162
 
            return RevisionSpec(None, _internal=True)
163
 
        for spectype in SPEC_TYPES:
164
 
            if spec.startswith(spectype.prefix):
165
 
                trace.mutter('Returning RevisionSpec %s for %s',
166
 
                             spectype.__name__, spec)
167
 
                return spectype(spec, _internal=True)
 
112
            return object.__new__(RevisionSpec, spec)
 
113
 
 
114
        try:
 
115
            spec = int(spec)
 
116
        except ValueError:
 
117
            pass
 
118
 
 
119
        if isinstance(spec, int):
 
120
            return object.__new__(RevisionSpec_int, spec)
 
121
        elif isinstance(spec, basestring):
 
122
            for spectype in SPEC_TYPES:
 
123
                if spec.startswith(spectype.prefix):
 
124
                    return object.__new__(spectype, spec)
 
125
            else:
 
126
                raise BzrError('No namespace registered for string: %r' %
 
127
                               spec)
168
128
        else:
169
 
            # RevisionSpec_revno is special cased, because it is the only
170
 
            # one that directly handles plain integers
171
 
            # TODO: This should not be special cased rather it should be
172
 
            # a method invocation on spectype.canparse()
173
 
            global _revno_regex
174
 
            if _revno_regex is None:
175
 
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
176
 
            if _revno_regex.match(spec) is not None:
177
 
                return RevisionSpec_revno(spec, _internal=True)
178
 
 
179
 
            raise errors.NoSuchRevisionSpec(spec)
180
 
 
181
 
    def __init__(self, spec, _internal=False):
182
 
        """Create a RevisionSpec referring to the Null revision.
183
 
 
184
 
        :param spec: The original spec supplied by the user
185
 
        :param _internal: Used to ensure that RevisionSpec is not being
186
 
            called directly. Only from RevisionSpec.from_string()
187
 
        """
188
 
        if not _internal:
189
 
            # XXX: Update this after 0.10 is released
190
 
            symbol_versioning.warn('Creating a RevisionSpec directly has'
191
 
                                   ' been deprecated in version 0.11. Use'
192
 
                                   ' RevisionSpec.from_string()'
193
 
                                   ' instead.',
194
 
                                   DeprecationWarning, stacklevel=2)
195
 
        self.user_spec = spec
 
129
            raise TypeError('Unhandled revision type %s' % spec)
 
130
 
 
131
    def __init__(self, spec):
196
132
        if self.prefix and spec.startswith(self.prefix):
197
133
            spec = spec[len(self.prefix):]
198
134
        self.spec = spec
199
135
 
200
136
    def _match_on(self, branch, revs):
201
 
        trace.mutter('Returning RevisionSpec._match_on: None')
202
 
        return RevisionInfo(branch, None, None)
 
137
        return RevisionInfo(branch, 0, None)
203
138
 
204
139
    def _match_on_and_check(self, branch, revs):
205
140
        info = self._match_on(branch, revs)
206
141
        if info:
207
142
            return info
208
 
        elif info == (None, None):
209
 
            # special case - nothing supplied
 
143
        elif info == (0, None):
 
144
            # special case - the empty tree
210
145
            return info
211
146
        elif self.prefix:
212
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
147
            raise NoSuchRevision(branch, self.prefix + str(self.spec))
213
148
        else:
214
 
            raise errors.InvalidRevisionSpec(self.spec, branch)
 
149
            raise NoSuchRevision(branch, str(self.spec))
215
150
 
216
151
    def in_history(self, branch):
217
152
        if branch:
218
153
            revs = branch.revision_history()
219
154
        else:
220
 
            # this should never trigger.
221
 
            # TODO: make it a deprecated code path. RBC 20060928
222
155
            revs = None
223
156
        return self._match_on_and_check(branch, revs)
224
157
 
231
164
    # will do what you expect.
232
165
    in_store = in_history
233
166
    in_branch = in_store
234
 
 
235
 
    def as_revision_id(self, context_branch):
236
 
        """Return just the revision_id for this revisions spec.
237
 
 
238
 
        Some revision specs require a context_branch to be able to determine
239
 
        their value. Not all specs will make use of it.
240
 
        """
241
 
        return self._as_revision_id(context_branch)
242
 
 
243
 
    def _as_revision_id(self, context_branch):
244
 
        """Implementation of as_revision_id()
245
 
 
246
 
        Classes should override this function to provide appropriate
247
 
        functionality. The default is to just call '.in_history().rev_id'
248
 
        """
249
 
        return self.in_history(context_branch).rev_id
250
 
 
 
167
        
251
168
    def __repr__(self):
252
169
        # this is mostly for helping with testing
253
 
        return '<%s %s>' % (self.__class__.__name__,
254
 
                              self.user_spec)
 
170
        return '<%s %s%s>' % (self.__class__.__name__,
 
171
                              self.prefix or '',
 
172
                              self.spec)
255
173
    
256
174
    def needs_branch(self):
257
175
        """Whether this revision spec needs a branch.
260
178
        """
261
179
        return True
262
180
 
263
 
    def get_branch(self):
264
 
        """When the revision specifier contains a branch location, return it.
265
 
        
266
 
        Otherwise, return None.
267
 
        """
268
 
        return None
269
 
 
270
 
 
271
181
# private API
272
182
 
 
183
class RevisionSpec_int(RevisionSpec):
 
184
    """Spec is a number.  Special case."""
 
185
    def __init__(self, spec):
 
186
        self.spec = int(spec)
 
187
 
 
188
    def _match_on(self, branch, revs):
 
189
        if self.spec < 0:
 
190
            revno = len(revs) + self.spec + 1
 
191
        else:
 
192
            revno = self.spec
 
193
        rev_id = branch.get_rev_id(revno, revs)
 
194
        return RevisionInfo(branch, revno, rev_id)
 
195
 
 
196
 
273
197
class RevisionSpec_revno(RevisionSpec):
274
 
    """Selects a revision using a number."""
275
 
 
276
 
    help_txt = """Selects a revision using a number.
277
 
 
278
 
    Use an integer to specify a revision in the history of the branch.
279
 
    Optionally a branch can be specified. The 'revno:' prefix is optional.
280
 
    A negative number will count from the end of the branch (-1 is the
281
 
    last revision, -2 the previous one). If the negative number is larger
282
 
    than the branch's history, the first revision is returned.
283
 
    Examples::
284
 
 
285
 
      revno:1                   -> return the first revision
286
 
      revno:3:/path/to/branch   -> return the 3rd revision of
287
 
                                   the branch '/path/to/branch'
288
 
      revno:-1                  -> The last revision in a branch.
289
 
      -2:http://other/branch    -> The second to last revision in the
290
 
                                   remote branch.
291
 
      -1000000                  -> Most likely the first revision, unless
292
 
                                   your history is very long.
293
 
    """
294
198
    prefix = 'revno:'
295
199
 
296
200
    def _match_on(self, branch, revs):
297
201
        """Lookup a revision by revision number"""
298
 
        branch, revno, revision_id = self._lookup(branch, revs)
299
 
        return RevisionInfo(branch, revno, revision_id)
300
 
 
301
 
    def _lookup(self, branch, revs_or_none):
302
 
        loc = self.spec.find(':')
303
 
        if loc == -1:
304
 
            revno_spec = self.spec
305
 
            branch_spec = None
306
 
        else:
307
 
            revno_spec = self.spec[:loc]
308
 
            branch_spec = self.spec[loc+1:]
309
 
 
310
 
        if revno_spec == '':
311
 
            if not branch_spec:
312
 
                raise errors.InvalidRevisionSpec(self.user_spec,
313
 
                        branch, 'cannot have an empty revno and no branch')
314
 
            revno = None
315
 
        else:
316
 
            try:
317
 
                revno = int(revno_spec)
318
 
                dotted = False
319
 
            except ValueError:
320
 
                # dotted decimal. This arguably should not be here
321
 
                # but the from_string method is a little primitive 
322
 
                # right now - RBC 20060928
323
 
                try:
324
 
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
325
 
                except ValueError, e:
326
 
                    raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
327
 
 
328
 
                dotted = True
329
 
 
330
 
        if branch_spec:
331
 
            # the user has override the branch to look in.
332
 
            # we need to refresh the revision_history map and
333
 
            # the branch object.
334
 
            from bzrlib.branch import Branch
335
 
            branch = Branch.open(branch_spec)
336
 
            revs_or_none = None
337
 
 
338
 
        if dotted:
339
 
            branch.lock_read()
340
 
            try:
341
 
                revision_id_to_revno = branch.get_revision_id_to_revno_map()
342
 
                revisions = [revision_id for revision_id, revno
343
 
                             in revision_id_to_revno.iteritems()
344
 
                             if revno == match_revno]
345
 
            finally:
346
 
                branch.unlock()
347
 
            if len(revisions) != 1:
348
 
                return branch, None, None
349
 
            else:
350
 
                # there is no traditional 'revno' for dotted-decimal revnos.
351
 
                # so for  API compatability we return None.
352
 
                return branch, None, revisions[0]
353
 
        else:
354
 
            last_revno, last_revision_id = branch.last_revision_info()
355
 
            if revno < 0:
356
 
                # if get_rev_id supported negative revnos, there would not be a
357
 
                # need for this special case.
358
 
                if (-revno) >= last_revno:
359
 
                    revno = 1
360
 
                else:
361
 
                    revno = last_revno + revno + 1
362
 
            try:
363
 
                revision_id = branch.get_rev_id(revno, revs_or_none)
364
 
            except errors.NoSuchRevision:
365
 
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
366
 
        return branch, revno, revision_id
367
 
 
368
 
    def _as_revision_id(self, context_branch):
369
 
        # We would have the revno here, but we don't really care
370
 
        branch, revno, revision_id = self._lookup(context_branch, None)
371
 
        return revision_id
372
 
 
 
202
        if self.spec.find(':') == -1:
 
203
            try:
 
204
                return RevisionInfo(branch, int(self.spec))
 
205
            except ValueError:
 
206
                return RevisionInfo(branch, None)
 
207
        else:
 
208
            from branch import Branch
 
209
            revname = self.spec[self.spec.find(':')+1:]
 
210
            other_branch = Branch.open_containing(revname)[0]
 
211
            try:
 
212
                revno = int(self.spec[:self.spec.find(':')])
 
213
            except ValueError:
 
214
                return RevisionInfo(other_branch, None)
 
215
            revid = other_branch.get_rev_id(revno)
 
216
            return RevisionInfo(other_branch, revno)
 
217
        
373
218
    def needs_branch(self):
374
219
        return self.spec.find(':') == -1
375
220
 
376
 
    def get_branch(self):
377
 
        if self.spec.find(':') == -1:
378
 
            return None
379
 
        else:
380
 
            return self.spec[self.spec.find(':')+1:]
381
 
 
382
 
# Old compatibility 
383
 
RevisionSpec_int = RevisionSpec_revno
384
 
 
385
221
SPEC_TYPES.append(RevisionSpec_revno)
386
222
 
387
223
 
388
224
class RevisionSpec_revid(RevisionSpec):
389
 
    """Selects a revision using the revision id."""
390
 
 
391
 
    help_txt = """Selects a revision using the revision id.
392
 
 
393
 
    Supply a specific revision id, that can be used to specify any
394
 
    revision id in the ancestry of the branch. 
395
 
    Including merges, and pending merges.
396
 
    Examples::
397
 
 
398
 
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
399
 
    """
400
 
 
401
225
    prefix = 'revid:'
402
226
 
403
227
    def _match_on(self, branch, revs):
404
 
        # self.spec comes straight from parsing the command line arguments,
405
 
        # so we expect it to be a Unicode string. Switch it to the internal
406
 
        # representation.
407
 
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
408
 
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
409
 
 
410
 
    def _as_revision_id(self, context_branch):
411
 
        return osutils.safe_revision_id(self.spec, warn=False)
 
228
        try:
 
229
            return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
 
230
        except ValueError:
 
231
            return RevisionInfo(branch, None, self.spec)
412
232
 
413
233
SPEC_TYPES.append(RevisionSpec_revid)
414
234
 
415
235
 
416
236
class RevisionSpec_last(RevisionSpec):
417
 
    """Selects the nth revision from the end."""
418
 
 
419
 
    help_txt = """Selects the nth revision from the end.
420
 
 
421
 
    Supply a positive number to get the nth revision from the end.
422
 
    This is the same as supplying negative numbers to the 'revno:' spec.
423
 
    Examples::
424
 
 
425
 
      last:1        -> return the last revision
426
 
      last:3        -> return the revision 2 before the end.
427
 
    """
428
237
 
429
238
    prefix = 'last:'
430
239
 
431
240
    def _match_on(self, branch, revs):
432
 
        revno, revision_id = self._revno_and_revision_id(branch, revs)
433
 
        return RevisionInfo(branch, revno, revision_id)
434
 
 
435
 
    def _revno_and_revision_id(self, context_branch, revs_or_none):
436
 
        last_revno, last_revision_id = context_branch.last_revision_info()
437
 
 
438
 
        if self.spec == '':
439
 
            if not last_revno:
440
 
                raise errors.NoCommits(context_branch)
441
 
            return last_revno, last_revision_id
442
 
 
443
241
        try:
444
242
            offset = int(self.spec)
445
 
        except ValueError, e:
446
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
447
 
 
448
 
        if offset <= 0:
449
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
450
 
                                             'you must supply a positive value')
451
 
 
452
 
        revno = last_revno - offset + 1
453
 
        try:
454
 
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
455
 
        except errors.NoSuchRevision:
456
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
457
 
        return revno, revision_id
458
 
 
459
 
    def _as_revision_id(self, context_branch):
460
 
        # We compute the revno as part of the process, but we don't really care
461
 
        # about it.
462
 
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
463
 
        return revision_id
 
243
        except ValueError:
 
244
            return RevisionInfo(branch, None)
 
245
        else:
 
246
            if offset <= 0:
 
247
                raise BzrError('You must supply a positive value for --revision last:XXX')
 
248
            return RevisionInfo(branch, len(revs) - offset + 1)
464
249
 
465
250
SPEC_TYPES.append(RevisionSpec_last)
466
251
 
467
252
 
468
253
class RevisionSpec_before(RevisionSpec):
469
 
    """Selects the parent of the revision specified."""
470
 
 
471
 
    help_txt = """Selects the parent of the revision specified.
472
 
 
473
 
    Supply any revision spec to return the parent of that revision.
474
 
    It is an error to request the parent of the null revision (before:0).
475
 
    This is mostly useful when inspecting revisions that are not in the
476
 
    revision history of a branch.
477
 
 
478
 
    Examples::
479
 
 
480
 
      before:1913    -> Return the parent of revno 1913 (revno 1912)
481
 
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
482
 
                                            aaaa@bbbb-1234567890
483
 
      bzr diff -r before:revid:aaaa..revid:aaaa
484
 
            -> Find the changes between revision 'aaaa' and its parent.
485
 
               (what changes did 'aaaa' introduce)
486
 
    """
487
254
 
488
255
    prefix = 'before:'
489
256
    
490
257
    def _match_on(self, branch, revs):
491
 
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
492
 
        if r.revno == 0:
493
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
494
 
                                         'cannot go before the null: revision')
495
 
        if r.revno is None:
496
 
            # We need to use the repository history here
497
 
            rev = branch.repository.get_revision(r.rev_id)
498
 
            if not rev.parent_ids:
499
 
                revno = 0
500
 
                revision_id = revision.NULL_REVISION
501
 
            else:
502
 
                revision_id = rev.parent_ids[0]
503
 
                try:
504
 
                    revno = revs.index(revision_id) + 1
505
 
                except ValueError:
506
 
                    revno = None
507
 
        else:
508
 
            revno = r.revno - 1
509
 
            try:
510
 
                revision_id = branch.get_rev_id(revno, revs)
511
 
            except errors.NoSuchRevision:
512
 
                raise errors.InvalidRevisionSpec(self.user_spec,
513
 
                                                 branch)
514
 
        return RevisionInfo(branch, revno, revision_id)
515
 
 
516
 
    def _as_revision_id(self, context_branch):
517
 
        base_revspec = RevisionSpec.from_string(self.spec)
518
 
        base_revision_id = base_revspec.as_revision_id(context_branch)
519
 
        if base_revision_id == revision.NULL_REVISION:
520
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
521
 
                                         'cannot go before the null: revision')
522
 
        context_repo = context_branch.repository
523
 
        context_repo.lock_read()
524
 
        try:
525
 
            parent_map = context_repo.get_parent_map([base_revision_id])
526
 
        finally:
527
 
            context_repo.unlock()
528
 
        if base_revision_id not in parent_map:
529
 
            # Ghost, or unknown revision id
530
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
531
 
                'cannot find the matching revision')
532
 
        parents = parent_map[base_revision_id]
533
 
        if len(parents) < 1:
534
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
535
 
                'No parents for revision.')
536
 
        return parents[0]
 
258
        r = RevisionSpec(self.spec)._match_on(branch, revs)
 
259
        if (r.revno is None) or (r.revno == 0):
 
260
            return r
 
261
        return RevisionInfo(branch, r.revno - 1)
537
262
 
538
263
SPEC_TYPES.append(RevisionSpec_before)
539
264
 
540
265
 
541
266
class RevisionSpec_tag(RevisionSpec):
542
 
    """Select a revision identified by tag name"""
543
 
 
544
 
    help_txt = """Selects a revision identified by a tag name.
545
 
 
546
 
    Tags are stored in the branch and created by the 'tag' command.
547
 
    """
548
 
 
549
267
    prefix = 'tag:'
550
268
 
551
269
    def _match_on(self, branch, revs):
552
 
        # Can raise tags not supported, NoSuchTag, etc
553
 
        return RevisionInfo.from_revision_id(branch,
554
 
            branch.tags.lookup_tag(self.spec),
555
 
            revs)
556
 
 
557
 
    def _as_revision_id(self, context_branch):
558
 
        return context_branch.tags.lookup_tag(self.spec)
 
270
        raise BzrError('tag: namespace registered, but not implemented.')
559
271
 
560
272
SPEC_TYPES.append(RevisionSpec_tag)
561
273
 
562
274
 
563
 
class _RevListToTimestamps(object):
564
 
    """This takes a list of revisions, and allows you to bisect by date"""
565
 
 
566
 
    __slots__ = ['revs', 'branch']
567
 
 
 
275
class RevisionSpec_revs:
568
276
    def __init__(self, revs, branch):
569
277
        self.revs = revs
570
278
        self.branch = branch
571
 
 
572
279
    def __getitem__(self, index):
573
 
        """Get the date of the index'd item"""
574
280
        r = self.branch.repository.get_revision(self.revs[index])
575
281
        # TODO: Handle timezone.
576
282
        return datetime.datetime.fromtimestamp(r.timestamp)
577
 
 
578
283
    def __len__(self):
579
284
        return len(self.revs)
580
285
 
581
286
 
582
287
class RevisionSpec_date(RevisionSpec):
583
 
    """Selects a revision on the basis of a datestamp."""
584
 
 
585
 
    help_txt = """Selects a revision on the basis of a datestamp.
586
 
 
587
 
    Supply a datestamp to select the first revision that matches the date.
588
 
    Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
589
 
    Matches the first entry after a given date (either at midnight or
590
 
    at a specified time).
591
 
 
592
 
    One way to display all the changes since yesterday would be::
593
 
 
594
 
        bzr log -r date:yesterday..-1
595
 
 
596
 
    Examples::
597
 
 
598
 
      date:yesterday            -> select the first revision since yesterday
599
 
      date:2006-08-14,17:10:14  -> select the first revision after
600
 
                                   August 14th, 2006 at 5:10pm.
601
 
    """    
602
288
    prefix = 'date:'
603
289
    _date_re = re.compile(
604
290
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
607
293
        )
608
294
 
609
295
    def _match_on(self, branch, revs):
610
 
        """Spec for date revisions:
 
296
        """
 
297
        Spec for date revisions:
611
298
          date:value
612
299
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
613
300
          matches the first entry after a given date (either at midnight or
614
301
          at a specified time).
 
302
 
 
303
          So the proper way of saying 'give me all entries for today' is:
 
304
              -r date:yesterday..date:today
615
305
        """
616
 
        #  XXX: This doesn't actually work
617
 
        #  So the proper way of saying 'give me all entries for today' is:
618
 
        #      -r date:yesterday..date:today
619
306
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
620
307
        if self.spec.lower() == 'yesterday':
621
308
            dt = today - datetime.timedelta(days=1)
626
313
        else:
627
314
            m = self._date_re.match(self.spec)
628
315
            if not m or (not m.group('date') and not m.group('time')):
629
 
                raise errors.InvalidRevisionSpec(self.user_spec,
630
 
                                                 branch, 'invalid date')
631
 
 
632
 
            try:
633
 
                if m.group('date'):
634
 
                    year = int(m.group('year'))
635
 
                    month = int(m.group('month'))
636
 
                    day = int(m.group('day'))
637
 
                else:
638
 
                    year = today.year
639
 
                    month = today.month
640
 
                    day = today.day
641
 
 
642
 
                if m.group('time'):
643
 
                    hour = int(m.group('hour'))
644
 
                    minute = int(m.group('minute'))
645
 
                    if m.group('second'):
646
 
                        second = int(m.group('second'))
647
 
                    else:
648
 
                        second = 0
649
 
                else:
650
 
                    hour, minute, second = 0,0,0
651
 
            except ValueError:
652
 
                raise errors.InvalidRevisionSpec(self.user_spec,
653
 
                                                 branch, 'invalid date')
 
316
                raise BzrError('Invalid revision date %r' % self.spec)
 
317
 
 
318
            if m.group('date'):
 
319
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
320
            else:
 
321
                year, month, day = today.year, today.month, today.day
 
322
            if m.group('time'):
 
323
                hour = int(m.group('hour'))
 
324
                minute = int(m.group('minute'))
 
325
                if m.group('second'):
 
326
                    second = int(m.group('second'))
 
327
                else:
 
328
                    second = 0
 
329
            else:
 
330
                hour, minute, second = 0,0,0
654
331
 
655
332
            dt = datetime.datetime(year=year, month=month, day=day,
656
333
                    hour=hour, minute=minute, second=second)
657
334
        branch.lock_read()
658
335
        try:
659
 
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
 
336
            rev = bisect.bisect(RevisionSpec_revs(revs, branch), dt)
660
337
        finally:
661
338
            branch.unlock()
662
339
        if rev == len(revs):
663
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
340
            return RevisionInfo(branch, None)
664
341
        else:
665
342
            return RevisionInfo(branch, rev + 1)
666
343
 
668
345
 
669
346
 
670
347
class RevisionSpec_ancestor(RevisionSpec):
671
 
    """Selects a common ancestor with a second branch."""
672
 
 
673
 
    help_txt = """Selects a common ancestor with a second branch.
674
 
 
675
 
    Supply the path to a branch to select the common ancestor.
676
 
 
677
 
    The common ancestor is the last revision that existed in both
678
 
    branches. Usually this is the branch point, but it could also be
679
 
    a revision that was merged.
680
 
 
681
 
    This is frequently used with 'diff' to return all of the changes
682
 
    that your branch introduces, while excluding the changes that you
683
 
    have not merged from the remote branch.
684
 
 
685
 
    Examples::
686
 
 
687
 
      ancestor:/path/to/branch
688
 
      $ bzr diff -r ancestor:../../mainline/branch
689
 
    """
690
348
    prefix = 'ancestor:'
691
349
 
692
350
    def _match_on(self, branch, revs):
693
 
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
694
 
        return self._find_revision_info(branch, self.spec)
695
 
 
696
 
    def _as_revision_id(self, context_branch):
697
 
        return self._find_revision_id(context_branch, self.spec)
698
 
 
699
 
    @staticmethod
700
 
    def _find_revision_info(branch, other_location):
701
 
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
702
 
                                                              other_location)
 
351
        from branch import Branch
 
352
        from revision import common_ancestor, MultipleRevisionSources
 
353
        other_branch = Branch.open_containing(self.spec)[0]
 
354
        revision_a = branch.last_revision()
 
355
        revision_b = other_branch.last_revision()
 
356
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
 
357
            if r is None:
 
358
                raise NoCommits(b)
 
359
        revision_source = MultipleRevisionSources(branch.repository,
 
360
                                                  other_branch.repository)
 
361
        rev_id = common_ancestor(revision_a, revision_b, revision_source)
703
362
        try:
704
 
            revno = branch.revision_id_to_revno(revision_id)
705
 
        except errors.NoSuchRevision:
 
363
            revno = branch.revision_id_to_revno(rev_id)
 
364
        except NoSuchRevision:
706
365
            revno = None
707
 
        return RevisionInfo(branch, revno, revision_id)
708
 
 
709
 
    @staticmethod
710
 
    def _find_revision_id(branch, other_location):
711
 
        from bzrlib.branch import Branch
712
 
 
713
 
        branch.lock_read()
714
 
        try:
715
 
            revision_a = revision.ensure_null(branch.last_revision())
716
 
            if revision_a == revision.NULL_REVISION:
717
 
                raise errors.NoCommits(branch)
718
 
            other_branch = Branch.open(other_location)
719
 
            other_branch.lock_read()
720
 
            try:
721
 
                revision_b = revision.ensure_null(other_branch.last_revision())
722
 
                if revision_b == revision.NULL_REVISION:
723
 
                    raise errors.NoCommits(other_branch)
724
 
                graph = branch.repository.get_graph(other_branch.repository)
725
 
                rev_id = graph.find_unique_lca(revision_a, revision_b)
726
 
            finally:
727
 
                other_branch.unlock()
728
 
            if rev_id == revision.NULL_REVISION:
729
 
                raise errors.NoCommonAncestor(revision_a, revision_b)
730
 
            return rev_id
731
 
        finally:
732
 
            branch.unlock()
733
 
 
734
 
 
 
366
        return RevisionInfo(branch, revno, rev_id)
 
367
        
735
368
SPEC_TYPES.append(RevisionSpec_ancestor)
736
369
 
737
 
 
738
370
class RevisionSpec_branch(RevisionSpec):
739
 
    """Selects the last revision of a specified branch."""
740
 
 
741
 
    help_txt = """Selects the last revision of a specified branch.
742
 
 
743
 
    Supply the path to a branch to select its last revision.
744
 
 
745
 
    Examples::
746
 
 
747
 
      branch:/path/to/branch
 
371
    """A branch: revision specifier.
 
372
 
 
373
    This takes the path to a branch and returns its tip revision id.
748
374
    """
749
375
    prefix = 'branch:'
750
376
 
751
377
    def _match_on(self, branch, revs):
752
 
        from bzrlib.branch import Branch
753
 
        other_branch = Branch.open(self.spec)
 
378
        from branch import Branch
 
379
        other_branch = Branch.open_containing(self.spec)[0]
754
380
        revision_b = other_branch.last_revision()
755
 
        if revision_b in (None, revision.NULL_REVISION):
756
 
            raise errors.NoCommits(other_branch)
 
381
        if revision_b is None:
 
382
            raise NoCommits(other_branch)
757
383
        # pull in the remote revisions so we can diff
758
384
        branch.fetch(other_branch, revision_b)
759
385
        try:
760
386
            revno = branch.revision_id_to_revno(revision_b)
761
 
        except errors.NoSuchRevision:
 
387
        except NoSuchRevision:
762
388
            revno = None
763
389
        return RevisionInfo(branch, revno, revision_b)
764
 
 
765
 
    def _as_revision_id(self, context_branch):
766
 
        from bzrlib.branch import Branch
767
 
        other_branch = Branch.open(self.spec)
768
 
        last_revision = other_branch.last_revision()
769
 
        last_revision = revision.ensure_null(last_revision)
770
 
        context_branch.fetch(other_branch, last_revision)
771
 
        if last_revision == revision.NULL_REVISION:
772
 
            raise errors.NoCommits(other_branch)
773
 
        return last_revision
774
 
 
 
390
        
775
391
SPEC_TYPES.append(RevisionSpec_branch)
776
 
 
777
 
 
778
 
class RevisionSpec_submit(RevisionSpec_ancestor):
779
 
    """Selects a common ancestor with a submit branch."""
780
 
 
781
 
    help_txt = """Selects a common ancestor with the submit branch.
782
 
 
783
 
    Diffing against this shows all the changes that were made in this branch,
784
 
    and is a good predictor of what merge will do.  The submit branch is
785
 
    used by the bundle and merge directive comands.  If no submit branch
786
 
    is specified, the parent branch is used instead.
787
 
 
788
 
    The common ancestor is the last revision that existed in both
789
 
    branches. Usually this is the branch point, but it could also be
790
 
    a revision that was merged.
791
 
 
792
 
    Examples::
793
 
 
794
 
      $ bzr diff -r submit:
795
 
    """
796
 
 
797
 
    prefix = 'submit:'
798
 
 
799
 
    def _get_submit_location(self, branch):
800
 
        submit_location = branch.get_submit_branch()
801
 
        location_type = 'submit branch'
802
 
        if submit_location is None:
803
 
            submit_location = branch.get_parent()
804
 
            location_type = 'parent branch'
805
 
        if submit_location is None:
806
 
            raise errors.NoSubmitBranch(branch)
807
 
        trace.note('Using %s %s', location_type, submit_location)
808
 
        return submit_location
809
 
 
810
 
    def _match_on(self, branch, revs):
811
 
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
812
 
        return self._find_revision_info(branch,
813
 
            self._get_submit_location(branch))
814
 
 
815
 
    def _as_revision_id(self, context_branch):
816
 
        return self._find_revision_id(context_branch,
817
 
            self._get_submit_location(context_branch))
818
 
 
819
 
 
820
 
SPEC_TYPES.append(RevisionSpec_submit)