##// END OF EJS Templates
formatter: remove template resources from nested items before generating JSON
Yuya Nishihara -
r37520:40c7347f default
parent child Browse files
Show More
@@ -1,682 +1,689 b''
1 # templateutil.py - utility for template evaluation
1 # templateutil.py - utility for template evaluation
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import abc
10 import abc
11 import types
11 import types
12
12
13 from .i18n import _
13 from .i18n import _
14 from . import (
14 from . import (
15 error,
15 error,
16 pycompat,
16 pycompat,
17 util,
17 util,
18 )
18 )
19 from .utils import (
19 from .utils import (
20 dateutil,
20 dateutil,
21 stringutil,
21 stringutil,
22 )
22 )
23
23
24 class ResourceUnavailable(error.Abort):
24 class ResourceUnavailable(error.Abort):
25 pass
25 pass
26
26
27 class TemplateNotFound(error.Abort):
27 class TemplateNotFound(error.Abort):
28 pass
28 pass
29
29
30 class wrapped(object):
30 class wrapped(object):
31 """Object requiring extra conversion prior to displaying or processing
31 """Object requiring extra conversion prior to displaying or processing
32 as value
32 as value
33
33
34 Use unwrapvalue(), unwrapastype(), or unwraphybrid() to obtain the inner
34 Use unwrapvalue(), unwrapastype(), or unwraphybrid() to obtain the inner
35 object.
35 object.
36 """
36 """
37
37
38 __metaclass__ = abc.ABCMeta
38 __metaclass__ = abc.ABCMeta
39
39
40 @abc.abstractmethod
40 @abc.abstractmethod
41 def itermaps(self, context):
41 def itermaps(self, context):
42 """Yield each template mapping"""
42 """Yield each template mapping"""
43
43
44 @abc.abstractmethod
44 @abc.abstractmethod
45 def join(self, context, mapping, sep):
45 def join(self, context, mapping, sep):
46 """Join items with the separator; Returns a bytes or (possibly nested)
46 """Join items with the separator; Returns a bytes or (possibly nested)
47 generator of bytes
47 generator of bytes
48
48
49 A pre-configured template may be rendered per item if this container
49 A pre-configured template may be rendered per item if this container
50 holds unprintable items.
50 holds unprintable items.
51 """
51 """
52
52
53 @abc.abstractmethod
53 @abc.abstractmethod
54 def show(self, context, mapping):
54 def show(self, context, mapping):
55 """Return a bytes or (possibly nested) generator of bytes representing
55 """Return a bytes or (possibly nested) generator of bytes representing
56 the underlying object
56 the underlying object
57
57
58 A pre-configured template may be rendered if the underlying object is
58 A pre-configured template may be rendered if the underlying object is
59 not printable.
59 not printable.
60 """
60 """
61
61
62 @abc.abstractmethod
62 @abc.abstractmethod
63 def tovalue(self, context, mapping):
63 def tovalue(self, context, mapping):
64 """Move the inner value object out or create a value representation
64 """Move the inner value object out or create a value representation
65
65
66 A returned value must be serializable by templaterfilters.json().
66 A returned value must be serializable by templaterfilters.json().
67 """
67 """
68
68
69 # stub for representing a date type; may be a real date type that can
69 # stub for representing a date type; may be a real date type that can
70 # provide a readable string value
70 # provide a readable string value
71 class date(object):
71 class date(object):
72 pass
72 pass
73
73
74 class hybrid(wrapped):
74 class hybrid(wrapped):
75 """Wrapper for list or dict to support legacy template
75 """Wrapper for list or dict to support legacy template
76
76
77 This class allows us to handle both:
77 This class allows us to handle both:
78 - "{files}" (legacy command-line-specific list hack) and
78 - "{files}" (legacy command-line-specific list hack) and
79 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
79 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
80 and to access raw values:
80 and to access raw values:
81 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
81 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
82 - "{get(extras, key)}"
82 - "{get(extras, key)}"
83 - "{files|json}"
83 - "{files|json}"
84 """
84 """
85
85
86 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
86 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
87 self._gen = gen # generator or function returning generator
87 self._gen = gen # generator or function returning generator
88 self._values = values
88 self._values = values
89 self._makemap = makemap
89 self._makemap = makemap
90 self._joinfmt = joinfmt
90 self._joinfmt = joinfmt
91 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
91 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
92
92
93 def itermaps(self, context):
93 def itermaps(self, context):
94 makemap = self._makemap
94 makemap = self._makemap
95 for x in self._values:
95 for x in self._values:
96 yield makemap(x)
96 yield makemap(x)
97
97
98 def join(self, context, mapping, sep):
98 def join(self, context, mapping, sep):
99 # TODO: switch gen to (context, mapping) API?
99 # TODO: switch gen to (context, mapping) API?
100 return joinitems((self._joinfmt(x) for x in self._values), sep)
100 return joinitems((self._joinfmt(x) for x in self._values), sep)
101
101
102 def show(self, context, mapping):
102 def show(self, context, mapping):
103 # TODO: switch gen to (context, mapping) API?
103 # TODO: switch gen to (context, mapping) API?
104 gen = self._gen
104 gen = self._gen
105 if gen is None:
105 if gen is None:
106 return self.join(context, mapping, ' ')
106 return self.join(context, mapping, ' ')
107 if callable(gen):
107 if callable(gen):
108 return gen()
108 return gen()
109 return gen
109 return gen
110
110
111 def tovalue(self, context, mapping):
111 def tovalue(self, context, mapping):
112 # TODO: return self._values and get rid of proxy methods
112 # TODO: return self._values and get rid of proxy methods
113 return self
113 return self
114
114
115 def __contains__(self, x):
115 def __contains__(self, x):
116 return x in self._values
116 return x in self._values
117 def __getitem__(self, key):
117 def __getitem__(self, key):
118 return self._values[key]
118 return self._values[key]
119 def __len__(self):
119 def __len__(self):
120 return len(self._values)
120 return len(self._values)
121 def __iter__(self):
121 def __iter__(self):
122 return iter(self._values)
122 return iter(self._values)
123 def __getattr__(self, name):
123 def __getattr__(self, name):
124 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
124 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
125 r'itervalues', r'keys', r'values'):
125 r'itervalues', r'keys', r'values'):
126 raise AttributeError(name)
126 raise AttributeError(name)
127 return getattr(self._values, name)
127 return getattr(self._values, name)
128
128
129 class mappable(wrapped):
129 class mappable(wrapped):
130 """Wrapper for non-list/dict object to support map operation
130 """Wrapper for non-list/dict object to support map operation
131
131
132 This class allows us to handle both:
132 This class allows us to handle both:
133 - "{manifest}"
133 - "{manifest}"
134 - "{manifest % '{rev}:{node}'}"
134 - "{manifest % '{rev}:{node}'}"
135 - "{manifest.rev}"
135 - "{manifest.rev}"
136
136
137 Unlike a hybrid, this does not simulate the behavior of the underling
137 Unlike a hybrid, this does not simulate the behavior of the underling
138 value.
138 value.
139 """
139 """
140
140
141 def __init__(self, gen, key, value, makemap):
141 def __init__(self, gen, key, value, makemap):
142 self._gen = gen # generator or function returning generator
142 self._gen = gen # generator or function returning generator
143 self._key = key
143 self._key = key
144 self._value = value # may be generator of strings
144 self._value = value # may be generator of strings
145 self._makemap = makemap
145 self._makemap = makemap
146
146
147 def tomap(self):
147 def tomap(self):
148 return self._makemap(self._key)
148 return self._makemap(self._key)
149
149
150 def itermaps(self, context):
150 def itermaps(self, context):
151 yield self.tomap()
151 yield self.tomap()
152
152
153 def join(self, context, mapping, sep):
153 def join(self, context, mapping, sep):
154 # TODO: just copies the old behavior where a value was a generator
154 # TODO: just copies the old behavior where a value was a generator
155 # yielding one item, but reconsider about it. join() over a string
155 # yielding one item, but reconsider about it. join() over a string
156 # has no consistent result because a string may be a bytes, or a
156 # has no consistent result because a string may be a bytes, or a
157 # generator yielding an item, or a generator yielding multiple items.
157 # generator yielding an item, or a generator yielding multiple items.
158 # Preserving all of the current behaviors wouldn't make any sense.
158 # Preserving all of the current behaviors wouldn't make any sense.
159 return self.show(context, mapping)
159 return self.show(context, mapping)
160
160
161 def show(self, context, mapping):
161 def show(self, context, mapping):
162 # TODO: switch gen to (context, mapping) API?
162 # TODO: switch gen to (context, mapping) API?
163 gen = self._gen
163 gen = self._gen
164 if gen is None:
164 if gen is None:
165 return pycompat.bytestr(self._value)
165 return pycompat.bytestr(self._value)
166 if callable(gen):
166 if callable(gen):
167 return gen()
167 return gen()
168 return gen
168 return gen
169
169
170 def tovalue(self, context, mapping):
170 def tovalue(self, context, mapping):
171 return _unthunk(context, mapping, self._value)
171 return _unthunk(context, mapping, self._value)
172
172
173 class _mappingsequence(wrapped):
173 class _mappingsequence(wrapped):
174 """Wrapper for sequence of template mappings
174 """Wrapper for sequence of template mappings
175
175
176 This represents an inner template structure (i.e. a list of dicts),
176 This represents an inner template structure (i.e. a list of dicts),
177 which can also be rendered by the specified named/literal template.
177 which can also be rendered by the specified named/literal template.
178
178
179 Template mappings may be nested.
179 Template mappings may be nested.
180 """
180 """
181
181
182 def __init__(self, name=None, tmpl=None, sep=''):
182 def __init__(self, name=None, tmpl=None, sep=''):
183 if name is not None and tmpl is not None:
183 if name is not None and tmpl is not None:
184 raise error.ProgrammingError('name and tmpl are mutually exclusive')
184 raise error.ProgrammingError('name and tmpl are mutually exclusive')
185 self._name = name
185 self._name = name
186 self._tmpl = tmpl
186 self._tmpl = tmpl
187 self._defaultsep = sep
187 self._defaultsep = sep
188
188
189 def join(self, context, mapping, sep):
189 def join(self, context, mapping, sep):
190 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
190 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
191 if self._name:
191 if self._name:
192 itemiter = (context.process(self._name, m) for m in mapsiter)
192 itemiter = (context.process(self._name, m) for m in mapsiter)
193 elif self._tmpl:
193 elif self._tmpl:
194 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
194 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
195 else:
195 else:
196 raise error.ParseError(_('not displayable without template'))
196 raise error.ParseError(_('not displayable without template'))
197 return joinitems(itemiter, sep)
197 return joinitems(itemiter, sep)
198
198
199 def show(self, context, mapping):
199 def show(self, context, mapping):
200 return self.join(context, mapping, self._defaultsep)
200 return self.join(context, mapping, self._defaultsep)
201
201
202 def tovalue(self, context, mapping):
202 def tovalue(self, context, mapping):
203 return list(self.itermaps(context))
203 knownres = context.knownresourcekeys()
204 items = []
205 for nm in self.itermaps(context):
206 # drop internal resources (recursively) which shouldn't be displayed
207 lm = context.overlaymap(mapping, nm)
208 items.append({k: unwrapvalue(context, lm, v)
209 for k, v in nm.iteritems() if k not in knownres})
210 return items
204
211
205 class mappinggenerator(_mappingsequence):
212 class mappinggenerator(_mappingsequence):
206 """Wrapper for generator of template mappings
213 """Wrapper for generator of template mappings
207
214
208 The function ``make(context, *args)`` should return a generator of
215 The function ``make(context, *args)`` should return a generator of
209 mapping dicts.
216 mapping dicts.
210 """
217 """
211
218
212 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
219 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
213 super(mappinggenerator, self).__init__(name, tmpl, sep)
220 super(mappinggenerator, self).__init__(name, tmpl, sep)
214 self._make = make
221 self._make = make
215 self._args = args
222 self._args = args
216
223
217 def itermaps(self, context):
224 def itermaps(self, context):
218 return self._make(context, *self._args)
225 return self._make(context, *self._args)
219
226
220 class mappinglist(_mappingsequence):
227 class mappinglist(_mappingsequence):
221 """Wrapper for list of template mappings"""
228 """Wrapper for list of template mappings"""
222
229
223 def __init__(self, mappings, name=None, tmpl=None, sep=''):
230 def __init__(self, mappings, name=None, tmpl=None, sep=''):
224 super(mappinglist, self).__init__(name, tmpl, sep)
231 super(mappinglist, self).__init__(name, tmpl, sep)
225 self._mappings = mappings
232 self._mappings = mappings
226
233
227 def itermaps(self, context):
234 def itermaps(self, context):
228 return iter(self._mappings)
235 return iter(self._mappings)
229
236
230 class mappedgenerator(wrapped):
237 class mappedgenerator(wrapped):
231 """Wrapper for generator of strings which acts as a list
238 """Wrapper for generator of strings which acts as a list
232
239
233 The function ``make(context, *args)`` should return a generator of
240 The function ``make(context, *args)`` should return a generator of
234 byte strings, or a generator of (possibly nested) generators of byte
241 byte strings, or a generator of (possibly nested) generators of byte
235 strings (i.e. a generator for a list of byte strings.)
242 strings (i.e. a generator for a list of byte strings.)
236 """
243 """
237
244
238 def __init__(self, make, args=()):
245 def __init__(self, make, args=()):
239 self._make = make
246 self._make = make
240 self._args = args
247 self._args = args
241
248
242 def _gen(self, context):
249 def _gen(self, context):
243 return self._make(context, *self._args)
250 return self._make(context, *self._args)
244
251
245 def itermaps(self, context):
252 def itermaps(self, context):
246 raise error.ParseError(_('list of strings is not mappable'))
253 raise error.ParseError(_('list of strings is not mappable'))
247
254
248 def join(self, context, mapping, sep):
255 def join(self, context, mapping, sep):
249 return joinitems(self._gen(context), sep)
256 return joinitems(self._gen(context), sep)
250
257
251 def show(self, context, mapping):
258 def show(self, context, mapping):
252 return self.join(context, mapping, '')
259 return self.join(context, mapping, '')
253
260
254 def tovalue(self, context, mapping):
261 def tovalue(self, context, mapping):
255 return [stringify(context, mapping, x) for x in self._gen(context)]
262 return [stringify(context, mapping, x) for x in self._gen(context)]
256
263
257 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
264 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
258 """Wrap data to support both dict-like and string-like operations"""
265 """Wrap data to support both dict-like and string-like operations"""
259 prefmt = pycompat.identity
266 prefmt = pycompat.identity
260 if fmt is None:
267 if fmt is None:
261 fmt = '%s=%s'
268 fmt = '%s=%s'
262 prefmt = pycompat.bytestr
269 prefmt = pycompat.bytestr
263 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
270 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
264 lambda k: fmt % (prefmt(k), prefmt(data[k])))
271 lambda k: fmt % (prefmt(k), prefmt(data[k])))
265
272
266 def hybridlist(data, name, fmt=None, gen=None):
273 def hybridlist(data, name, fmt=None, gen=None):
267 """Wrap data to support both list-like and string-like operations"""
274 """Wrap data to support both list-like and string-like operations"""
268 prefmt = pycompat.identity
275 prefmt = pycompat.identity
269 if fmt is None:
276 if fmt is None:
270 fmt = '%s'
277 fmt = '%s'
271 prefmt = pycompat.bytestr
278 prefmt = pycompat.bytestr
272 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
279 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
273
280
274 def unwraphybrid(context, mapping, thing):
281 def unwraphybrid(context, mapping, thing):
275 """Return an object which can be stringified possibly by using a legacy
282 """Return an object which can be stringified possibly by using a legacy
276 template"""
283 template"""
277 if not isinstance(thing, wrapped):
284 if not isinstance(thing, wrapped):
278 return thing
285 return thing
279 return thing.show(context, mapping)
286 return thing.show(context, mapping)
280
287
281 def unwrapvalue(context, mapping, thing):
288 def unwrapvalue(context, mapping, thing):
282 """Move the inner value object out of the wrapper"""
289 """Move the inner value object out of the wrapper"""
283 if not isinstance(thing, wrapped):
290 if not isinstance(thing, wrapped):
284 return thing
291 return thing
285 return thing.tovalue(context, mapping)
292 return thing.tovalue(context, mapping)
286
293
287 def wraphybridvalue(container, key, value):
294 def wraphybridvalue(container, key, value):
288 """Wrap an element of hybrid container to be mappable
295 """Wrap an element of hybrid container to be mappable
289
296
290 The key is passed to the makemap function of the given container, which
297 The key is passed to the makemap function of the given container, which
291 should be an item generated by iter(container).
298 should be an item generated by iter(container).
292 """
299 """
293 makemap = getattr(container, '_makemap', None)
300 makemap = getattr(container, '_makemap', None)
294 if makemap is None:
301 if makemap is None:
295 return value
302 return value
296 if util.safehasattr(value, '_makemap'):
303 if util.safehasattr(value, '_makemap'):
297 # a nested hybrid list/dict, which has its own way of map operation
304 # a nested hybrid list/dict, which has its own way of map operation
298 return value
305 return value
299 return mappable(None, key, value, makemap)
306 return mappable(None, key, value, makemap)
300
307
301 def compatdict(context, mapping, name, data, key='key', value='value',
308 def compatdict(context, mapping, name, data, key='key', value='value',
302 fmt=None, plural=None, separator=' '):
309 fmt=None, plural=None, separator=' '):
303 """Wrap data like hybriddict(), but also supports old-style list template
310 """Wrap data like hybriddict(), but also supports old-style list template
304
311
305 This exists for backward compatibility with the old-style template. Use
312 This exists for backward compatibility with the old-style template. Use
306 hybriddict() for new template keywords.
313 hybriddict() for new template keywords.
307 """
314 """
308 c = [{key: k, value: v} for k, v in data.iteritems()]
315 c = [{key: k, value: v} for k, v in data.iteritems()]
309 f = _showcompatlist(context, mapping, name, c, plural, separator)
316 f = _showcompatlist(context, mapping, name, c, plural, separator)
310 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
317 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
311
318
312 def compatlist(context, mapping, name, data, element=None, fmt=None,
319 def compatlist(context, mapping, name, data, element=None, fmt=None,
313 plural=None, separator=' '):
320 plural=None, separator=' '):
314 """Wrap data like hybridlist(), but also supports old-style list template
321 """Wrap data like hybridlist(), but also supports old-style list template
315
322
316 This exists for backward compatibility with the old-style template. Use
323 This exists for backward compatibility with the old-style template. Use
317 hybridlist() for new template keywords.
324 hybridlist() for new template keywords.
318 """
325 """
319 f = _showcompatlist(context, mapping, name, data, plural, separator)
326 f = _showcompatlist(context, mapping, name, data, plural, separator)
320 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
327 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
321
328
322 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
329 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
323 """Return a generator that renders old-style list template
330 """Return a generator that renders old-style list template
324
331
325 name is name of key in template map.
332 name is name of key in template map.
326 values is list of strings or dicts.
333 values is list of strings or dicts.
327 plural is plural of name, if not simply name + 's'.
334 plural is plural of name, if not simply name + 's'.
328 separator is used to join values as a string
335 separator is used to join values as a string
329
336
330 expansion works like this, given name 'foo'.
337 expansion works like this, given name 'foo'.
331
338
332 if values is empty, expand 'no_foos'.
339 if values is empty, expand 'no_foos'.
333
340
334 if 'foo' not in template map, return values as a string,
341 if 'foo' not in template map, return values as a string,
335 joined by 'separator'.
342 joined by 'separator'.
336
343
337 expand 'start_foos'.
344 expand 'start_foos'.
338
345
339 for each value, expand 'foo'. if 'last_foo' in template
346 for each value, expand 'foo'. if 'last_foo' in template
340 map, expand it instead of 'foo' for last key.
347 map, expand it instead of 'foo' for last key.
341
348
342 expand 'end_foos'.
349 expand 'end_foos'.
343 """
350 """
344 if not plural:
351 if not plural:
345 plural = name + 's'
352 plural = name + 's'
346 if not values:
353 if not values:
347 noname = 'no_' + plural
354 noname = 'no_' + plural
348 if context.preload(noname):
355 if context.preload(noname):
349 yield context.process(noname, mapping)
356 yield context.process(noname, mapping)
350 return
357 return
351 if not context.preload(name):
358 if not context.preload(name):
352 if isinstance(values[0], bytes):
359 if isinstance(values[0], bytes):
353 yield separator.join(values)
360 yield separator.join(values)
354 else:
361 else:
355 for v in values:
362 for v in values:
356 r = dict(v)
363 r = dict(v)
357 r.update(mapping)
364 r.update(mapping)
358 yield r
365 yield r
359 return
366 return
360 startname = 'start_' + plural
367 startname = 'start_' + plural
361 if context.preload(startname):
368 if context.preload(startname):
362 yield context.process(startname, mapping)
369 yield context.process(startname, mapping)
363 def one(v, tag=name):
370 def one(v, tag=name):
364 vmapping = {}
371 vmapping = {}
365 try:
372 try:
366 vmapping.update(v)
373 vmapping.update(v)
367 # Python 2 raises ValueError if the type of v is wrong. Python
374 # Python 2 raises ValueError if the type of v is wrong. Python
368 # 3 raises TypeError.
375 # 3 raises TypeError.
369 except (AttributeError, TypeError, ValueError):
376 except (AttributeError, TypeError, ValueError):
370 try:
377 try:
371 # Python 2 raises ValueError trying to destructure an e.g.
378 # Python 2 raises ValueError trying to destructure an e.g.
372 # bytes. Python 3 raises TypeError.
379 # bytes. Python 3 raises TypeError.
373 for a, b in v:
380 for a, b in v:
374 vmapping[a] = b
381 vmapping[a] = b
375 except (TypeError, ValueError):
382 except (TypeError, ValueError):
376 vmapping[name] = v
383 vmapping[name] = v
377 vmapping = context.overlaymap(mapping, vmapping)
384 vmapping = context.overlaymap(mapping, vmapping)
378 return context.process(tag, vmapping)
385 return context.process(tag, vmapping)
379 lastname = 'last_' + name
386 lastname = 'last_' + name
380 if context.preload(lastname):
387 if context.preload(lastname):
381 last = values.pop()
388 last = values.pop()
382 else:
389 else:
383 last = None
390 last = None
384 for v in values:
391 for v in values:
385 yield one(v)
392 yield one(v)
386 if last is not None:
393 if last is not None:
387 yield one(last, tag=lastname)
394 yield one(last, tag=lastname)
388 endname = 'end_' + plural
395 endname = 'end_' + plural
389 if context.preload(endname):
396 if context.preload(endname):
390 yield context.process(endname, mapping)
397 yield context.process(endname, mapping)
391
398
392 def flatten(context, mapping, thing):
399 def flatten(context, mapping, thing):
393 """Yield a single stream from a possibly nested set of iterators"""
400 """Yield a single stream from a possibly nested set of iterators"""
394 thing = unwraphybrid(context, mapping, thing)
401 thing = unwraphybrid(context, mapping, thing)
395 if isinstance(thing, bytes):
402 if isinstance(thing, bytes):
396 yield thing
403 yield thing
397 elif isinstance(thing, str):
404 elif isinstance(thing, str):
398 # We can only hit this on Python 3, and it's here to guard
405 # We can only hit this on Python 3, and it's here to guard
399 # against infinite recursion.
406 # against infinite recursion.
400 raise error.ProgrammingError('Mercurial IO including templates is done'
407 raise error.ProgrammingError('Mercurial IO including templates is done'
401 ' with bytes, not strings, got %r' % thing)
408 ' with bytes, not strings, got %r' % thing)
402 elif thing is None:
409 elif thing is None:
403 pass
410 pass
404 elif not util.safehasattr(thing, '__iter__'):
411 elif not util.safehasattr(thing, '__iter__'):
405 yield pycompat.bytestr(thing)
412 yield pycompat.bytestr(thing)
406 else:
413 else:
407 for i in thing:
414 for i in thing:
408 i = unwraphybrid(context, mapping, i)
415 i = unwraphybrid(context, mapping, i)
409 if isinstance(i, bytes):
416 if isinstance(i, bytes):
410 yield i
417 yield i
411 elif i is None:
418 elif i is None:
412 pass
419 pass
413 elif not util.safehasattr(i, '__iter__'):
420 elif not util.safehasattr(i, '__iter__'):
414 yield pycompat.bytestr(i)
421 yield pycompat.bytestr(i)
415 else:
422 else:
416 for j in flatten(context, mapping, i):
423 for j in flatten(context, mapping, i):
417 yield j
424 yield j
418
425
419 def stringify(context, mapping, thing):
426 def stringify(context, mapping, thing):
420 """Turn values into bytes by converting into text and concatenating them"""
427 """Turn values into bytes by converting into text and concatenating them"""
421 if isinstance(thing, bytes):
428 if isinstance(thing, bytes):
422 return thing # retain localstr to be round-tripped
429 return thing # retain localstr to be round-tripped
423 return b''.join(flatten(context, mapping, thing))
430 return b''.join(flatten(context, mapping, thing))
424
431
425 def findsymbolicname(arg):
432 def findsymbolicname(arg):
426 """Find symbolic name for the given compiled expression; returns None
433 """Find symbolic name for the given compiled expression; returns None
427 if nothing found reliably"""
434 if nothing found reliably"""
428 while True:
435 while True:
429 func, data = arg
436 func, data = arg
430 if func is runsymbol:
437 if func is runsymbol:
431 return data
438 return data
432 elif func is runfilter:
439 elif func is runfilter:
433 arg = data[0]
440 arg = data[0]
434 else:
441 else:
435 return None
442 return None
436
443
437 def _unthunk(context, mapping, thing):
444 def _unthunk(context, mapping, thing):
438 """Evaluate a lazy byte string into value"""
445 """Evaluate a lazy byte string into value"""
439 if not isinstance(thing, types.GeneratorType):
446 if not isinstance(thing, types.GeneratorType):
440 return thing
447 return thing
441 return stringify(context, mapping, thing)
448 return stringify(context, mapping, thing)
442
449
443 def evalrawexp(context, mapping, arg):
450 def evalrawexp(context, mapping, arg):
444 """Evaluate given argument as a bare template object which may require
451 """Evaluate given argument as a bare template object which may require
445 further processing (such as folding generator of strings)"""
452 further processing (such as folding generator of strings)"""
446 func, data = arg
453 func, data = arg
447 return func(context, mapping, data)
454 return func(context, mapping, data)
448
455
449 def evalfuncarg(context, mapping, arg):
456 def evalfuncarg(context, mapping, arg):
450 """Evaluate given argument as value type"""
457 """Evaluate given argument as value type"""
451 return _unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
458 return _unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
452
459
453 # TODO: unify this with unwrapvalue() once the bug of templatefunc.join()
460 # TODO: unify this with unwrapvalue() once the bug of templatefunc.join()
454 # is fixed. we can't do that right now because join() has to take a generator
461 # is fixed. we can't do that right now because join() has to take a generator
455 # of byte strings as it is, not a lazy byte string.
462 # of byte strings as it is, not a lazy byte string.
456 def _unwrapvalue(context, mapping, thing):
463 def _unwrapvalue(context, mapping, thing):
457 thing = unwrapvalue(context, mapping, thing)
464 thing = unwrapvalue(context, mapping, thing)
458 # evalrawexp() may return string, generator of strings or arbitrary object
465 # evalrawexp() may return string, generator of strings or arbitrary object
459 # such as date tuple, but filter does not want generator.
466 # such as date tuple, but filter does not want generator.
460 return _unthunk(context, mapping, thing)
467 return _unthunk(context, mapping, thing)
461
468
462 def evalboolean(context, mapping, arg):
469 def evalboolean(context, mapping, arg):
463 """Evaluate given argument as boolean, but also takes boolean literals"""
470 """Evaluate given argument as boolean, but also takes boolean literals"""
464 func, data = arg
471 func, data = arg
465 if func is runsymbol:
472 if func is runsymbol:
466 thing = func(context, mapping, data, default=None)
473 thing = func(context, mapping, data, default=None)
467 if thing is None:
474 if thing is None:
468 # not a template keyword, takes as a boolean literal
475 # not a template keyword, takes as a boolean literal
469 thing = stringutil.parsebool(data)
476 thing = stringutil.parsebool(data)
470 else:
477 else:
471 thing = func(context, mapping, data)
478 thing = func(context, mapping, data)
472 thing = unwrapvalue(context, mapping, thing)
479 thing = unwrapvalue(context, mapping, thing)
473 if isinstance(thing, bool):
480 if isinstance(thing, bool):
474 return thing
481 return thing
475 # other objects are evaluated as strings, which means 0 is True, but
482 # other objects are evaluated as strings, which means 0 is True, but
476 # empty dict/list should be False as they are expected to be ''
483 # empty dict/list should be False as they are expected to be ''
477 return bool(stringify(context, mapping, thing))
484 return bool(stringify(context, mapping, thing))
478
485
479 def evaldate(context, mapping, arg, err=None):
486 def evaldate(context, mapping, arg, err=None):
480 """Evaluate given argument as a date tuple or a date string; returns
487 """Evaluate given argument as a date tuple or a date string; returns
481 a (unixtime, offset) tuple"""
488 a (unixtime, offset) tuple"""
482 thing = evalrawexp(context, mapping, arg)
489 thing = evalrawexp(context, mapping, arg)
483 return unwrapdate(context, mapping, thing, err)
490 return unwrapdate(context, mapping, thing, err)
484
491
485 def unwrapdate(context, mapping, thing, err=None):
492 def unwrapdate(context, mapping, thing, err=None):
486 thing = _unwrapvalue(context, mapping, thing)
493 thing = _unwrapvalue(context, mapping, thing)
487 try:
494 try:
488 return dateutil.parsedate(thing)
495 return dateutil.parsedate(thing)
489 except AttributeError:
496 except AttributeError:
490 raise error.ParseError(err or _('not a date tuple nor a string'))
497 raise error.ParseError(err or _('not a date tuple nor a string'))
491 except error.ParseError:
498 except error.ParseError:
492 if not err:
499 if not err:
493 raise
500 raise
494 raise error.ParseError(err)
501 raise error.ParseError(err)
495
502
496 def evalinteger(context, mapping, arg, err=None):
503 def evalinteger(context, mapping, arg, err=None):
497 thing = evalrawexp(context, mapping, arg)
504 thing = evalrawexp(context, mapping, arg)
498 return unwrapinteger(context, mapping, thing, err)
505 return unwrapinteger(context, mapping, thing, err)
499
506
500 def unwrapinteger(context, mapping, thing, err=None):
507 def unwrapinteger(context, mapping, thing, err=None):
501 thing = _unwrapvalue(context, mapping, thing)
508 thing = _unwrapvalue(context, mapping, thing)
502 try:
509 try:
503 return int(thing)
510 return int(thing)
504 except (TypeError, ValueError):
511 except (TypeError, ValueError):
505 raise error.ParseError(err or _('not an integer'))
512 raise error.ParseError(err or _('not an integer'))
506
513
507 def evalstring(context, mapping, arg):
514 def evalstring(context, mapping, arg):
508 return stringify(context, mapping, evalrawexp(context, mapping, arg))
515 return stringify(context, mapping, evalrawexp(context, mapping, arg))
509
516
510 def evalstringliteral(context, mapping, arg):
517 def evalstringliteral(context, mapping, arg):
511 """Evaluate given argument as string template, but returns symbol name
518 """Evaluate given argument as string template, but returns symbol name
512 if it is unknown"""
519 if it is unknown"""
513 func, data = arg
520 func, data = arg
514 if func is runsymbol:
521 if func is runsymbol:
515 thing = func(context, mapping, data, default=data)
522 thing = func(context, mapping, data, default=data)
516 else:
523 else:
517 thing = func(context, mapping, data)
524 thing = func(context, mapping, data)
518 return stringify(context, mapping, thing)
525 return stringify(context, mapping, thing)
519
526
520 _unwrapfuncbytype = {
527 _unwrapfuncbytype = {
521 None: _unwrapvalue,
528 None: _unwrapvalue,
522 bytes: stringify,
529 bytes: stringify,
523 date: unwrapdate,
530 date: unwrapdate,
524 int: unwrapinteger,
531 int: unwrapinteger,
525 }
532 }
526
533
527 def unwrapastype(context, mapping, thing, typ):
534 def unwrapastype(context, mapping, thing, typ):
528 """Move the inner value object out of the wrapper and coerce its type"""
535 """Move the inner value object out of the wrapper and coerce its type"""
529 try:
536 try:
530 f = _unwrapfuncbytype[typ]
537 f = _unwrapfuncbytype[typ]
531 except KeyError:
538 except KeyError:
532 raise error.ProgrammingError('invalid type specified: %r' % typ)
539 raise error.ProgrammingError('invalid type specified: %r' % typ)
533 return f(context, mapping, thing)
540 return f(context, mapping, thing)
534
541
535 def runinteger(context, mapping, data):
542 def runinteger(context, mapping, data):
536 return int(data)
543 return int(data)
537
544
538 def runstring(context, mapping, data):
545 def runstring(context, mapping, data):
539 return data
546 return data
540
547
541 def _recursivesymbolblocker(key):
548 def _recursivesymbolblocker(key):
542 def showrecursion(**args):
549 def showrecursion(**args):
543 raise error.Abort(_("recursive reference '%s' in template") % key)
550 raise error.Abort(_("recursive reference '%s' in template") % key)
544 return showrecursion
551 return showrecursion
545
552
546 def runsymbol(context, mapping, key, default=''):
553 def runsymbol(context, mapping, key, default=''):
547 v = context.symbol(mapping, key)
554 v = context.symbol(mapping, key)
548 if v is None:
555 if v is None:
549 # put poison to cut recursion. we can't move this to parsing phase
556 # put poison to cut recursion. we can't move this to parsing phase
550 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
557 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
551 safemapping = mapping.copy()
558 safemapping = mapping.copy()
552 safemapping[key] = _recursivesymbolblocker(key)
559 safemapping[key] = _recursivesymbolblocker(key)
553 try:
560 try:
554 v = context.process(key, safemapping)
561 v = context.process(key, safemapping)
555 except TemplateNotFound:
562 except TemplateNotFound:
556 v = default
563 v = default
557 if callable(v) and getattr(v, '_requires', None) is None:
564 if callable(v) and getattr(v, '_requires', None) is None:
558 # old templatekw: expand all keywords and resources
565 # old templatekw: expand all keywords and resources
559 # (TODO: deprecate this after porting web template keywords to new API)
566 # (TODO: deprecate this after porting web template keywords to new API)
560 props = {k: context._resources.lookup(context, mapping, k)
567 props = {k: context._resources.lookup(context, mapping, k)
561 for k in context._resources.knownkeys()}
568 for k in context._resources.knownkeys()}
562 # pass context to _showcompatlist() through templatekw._showlist()
569 # pass context to _showcompatlist() through templatekw._showlist()
563 props['templ'] = context
570 props['templ'] = context
564 props.update(mapping)
571 props.update(mapping)
565 return v(**pycompat.strkwargs(props))
572 return v(**pycompat.strkwargs(props))
566 if callable(v):
573 if callable(v):
567 # new templatekw
574 # new templatekw
568 try:
575 try:
569 return v(context, mapping)
576 return v(context, mapping)
570 except ResourceUnavailable:
577 except ResourceUnavailable:
571 # unsupported keyword is mapped to empty just like unknown keyword
578 # unsupported keyword is mapped to empty just like unknown keyword
572 return None
579 return None
573 return v
580 return v
574
581
575 def runtemplate(context, mapping, template):
582 def runtemplate(context, mapping, template):
576 for arg in template:
583 for arg in template:
577 yield evalrawexp(context, mapping, arg)
584 yield evalrawexp(context, mapping, arg)
578
585
579 def runfilter(context, mapping, data):
586 def runfilter(context, mapping, data):
580 arg, filt = data
587 arg, filt = data
581 thing = evalrawexp(context, mapping, arg)
588 thing = evalrawexp(context, mapping, arg)
582 intype = getattr(filt, '_intype', None)
589 intype = getattr(filt, '_intype', None)
583 try:
590 try:
584 thing = unwrapastype(context, mapping, thing, intype)
591 thing = unwrapastype(context, mapping, thing, intype)
585 return filt(thing)
592 return filt(thing)
586 except error.ParseError as e:
593 except error.ParseError as e:
587 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
594 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
588
595
589 def _formatfiltererror(arg, filt):
596 def _formatfiltererror(arg, filt):
590 fn = pycompat.sysbytes(filt.__name__)
597 fn = pycompat.sysbytes(filt.__name__)
591 sym = findsymbolicname(arg)
598 sym = findsymbolicname(arg)
592 if not sym:
599 if not sym:
593 return _("incompatible use of template filter '%s'") % fn
600 return _("incompatible use of template filter '%s'") % fn
594 return (_("template filter '%s' is not compatible with keyword '%s'")
601 return (_("template filter '%s' is not compatible with keyword '%s'")
595 % (fn, sym))
602 % (fn, sym))
596
603
597 def _checkeditermaps(darg, d):
604 def _checkeditermaps(darg, d):
598 try:
605 try:
599 for v in d:
606 for v in d:
600 if not isinstance(v, dict):
607 if not isinstance(v, dict):
601 raise TypeError
608 raise TypeError
602 yield v
609 yield v
603 except TypeError:
610 except TypeError:
604 sym = findsymbolicname(darg)
611 sym = findsymbolicname(darg)
605 if sym:
612 if sym:
606 raise error.ParseError(_("keyword '%s' is not iterable of mappings")
613 raise error.ParseError(_("keyword '%s' is not iterable of mappings")
607 % sym)
614 % sym)
608 else:
615 else:
609 raise error.ParseError(_("%r is not iterable of mappings") % d)
616 raise error.ParseError(_("%r is not iterable of mappings") % d)
610
617
611 def _iteroverlaymaps(context, origmapping, newmappings):
618 def _iteroverlaymaps(context, origmapping, newmappings):
612 """Generate combined mappings from the original mapping and an iterable
619 """Generate combined mappings from the original mapping and an iterable
613 of partial mappings to override the original"""
620 of partial mappings to override the original"""
614 for i, nm in enumerate(newmappings):
621 for i, nm in enumerate(newmappings):
615 lm = context.overlaymap(origmapping, nm)
622 lm = context.overlaymap(origmapping, nm)
616 lm['index'] = i
623 lm['index'] = i
617 yield lm
624 yield lm
618
625
619 def _applymap(context, mapping, diter, targ):
626 def _applymap(context, mapping, diter, targ):
620 for lm in _iteroverlaymaps(context, mapping, diter):
627 for lm in _iteroverlaymaps(context, mapping, diter):
621 yield evalrawexp(context, lm, targ)
628 yield evalrawexp(context, lm, targ)
622
629
623 def runmap(context, mapping, data):
630 def runmap(context, mapping, data):
624 darg, targ = data
631 darg, targ = data
625 d = evalrawexp(context, mapping, darg)
632 d = evalrawexp(context, mapping, darg)
626 # TODO: a generator should be rejected because it is a thunk of lazy
633 # TODO: a generator should be rejected because it is a thunk of lazy
627 # string, but we can't because hgweb abuses generator as a keyword
634 # string, but we can't because hgweb abuses generator as a keyword
628 # that returns a list of dicts.
635 # that returns a list of dicts.
629 # TODO: drop _checkeditermaps() and pass 'd' to mappedgenerator so it
636 # TODO: drop _checkeditermaps() and pass 'd' to mappedgenerator so it
630 # can be restarted.
637 # can be restarted.
631 if isinstance(d, wrapped):
638 if isinstance(d, wrapped):
632 diter = d.itermaps(context)
639 diter = d.itermaps(context)
633 else:
640 else:
634 diter = _checkeditermaps(darg, d)
641 diter = _checkeditermaps(darg, d)
635 return mappedgenerator(_applymap, args=(mapping, diter, targ))
642 return mappedgenerator(_applymap, args=(mapping, diter, targ))
636
643
637 def runmember(context, mapping, data):
644 def runmember(context, mapping, data):
638 darg, memb = data
645 darg, memb = data
639 d = evalrawexp(context, mapping, darg)
646 d = evalrawexp(context, mapping, darg)
640 if util.safehasattr(d, 'tomap'):
647 if util.safehasattr(d, 'tomap'):
641 lm = context.overlaymap(mapping, d.tomap())
648 lm = context.overlaymap(mapping, d.tomap())
642 return runsymbol(context, lm, memb)
649 return runsymbol(context, lm, memb)
643 if util.safehasattr(d, 'get'):
650 if util.safehasattr(d, 'get'):
644 return getdictitem(d, memb)
651 return getdictitem(d, memb)
645
652
646 sym = findsymbolicname(darg)
653 sym = findsymbolicname(darg)
647 if sym:
654 if sym:
648 raise error.ParseError(_("keyword '%s' has no member") % sym)
655 raise error.ParseError(_("keyword '%s' has no member") % sym)
649 else:
656 else:
650 raise error.ParseError(_("%r has no member") % pycompat.bytestr(d))
657 raise error.ParseError(_("%r has no member") % pycompat.bytestr(d))
651
658
652 def runnegate(context, mapping, data):
659 def runnegate(context, mapping, data):
653 data = evalinteger(context, mapping, data,
660 data = evalinteger(context, mapping, data,
654 _('negation needs an integer argument'))
661 _('negation needs an integer argument'))
655 return -data
662 return -data
656
663
657 def runarithmetic(context, mapping, data):
664 def runarithmetic(context, mapping, data):
658 func, left, right = data
665 func, left, right = data
659 left = evalinteger(context, mapping, left,
666 left = evalinteger(context, mapping, left,
660 _('arithmetic only defined on integers'))
667 _('arithmetic only defined on integers'))
661 right = evalinteger(context, mapping, right,
668 right = evalinteger(context, mapping, right,
662 _('arithmetic only defined on integers'))
669 _('arithmetic only defined on integers'))
663 try:
670 try:
664 return func(left, right)
671 return func(left, right)
665 except ZeroDivisionError:
672 except ZeroDivisionError:
666 raise error.Abort(_('division by zero is not defined'))
673 raise error.Abort(_('division by zero is not defined'))
667
674
668 def getdictitem(dictarg, key):
675 def getdictitem(dictarg, key):
669 val = dictarg.get(key)
676 val = dictarg.get(key)
670 if val is None:
677 if val is None:
671 return
678 return
672 return wraphybridvalue(dictarg, key, val)
679 return wraphybridvalue(dictarg, key, val)
673
680
674 def joinitems(itemiter, sep):
681 def joinitems(itemiter, sep):
675 """Join items with the separator; Returns generator of bytes"""
682 """Join items with the separator; Returns generator of bytes"""
676 first = True
683 first = True
677 for x in itemiter:
684 for x in itemiter:
678 if first:
685 if first:
679 first = False
686 first = False
680 elif sep:
687 elif sep:
681 yield sep
688 yield sep
682 yield x
689 yield x
@@ -1,1068 +1,1070 b''
1 $ HGMERGE=true; export HGMERGE
1 $ HGMERGE=true; export HGMERGE
2
2
3 init
3 init
4
4
5 $ hg init repo
5 $ hg init repo
6 $ cd repo
6 $ cd repo
7
7
8 commit
8 commit
9
9
10 $ echo 'a' > a
10 $ echo 'a' > a
11 $ hg ci -A -m test -u nobody -d '1 0'
11 $ hg ci -A -m test -u nobody -d '1 0'
12 adding a
12 adding a
13
13
14 annotate -c
14 annotate -c
15
15
16 $ hg annotate -c a
16 $ hg annotate -c a
17 8435f90966e4: a
17 8435f90966e4: a
18
18
19 annotate -cl
19 annotate -cl
20
20
21 $ hg annotate -cl a
21 $ hg annotate -cl a
22 8435f90966e4:1: a
22 8435f90966e4:1: a
23
23
24 annotate -d
24 annotate -d
25
25
26 $ hg annotate -d a
26 $ hg annotate -d a
27 Thu Jan 01 00:00:01 1970 +0000: a
27 Thu Jan 01 00:00:01 1970 +0000: a
28
28
29 annotate -n
29 annotate -n
30
30
31 $ hg annotate -n a
31 $ hg annotate -n a
32 0: a
32 0: a
33
33
34 annotate -nl
34 annotate -nl
35
35
36 $ hg annotate -nl a
36 $ hg annotate -nl a
37 0:1: a
37 0:1: a
38
38
39 annotate -u
39 annotate -u
40
40
41 $ hg annotate -u a
41 $ hg annotate -u a
42 nobody: a
42 nobody: a
43
43
44 annotate -cdnu
44 annotate -cdnu
45
45
46 $ hg annotate -cdnu a
46 $ hg annotate -cdnu a
47 nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000: a
47 nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000: a
48
48
49 annotate -cdnul
49 annotate -cdnul
50
50
51 $ hg annotate -cdnul a
51 $ hg annotate -cdnul a
52 nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000:1: a
52 nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000:1: a
53
53
54 annotate (JSON)
54 annotate (JSON)
55
55
56 $ hg annotate -Tjson a
56 $ hg annotate -Tjson a
57 [
57 [
58 {
58 {
59 "abspath": "a",
59 "abspath": "a",
60 "lines": [{"line": "a\n", "rev": 0}],
60 "lines": [{"line": "a\n", "rev": 0}],
61 "path": "a"
61 "path": "a"
62 }
62 }
63 ]
63 ]
64
64
65 $ hg annotate -Tjson -cdfnul a
65 $ hg annotate -Tjson -cdfnul a
66 [
66 [
67 {
67 {
68 "abspath": "a",
68 "abspath": "a",
69 "lines": [{"date": [1.0, 0], "file": "a", "line": "a\n", "line_number": 1, "node": "8435f90966e442695d2ded29fdade2bac5ad8065", "rev": 0, "user": "nobody"}],
69 "lines": [{"date": [1.0, 0], "file": "a", "line": "a\n", "line_number": 1, "node": "8435f90966e442695d2ded29fdade2bac5ad8065", "rev": 0, "user": "nobody"}],
70 "path": "a"
70 "path": "a"
71 }
71 }
72 ]
72 ]
73
73
74 log-like templating
74 log-like templating
75
75
76 $ hg annotate -T'{lines % "{rev} {node|shortest}: {line}"}' a
76 $ hg annotate -T'{lines % "{rev} {node|shortest}: {line}"}' a
77 0 8435: a
77 0 8435: a
78
78
79 $ cat <<EOF >>a
79 $ cat <<EOF >>a
80 > a
80 > a
81 > a
81 > a
82 > EOF
82 > EOF
83 $ hg ci -ma1 -d '1 0'
83 $ hg ci -ma1 -d '1 0'
84 $ hg cp a b
84 $ hg cp a b
85 $ hg ci -mb -d '1 0'
85 $ hg ci -mb -d '1 0'
86 $ cat <<EOF >> b
86 $ cat <<EOF >> b
87 > b4
87 > b4
88 > b5
88 > b5
89 > b6
89 > b6
90 > EOF
90 > EOF
91 $ hg ci -mb2 -d '2 0'
91 $ hg ci -mb2 -d '2 0'
92
92
93 default output of '{lines}' should be readable
93 default output of '{lines}' should be readable
94
94
95 $ hg annotate -T'{lines}' a
95 $ hg annotate -T'{lines}' a
96 0: a
96 0: a
97 1: a
97 1: a
98 1: a
98 1: a
99 $ hg annotate -T'{join(lines, "\n")}' a
99 $ hg annotate -T'{join(lines, "\n")}' a
100 0: a
100 0: a
101
101
102 1: a
102 1: a
103
103
104 1: a
104 1: a
105
105
106 several filters can be applied to '{lines}'
106 several filters can be applied to '{lines}'
107
107
108 $ hg annotate -T'{lines|json}\n' a
109 [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}]
108 $ hg annotate -T'{lines|stringify}' a
110 $ hg annotate -T'{lines|stringify}' a
109 0: a
111 0: a
110 1: a
112 1: a
111 1: a
113 1: a
112 $ hg annotate -T'{lines|count}\n' a
114 $ hg annotate -T'{lines|count}\n' a
113 3
115 3
114
116
115 annotate multiple files (JSON)
117 annotate multiple files (JSON)
116
118
117 $ hg annotate -Tjson a b
119 $ hg annotate -Tjson a b
118 [
120 [
119 {
121 {
120 "abspath": "a",
122 "abspath": "a",
121 "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}],
123 "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}],
122 "path": "a"
124 "path": "a"
123 },
125 },
124 {
126 {
125 "abspath": "b",
127 "abspath": "b",
126 "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}, {"line": "b4\n", "rev": 3}, {"line": "b5\n", "rev": 3}, {"line": "b6\n", "rev": 3}],
128 "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}, {"line": "b4\n", "rev": 3}, {"line": "b5\n", "rev": 3}, {"line": "b6\n", "rev": 3}],
127 "path": "b"
129 "path": "b"
128 }
130 }
129 ]
131 ]
130
132
131 annotate multiple files (template)
133 annotate multiple files (template)
132
134
133 $ hg annotate -T'== {abspath} ==\n{lines % "{rev}: {line}"}' a b
135 $ hg annotate -T'== {abspath} ==\n{lines % "{rev}: {line}"}' a b
134 == a ==
136 == a ==
135 0: a
137 0: a
136 1: a
138 1: a
137 1: a
139 1: a
138 == b ==
140 == b ==
139 0: a
141 0: a
140 1: a
142 1: a
141 1: a
143 1: a
142 3: b4
144 3: b4
143 3: b5
145 3: b5
144 3: b6
146 3: b6
145
147
146 annotate -n b
148 annotate -n b
147
149
148 $ hg annotate -n b
150 $ hg annotate -n b
149 0: a
151 0: a
150 1: a
152 1: a
151 1: a
153 1: a
152 3: b4
154 3: b4
153 3: b5
155 3: b5
154 3: b6
156 3: b6
155
157
156 annotate --no-follow b
158 annotate --no-follow b
157
159
158 $ hg annotate --no-follow b
160 $ hg annotate --no-follow b
159 2: a
161 2: a
160 2: a
162 2: a
161 2: a
163 2: a
162 3: b4
164 3: b4
163 3: b5
165 3: b5
164 3: b6
166 3: b6
165
167
166 annotate -nl b
168 annotate -nl b
167
169
168 $ hg annotate -nl b
170 $ hg annotate -nl b
169 0:1: a
171 0:1: a
170 1:2: a
172 1:2: a
171 1:3: a
173 1:3: a
172 3:4: b4
174 3:4: b4
173 3:5: b5
175 3:5: b5
174 3:6: b6
176 3:6: b6
175
177
176 annotate -nf b
178 annotate -nf b
177
179
178 $ hg annotate -nf b
180 $ hg annotate -nf b
179 0 a: a
181 0 a: a
180 1 a: a
182 1 a: a
181 1 a: a
183 1 a: a
182 3 b: b4
184 3 b: b4
183 3 b: b5
185 3 b: b5
184 3 b: b6
186 3 b: b6
185
187
186 annotate -nlf b
188 annotate -nlf b
187
189
188 $ hg annotate -nlf b
190 $ hg annotate -nlf b
189 0 a:1: a
191 0 a:1: a
190 1 a:2: a
192 1 a:2: a
191 1 a:3: a
193 1 a:3: a
192 3 b:4: b4
194 3 b:4: b4
193 3 b:5: b5
195 3 b:5: b5
194 3 b:6: b6
196 3 b:6: b6
195
197
196 $ hg up -C 2
198 $ hg up -C 2
197 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
199 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
198 $ cat <<EOF >> b
200 $ cat <<EOF >> b
199 > b4
201 > b4
200 > c
202 > c
201 > b5
203 > b5
202 > EOF
204 > EOF
203 $ hg ci -mb2.1 -d '2 0'
205 $ hg ci -mb2.1 -d '2 0'
204 created new head
206 created new head
205 $ hg merge
207 $ hg merge
206 merging b
208 merging b
207 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
209 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
208 (branch merge, don't forget to commit)
210 (branch merge, don't forget to commit)
209 $ hg ci -mmergeb -d '3 0'
211 $ hg ci -mmergeb -d '3 0'
210
212
211 annotate after merge
213 annotate after merge
212
214
213 $ hg annotate -nf b
215 $ hg annotate -nf b
214 0 a: a
216 0 a: a
215 1 a: a
217 1 a: a
216 1 a: a
218 1 a: a
217 3 b: b4
219 3 b: b4
218 4 b: c
220 4 b: c
219 3 b: b5
221 3 b: b5
220
222
221 annotate after merge with -l
223 annotate after merge with -l
222
224
223 $ hg annotate -nlf b
225 $ hg annotate -nlf b
224 0 a:1: a
226 0 a:1: a
225 1 a:2: a
227 1 a:2: a
226 1 a:3: a
228 1 a:3: a
227 3 b:4: b4
229 3 b:4: b4
228 4 b:5: c
230 4 b:5: c
229 3 b:5: b5
231 3 b:5: b5
230
232
231 $ hg up -C 1
233 $ hg up -C 1
232 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
234 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
233 $ hg cp a b
235 $ hg cp a b
234 $ cat <<EOF > b
236 $ cat <<EOF > b
235 > a
237 > a
236 > z
238 > z
237 > a
239 > a
238 > EOF
240 > EOF
239 $ hg ci -mc -d '3 0'
241 $ hg ci -mc -d '3 0'
240 created new head
242 created new head
241 $ hg merge
243 $ hg merge
242 merging b
244 merging b
243 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
245 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
244 (branch merge, don't forget to commit)
246 (branch merge, don't forget to commit)
245 $ cat <<EOF >> b
247 $ cat <<EOF >> b
246 > b4
248 > b4
247 > c
249 > c
248 > b5
250 > b5
249 > EOF
251 > EOF
250 $ echo d >> b
252 $ echo d >> b
251 $ hg ci -mmerge2 -d '4 0'
253 $ hg ci -mmerge2 -d '4 0'
252
254
253 annotate after rename merge
255 annotate after rename merge
254
256
255 $ hg annotate -nf b
257 $ hg annotate -nf b
256 0 a: a
258 0 a: a
257 6 b: z
259 6 b: z
258 1 a: a
260 1 a: a
259 3 b: b4
261 3 b: b4
260 4 b: c
262 4 b: c
261 3 b: b5
263 3 b: b5
262 7 b: d
264 7 b: d
263
265
264 annotate after rename merge with -l
266 annotate after rename merge with -l
265
267
266 $ hg annotate -nlf b
268 $ hg annotate -nlf b
267 0 a:1: a
269 0 a:1: a
268 6 b:2: z
270 6 b:2: z
269 1 a:3: a
271 1 a:3: a
270 3 b:4: b4
272 3 b:4: b4
271 4 b:5: c
273 4 b:5: c
272 3 b:5: b5
274 3 b:5: b5
273 7 b:7: d
275 7 b:7: d
274
276
275 --skip nothing (should be the same as no --skip at all)
277 --skip nothing (should be the same as no --skip at all)
276
278
277 $ hg annotate -nlf b --skip '1::0'
279 $ hg annotate -nlf b --skip '1::0'
278 0 a:1: a
280 0 a:1: a
279 6 b:2: z
281 6 b:2: z
280 1 a:3: a
282 1 a:3: a
281 3 b:4: b4
283 3 b:4: b4
282 4 b:5: c
284 4 b:5: c
283 3 b:5: b5
285 3 b:5: b5
284 7 b:7: d
286 7 b:7: d
285
287
286 --skip a modified line. Note a slight behavior difference in pure - this is
288 --skip a modified line. Note a slight behavior difference in pure - this is
287 because the pure code comes up with slightly different deltas internally.
289 because the pure code comes up with slightly different deltas internally.
288
290
289 $ hg annotate -nlf b --skip 6
291 $ hg annotate -nlf b --skip 6
290 0 a:1: a
292 0 a:1: a
291 1 a:2* z (no-pure !)
293 1 a:2* z (no-pure !)
292 0 a:1* z (pure !)
294 0 a:1* z (pure !)
293 1 a:3: a
295 1 a:3: a
294 3 b:4: b4
296 3 b:4: b4
295 4 b:5: c
297 4 b:5: c
296 3 b:5: b5
298 3 b:5: b5
297 7 b:7: d
299 7 b:7: d
298
300
299 --skip added lines (and test multiple skip)
301 --skip added lines (and test multiple skip)
300
302
301 $ hg annotate -nlf b --skip 3
303 $ hg annotate -nlf b --skip 3
302 0 a:1: a
304 0 a:1: a
303 6 b:2: z
305 6 b:2: z
304 1 a:3: a
306 1 a:3: a
305 1 a:3* b4
307 1 a:3* b4
306 4 b:5: c
308 4 b:5: c
307 1 a:3* b5
309 1 a:3* b5
308 7 b:7: d
310 7 b:7: d
309
311
310 $ hg annotate -nlf b --skip 4
312 $ hg annotate -nlf b --skip 4
311 0 a:1: a
313 0 a:1: a
312 6 b:2: z
314 6 b:2: z
313 1 a:3: a
315 1 a:3: a
314 3 b:4: b4
316 3 b:4: b4
315 1 a:3* c
317 1 a:3* c
316 3 b:5: b5
318 3 b:5: b5
317 7 b:7: d
319 7 b:7: d
318
320
319 $ hg annotate -nlf b --skip 3 --skip 4
321 $ hg annotate -nlf b --skip 3 --skip 4
320 0 a:1: a
322 0 a:1: a
321 6 b:2: z
323 6 b:2: z
322 1 a:3: a
324 1 a:3: a
323 1 a:3* b4
325 1 a:3* b4
324 1 a:3* c
326 1 a:3* c
325 1 a:3* b5
327 1 a:3* b5
326 7 b:7: d
328 7 b:7: d
327
329
328 $ hg annotate -nlf b --skip 'merge()'
330 $ hg annotate -nlf b --skip 'merge()'
329 0 a:1: a
331 0 a:1: a
330 6 b:2: z
332 6 b:2: z
331 1 a:3: a
333 1 a:3: a
332 3 b:4: b4
334 3 b:4: b4
333 4 b:5: c
335 4 b:5: c
334 3 b:5: b5
336 3 b:5: b5
335 3 b:5* d
337 3 b:5* d
336
338
337 --skip everything -- use the revision the file was introduced in
339 --skip everything -- use the revision the file was introduced in
338
340
339 $ hg annotate -nlf b --skip 'all()'
341 $ hg annotate -nlf b --skip 'all()'
340 0 a:1: a
342 0 a:1: a
341 0 a:1* z
343 0 a:1* z
342 0 a:1* a
344 0 a:1* a
343 0 a:1* b4
345 0 a:1* b4
344 0 a:1* c
346 0 a:1* c
345 0 a:1* b5
347 0 a:1* b5
346 0 a:1* d
348 0 a:1* d
347
349
348 Issue2807: alignment of line numbers with -l
350 Issue2807: alignment of line numbers with -l
349
351
350 $ echo more >> b
352 $ echo more >> b
351 $ hg ci -mmore -d '5 0'
353 $ hg ci -mmore -d '5 0'
352 $ echo more >> b
354 $ echo more >> b
353 $ hg ci -mmore -d '6 0'
355 $ hg ci -mmore -d '6 0'
354 $ echo more >> b
356 $ echo more >> b
355 $ hg ci -mmore -d '7 0'
357 $ hg ci -mmore -d '7 0'
356 $ hg annotate -nlf b
358 $ hg annotate -nlf b
357 0 a: 1: a
359 0 a: 1: a
358 6 b: 2: z
360 6 b: 2: z
359 1 a: 3: a
361 1 a: 3: a
360 3 b: 4: b4
362 3 b: 4: b4
361 4 b: 5: c
363 4 b: 5: c
362 3 b: 5: b5
364 3 b: 5: b5
363 7 b: 7: d
365 7 b: 7: d
364 8 b: 8: more
366 8 b: 8: more
365 9 b: 9: more
367 9 b: 9: more
366 10 b:10: more
368 10 b:10: more
367
369
368 linkrev vs rev
370 linkrev vs rev
369
371
370 $ hg annotate -r tip -n a
372 $ hg annotate -r tip -n a
371 0: a
373 0: a
372 1: a
374 1: a
373 1: a
375 1: a
374
376
375 linkrev vs rev with -l
377 linkrev vs rev with -l
376
378
377 $ hg annotate -r tip -nl a
379 $ hg annotate -r tip -nl a
378 0:1: a
380 0:1: a
379 1:2: a
381 1:2: a
380 1:3: a
382 1:3: a
381
383
382 Issue589: "undelete" sequence leads to crash
384 Issue589: "undelete" sequence leads to crash
383
385
384 annotate was crashing when trying to --follow something
386 annotate was crashing when trying to --follow something
385
387
386 like A -> B -> A
388 like A -> B -> A
387
389
388 generate ABA rename configuration
390 generate ABA rename configuration
389
391
390 $ echo foo > foo
392 $ echo foo > foo
391 $ hg add foo
393 $ hg add foo
392 $ hg ci -m addfoo
394 $ hg ci -m addfoo
393 $ hg rename foo bar
395 $ hg rename foo bar
394 $ hg ci -m renamefoo
396 $ hg ci -m renamefoo
395 $ hg rename bar foo
397 $ hg rename bar foo
396 $ hg ci -m renamebar
398 $ hg ci -m renamebar
397
399
398 annotate after ABA with follow
400 annotate after ABA with follow
399
401
400 $ hg annotate --follow foo
402 $ hg annotate --follow foo
401 foo: foo
403 foo: foo
402
404
403 missing file
405 missing file
404
406
405 $ hg ann nosuchfile
407 $ hg ann nosuchfile
406 abort: nosuchfile: no such file in rev e9e6b4fa872f
408 abort: nosuchfile: no such file in rev e9e6b4fa872f
407 [255]
409 [255]
408
410
409 annotate file without '\n' on last line
411 annotate file without '\n' on last line
410
412
411 $ printf "" > c
413 $ printf "" > c
412 $ hg ci -A -m test -u nobody -d '1 0'
414 $ hg ci -A -m test -u nobody -d '1 0'
413 adding c
415 adding c
414 $ hg annotate c
416 $ hg annotate c
415 $ printf "a\nb" > c
417 $ printf "a\nb" > c
416 $ hg ci -m test
418 $ hg ci -m test
417 $ hg annotate c
419 $ hg annotate c
418 [0-9]+: a (re)
420 [0-9]+: a (re)
419 [0-9]+: b (re)
421 [0-9]+: b (re)
420
422
421 Issue3841: check annotation of the file of which filelog includes
423 Issue3841: check annotation of the file of which filelog includes
422 merging between the revision and its ancestor
424 merging between the revision and its ancestor
423
425
424 to reproduce the situation with recent Mercurial, this script uses (1)
426 to reproduce the situation with recent Mercurial, this script uses (1)
425 "hg debugsetparents" to merge without ancestor check by "hg merge",
427 "hg debugsetparents" to merge without ancestor check by "hg merge",
426 and (2) the extension to allow filelog merging between the revision
428 and (2) the extension to allow filelog merging between the revision
427 and its ancestor by overriding "repo._filecommit".
429 and its ancestor by overriding "repo._filecommit".
428
430
429 $ cat > ../legacyrepo.py <<EOF
431 $ cat > ../legacyrepo.py <<EOF
430 > from __future__ import absolute_import
432 > from __future__ import absolute_import
431 > from mercurial import error, node
433 > from mercurial import error, node
432 > def reposetup(ui, repo):
434 > def reposetup(ui, repo):
433 > class legacyrepo(repo.__class__):
435 > class legacyrepo(repo.__class__):
434 > def _filecommit(self, fctx, manifest1, manifest2,
436 > def _filecommit(self, fctx, manifest1, manifest2,
435 > linkrev, tr, changelist):
437 > linkrev, tr, changelist):
436 > fname = fctx.path()
438 > fname = fctx.path()
437 > text = fctx.data()
439 > text = fctx.data()
438 > flog = self.file(fname)
440 > flog = self.file(fname)
439 > fparent1 = manifest1.get(fname, node.nullid)
441 > fparent1 = manifest1.get(fname, node.nullid)
440 > fparent2 = manifest2.get(fname, node.nullid)
442 > fparent2 = manifest2.get(fname, node.nullid)
441 > meta = {}
443 > meta = {}
442 > copy = fctx.renamed()
444 > copy = fctx.renamed()
443 > if copy and copy[0] != fname:
445 > if copy and copy[0] != fname:
444 > raise error.Abort('copying is not supported')
446 > raise error.Abort('copying is not supported')
445 > if fparent2 != node.nullid:
447 > if fparent2 != node.nullid:
446 > changelist.append(fname)
448 > changelist.append(fname)
447 > return flog.add(text, meta, tr, linkrev,
449 > return flog.add(text, meta, tr, linkrev,
448 > fparent1, fparent2)
450 > fparent1, fparent2)
449 > raise error.Abort('only merging is supported')
451 > raise error.Abort('only merging is supported')
450 > repo.__class__ = legacyrepo
452 > repo.__class__ = legacyrepo
451 > EOF
453 > EOF
452
454
453 $ cat > baz <<EOF
455 $ cat > baz <<EOF
454 > 1
456 > 1
455 > 2
457 > 2
456 > 3
458 > 3
457 > 4
459 > 4
458 > 5
460 > 5
459 > EOF
461 > EOF
460 $ hg add baz
462 $ hg add baz
461 $ hg commit -m "baz:0"
463 $ hg commit -m "baz:0"
462
464
463 $ cat > baz <<EOF
465 $ cat > baz <<EOF
464 > 1 baz:1
466 > 1 baz:1
465 > 2
467 > 2
466 > 3
468 > 3
467 > 4
469 > 4
468 > 5
470 > 5
469 > EOF
471 > EOF
470 $ hg commit -m "baz:1"
472 $ hg commit -m "baz:1"
471
473
472 $ cat > baz <<EOF
474 $ cat > baz <<EOF
473 > 1 baz:1
475 > 1 baz:1
474 > 2 baz:2
476 > 2 baz:2
475 > 3
477 > 3
476 > 4
478 > 4
477 > 5
479 > 5
478 > EOF
480 > EOF
479 $ hg debugsetparents 17 17
481 $ hg debugsetparents 17 17
480 $ hg --config extensions.legacyrepo=../legacyrepo.py commit -m "baz:2"
482 $ hg --config extensions.legacyrepo=../legacyrepo.py commit -m "baz:2"
481 $ hg debugindexdot baz
483 $ hg debugindexdot baz
482 digraph G {
484 digraph G {
483 -1 -> 0
485 -1 -> 0
484 0 -> 1
486 0 -> 1
485 1 -> 2
487 1 -> 2
486 1 -> 2
488 1 -> 2
487 }
489 }
488 $ hg annotate baz
490 $ hg annotate baz
489 17: 1 baz:1
491 17: 1 baz:1
490 18: 2 baz:2
492 18: 2 baz:2
491 16: 3
493 16: 3
492 16: 4
494 16: 4
493 16: 5
495 16: 5
494
496
495 $ cat > baz <<EOF
497 $ cat > baz <<EOF
496 > 1 baz:1
498 > 1 baz:1
497 > 2 baz:2
499 > 2 baz:2
498 > 3 baz:3
500 > 3 baz:3
499 > 4
501 > 4
500 > 5
502 > 5
501 > EOF
503 > EOF
502 $ hg commit -m "baz:3"
504 $ hg commit -m "baz:3"
503
505
504 $ cat > baz <<EOF
506 $ cat > baz <<EOF
505 > 1 baz:1
507 > 1 baz:1
506 > 2 baz:2
508 > 2 baz:2
507 > 3 baz:3
509 > 3 baz:3
508 > 4 baz:4
510 > 4 baz:4
509 > 5
511 > 5
510 > EOF
512 > EOF
511 $ hg debugsetparents 19 18
513 $ hg debugsetparents 19 18
512 $ hg --config extensions.legacyrepo=../legacyrepo.py commit -m "baz:4"
514 $ hg --config extensions.legacyrepo=../legacyrepo.py commit -m "baz:4"
513 $ hg debugindexdot baz
515 $ hg debugindexdot baz
514 digraph G {
516 digraph G {
515 -1 -> 0
517 -1 -> 0
516 0 -> 1
518 0 -> 1
517 1 -> 2
519 1 -> 2
518 1 -> 2
520 1 -> 2
519 2 -> 3
521 2 -> 3
520 3 -> 4
522 3 -> 4
521 2 -> 4
523 2 -> 4
522 }
524 }
523 $ hg annotate baz
525 $ hg annotate baz
524 17: 1 baz:1
526 17: 1 baz:1
525 18: 2 baz:2
527 18: 2 baz:2
526 19: 3 baz:3
528 19: 3 baz:3
527 20: 4 baz:4
529 20: 4 baz:4
528 16: 5
530 16: 5
529
531
530 annotate clean file
532 annotate clean file
531
533
532 $ hg annotate -ncr "wdir()" foo
534 $ hg annotate -ncr "wdir()" foo
533 11 472b18db256d : foo
535 11 472b18db256d : foo
534
536
535 annotate modified file
537 annotate modified file
536
538
537 $ echo foofoo >> foo
539 $ echo foofoo >> foo
538 $ hg annotate -r "wdir()" foo
540 $ hg annotate -r "wdir()" foo
539 11 : foo
541 11 : foo
540 20+: foofoo
542 20+: foofoo
541
543
542 $ hg annotate -cr "wdir()" foo
544 $ hg annotate -cr "wdir()" foo
543 472b18db256d : foo
545 472b18db256d : foo
544 b6bedd5477e7+: foofoo
546 b6bedd5477e7+: foofoo
545
547
546 $ hg annotate -ncr "wdir()" foo
548 $ hg annotate -ncr "wdir()" foo
547 11 472b18db256d : foo
549 11 472b18db256d : foo
548 20 b6bedd5477e7+: foofoo
550 20 b6bedd5477e7+: foofoo
549
551
550 $ hg annotate --debug -ncr "wdir()" foo
552 $ hg annotate --debug -ncr "wdir()" foo
551 11 472b18db256d1e8282064eab4bfdaf48cbfe83cd : foo
553 11 472b18db256d1e8282064eab4bfdaf48cbfe83cd : foo
552 20 b6bedd5477e797f25e568a6402d4697f3f895a72+: foofoo
554 20 b6bedd5477e797f25e568a6402d4697f3f895a72+: foofoo
553
555
554 $ hg annotate -udr "wdir()" foo
556 $ hg annotate -udr "wdir()" foo
555 test Thu Jan 01 00:00:00 1970 +0000: foo
557 test Thu Jan 01 00:00:00 1970 +0000: foo
556 test [A-Za-z0-9:+ ]+: foofoo (re)
558 test [A-Za-z0-9:+ ]+: foofoo (re)
557
559
558 $ hg annotate -ncr "wdir()" -Tjson foo
560 $ hg annotate -ncr "wdir()" -Tjson foo
559 [
561 [
560 {
562 {
561 "abspath": "foo",
563 "abspath": "foo",
562 "lines": [{"line": "foo\n", "node": "472b18db256d1e8282064eab4bfdaf48cbfe83cd", "rev": 11}, {"line": "foofoo\n", "node": null, "rev": null}],
564 "lines": [{"line": "foo\n", "node": "472b18db256d1e8282064eab4bfdaf48cbfe83cd", "rev": 11}, {"line": "foofoo\n", "node": null, "rev": null}],
563 "path": "foo"
565 "path": "foo"
564 }
566 }
565 ]
567 ]
566
568
567 annotate added file
569 annotate added file
568
570
569 $ echo bar > bar
571 $ echo bar > bar
570 $ hg add bar
572 $ hg add bar
571 $ hg annotate -ncr "wdir()" bar
573 $ hg annotate -ncr "wdir()" bar
572 20 b6bedd5477e7+: bar
574 20 b6bedd5477e7+: bar
573
575
574 annotate renamed file
576 annotate renamed file
575
577
576 $ hg rename foo renamefoo2
578 $ hg rename foo renamefoo2
577 $ hg annotate -ncr "wdir()" renamefoo2
579 $ hg annotate -ncr "wdir()" renamefoo2
578 11 472b18db256d : foo
580 11 472b18db256d : foo
579 20 b6bedd5477e7+: foofoo
581 20 b6bedd5477e7+: foofoo
580
582
581 annotate missing file
583 annotate missing file
582
584
583 $ rm baz
585 $ rm baz
584
586
585 $ hg annotate -ncr "wdir()" baz
587 $ hg annotate -ncr "wdir()" baz
586 abort: $TESTTMP\repo\baz: $ENOENT$ (windows !)
588 abort: $TESTTMP\repo\baz: $ENOENT$ (windows !)
587 abort: $ENOENT$: $TESTTMP/repo/baz (no-windows !)
589 abort: $ENOENT$: $TESTTMP/repo/baz (no-windows !)
588 [255]
590 [255]
589
591
590 annotate removed file
592 annotate removed file
591
593
592 $ hg rm baz
594 $ hg rm baz
593
595
594 $ hg annotate -ncr "wdir()" baz
596 $ hg annotate -ncr "wdir()" baz
595 abort: $TESTTMP\repo\baz: $ENOENT$ (windows !)
597 abort: $TESTTMP\repo\baz: $ENOENT$ (windows !)
596 abort: $ENOENT$: $TESTTMP/repo/baz (no-windows !)
598 abort: $ENOENT$: $TESTTMP/repo/baz (no-windows !)
597 [255]
599 [255]
598
600
599 $ hg revert --all --no-backup --quiet
601 $ hg revert --all --no-backup --quiet
600 $ hg id -n
602 $ hg id -n
601 20
603 20
602
604
603 Test followlines() revset; we usually check both followlines(pat, range) and
605 Test followlines() revset; we usually check both followlines(pat, range) and
604 followlines(pat, range, descend=True) to make sure both give the same result
606 followlines(pat, range, descend=True) to make sure both give the same result
605 when they should.
607 when they should.
606
608
607 $ echo a >> foo
609 $ echo a >> foo
608 $ hg ci -m 'foo: add a'
610 $ hg ci -m 'foo: add a'
609 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5)'
611 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5)'
610 16: baz:0
612 16: baz:0
611 19: baz:3
613 19: baz:3
612 20: baz:4
614 20: baz:4
613 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=20)'
615 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=20)'
614 16: baz:0
616 16: baz:0
615 19: baz:3
617 19: baz:3
616 20: baz:4
618 20: baz:4
617 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19)'
619 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19)'
618 16: baz:0
620 16: baz:0
619 19: baz:3
621 19: baz:3
620 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=True)'
622 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=True)'
621 19: baz:3
623 19: baz:3
622 20: baz:4
624 20: baz:4
623 $ printf "0\n0\n" | cat - baz > baz1
625 $ printf "0\n0\n" | cat - baz > baz1
624 $ mv baz1 baz
626 $ mv baz1 baz
625 $ hg ci -m 'added two lines with 0'
627 $ hg ci -m 'added two lines with 0'
626 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
628 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
627 16: baz:0
629 16: baz:0
628 19: baz:3
630 19: baz:3
629 20: baz:4
631 20: baz:4
630 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, descend=true, startrev=19)'
632 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, descend=true, startrev=19)'
631 19: baz:3
633 19: baz:3
632 20: baz:4
634 20: baz:4
633 $ echo 6 >> baz
635 $ echo 6 >> baz
634 $ hg ci -m 'added line 8'
636 $ hg ci -m 'added line 8'
635 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
637 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
636 16: baz:0
638 16: baz:0
637 19: baz:3
639 19: baz:3
638 20: baz:4
640 20: baz:4
639 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=1)'
641 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=1)'
640 19: baz:3
642 19: baz:3
641 20: baz:4
643 20: baz:4
642 $ sed 's/3/3+/' baz > baz.new
644 $ sed 's/3/3+/' baz > baz.new
643 $ mv baz.new baz
645 $ mv baz.new baz
644 $ hg ci -m 'baz:3->3+'
646 $ hg ci -m 'baz:3->3+'
645 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, descend=0)'
647 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, descend=0)'
646 16: baz:0
648 16: baz:0
647 19: baz:3
649 19: baz:3
648 20: baz:4
650 20: baz:4
649 24: baz:3->3+
651 24: baz:3->3+
650 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=17, descend=True)'
652 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=17, descend=True)'
651 19: baz:3
653 19: baz:3
652 20: baz:4
654 20: baz:4
653 24: baz:3->3+
655 24: baz:3->3+
654 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 1:2, descend=false)'
656 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 1:2, descend=false)'
655 22: added two lines with 0
657 22: added two lines with 0
656
658
657 file patterns are okay
659 file patterns are okay
658 $ hg log -T '{rev}: {desc}\n' -r 'followlines("path:baz", 1:2)'
660 $ hg log -T '{rev}: {desc}\n' -r 'followlines("path:baz", 1:2)'
659 22: added two lines with 0
661 22: added two lines with 0
660
662
661 renames are followed
663 renames are followed
662 $ hg mv baz qux
664 $ hg mv baz qux
663 $ sed 's/4/4+/' qux > qux.new
665 $ sed 's/4/4+/' qux > qux.new
664 $ mv qux.new qux
666 $ mv qux.new qux
665 $ hg ci -m 'qux:4->4+'
667 $ hg ci -m 'qux:4->4+'
666 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
668 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
667 16: baz:0
669 16: baz:0
668 19: baz:3
670 19: baz:3
669 20: baz:4
671 20: baz:4
670 24: baz:3->3+
672 24: baz:3->3+
671 25: qux:4->4+
673 25: qux:4->4+
672
674
673 but are missed when following children
675 but are missed when following children
674 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=22, descend=True)'
676 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=22, descend=True)'
675 24: baz:3->3+
677 24: baz:3->3+
676
678
677 merge
679 merge
678 $ hg up 24 --quiet
680 $ hg up 24 --quiet
679 $ echo 7 >> baz
681 $ echo 7 >> baz
680 $ hg ci -m 'one more line, out of line range'
682 $ hg ci -m 'one more line, out of line range'
681 created new head
683 created new head
682 $ sed 's/3+/3-/' baz > baz.new
684 $ sed 's/3+/3-/' baz > baz.new
683 $ mv baz.new baz
685 $ mv baz.new baz
684 $ hg ci -m 'baz:3+->3-'
686 $ hg ci -m 'baz:3+->3-'
685 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
687 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
686 16: baz:0
688 16: baz:0
687 19: baz:3
689 19: baz:3
688 20: baz:4
690 20: baz:4
689 24: baz:3->3+
691 24: baz:3->3+
690 27: baz:3+->3-
692 27: baz:3+->3-
691 $ hg merge 25
693 $ hg merge 25
692 merging baz and qux to qux
694 merging baz and qux to qux
693 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
695 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
694 (branch merge, don't forget to commit)
696 (branch merge, don't forget to commit)
695 $ hg ci -m merge
697 $ hg ci -m merge
696 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
698 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
697 16: baz:0
699 16: baz:0
698 19: baz:3
700 19: baz:3
699 20: baz:4
701 20: baz:4
700 24: baz:3->3+
702 24: baz:3->3+
701 25: qux:4->4+
703 25: qux:4->4+
702 27: baz:3+->3-
704 27: baz:3+->3-
703 28: merge
705 28: merge
704 $ hg up 25 --quiet
706 $ hg up 25 --quiet
705 $ hg merge 27
707 $ hg merge 27
706 merging qux and baz to qux
708 merging qux and baz to qux
707 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
709 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
708 (branch merge, don't forget to commit)
710 (branch merge, don't forget to commit)
709 $ hg ci -m 'merge from other side'
711 $ hg ci -m 'merge from other side'
710 created new head
712 created new head
711 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
713 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
712 16: baz:0
714 16: baz:0
713 19: baz:3
715 19: baz:3
714 20: baz:4
716 20: baz:4
715 24: baz:3->3+
717 24: baz:3->3+
716 25: qux:4->4+
718 25: qux:4->4+
717 27: baz:3+->3-
719 27: baz:3+->3-
718 29: merge from other side
720 29: merge from other side
719 $ hg up 24 --quiet
721 $ hg up 24 --quiet
720
722
721 we are missing the branch with rename when following children
723 we are missing the branch with rename when following children
722 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=26, descend=True)'
724 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=26, descend=True)'
723 27: baz:3+->3-
725 27: baz:3+->3-
724
726
725 we follow all branches in descending direction
727 we follow all branches in descending direction
726 $ hg up 23 --quiet
728 $ hg up 23 --quiet
727 $ sed 's/3/+3/' baz > baz.new
729 $ sed 's/3/+3/' baz > baz.new
728 $ mv baz.new baz
730 $ mv baz.new baz
729 $ hg ci -m 'baz:3->+3'
731 $ hg ci -m 'baz:3->+3'
730 created new head
732 created new head
731 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 2:5, startrev=16, descend=True)' --graph
733 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 2:5, startrev=16, descend=True)' --graph
732 @ 30: baz:3->+3
734 @ 30: baz:3->+3
733 :
735 :
734 : o 27: baz:3+->3-
736 : o 27: baz:3+->3-
735 : :
737 : :
736 : o 24: baz:3->3+
738 : o 24: baz:3->3+
737 :/
739 :/
738 o 20: baz:4
740 o 20: baz:4
739 |\
741 |\
740 | o 19: baz:3
742 | o 19: baz:3
741 |/
743 |/
742 o 18: baz:2
744 o 18: baz:2
743 :
745 :
744 o 16: baz:0
746 o 16: baz:0
745 |
747 |
746 ~
748 ~
747
749
748 Issue5595: on a merge changeset with different line ranges depending on
750 Issue5595: on a merge changeset with different line ranges depending on
749 parent, be conservative and use the surrounding interval to avoid loosing
751 parent, be conservative and use the surrounding interval to avoid loosing
750 track of possible further descendants in specified range.
752 track of possible further descendants in specified range.
751
753
752 $ hg up 23 --quiet
754 $ hg up 23 --quiet
753 $ hg cat baz -r 24
755 $ hg cat baz -r 24
754 0
756 0
755 0
757 0
756 1 baz:1
758 1 baz:1
757 2 baz:2
759 2 baz:2
758 3+ baz:3
760 3+ baz:3
759 4 baz:4
761 4 baz:4
760 5
762 5
761 6
763 6
762 $ cat > baz << EOF
764 $ cat > baz << EOF
763 > 0
765 > 0
764 > 0
766 > 0
765 > a
767 > a
766 > b
768 > b
767 > 3+ baz:3
769 > 3+ baz:3
768 > 4 baz:4
770 > 4 baz:4
769 > y
771 > y
770 > z
772 > z
771 > EOF
773 > EOF
772 $ hg ci -m 'baz: mostly rewrite with some content from 24'
774 $ hg ci -m 'baz: mostly rewrite with some content from 24'
773 created new head
775 created new head
774 $ hg merge --tool :merge-other 24
776 $ hg merge --tool :merge-other 24
775 merging baz
777 merging baz
776 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
778 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
777 (branch merge, don't forget to commit)
779 (branch merge, don't forget to commit)
778 $ hg ci -m 'merge forgetting about baz rewrite'
780 $ hg ci -m 'merge forgetting about baz rewrite'
779 $ cat > baz << EOF
781 $ cat > baz << EOF
780 > 0
782 > 0
781 > 0
783 > 0
782 > 1 baz:1
784 > 1 baz:1
783 > 2+ baz:2
785 > 2+ baz:2
784 > 3+ baz:3
786 > 3+ baz:3
785 > 4 baz:4
787 > 4 baz:4
786 > 5
788 > 5
787 > 6
789 > 6
788 > EOF
790 > EOF
789 $ hg ci -m 'baz: narrow change (2->2+)'
791 $ hg ci -m 'baz: narrow change (2->2+)'
790 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:4, startrev=20, descend=True)' --graph
792 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:4, startrev=20, descend=True)' --graph
791 @ 33: baz: narrow change (2->2+)
793 @ 33: baz: narrow change (2->2+)
792 |
794 |
793 o 32: merge forgetting about baz rewrite
795 o 32: merge forgetting about baz rewrite
794 |\
796 |\
795 | o 31: baz: mostly rewrite with some content from 24
797 | o 31: baz: mostly rewrite with some content from 24
796 | :
798 | :
797 | : o 30: baz:3->+3
799 | : o 30: baz:3->+3
798 | :/
800 | :/
799 +---o 27: baz:3+->3-
801 +---o 27: baz:3+->3-
800 | :
802 | :
801 o : 24: baz:3->3+
803 o : 24: baz:3->3+
802 :/
804 :/
803 o 20: baz:4
805 o 20: baz:4
804 |\
806 |\
805 ~ ~
807 ~ ~
806
808
807 check error cases
809 check error cases
808 $ hg up 24 --quiet
810 $ hg up 24 --quiet
809 $ hg log -r 'followlines()'
811 $ hg log -r 'followlines()'
810 hg: parse error: followlines takes at least 1 positional arguments
812 hg: parse error: followlines takes at least 1 positional arguments
811 [255]
813 [255]
812 $ hg log -r 'followlines(baz)'
814 $ hg log -r 'followlines(baz)'
813 hg: parse error: followlines requires a line range
815 hg: parse error: followlines requires a line range
814 [255]
816 [255]
815 $ hg log -r 'followlines(baz, 1)'
817 $ hg log -r 'followlines(baz, 1)'
816 hg: parse error: followlines expects a line range
818 hg: parse error: followlines expects a line range
817 [255]
819 [255]
818 $ hg log -r 'followlines(baz, 1:2, startrev=desc("b"))'
820 $ hg log -r 'followlines(baz, 1:2, startrev=desc("b"))'
819 hg: parse error: followlines expects exactly one revision
821 hg: parse error: followlines expects exactly one revision
820 [255]
822 [255]
821 $ hg log -r 'followlines("glob:*", 1:2)'
823 $ hg log -r 'followlines("glob:*", 1:2)'
822 hg: parse error: followlines expects exactly one file
824 hg: parse error: followlines expects exactly one file
823 [255]
825 [255]
824 $ hg log -r 'followlines(baz, 1:)'
826 $ hg log -r 'followlines(baz, 1:)'
825 hg: parse error: line range bounds must be integers
827 hg: parse error: line range bounds must be integers
826 [255]
828 [255]
827 $ hg log -r 'followlines(baz, :1)'
829 $ hg log -r 'followlines(baz, :1)'
828 hg: parse error: line range bounds must be integers
830 hg: parse error: line range bounds must be integers
829 [255]
831 [255]
830 $ hg log -r 'followlines(baz, x:4)'
832 $ hg log -r 'followlines(baz, x:4)'
831 hg: parse error: line range bounds must be integers
833 hg: parse error: line range bounds must be integers
832 [255]
834 [255]
833 $ hg log -r 'followlines(baz, 5:4)'
835 $ hg log -r 'followlines(baz, 5:4)'
834 hg: parse error: line range must be positive
836 hg: parse error: line range must be positive
835 [255]
837 [255]
836 $ hg log -r 'followlines(baz, 0:4)'
838 $ hg log -r 'followlines(baz, 0:4)'
837 hg: parse error: fromline must be strictly positive
839 hg: parse error: fromline must be strictly positive
838 [255]
840 [255]
839 $ hg log -r 'followlines(baz, 2:40)'
841 $ hg log -r 'followlines(baz, 2:40)'
840 abort: line range exceeds file size
842 abort: line range exceeds file size
841 [255]
843 [255]
842 $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=[1])'
844 $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=[1])'
843 hg: parse error at 43: not a prefix: [
845 hg: parse error at 43: not a prefix: [
844 (followlines(baz, 2:4, startrev=20, descend=[1])
846 (followlines(baz, 2:4, startrev=20, descend=[1])
845 ^ here)
847 ^ here)
846 [255]
848 [255]
847 $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=a)'
849 $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=a)'
848 hg: parse error: descend argument must be a boolean
850 hg: parse error: descend argument must be a boolean
849 [255]
851 [255]
850
852
851 Test empty annotate output
853 Test empty annotate output
852
854
853 $ printf '\0' > binary
855 $ printf '\0' > binary
854 $ touch empty
856 $ touch empty
855 $ hg ci -qAm 'add binary and empty files'
857 $ hg ci -qAm 'add binary and empty files'
856
858
857 $ hg annotate binary empty
859 $ hg annotate binary empty
858 binary: binary file
860 binary: binary file
859
861
860 $ hg annotate -Tjson binary empty
862 $ hg annotate -Tjson binary empty
861 [
863 [
862 {
864 {
863 "abspath": "binary",
865 "abspath": "binary",
864 "path": "binary"
866 "path": "binary"
865 },
867 },
866 {
868 {
867 "abspath": "empty",
869 "abspath": "empty",
868 "lines": [],
870 "lines": [],
869 "path": "empty"
871 "path": "empty"
870 }
872 }
871 ]
873 ]
872
874
873 Test annotate with whitespace options
875 Test annotate with whitespace options
874
876
875 $ cd ..
877 $ cd ..
876 $ hg init repo-ws
878 $ hg init repo-ws
877 $ cd repo-ws
879 $ cd repo-ws
878 $ cat > a <<EOF
880 $ cat > a <<EOF
879 > aa
881 > aa
880 >
882 >
881 > b b
883 > b b
882 > EOF
884 > EOF
883 $ hg ci -Am "adda"
885 $ hg ci -Am "adda"
884 adding a
886 adding a
885 $ sed 's/EOL$//g' > a <<EOF
887 $ sed 's/EOL$//g' > a <<EOF
886 > a a
888 > a a
887 >
889 >
888 > EOL
890 > EOL
889 > b b
891 > b b
890 > EOF
892 > EOF
891 $ hg ci -m "changea"
893 $ hg ci -m "changea"
892
894
893 Annotate with no option
895 Annotate with no option
894
896
895 $ hg annotate a
897 $ hg annotate a
896 1: a a
898 1: a a
897 0:
899 0:
898 1:
900 1:
899 1: b b
901 1: b b
900
902
901 Annotate with --ignore-space-change
903 Annotate with --ignore-space-change
902
904
903 $ hg annotate --ignore-space-change a
905 $ hg annotate --ignore-space-change a
904 1: a a
906 1: a a
905 1:
907 1:
906 0:
908 0:
907 0: b b
909 0: b b
908
910
909 Annotate with --ignore-all-space
911 Annotate with --ignore-all-space
910
912
911 $ hg annotate --ignore-all-space a
913 $ hg annotate --ignore-all-space a
912 0: a a
914 0: a a
913 0:
915 0:
914 1:
916 1:
915 0: b b
917 0: b b
916
918
917 Annotate with --ignore-blank-lines (similar to no options case)
919 Annotate with --ignore-blank-lines (similar to no options case)
918
920
919 $ hg annotate --ignore-blank-lines a
921 $ hg annotate --ignore-blank-lines a
920 1: a a
922 1: a a
921 0:
923 0:
922 1:
924 1:
923 1: b b
925 1: b b
924
926
925 $ cd ..
927 $ cd ..
926
928
927 Annotate with orphaned CR (issue5798)
929 Annotate with orphaned CR (issue5798)
928 -------------------------------------
930 -------------------------------------
929
931
930 $ hg init repo-cr
932 $ hg init repo-cr
931 $ cd repo-cr
933 $ cd repo-cr
932
934
933 $ cat <<'EOF' >> "$TESTTMP/substcr.py"
935 $ cat <<'EOF' >> "$TESTTMP/substcr.py"
934 > import sys
936 > import sys
935 > from mercurial.utils import procutil
937 > from mercurial.utils import procutil
936 > procutil.setbinary(sys.stdin)
938 > procutil.setbinary(sys.stdin)
937 > procutil.setbinary(sys.stdout)
939 > procutil.setbinary(sys.stdout)
938 > stdin = getattr(sys.stdin, 'buffer', sys.stdin)
940 > stdin = getattr(sys.stdin, 'buffer', sys.stdin)
939 > stdout = getattr(sys.stdout, 'buffer', sys.stdout)
941 > stdout = getattr(sys.stdout, 'buffer', sys.stdout)
940 > stdout.write(stdin.read().replace(b'\r', b'[CR]'))
942 > stdout.write(stdin.read().replace(b'\r', b'[CR]'))
941 > EOF
943 > EOF
942
944
943 >>> with open('a', 'wb') as f:
945 >>> with open('a', 'wb') as f:
944 ... f.write(b'0a\r0b\r\n0c\r0d\r\n0e\n0f\n0g') and None
946 ... f.write(b'0a\r0b\r\n0c\r0d\r\n0e\n0f\n0g') and None
945 $ hg ci -qAm0
947 $ hg ci -qAm0
946 >>> with open('a', 'wb') as f:
948 >>> with open('a', 'wb') as f:
947 ... f.write(b'0a\r0b\r\n1c\r1d\r\n0e\n1f\n0g') and None
949 ... f.write(b'0a\r0b\r\n1c\r1d\r\n0e\n1f\n0g') and None
948 $ hg ci -m1
950 $ hg ci -m1
949
951
950 $ hg annotate -r0 a | $PYTHON "$TESTTMP/substcr.py"
952 $ hg annotate -r0 a | $PYTHON "$TESTTMP/substcr.py"
951 0: 0a[CR]0b[CR]
953 0: 0a[CR]0b[CR]
952 0: 0c[CR]0d[CR]
954 0: 0c[CR]0d[CR]
953 0: 0e
955 0: 0e
954 0: 0f
956 0: 0f
955 0: 0g
957 0: 0g
956 $ hg annotate -r1 a | $PYTHON "$TESTTMP/substcr.py"
958 $ hg annotate -r1 a | $PYTHON "$TESTTMP/substcr.py"
957 0: 0a[CR]0b[CR]
959 0: 0a[CR]0b[CR]
958 1: 1c[CR]1d[CR]
960 1: 1c[CR]1d[CR]
959 0: 0e
961 0: 0e
960 1: 1f
962 1: 1f
961 0: 0g
963 0: 0g
962
964
963 $ cd ..
965 $ cd ..
964
966
965 Annotate with linkrev pointing to another branch
967 Annotate with linkrev pointing to another branch
966 ------------------------------------------------
968 ------------------------------------------------
967
969
968 create history with a filerev whose linkrev points to another branch
970 create history with a filerev whose linkrev points to another branch
969
971
970 $ hg init branchedlinkrev
972 $ hg init branchedlinkrev
971 $ cd branchedlinkrev
973 $ cd branchedlinkrev
972 $ echo A > a
974 $ echo A > a
973 $ hg commit -Am 'contentA'
975 $ hg commit -Am 'contentA'
974 adding a
976 adding a
975 $ echo B >> a
977 $ echo B >> a
976 $ hg commit -m 'contentB'
978 $ hg commit -m 'contentB'
977 $ hg up --rev 'desc(contentA)'
979 $ hg up --rev 'desc(contentA)'
978 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
980 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
979 $ echo unrelated > unrelated
981 $ echo unrelated > unrelated
980 $ hg commit -Am 'unrelated'
982 $ hg commit -Am 'unrelated'
981 adding unrelated
983 adding unrelated
982 created new head
984 created new head
983 $ hg graft -r 'desc(contentB)'
985 $ hg graft -r 'desc(contentB)'
984 grafting 1:fd27c222e3e6 "contentB"
986 grafting 1:fd27c222e3e6 "contentB"
985 $ echo C >> a
987 $ echo C >> a
986 $ hg commit -m 'contentC'
988 $ hg commit -m 'contentC'
987 $ echo W >> a
989 $ echo W >> a
988 $ hg log -G
990 $ hg log -G
989 @ changeset: 4:072f1e8df249
991 @ changeset: 4:072f1e8df249
990 | tag: tip
992 | tag: tip
991 | user: test
993 | user: test
992 | date: Thu Jan 01 00:00:00 1970 +0000
994 | date: Thu Jan 01 00:00:00 1970 +0000
993 | summary: contentC
995 | summary: contentC
994 |
996 |
995 o changeset: 3:ff38df03cc4b
997 o changeset: 3:ff38df03cc4b
996 | user: test
998 | user: test
997 | date: Thu Jan 01 00:00:00 1970 +0000
999 | date: Thu Jan 01 00:00:00 1970 +0000
998 | summary: contentB
1000 | summary: contentB
999 |
1001 |
1000 o changeset: 2:62aaf3f6fc06
1002 o changeset: 2:62aaf3f6fc06
1001 | parent: 0:f0932f74827e
1003 | parent: 0:f0932f74827e
1002 | user: test
1004 | user: test
1003 | date: Thu Jan 01 00:00:00 1970 +0000
1005 | date: Thu Jan 01 00:00:00 1970 +0000
1004 | summary: unrelated
1006 | summary: unrelated
1005 |
1007 |
1006 | o changeset: 1:fd27c222e3e6
1008 | o changeset: 1:fd27c222e3e6
1007 |/ user: test
1009 |/ user: test
1008 | date: Thu Jan 01 00:00:00 1970 +0000
1010 | date: Thu Jan 01 00:00:00 1970 +0000
1009 | summary: contentB
1011 | summary: contentB
1010 |
1012 |
1011 o changeset: 0:f0932f74827e
1013 o changeset: 0:f0932f74827e
1012 user: test
1014 user: test
1013 date: Thu Jan 01 00:00:00 1970 +0000
1015 date: Thu Jan 01 00:00:00 1970 +0000
1014 summary: contentA
1016 summary: contentA
1015
1017
1016
1018
1017 Annotate should list ancestor of starting revision only
1019 Annotate should list ancestor of starting revision only
1018
1020
1019 $ hg annotate a
1021 $ hg annotate a
1020 0: A
1022 0: A
1021 3: B
1023 3: B
1022 4: C
1024 4: C
1023
1025
1024 $ hg annotate a -r 'wdir()'
1026 $ hg annotate a -r 'wdir()'
1025 0 : A
1027 0 : A
1026 3 : B
1028 3 : B
1027 4 : C
1029 4 : C
1028 4+: W
1030 4+: W
1029
1031
1030 Even when the starting revision is the linkrev-shadowed one:
1032 Even when the starting revision is the linkrev-shadowed one:
1031
1033
1032 $ hg annotate a -r 3
1034 $ hg annotate a -r 3
1033 0: A
1035 0: A
1034 3: B
1036 3: B
1035
1037
1036 $ cd ..
1038 $ cd ..
1037
1039
1038 Issue5360: Deleted chunk in p1 of a merge changeset
1040 Issue5360: Deleted chunk in p1 of a merge changeset
1039
1041
1040 $ hg init repo-5360
1042 $ hg init repo-5360
1041 $ cd repo-5360
1043 $ cd repo-5360
1042 $ echo 1 > a
1044 $ echo 1 > a
1043 $ hg commit -A a -m 1
1045 $ hg commit -A a -m 1
1044 $ echo 2 >> a
1046 $ echo 2 >> a
1045 $ hg commit -m 2
1047 $ hg commit -m 2
1046 $ echo a > a
1048 $ echo a > a
1047 $ hg commit -m a
1049 $ hg commit -m a
1048 $ hg update '.^' -q
1050 $ hg update '.^' -q
1049 $ echo 3 >> a
1051 $ echo 3 >> a
1050 $ hg commit -m 3 -q
1052 $ hg commit -m 3 -q
1051 $ hg merge 2 -q
1053 $ hg merge 2 -q
1052 $ cat > a << EOF
1054 $ cat > a << EOF
1053 > b
1055 > b
1054 > 1
1056 > 1
1055 > 2
1057 > 2
1056 > 3
1058 > 3
1057 > a
1059 > a
1058 > EOF
1060 > EOF
1059 $ hg resolve --mark -q
1061 $ hg resolve --mark -q
1060 $ hg commit -m m
1062 $ hg commit -m m
1061 $ hg annotate a
1063 $ hg annotate a
1062 4: b
1064 4: b
1063 0: 1
1065 0: 1
1064 1: 2
1066 1: 2
1065 3: 3
1067 3: 3
1066 2: a
1068 2: a
1067
1069
1068 $ cd ..
1070 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now