Merge lp:~fullermd/bzr/revspec-dwim into lp:bzr
- revspec-dwim
- Merge into bzr.dev
Status: | Superseded |
---|---|
Proposed branch: | lp:~fullermd/bzr/revspec-dwim |
Merge into: | lp:bzr |
Diff against target: |
396 lines 6 files modified
NEWS (+4/-0) bzrlib/help_topics/__init__.py (+10/-4) bzrlib/option.py (+14/-18) bzrlib/revisionspec.py (+80/-21) bzrlib/tests/test_revisionspec.py (+43/-18) doc/en/user-guide/specifying_revisions.txt (+10/-4) |
To merge this branch: | bzr merge lp:~fullermd/bzr/revspec-dwim |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Collins (community) | Needs Fixing | ||
Ian Clatworthy | Approve | ||
Review via email: mp+13164@code.launchpad.net |
This proposal has been superseded by a proposal from 2009-10-22.
Commit message
Description of the change
Matthew Fuller (fullermd) wrote : | # |
Ian Clatworthy (ian-clatworthy) wrote : | # |
I agree in principle with this and the code looks ok to me. There are 2 main concerns I have:
1. If something looks like a revno and isn't, I'd prefer that we raised an error in that case rather than continuing to look. The main issue here is tags like x.y.z I guess. I'd prefer we err'ed on the side of safety. It's not a big deal for projects to use a more explicit tag (like release-x.y.z) or for the user to type tag:x.y.z if they choose not to.
2. The time to look up a revision-id needs discussion. I'd prefer if it was done last. (If that's a performance issue, I'd prefer it was done immediately after revno but before the other specifiers.)
Ian Clatworthy (ian-clatworthy) wrote : | # |
After discussing this on IRC with a few people, I'm ok to proceed with this as is. Matthew has pinged the list just in case others have strong opinions. We ought to give that a day or two to see what feedback, if any, there is.
Robert Collins (lifeless) wrote : | # |
review needs-fixing
(however thats said now :()
This manually reproduces the registry of revspecs. Lets not do that,
instead query it dynamically.
If an ordering is needed, see the transport registry which has some
sorting done to it.
-Rob
Matthew Fuller (fullermd) wrote : | # |
On Thu, Oct 15, 2009 at 05:33:12AM -0000 I heard the voice of
Robert Collins, and lo! it spake thus:
>
> This manually reproduces the registry of revspecs. Lets not do that,
> instead query it dynamically.
>
> If an ordering is needed, see the transport registry which has some
> sorting done to it.
I disagree.
Leaving aside the very large practical difficulties in doing it out of
the registry (and there are several), I consider it the wrong
approach. This isn't RevisionSpec_
RevisionSpec_dwim. An important part of a non-screwyou DWIM system is
carefully limiting to scope, and precisely defining the order within
that.
In this case, the dwim tries just half (rounded) of the revspecs we
have, because trying the rest makes no sense at all (e.g., if we don't
match it somewhere else, trying to match it to 'before:' would be
nonsensical, and in the bizarre case it actually DID something, it's
sure to be Wrong).
--
Matthew Fuller
<email address hidden>
Robert Collins (lifeless) wrote : | # |
I disagree with your disagreement :)
specifically, git:, hg:, svn:, and thread: specs are added by plugins
and desirable to search too.
You can add this as a future enchancement if you like, but I think the
code will be smaller and more compact if you go straight to this.
-Rob
Matthew Fuller (fullermd) wrote : | # |
> specifically, git:, hg:, svn:, and thread: specs are added by
> plugins and desirable to search too.
OK, so it sounds like there're actually two separate issues being a
little conflated here:
1) Make the DWIM use a registry instead of hand-written code.
I'm not necessarily opposed to that; certainly IWBNI plugins could
register their own specs DWIM should try. But I don't think I'll
be doing it (certainly not anytime soon; $freetime--). And it's
not quite as easy as it sounds, since we have to keep track of
which exceptions each class raises for 'no, I didn't find this' (as
opposed to 'crap, something went wrong').
Also, instantiating the RevSpec_* classes _doesn't_ determine
whether the given value points at anything, it only sets up the
object to lazily search later. We reach inside them in the _dwim
class to evaluate on the spot (when _dwim is lazily searched) to
find out whether the value means anything to that spectype.
So it's not just as simple as "do this then this then this";
encoding that in a registry will take more knowledge of what you
can do with python than I have. As it is, the body of the dwim
_match_on() is about 40 lines (and with my characteristic heavy use
of whitespace and commenting, so the actual code is probably under
25), and explicit about the complications and what's going on.
2) Have that registry be the same as the revspec_registry registry
that the prefix:'s are searched in.
This I would be against, because 'prefixes we recognize' and
'things we want to DWIM' are rather different, and cramming all of
the above info, plus ordering, plus info on which specs we even
DWIM at all, into that registry is a giant mess of making one thing
do two almost-
conceptually arguing against in the previous message, not so much
(1) above.
--
Matthew Fuller
<email address hidden>
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-10-22 08:49:42 +0000 |
3 | +++ NEWS 2009-10-22 19:16:16 +0000 |
4 | @@ -233,6 +233,10 @@ |
5 | Improvements |
6 | ************ |
7 | |
8 | +* Revision specifiers can now be given in a more DWIM form, without |
9 | + needing explicit prefixes for specifiers like tags or revision id's. |
10 | + See ``bzr help revisionspec`` for full details. (Matthew Fuller) |
11 | + |
12 | * Bazaar gives a warning before exiting, and writes into ``.bzr.log``, if |
13 | compiled extensions can't be loaded. This typically indicates a |
14 | packaging or installation problem. In this case Bazaar will keep |
15 | |
16 | === modified file 'bzrlib/help_topics/__init__.py' |
17 | --- bzrlib/help_topics/__init__.py 2009-10-13 20:15:45 +0000 |
18 | +++ bzrlib/help_topics/__init__.py 2009-10-22 19:16:16 +0000 |
19 | @@ -152,10 +152,16 @@ |
20 | out.append( |
21 | """Revision Identifiers |
22 | |
23 | -A revision identifier refers to a specific state of a branch's history. It can |
24 | -be a revision number, or a keyword followed by ':' and often other |
25 | -parameters. Some examples of identifiers are '3', 'last:1', 'before:yesterday' |
26 | -and 'submit:'. |
27 | +A revision identifier refers to a specific state of a branch's history. It |
28 | +can be expressed in several ways. It can begin with a keyword to |
29 | +unambiguously specify a given lookup type; some examples are 'last:1', |
30 | +'before:yesterday' and 'submit:'. |
31 | + |
32 | +Alternately, it can be given without a keyword, in which case it will be |
33 | +checked as a revision number, a tag, a revision id, a date specification, or a |
34 | +branch specification, in that order. For example, 'date:today' could be |
35 | +written as simply 'today', though if you have a tag called 'today' that will |
36 | +be found first. |
37 | |
38 | If 'REV1' and 'REV2' are revision identifiers, then 'REV1..REV2' denotes a |
39 | revision range. Examples: '3647..3649', 'date:yesterday..-1' and |
40 | |
41 | === modified file 'bzrlib/option.py' |
42 | --- bzrlib/option.py 2009-04-03 20:05:25 +0000 |
43 | +++ bzrlib/option.py 2009-10-22 19:16:16 +0000 |
44 | @@ -40,25 +40,25 @@ |
45 | each revision specifier supplied. |
46 | |
47 | >>> _parse_revision_str('234') |
48 | - [<RevisionSpec_revno 234>] |
49 | + [<RevisionSpec_dwim 234>] |
50 | >>> _parse_revision_str('234..567') |
51 | - [<RevisionSpec_revno 234>, <RevisionSpec_revno 567>] |
52 | + [<RevisionSpec_dwim 234>, <RevisionSpec_dwim 567>] |
53 | >>> _parse_revision_str('..') |
54 | [<RevisionSpec None>, <RevisionSpec None>] |
55 | >>> _parse_revision_str('..234') |
56 | - [<RevisionSpec None>, <RevisionSpec_revno 234>] |
57 | + [<RevisionSpec None>, <RevisionSpec_dwim 234>] |
58 | >>> _parse_revision_str('234..') |
59 | - [<RevisionSpec_revno 234>, <RevisionSpec None>] |
60 | + [<RevisionSpec_dwim 234>, <RevisionSpec None>] |
61 | >>> _parse_revision_str('234..456..789') # Maybe this should be an error |
62 | - [<RevisionSpec_revno 234>, <RevisionSpec_revno 456>, <RevisionSpec_revno 789>] |
63 | + [<RevisionSpec_dwim 234>, <RevisionSpec_dwim 456>, <RevisionSpec_dwim 789>] |
64 | >>> _parse_revision_str('234....789') #Error ? |
65 | - [<RevisionSpec_revno 234>, <RevisionSpec None>, <RevisionSpec_revno 789>] |
66 | + [<RevisionSpec_dwim 234>, <RevisionSpec None>, <RevisionSpec_dwim 789>] |
67 | >>> _parse_revision_str('revid:test@other.com-234234') |
68 | [<RevisionSpec_revid revid:test@other.com-234234>] |
69 | >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235') |
70 | [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revid revid:test@other.com-234235>] |
71 | >>> _parse_revision_str('revid:test@other.com-234234..23') |
72 | - [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revno 23>] |
73 | + [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_dwim 23>] |
74 | >>> _parse_revision_str('date:2005-04-12') |
75 | [<RevisionSpec_date date:2005-04-12>] |
76 | >>> _parse_revision_str('date:2005-04-12 12:24:33') |
77 | @@ -68,27 +68,23 @@ |
78 | >>> _parse_revision_str('date:2005-04-12,12:24:33') |
79 | [<RevisionSpec_date date:2005-04-12,12:24:33>] |
80 | >>> _parse_revision_str('-5..23') |
81 | - [<RevisionSpec_revno -5>, <RevisionSpec_revno 23>] |
82 | + [<RevisionSpec_dwim -5>, <RevisionSpec_dwim 23>] |
83 | >>> _parse_revision_str('-5') |
84 | - [<RevisionSpec_revno -5>] |
85 | + [<RevisionSpec_dwim -5>] |
86 | >>> _parse_revision_str('123a') |
87 | - Traceback (most recent call last): |
88 | - ... |
89 | - NoSuchRevisionSpec: No namespace registered for string: '123a' |
90 | + [<RevisionSpec_dwim 123a>] |
91 | >>> _parse_revision_str('abc') |
92 | - Traceback (most recent call last): |
93 | - ... |
94 | - NoSuchRevisionSpec: No namespace registered for string: 'abc' |
95 | + [<RevisionSpec_dwim abc>] |
96 | >>> _parse_revision_str('branch:../branch2') |
97 | [<RevisionSpec_branch branch:../branch2>] |
98 | >>> _parse_revision_str('branch:../../branch2') |
99 | [<RevisionSpec_branch branch:../../branch2>] |
100 | >>> _parse_revision_str('branch:../../branch2..23') |
101 | - [<RevisionSpec_branch branch:../../branch2>, <RevisionSpec_revno 23>] |
102 | + [<RevisionSpec_branch branch:../../branch2>, <RevisionSpec_dwim 23>] |
103 | >>> _parse_revision_str('branch:..\\\\branch2') |
104 | [<RevisionSpec_branch branch:..\\branch2>] |
105 | >>> _parse_revision_str('branch:..\\\\..\\\\branch2..23') |
106 | - [<RevisionSpec_branch branch:..\\..\\branch2>, <RevisionSpec_revno 23>] |
107 | + [<RevisionSpec_branch branch:..\\..\\branch2>, <RevisionSpec_dwim 23>] |
108 | """ |
109 | # TODO: Maybe move this into revisionspec.py |
110 | revs = [] |
111 | @@ -104,7 +100,7 @@ |
112 | parent of the revision. |
113 | |
114 | >>> _parse_change_str('123') |
115 | - (<RevisionSpec_before before:123>, <RevisionSpec_revno 123>) |
116 | + (<RevisionSpec_before before:123>, <RevisionSpec_dwim 123>) |
117 | >>> _parse_change_str('123..124') |
118 | Traceback (most recent call last): |
119 | ... |
120 | |
121 | === modified file 'bzrlib/revisionspec.py' |
122 | --- bzrlib/revisionspec.py 2009-07-25 08:26:42 +0000 |
123 | +++ bzrlib/revisionspec.py 2009-10-22 19:16:16 +0000 |
124 | @@ -113,8 +113,6 @@ |
125 | return RevisionInfo(branch, revno, revision_id) |
126 | |
127 | |
128 | -# classes in this list should have a "prefix" attribute, against which |
129 | -# string specs are matched |
130 | _revno_regex = None |
131 | |
132 | |
133 | @@ -123,10 +121,10 @@ |
134 | |
135 | help_txt = """A parsed revision specification. |
136 | |
137 | - A revision specification can be an integer, in which case it is |
138 | - assumed to be a revno (though this will translate negative values |
139 | - into positive ones); or it can be a string, in which case it is |
140 | - parsed for something like 'date:' or 'revid:' etc. |
141 | + A revision specification is a string, which may be unambiguous about |
142 | + what it represents by giving a prefix like 'date:' or 'revid:' etc, |
143 | + or it may have no prefix, in which case it's tried against several |
144 | + specifier types in sequence to determine what the user meant. |
145 | |
146 | Revision specs are an UI element, and they have been moved out |
147 | of the branch class to leave "back-end" classes unaware of such |
148 | @@ -139,6 +137,14 @@ |
149 | |
150 | prefix = None |
151 | wants_revision_history = True |
152 | + dwim_catchable_exceptions = (errors.InvalidRevisionSpec,) |
153 | + """Expections that RevisionSpec_dwim._match_on will catch. |
154 | + |
155 | + If the revspec is part of dwim_revspecs, it may be tried with an invalid |
156 | + revspec and raise some exception. The exceptions mentioned here will not be |
157 | + reported to the user but simply ignored without stopping the dwim |
158 | + processing. |
159 | + """ |
160 | |
161 | @staticmethod |
162 | def from_string(spec): |
163 | @@ -165,17 +171,9 @@ |
164 | trace.mutter('Returning RevisionSpec %s for %s', |
165 | spectype.__name__, spec) |
166 | return spectype(spec, _internal=True) |
167 | - # RevisionSpec_revno is special cased, because it is the only |
168 | - # one that directly handles plain integers |
169 | - # TODO: This should not be special cased rather it should be |
170 | - # a method invocation on spectype.canparse() |
171 | - global _revno_regex |
172 | - if _revno_regex is None: |
173 | - _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$') |
174 | - if _revno_regex.match(spec) is not None: |
175 | - return RevisionSpec_revno(spec, _internal=True) |
176 | - |
177 | - raise errors.NoSuchRevisionSpec(spec) |
178 | + # Otherwise treat it as a DWIM, build the RevisionSpec object and |
179 | + # wait for _match_on to be called. |
180 | + return RevisionSpec_dwim(spec, _internal=True) |
181 | |
182 | def __init__(self, spec, _internal=False): |
183 | """Create a RevisionSpec referring to the Null revision. |
184 | @@ -290,16 +288,62 @@ |
185 | |
186 | # private API |
187 | |
188 | +class RevisionSpec_dwim(RevisionSpec): |
189 | + """Provides a DWIMish revision specifier lookup. |
190 | + |
191 | + Note that this does not go in the revspec_registry because by definition |
192 | + there is no prefix to identify it. It's solely called from |
193 | + RevisionSpec.from_string() because the DWIMification happen when _match_on |
194 | + is called so the string describing the revision is kept here until needed. |
195 | + """ |
196 | + |
197 | + help_txt = None |
198 | + # We don't need to build the revision history ourself, that's delegated to |
199 | + # each revspec we try. |
200 | + wants_revision_history = False |
201 | + |
202 | + def _try_spectype(self, rstype, branch): |
203 | + rs = rstype(self.spec, _internal=True) |
204 | + # Hit in_history to find out if it exists, or we need to try the |
205 | + # next type. |
206 | + return rs.in_history(branch) |
207 | + |
208 | + def _match_on(self, branch, revs): |
209 | + """Run the lookup and see what we can get.""" |
210 | + |
211 | + # First, see if it's a revno |
212 | + global _revno_regex |
213 | + if _revno_regex is None: |
214 | + _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$') |
215 | + if _revno_regex.match(self.spec) is not None: |
216 | + try: |
217 | + return self._try_spectype(RevisionSpec_revno, branch) |
218 | + except RevisionSpec_revno.dwim_catchable_exceptions: |
219 | + pass |
220 | + |
221 | + # Next see what has been registered |
222 | + for rs_class in dwim_revspecs: |
223 | + try: |
224 | + return self._try_spectype(rs_class, branch) |
225 | + except rs_class.dwim_catchable_exceptions: |
226 | + pass |
227 | + |
228 | + # Well, I dunno what it is. Note that we don't try to keep track of the |
229 | + # first of last exception raised during the DWIM tries as none seems |
230 | + # really relevant. |
231 | + raise errors.InvalidRevisionSpec(self.spec, branch) |
232 | + |
233 | + |
234 | class RevisionSpec_revno(RevisionSpec): |
235 | """Selects a revision using a number.""" |
236 | |
237 | help_txt = """Selects a revision using a number. |
238 | |
239 | Use an integer to specify a revision in the history of the branch. |
240 | - Optionally a branch can be specified. The 'revno:' prefix is optional. |
241 | - A negative number will count from the end of the branch (-1 is the |
242 | - last revision, -2 the previous one). If the negative number is larger |
243 | - than the branch's history, the first revision is returned. |
244 | + Optionally a branch can be specified. A negative number will count |
245 | + from the end of the branch (-1 is the last revision, -2 the previous |
246 | + one). If the negative number is larger than the branch's history, the |
247 | + first revision is returned. |
248 | Examples:: |
249 | |
250 | revno:1 -> return the first revision of this branch |
251 | @@ -561,6 +605,7 @@ |
252 | """ |
253 | |
254 | prefix = 'tag:' |
255 | + dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported) |
256 | |
257 | def _match_on(self, branch, revs): |
258 | # Can raise tags not supported, NoSuchTag, etc |
259 | @@ -760,6 +805,7 @@ |
260 | branch:/path/to/branch |
261 | """ |
262 | prefix = 'branch:' |
263 | + dwim_catchable_exceptions = (errors.NotBranchError,) |
264 | |
265 | def _match_on(self, branch, revs): |
266 | from bzrlib.branch import Branch |
267 | @@ -838,6 +884,17 @@ |
268 | self._get_submit_location(context_branch)) |
269 | |
270 | |
271 | +# The order in which we want to DWIM a revision spec without any prefix. |
272 | +# revno is always tried first and isn't listed here, this is used by |
273 | +# RevisionSpec_dwim._match_on |
274 | +dwim_revspecs = [ |
275 | + RevisionSpec_tag, # Let's try for a tag |
276 | + RevisionSpec_revid, # Maybe it's a revid? |
277 | + RevisionSpec_date, # Perhaps a date? |
278 | + RevisionSpec_branch, # OK, last try, maybe it's a branch |
279 | + ] |
280 | + |
281 | + |
282 | revspec_registry = registry.Registry() |
283 | def _register_revspec(revspec): |
284 | revspec_registry.register(revspec.prefix, revspec) |
285 | @@ -852,5 +909,7 @@ |
286 | _register_revspec(RevisionSpec_branch) |
287 | _register_revspec(RevisionSpec_submit) |
288 | |
289 | +# classes in this list should have a "prefix" attribute, against which |
290 | +# string specs are matched |
291 | SPEC_TYPES = symbol_versioning.deprecated_list( |
292 | symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", []) |
293 | |
294 | === modified file 'bzrlib/tests/test_revisionspec.py' |
295 | --- bzrlib/tests/test_revisionspec.py 2009-03-23 14:59:43 +0000 |
296 | +++ bzrlib/tests/test_revisionspec.py 2009-10-22 19:16:16 +0000 |
297 | @@ -146,24 +146,49 @@ |
298 | def test_object(self): |
299 | self.assertRaises(TypeError, RevisionSpec.from_string, object()) |
300 | |
301 | - def test_unregistered_spec(self): |
302 | - self.assertRaises(errors.NoSuchRevisionSpec, |
303 | - RevisionSpec.from_string, 'foo') |
304 | - self.assertRaises(errors.NoSuchRevisionSpec, |
305 | - RevisionSpec.from_string, '123a') |
306 | - |
307 | - |
308 | - |
309 | -class TestRevnoFromString(TestCase): |
310 | - |
311 | - def test_from_string_dotted_decimal(self): |
312 | - self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '-1.1') |
313 | - self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '.1') |
314 | - self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1..1') |
315 | - self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.2..1') |
316 | - self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.') |
317 | - self.assertIsInstance(RevisionSpec.from_string('1.1'), RevisionSpec_revno) |
318 | - self.assertIsInstance(RevisionSpec.from_string('1.1.3'), RevisionSpec_revno) |
319 | + |
320 | +class TestRevisionSpec_dwim(TestRevisionSpec): |
321 | + |
322 | + # Don't need to test revno's explicitly since TRS_revno already |
323 | + # covers that well for us |
324 | + def test_dwim_spec_revno(self): |
325 | + self.assertInHistoryIs(2, 'r2', '2') |
326 | + self.assertAsRevisionId('alt_r2', '1.1.1') |
327 | + |
328 | + def test_dwim_spec_revid(self): |
329 | + self.assertInHistoryIs(2, 'r2', 'r2') |
330 | + |
331 | + def test_dwim_spec_tag(self): |
332 | + self.tree.branch.tags.set_tag('footag', 'r1') |
333 | + self.assertAsRevisionId('r1', 'footag') |
334 | + self.tree.branch.tags.delete_tag('footag') |
335 | + self.assertRaises(errors.InvalidRevisionSpec, |
336 | + self.get_in_history, 'footag') |
337 | + |
338 | + def test_dwim_spec_tag_that_looks_like_revno(self): |
339 | + # Test that we slip past revno with things that look like revnos, |
340 | + # but aren't. Tags are convenient for testing this since we can |
341 | + # make them look however we want. |
342 | + self.tree.branch.tags.set_tag('3', 'r2') |
343 | + self.assertAsRevisionId('r2', '3') |
344 | + self.build_tree(['tree/b']) |
345 | + self.tree.add(['b']) |
346 | + self.tree.commit('b', rev_id='r3') |
347 | + self.assertAsRevisionId('r3', '3') |
348 | + |
349 | + def test_dwim_spec_date(self): |
350 | + self.assertAsRevisionId('r1', 'today') |
351 | + |
352 | + def test_dwim_spec_branch(self): |
353 | + self.assertInHistoryIs(None, 'alt_r2', 'tree2') |
354 | + |
355 | + def test_dwim_spec_nonexistent(self): |
356 | + self.assertInvalid('somethingrandom', invalid_as_revision_id=False) |
357 | + self.assertInvalid('-1.1', invalid_as_revision_id=False) |
358 | + self.assertInvalid('.1', invalid_as_revision_id=False) |
359 | + self.assertInvalid('1..1', invalid_as_revision_id=False) |
360 | + self.assertInvalid('1.2..1', invalid_as_revision_id=False) |
361 | + self.assertInvalid('1.', invalid_as_revision_id=False) |
362 | |
363 | |
364 | class TestRevisionSpec_revno(TestRevisionSpec): |
365 | |
366 | === modified file 'doc/en/user-guide/specifying_revisions.txt' |
367 | --- doc/en/user-guide/specifying_revisions.txt 2009-04-04 02:57:47 +0000 |
368 | +++ doc/en/user-guide/specifying_revisions.txt 2009-10-22 19:16:16 +0000 |
369 | @@ -36,17 +36,23 @@ |
370 | +----------------------+------------------------------------+ |
371 | | *number* | revision number | |
372 | +----------------------+------------------------------------+ |
373 | - | **revno**:*number* | positive revision number | |
374 | + | **revno**:*number* | revision number | |
375 | +----------------------+------------------------------------+ |
376 | | **last**:*number* | negative revision number | |
377 | +----------------------+------------------------------------+ |
378 | + | *guid* | globally unique revision id | |
379 | + +----------------------+------------------------------------+ |
380 | | **revid**:*guid* | globally unique revision id | |
381 | +----------------------+------------------------------------+ |
382 | | **before**:*rev* | leftmost parent of ''rev'' | |
383 | +----------------------+------------------------------------+ |
384 | - | **date**:*value* | first entry after a given date | |
385 | - +----------------------+------------------------------------+ |
386 | - | **tag**:*value* | revision matching a given tag | |
387 | + | *date-value* | first entry after a given date | |
388 | + +----------------------+------------------------------------+ |
389 | + | **date**:*date-value*| first entry after a given date | |
390 | + +----------------------+------------------------------------+ |
391 | + | *tag-name* | revision matching a given tag | |
392 | + +----------------------+------------------------------------+ |
393 | + | **tag**:*tag-name* | revision matching a given tag | |
394 | +----------------------+------------------------------------+ |
395 | | **ancestor**:*path* | last merged revision from a branch | |
396 | +----------------------+------------------------------------+ |
This allows unqualified revspecs to be tried as revnos, tags, revids, dates, or branches (respectively) rather than being assumed to be revnos.