/usr/share/pyshared/bzrlib/revisionspec.py is in python-bzrlib 2.5.0-2ubuntu2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 | # Copyright (C) 2005-2010 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import absolute_import
from bzrlib.lazy_import import lazy_import
lazy_import(globals(), """
import bisect
import datetime
from bzrlib import (
branch as _mod_branch,
osutils,
revision,
symbol_versioning,
workingtree,
)
from bzrlib.i18n import gettext
""")
from bzrlib import (
errors,
lazy_regex,
registry,
trace,
)
class RevisionInfo(object):
"""The results of applying a revision specification to a branch."""
help_txt = """The results of applying a revision specification to a branch.
An instance has two useful attributes: revno, and rev_id.
They can also be accessed as spec[0] and spec[1] respectively,
so that you can write code like:
revno, rev_id = RevisionSpec(branch, spec)
although this is probably going to be deprecated later.
This class exists mostly to be the return value of a RevisionSpec,
so that you can access the member you're interested in (number or id)
or treat the result as a tuple.
"""
def __init__(self, branch, revno=None, rev_id=None):
self.branch = branch
self._has_revno = (revno is not None)
self._revno = revno
self.rev_id = rev_id
if self.rev_id is None and self._revno is not None:
# allow caller to be lazy
self.rev_id = branch.get_rev_id(self._revno)
@property
def revno(self):
if not self._has_revno and self.rev_id is not None:
try:
self._revno = self.branch.revision_id_to_revno(self.rev_id)
except errors.NoSuchRevision:
self._revno = None
self._has_revno = True
return self._revno
def __nonzero__(self):
if self.rev_id is None:
return False
# TODO: otherwise, it should depend on how I was built -
# if it's in_history(branch), then check revision_history(),
# if it's in_store(branch), do the check below
return self.branch.repository.has_revision(self.rev_id)
def __len__(self):
return 2
def __getitem__(self, index):
if index == 0: return self.revno
if index == 1: return self.rev_id
raise IndexError(index)
def get(self):
return self.branch.repository.get_revision(self.rev_id)
def __eq__(self, other):
if type(other) not in (tuple, list, type(self)):
return False
if type(other) is type(self) and self.branch is not other.branch:
return False
return tuple(self) == tuple(other)
def __repr__(self):
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
self.revno, self.rev_id, self.branch)
@staticmethod
def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
"""Construct a RevisionInfo given just the id.
Use this if you don't know or care what the revno is.
"""
if symbol_versioning.deprecated_passed(revs):
symbol_versioning.warn(
'RevisionInfo.from_revision_id(revs) was deprecated in 2.5.',
DeprecationWarning,
stacklevel=2)
return RevisionInfo(branch, revno=None, rev_id=revision_id)
class RevisionSpec(object):
"""A parsed revision specification."""
help_txt = """A parsed revision specification.
A revision specification is a string, which may be unambiguous about
what it represents by giving a prefix like 'date:' or 'revid:' etc,
or it may have no prefix, in which case it's tried against several
specifier types in sequence to determine what the user meant.
Revision specs are an UI element, and they have been moved out
of the branch class to leave "back-end" classes unaware of such
details. Code that gets a revno or rev_id from other code should
not be using revision specs - revnos and revision ids are the
accepted ways to refer to revisions internally.
(Equivalent to the old Branch method get_revision_info())
"""
prefix = None
# wants_revision_history has been deprecated in 2.5.
wants_revision_history = False
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
"""Exceptions that RevisionSpec_dwim._match_on will catch.
If the revspec is part of ``dwim_revspecs``, it may be tried with an
invalid revspec and raises some exception. The exceptions mentioned here
will not be reported to the user but simply ignored without stopping the
dwim processing.
"""
@staticmethod
def from_string(spec):
"""Parse a revision spec string into a RevisionSpec object.
:param spec: A string specified by the user
:return: A RevisionSpec object that understands how to parse the
supplied notation.
"""
if not isinstance(spec, (type(None), basestring)):
raise TypeError('error')
if spec is None:
return RevisionSpec(None, _internal=True)
match = revspec_registry.get_prefix(spec)
if match is not None:
spectype, specsuffix = match
trace.mutter('Returning RevisionSpec %s for %s',
spectype.__name__, spec)
return spectype(spec, _internal=True)
else:
# Otherwise treat it as a DWIM, build the RevisionSpec object and
# wait for _match_on to be called.
return RevisionSpec_dwim(spec, _internal=True)
def __init__(self, spec, _internal=False):
"""Create a RevisionSpec referring to the Null revision.
:param spec: The original spec supplied by the user
:param _internal: Used to ensure that RevisionSpec is not being
called directly. Only from RevisionSpec.from_string()
"""
if not _internal:
symbol_versioning.warn('Creating a RevisionSpec directly has'
' been deprecated in version 0.11. Use'
' RevisionSpec.from_string()'
' instead.',
DeprecationWarning, stacklevel=2)
self.user_spec = spec
if self.prefix and spec.startswith(self.prefix):
spec = spec[len(self.prefix):]
self.spec = spec
def _match_on(self, branch, revs):
trace.mutter('Returning RevisionSpec._match_on: None')
return RevisionInfo(branch, None, None)
def _match_on_and_check(self, branch, revs):
info = self._match_on(branch, revs)
if info:
return info
elif info == (None, None):
# special case - nothing supplied
return info
elif self.prefix:
raise errors.InvalidRevisionSpec(self.user_spec, branch)
else:
raise errors.InvalidRevisionSpec(self.spec, branch)
def in_history(self, branch):
if branch:
if self.wants_revision_history:
symbol_versioning.warn(
"RevisionSpec.wants_revision_history was "
"deprecated in 2.5 (%s)." % self.__class__.__name__,
DeprecationWarning)
branch.lock_read()
try:
graph = branch.repository.get_graph()
revs = list(graph.iter_lefthand_ancestry(
branch.last_revision(), [revision.NULL_REVISION]))
finally:
branch.unlock()
revs.reverse()
else:
revs = None
else:
# this should never trigger.
# TODO: make it a deprecated code path. RBC 20060928
revs = None
return self._match_on_and_check(branch, revs)
# FIXME: in_history is somewhat broken,
# it will return non-history revisions in many
# circumstances. The expected facility is that
# in_history only returns revision-history revs,
# in_store returns any rev. RBC 20051010
# aliases for now, when we fix the core logic, then they
# will do what you expect.
in_store = in_history
in_branch = in_store
def as_revision_id(self, context_branch):
"""Return just the revision_id for this revisions spec.
Some revision specs require a context_branch to be able to determine
their value. Not all specs will make use of it.
"""
return self._as_revision_id(context_branch)
def _as_revision_id(self, context_branch):
"""Implementation of as_revision_id()
Classes should override this function to provide appropriate
functionality. The default is to just call '.in_history().rev_id'
"""
return self.in_history(context_branch).rev_id
def as_tree(self, context_branch):
"""Return the tree object for this revisions spec.
Some revision specs require a context_branch to be able to determine
the revision id and access the repository. Not all specs will make
use of it.
"""
return self._as_tree(context_branch)
def _as_tree(self, context_branch):
"""Implementation of as_tree().
Classes should override this function to provide appropriate
functionality. The default is to just call '.as_revision_id()'
and get the revision tree from context_branch's repository.
"""
revision_id = self.as_revision_id(context_branch)
return context_branch.repository.revision_tree(revision_id)
def __repr__(self):
# this is mostly for helping with testing
return '<%s %s>' % (self.__class__.__name__,
self.user_spec)
def needs_branch(self):
"""Whether this revision spec needs a branch.
Set this to False the branch argument of _match_on is not used.
"""
return True
def get_branch(self):
"""When the revision specifier contains a branch location, return it.
Otherwise, return None.
"""
return None
# private API
class RevisionSpec_dwim(RevisionSpec):
"""Provides a DWIMish revision specifier lookup.
Note that this does not go in the revspec_registry because by definition
there is no prefix to identify it. It's solely called from
RevisionSpec.from_string() because the DWIMification happen when _match_on
is called so the string describing the revision is kept here until needed.
"""
help_txt = None
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
# The revspecs to try
_possible_revspecs = []
def _try_spectype(self, rstype, branch):
rs = rstype(self.spec, _internal=True)
# Hit in_history to find out if it exists, or we need to try the
# next type.
return rs.in_history(branch)
def _match_on(self, branch, revs):
"""Run the lookup and see what we can get."""
# First, see if it's a revno
if self._revno_regex.match(self.spec) is not None:
try:
return self._try_spectype(RevisionSpec_revno, branch)
except RevisionSpec_revno.dwim_catchable_exceptions:
pass
# Next see what has been registered
for objgetter in self._possible_revspecs:
rs_class = objgetter.get_obj()
try:
return self._try_spectype(rs_class, branch)
except rs_class.dwim_catchable_exceptions:
pass
# Try the old (deprecated) dwim list:
for rs_class in dwim_revspecs:
try:
return self._try_spectype(rs_class, branch)
except rs_class.dwim_catchable_exceptions:
pass
# Well, I dunno what it is. Note that we don't try to keep track of the
# first of last exception raised during the DWIM tries as none seems
# really relevant.
raise errors.InvalidRevisionSpec(self.spec, branch)
@classmethod
def append_possible_revspec(cls, revspec):
"""Append a possible DWIM revspec.
:param revspec: Revision spec to try.
"""
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
@classmethod
def append_possible_lazy_revspec(cls, module_name, member_name):
"""Append a possible lazily loaded DWIM revspec.
:param module_name: Name of the module with the revspec
:param member_name: Name of the revspec within the module
"""
cls._possible_revspecs.append(
registry._LazyObjectGetter(module_name, member_name))
class RevisionSpec_revno(RevisionSpec):
"""Selects a revision using a number."""
help_txt = """Selects a revision using a number.
Use an integer to specify a revision in the history of the branch.
Optionally a branch can be specified. A negative number will count
from the end of the branch (-1 is the last revision, -2 the previous
one). If the negative number is larger than the branch's history, the
first revision is returned.
Examples::
revno:1 -> return the first revision of this branch
revno:3:/path/to/branch -> return the 3rd revision of
the branch '/path/to/branch'
revno:-1 -> The last revision in a branch.
-2:http://other/branch -> The second to last revision in the
remote branch.
-1000000 -> Most likely the first revision, unless
your history is very long.
"""
prefix = 'revno:'
def _match_on(self, branch, revs):
"""Lookup a revision by revision number"""
branch, revno, revision_id = self._lookup(branch)
return RevisionInfo(branch, revno, revision_id)
def _lookup(self, branch):
loc = self.spec.find(':')
if loc == -1:
revno_spec = self.spec
branch_spec = None
else:
revno_spec = self.spec[:loc]
branch_spec = self.spec[loc+1:]
if revno_spec == '':
if not branch_spec:
raise errors.InvalidRevisionSpec(self.user_spec,
branch, 'cannot have an empty revno and no branch')
revno = None
else:
try:
revno = int(revno_spec)
dotted = False
except ValueError:
# dotted decimal. This arguably should not be here
# but the from_string method is a little primitive
# right now - RBC 20060928
try:
match_revno = tuple((int(number) for number in revno_spec.split('.')))
except ValueError, e:
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
dotted = True
if branch_spec:
# the user has overriden the branch to look in.
branch = _mod_branch.Branch.open(branch_spec)
if dotted:
try:
revision_id = branch.dotted_revno_to_revision_id(match_revno,
_cache_reverse=True)
except errors.NoSuchRevision:
raise errors.InvalidRevisionSpec(self.user_spec, branch)
else:
# there is no traditional 'revno' for dotted-decimal revnos.
# so for API compatibility we return None.
return branch, None, revision_id
else:
last_revno, last_revision_id = branch.last_revision_info()
if revno < 0:
# if get_rev_id supported negative revnos, there would not be a
# need for this special case.
if (-revno) >= last_revno:
revno = 1
else:
revno = last_revno + revno + 1
try:
revision_id = branch.get_rev_id(revno)
except errors.NoSuchRevision:
raise errors.InvalidRevisionSpec(self.user_spec, branch)
return branch, revno, revision_id
def _as_revision_id(self, context_branch):
# We would have the revno here, but we don't really care
branch, revno, revision_id = self._lookup(context_branch)
return revision_id
def needs_branch(self):
return self.spec.find(':') == -1
def get_branch(self):
if self.spec.find(':') == -1:
return None
else:
return self.spec[self.spec.find(':')+1:]
# Old compatibility
RevisionSpec_int = RevisionSpec_revno
class RevisionIDSpec(RevisionSpec):
def _match_on(self, branch, revs):
revision_id = self.as_revision_id(branch)
return RevisionInfo.from_revision_id(branch, revision_id)
class RevisionSpec_revid(RevisionIDSpec):
"""Selects a revision using the revision id."""
help_txt = """Selects a revision using the revision id.
Supply a specific revision id, that can be used to specify any
revision id in the ancestry of the branch.
Including merges, and pending merges.
Examples::
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
"""
prefix = 'revid:'
def _as_revision_id(self, context_branch):
# self.spec comes straight from parsing the command line arguments,
# so we expect it to be a Unicode string. Switch it to the internal
# representation.
return osutils.safe_revision_id(self.spec, warn=False)
class RevisionSpec_last(RevisionSpec):
"""Selects the nth revision from the end."""
help_txt = """Selects the nth revision from the end.
Supply a positive number to get the nth revision from the end.
This is the same as supplying negative numbers to the 'revno:' spec.
Examples::
last:1 -> return the last revision
last:3 -> return the revision 2 before the end.
"""
prefix = 'last:'
def _match_on(self, branch, revs):
revno, revision_id = self._revno_and_revision_id(branch)
return RevisionInfo(branch, revno, revision_id)
def _revno_and_revision_id(self, context_branch):
last_revno, last_revision_id = context_branch.last_revision_info()
if self.spec == '':
if not last_revno:
raise errors.NoCommits(context_branch)
return last_revno, last_revision_id
try:
offset = int(self.spec)
except ValueError, e:
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
if offset <= 0:
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
'you must supply a positive value')
revno = last_revno - offset + 1
try:
revision_id = context_branch.get_rev_id(revno)
except errors.NoSuchRevision:
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
return revno, revision_id
def _as_revision_id(self, context_branch):
# We compute the revno as part of the process, but we don't really care
# about it.
revno, revision_id = self._revno_and_revision_id(context_branch)
return revision_id
class RevisionSpec_before(RevisionSpec):
"""Selects the parent of the revision specified."""
help_txt = """Selects the parent of the revision specified.
Supply any revision spec to return the parent of that revision. This is
mostly useful when inspecting revisions that are not in the revision history
of a branch.
It is an error to request the parent of the null revision (before:0).
Examples::
before:1913 -> Return the parent of revno 1913 (revno 1912)
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
aaaa@bbbb-1234567890
bzr diff -r before:1913..1913
-> Find the changes between revision 1913 and its parent (1912).
(What changes did revision 1913 introduce).
This is equivalent to: bzr diff -c 1913
"""
prefix = 'before:'
def _match_on(self, branch, revs):
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
if r.revno == 0:
raise errors.InvalidRevisionSpec(self.user_spec, branch,
'cannot go before the null: revision')
if r.revno is None:
# We need to use the repository history here
rev = branch.repository.get_revision(r.rev_id)
if not rev.parent_ids:
revision_id = revision.NULL_REVISION
else:
revision_id = rev.parent_ids[0]
revno = None
else:
revno = r.revno - 1
try:
revision_id = branch.get_rev_id(revno, revs)
except errors.NoSuchRevision:
raise errors.InvalidRevisionSpec(self.user_spec,
branch)
return RevisionInfo(branch, revno, revision_id)
def _as_revision_id(self, context_branch):
base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
if base_revision_id == revision.NULL_REVISION:
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
'cannot go before the null: revision')
context_repo = context_branch.repository
context_repo.lock_read()
try:
parent_map = context_repo.get_parent_map([base_revision_id])
finally:
context_repo.unlock()
if base_revision_id not in parent_map:
# Ghost, or unknown revision id
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
'cannot find the matching revision')
parents = parent_map[base_revision_id]
if len(parents) < 1:
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
'No parents for revision.')
return parents[0]
class RevisionSpec_tag(RevisionSpec):
"""Select a revision identified by tag name"""
help_txt = """Selects a revision identified by a tag name.
Tags are stored in the branch and created by the 'tag' command.
"""
prefix = 'tag:'
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
def _match_on(self, branch, revs):
# Can raise tags not supported, NoSuchTag, etc
return RevisionInfo.from_revision_id(branch,
branch.tags.lookup_tag(self.spec))
def _as_revision_id(self, context_branch):
return context_branch.tags.lookup_tag(self.spec)
class _RevListToTimestamps(object):
"""This takes a list of revisions, and allows you to bisect by date"""
__slots__ = ['branch']
def __init__(self, branch):
self.branch = branch
def __getitem__(self, index):
"""Get the date of the index'd item"""
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
# TODO: Handle timezone.
return datetime.datetime.fromtimestamp(r.timestamp)
def __len__(self):
return self.branch.revno()
class RevisionSpec_date(RevisionSpec):
"""Selects a revision on the basis of a datestamp."""
help_txt = """Selects a revision on the basis of a datestamp.
Supply a datestamp to select the first revision that matches the date.
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
Matches the first entry after a given date (either at midnight or
at a specified time).
One way to display all the changes since yesterday would be::
bzr log -r date:yesterday..
Examples::
date:yesterday -> select the first revision since yesterday
date:2006-08-14,17:10:14 -> select the first revision after
August 14th, 2006 at 5:10pm.
"""
prefix = 'date:'
_date_regex = lazy_regex.lazy_compile(
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
r'(,|T)?\s*'
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
)
def _match_on(self, branch, revs):
"""Spec for date revisions:
date:value
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
matches the first entry after a given date (either at midnight or
at a specified time).
"""
# XXX: This doesn't actually work
# So the proper way of saying 'give me all entries for today' is:
# -r date:yesterday..date:today
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
if self.spec.lower() == 'yesterday':
dt = today - datetime.timedelta(days=1)
elif self.spec.lower() == 'today':
dt = today
elif self.spec.lower() == 'tomorrow':
dt = today + datetime.timedelta(days=1)
else:
m = self._date_regex.match(self.spec)
if not m or (not m.group('date') and not m.group('time')):
raise errors.InvalidRevisionSpec(self.user_spec,
branch, 'invalid date')
try:
if m.group('date'):
year = int(m.group('year'))
month = int(m.group('month'))
day = int(m.group('day'))
else:
year = today.year
month = today.month
day = today.day
if m.group('time'):
hour = int(m.group('hour'))
minute = int(m.group('minute'))
if m.group('second'):
second = int(m.group('second'))
else:
second = 0
else:
hour, minute, second = 0,0,0
except ValueError:
raise errors.InvalidRevisionSpec(self.user_spec,
branch, 'invalid date')
dt = datetime.datetime(year=year, month=month, day=day,
hour=hour, minute=minute, second=second)
branch.lock_read()
try:
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
finally:
branch.unlock()
if rev == branch.revno():
raise errors.InvalidRevisionSpec(self.user_spec, branch)
return RevisionInfo(branch, rev)
class RevisionSpec_ancestor(RevisionSpec):
"""Selects a common ancestor with a second branch."""
help_txt = """Selects a common ancestor with a second branch.
Supply the path to a branch to select the common ancestor.
The common ancestor is the last revision that existed in both
branches. Usually this is the branch point, but it could also be
a revision that was merged.
This is frequently used with 'diff' to return all of the changes
that your branch introduces, while excluding the changes that you
have not merged from the remote branch.
Examples::
ancestor:/path/to/branch
$ bzr diff -r ancestor:../../mainline/branch
"""
prefix = 'ancestor:'
def _match_on(self, branch, revs):
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
return self._find_revision_info(branch, self.spec)
def _as_revision_id(self, context_branch):
return self._find_revision_id(context_branch, self.spec)
@staticmethod
def _find_revision_info(branch, other_location):
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
other_location)
return RevisionInfo(branch, None, revision_id)
@staticmethod
def _find_revision_id(branch, other_location):
from bzrlib.branch import Branch
branch.lock_read()
try:
revision_a = revision.ensure_null(branch.last_revision())
if revision_a == revision.NULL_REVISION:
raise errors.NoCommits(branch)
if other_location == '':
other_location = branch.get_parent()
other_branch = Branch.open(other_location)
other_branch.lock_read()
try:
revision_b = revision.ensure_null(other_branch.last_revision())
if revision_b == revision.NULL_REVISION:
raise errors.NoCommits(other_branch)
graph = branch.repository.get_graph(other_branch.repository)
rev_id = graph.find_unique_lca(revision_a, revision_b)
finally:
other_branch.unlock()
if rev_id == revision.NULL_REVISION:
raise errors.NoCommonAncestor(revision_a, revision_b)
return rev_id
finally:
branch.unlock()
class RevisionSpec_branch(RevisionSpec):
"""Selects the last revision of a specified branch."""
help_txt = """Selects the last revision of a specified branch.
Supply the path to a branch to select its last revision.
Examples::
branch:/path/to/branch
"""
prefix = 'branch:'
dwim_catchable_exceptions = (errors.NotBranchError,)
def _match_on(self, branch, revs):
from bzrlib.branch import Branch
other_branch = Branch.open(self.spec)
revision_b = other_branch.last_revision()
if revision_b in (None, revision.NULL_REVISION):
raise errors.NoCommits(other_branch)
if branch is None:
branch = other_branch
else:
try:
# pull in the remote revisions so we can diff
branch.fetch(other_branch, revision_b)
except errors.ReadOnlyError:
branch = other_branch
return RevisionInfo(branch, None, revision_b)
def _as_revision_id(self, context_branch):
from bzrlib.branch import Branch
other_branch = Branch.open(self.spec)
last_revision = other_branch.last_revision()
last_revision = revision.ensure_null(last_revision)
context_branch.fetch(other_branch, last_revision)
if last_revision == revision.NULL_REVISION:
raise errors.NoCommits(other_branch)
return last_revision
def _as_tree(self, context_branch):
from bzrlib.branch import Branch
other_branch = Branch.open(self.spec)
last_revision = other_branch.last_revision()
last_revision = revision.ensure_null(last_revision)
if last_revision == revision.NULL_REVISION:
raise errors.NoCommits(other_branch)
return other_branch.repository.revision_tree(last_revision)
def needs_branch(self):
return False
def get_branch(self):
return self.spec
class RevisionSpec_submit(RevisionSpec_ancestor):
"""Selects a common ancestor with a submit branch."""
help_txt = """Selects a common ancestor with the submit branch.
Diffing against this shows all the changes that were made in this branch,
and is a good predictor of what merge will do. The submit branch is
used by the bundle and merge directive commands. If no submit branch
is specified, the parent branch is used instead.
The common ancestor is the last revision that existed in both
branches. Usually this is the branch point, but it could also be
a revision that was merged.
Examples::
$ bzr diff -r submit:
"""
prefix = 'submit:'
def _get_submit_location(self, branch):
submit_location = branch.get_submit_branch()
location_type = 'submit branch'
if submit_location is None:
submit_location = branch.get_parent()
location_type = 'parent branch'
if submit_location is None:
raise errors.NoSubmitBranch(branch)
trace.note(gettext('Using {0} {1}').format(location_type,
submit_location))
return submit_location
def _match_on(self, branch, revs):
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
return self._find_revision_info(branch,
self._get_submit_location(branch))
def _as_revision_id(self, context_branch):
return self._find_revision_id(context_branch,
self._get_submit_location(context_branch))
class RevisionSpec_annotate(RevisionIDSpec):
prefix = 'annotate:'
help_txt = """Select the revision that last modified the specified line.
Select the revision that last modified the specified line. Line is
specified as path:number. Path is a relative path to the file. Numbers
start at 1, and are relative to the current version, not the last-
committed version of the file.
"""
def _raise_invalid(self, numstring, context_branch):
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
'No such line: %s' % numstring)
def _as_revision_id(self, context_branch):
path, numstring = self.spec.rsplit(':', 1)
try:
index = int(numstring) - 1
except ValueError:
self._raise_invalid(numstring, context_branch)
tree, file_path = workingtree.WorkingTree.open_containing(path)
tree.lock_read()
try:
file_id = tree.path2id(file_path)
if file_id is None:
raise errors.InvalidRevisionSpec(self.user_spec,
context_branch, "File '%s' is not versioned." %
file_path)
revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
finally:
tree.unlock()
try:
revision_id = revision_ids[index]
except IndexError:
self._raise_invalid(numstring, context_branch)
if revision_id == revision.CURRENT_REVISION:
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
'Line %s has not been committed.' % numstring)
return revision_id
class RevisionSpec_mainline(RevisionIDSpec):
help_txt = """Select mainline revision that merged the specified revision.
Select the revision that merged the specified revision into mainline.
"""
prefix = 'mainline:'
def _as_revision_id(self, context_branch):
revspec = RevisionSpec.from_string(self.spec)
if revspec.get_branch() is None:
spec_branch = context_branch
else:
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
revision_id = revspec.as_revision_id(spec_branch)
graph = context_branch.repository.get_graph()
result = graph.find_lefthand_merger(revision_id,
context_branch.last_revision())
if result is None:
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
return result
# The order in which we want to DWIM a revision spec without any prefix.
# revno is always tried first and isn't listed here, this is used by
# RevisionSpec_dwim._match_on
dwim_revspecs = symbol_versioning.deprecated_list(
symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
revspec_registry = registry.Registry()
def _register_revspec(revspec):
revspec_registry.register(revspec.prefix, revspec)
_register_revspec(RevisionSpec_revno)
_register_revspec(RevisionSpec_revid)
_register_revspec(RevisionSpec_last)
_register_revspec(RevisionSpec_before)
_register_revspec(RevisionSpec_tag)
_register_revspec(RevisionSpec_date)
_register_revspec(RevisionSpec_ancestor)
_register_revspec(RevisionSpec_branch)
_register_revspec(RevisionSpec_submit)
_register_revspec(RevisionSpec_annotate)
_register_revspec(RevisionSpec_mainline)
|