##// END OF EJS Templates
date: refactor timezone parsing...
Matt Mackall -
r29636:84ef4517 stable
parent child Browse files
Show More
@@ -1,1161 +1,1163 b''
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
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 os
10 import os
11 import re
11 import re
12 import types
12 import types
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 config,
16 config,
17 error,
17 error,
18 minirst,
18 minirst,
19 parser,
19 parser,
20 registrar,
20 registrar,
21 revset as revsetmod,
21 revset as revsetmod,
22 templatefilters,
22 templatefilters,
23 templatekw,
23 templatekw,
24 util,
24 util,
25 )
25 )
26
26
27 # template parsing
27 # template parsing
28
28
29 elements = {
29 elements = {
30 # token-type: binding-strength, primary, prefix, infix, suffix
30 # token-type: binding-strength, primary, prefix, infix, suffix
31 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
31 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
32 ",": (2, None, None, ("list", 2), None),
32 ",": (2, None, None, ("list", 2), None),
33 "|": (5, None, None, ("|", 5), None),
33 "|": (5, None, None, ("|", 5), None),
34 "%": (6, None, None, ("%", 6), None),
34 "%": (6, None, None, ("%", 6), None),
35 ")": (0, None, None, None, None),
35 ")": (0, None, None, None, None),
36 "integer": (0, "integer", None, None, None),
36 "integer": (0, "integer", None, None, None),
37 "symbol": (0, "symbol", None, None, None),
37 "symbol": (0, "symbol", None, None, None),
38 "string": (0, "string", None, None, None),
38 "string": (0, "string", None, None, None),
39 "template": (0, "template", None, None, None),
39 "template": (0, "template", None, None, None),
40 "end": (0, None, None, None, None),
40 "end": (0, None, None, None, None),
41 }
41 }
42
42
43 def tokenize(program, start, end, term=None):
43 def tokenize(program, start, end, term=None):
44 """Parse a template expression into a stream of tokens, which must end
44 """Parse a template expression into a stream of tokens, which must end
45 with term if specified"""
45 with term if specified"""
46 pos = start
46 pos = start
47 while pos < end:
47 while pos < end:
48 c = program[pos]
48 c = program[pos]
49 if c.isspace(): # skip inter-token whitespace
49 if c.isspace(): # skip inter-token whitespace
50 pass
50 pass
51 elif c in "(,)%|": # handle simple operators
51 elif c in "(,)%|": # handle simple operators
52 yield (c, None, pos)
52 yield (c, None, pos)
53 elif c in '"\'': # handle quoted templates
53 elif c in '"\'': # handle quoted templates
54 s = pos + 1
54 s = pos + 1
55 data, pos = _parsetemplate(program, s, end, c)
55 data, pos = _parsetemplate(program, s, end, c)
56 yield ('template', data, s)
56 yield ('template', data, s)
57 pos -= 1
57 pos -= 1
58 elif c == 'r' and program[pos:pos + 2] in ("r'", 'r"'):
58 elif c == 'r' and program[pos:pos + 2] in ("r'", 'r"'):
59 # handle quoted strings
59 # handle quoted strings
60 c = program[pos + 1]
60 c = program[pos + 1]
61 s = pos = pos + 2
61 s = pos = pos + 2
62 while pos < end: # find closing quote
62 while pos < end: # find closing quote
63 d = program[pos]
63 d = program[pos]
64 if d == '\\': # skip over escaped characters
64 if d == '\\': # skip over escaped characters
65 pos += 2
65 pos += 2
66 continue
66 continue
67 if d == c:
67 if d == c:
68 yield ('string', program[s:pos], s)
68 yield ('string', program[s:pos], s)
69 break
69 break
70 pos += 1
70 pos += 1
71 else:
71 else:
72 raise error.ParseError(_("unterminated string"), s)
72 raise error.ParseError(_("unterminated string"), s)
73 elif c.isdigit() or c == '-':
73 elif c.isdigit() or c == '-':
74 s = pos
74 s = pos
75 if c == '-': # simply take negate operator as part of integer
75 if c == '-': # simply take negate operator as part of integer
76 pos += 1
76 pos += 1
77 if pos >= end or not program[pos].isdigit():
77 if pos >= end or not program[pos].isdigit():
78 raise error.ParseError(_("integer literal without digits"), s)
78 raise error.ParseError(_("integer literal without digits"), s)
79 pos += 1
79 pos += 1
80 while pos < end:
80 while pos < end:
81 d = program[pos]
81 d = program[pos]
82 if not d.isdigit():
82 if not d.isdigit():
83 break
83 break
84 pos += 1
84 pos += 1
85 yield ('integer', program[s:pos], s)
85 yield ('integer', program[s:pos], s)
86 pos -= 1
86 pos -= 1
87 elif (c == '\\' and program[pos:pos + 2] in (r"\'", r'\"')
87 elif (c == '\\' and program[pos:pos + 2] in (r"\'", r'\"')
88 or c == 'r' and program[pos:pos + 3] in (r"r\'", r'r\"')):
88 or c == 'r' and program[pos:pos + 3] in (r"r\'", r'r\"')):
89 # handle escaped quoted strings for compatibility with 2.9.2-3.4,
89 # handle escaped quoted strings for compatibility with 2.9.2-3.4,
90 # where some of nested templates were preprocessed as strings and
90 # where some of nested templates were preprocessed as strings and
91 # then compiled. therefore, \"...\" was allowed. (issue4733)
91 # then compiled. therefore, \"...\" was allowed. (issue4733)
92 #
92 #
93 # processing flow of _evalifliteral() at 5ab28a2e9962:
93 # processing flow of _evalifliteral() at 5ab28a2e9962:
94 # outer template string -> stringify() -> compiletemplate()
94 # outer template string -> stringify() -> compiletemplate()
95 # ------------------------ ------------ ------------------
95 # ------------------------ ------------ ------------------
96 # {f("\\\\ {g(\"\\\"\")}"} \\ {g("\"")} [r'\\', {g("\"")}]
96 # {f("\\\\ {g(\"\\\"\")}"} \\ {g("\"")} [r'\\', {g("\"")}]
97 # ~~~~~~~~
97 # ~~~~~~~~
98 # escaped quoted string
98 # escaped quoted string
99 if c == 'r':
99 if c == 'r':
100 pos += 1
100 pos += 1
101 token = 'string'
101 token = 'string'
102 else:
102 else:
103 token = 'template'
103 token = 'template'
104 quote = program[pos:pos + 2]
104 quote = program[pos:pos + 2]
105 s = pos = pos + 2
105 s = pos = pos + 2
106 while pos < end: # find closing escaped quote
106 while pos < end: # find closing escaped quote
107 if program.startswith('\\\\\\', pos, end):
107 if program.startswith('\\\\\\', pos, end):
108 pos += 4 # skip over double escaped characters
108 pos += 4 # skip over double escaped characters
109 continue
109 continue
110 if program.startswith(quote, pos, end):
110 if program.startswith(quote, pos, end):
111 # interpret as if it were a part of an outer string
111 # interpret as if it were a part of an outer string
112 data = parser.unescapestr(program[s:pos])
112 data = parser.unescapestr(program[s:pos])
113 if token == 'template':
113 if token == 'template':
114 data = _parsetemplate(data, 0, len(data))[0]
114 data = _parsetemplate(data, 0, len(data))[0]
115 yield (token, data, s)
115 yield (token, data, s)
116 pos += 1
116 pos += 1
117 break
117 break
118 pos += 1
118 pos += 1
119 else:
119 else:
120 raise error.ParseError(_("unterminated string"), s)
120 raise error.ParseError(_("unterminated string"), s)
121 elif c.isalnum() or c in '_':
121 elif c.isalnum() or c in '_':
122 s = pos
122 s = pos
123 pos += 1
123 pos += 1
124 while pos < end: # find end of symbol
124 while pos < end: # find end of symbol
125 d = program[pos]
125 d = program[pos]
126 if not (d.isalnum() or d == "_"):
126 if not (d.isalnum() or d == "_"):
127 break
127 break
128 pos += 1
128 pos += 1
129 sym = program[s:pos]
129 sym = program[s:pos]
130 yield ('symbol', sym, s)
130 yield ('symbol', sym, s)
131 pos -= 1
131 pos -= 1
132 elif c == term:
132 elif c == term:
133 yield ('end', None, pos + 1)
133 yield ('end', None, pos + 1)
134 return
134 return
135 else:
135 else:
136 raise error.ParseError(_("syntax error"), pos)
136 raise error.ParseError(_("syntax error"), pos)
137 pos += 1
137 pos += 1
138 if term:
138 if term:
139 raise error.ParseError(_("unterminated template expansion"), start)
139 raise error.ParseError(_("unterminated template expansion"), start)
140 yield ('end', None, pos)
140 yield ('end', None, pos)
141
141
142 def _parsetemplate(tmpl, start, stop, quote=''):
142 def _parsetemplate(tmpl, start, stop, quote=''):
143 r"""
143 r"""
144 >>> _parsetemplate('foo{bar}"baz', 0, 12)
144 >>> _parsetemplate('foo{bar}"baz', 0, 12)
145 ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
145 ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
146 >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"')
146 >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"')
147 ([('string', 'foo'), ('symbol', 'bar')], 9)
147 ([('string', 'foo'), ('symbol', 'bar')], 9)
148 >>> _parsetemplate('foo"{bar}', 0, 9, quote='"')
148 >>> _parsetemplate('foo"{bar}', 0, 9, quote='"')
149 ([('string', 'foo')], 4)
149 ([('string', 'foo')], 4)
150 >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"')
150 >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"')
151 ([('string', 'foo"'), ('string', 'bar')], 9)
151 ([('string', 'foo"'), ('string', 'bar')], 9)
152 >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"')
152 >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"')
153 ([('string', 'foo\\')], 6)
153 ([('string', 'foo\\')], 6)
154 """
154 """
155 parsed = []
155 parsed = []
156 sepchars = '{' + quote
156 sepchars = '{' + quote
157 pos = start
157 pos = start
158 p = parser.parser(elements)
158 p = parser.parser(elements)
159 while pos < stop:
159 while pos < stop:
160 n = min((tmpl.find(c, pos, stop) for c in sepchars),
160 n = min((tmpl.find(c, pos, stop) for c in sepchars),
161 key=lambda n: (n < 0, n))
161 key=lambda n: (n < 0, n))
162 if n < 0:
162 if n < 0:
163 parsed.append(('string', parser.unescapestr(tmpl[pos:stop])))
163 parsed.append(('string', parser.unescapestr(tmpl[pos:stop])))
164 pos = stop
164 pos = stop
165 break
165 break
166 c = tmpl[n]
166 c = tmpl[n]
167 bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
167 bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
168 if bs % 2 == 1:
168 if bs % 2 == 1:
169 # escaped (e.g. '\{', '\\\{', but not '\\{')
169 # escaped (e.g. '\{', '\\\{', but not '\\{')
170 parsed.append(('string', parser.unescapestr(tmpl[pos:n - 1]) + c))
170 parsed.append(('string', parser.unescapestr(tmpl[pos:n - 1]) + c))
171 pos = n + 1
171 pos = n + 1
172 continue
172 continue
173 if n > pos:
173 if n > pos:
174 parsed.append(('string', parser.unescapestr(tmpl[pos:n])))
174 parsed.append(('string', parser.unescapestr(tmpl[pos:n])))
175 if c == quote:
175 if c == quote:
176 return parsed, n + 1
176 return parsed, n + 1
177
177
178 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
178 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
179 parsed.append(parseres)
179 parsed.append(parseres)
180
180
181 if quote:
181 if quote:
182 raise error.ParseError(_("unterminated string"), start)
182 raise error.ParseError(_("unterminated string"), start)
183 return parsed, pos
183 return parsed, pos
184
184
185 def _unnesttemplatelist(tree):
185 def _unnesttemplatelist(tree):
186 """Expand list of templates to node tuple
186 """Expand list of templates to node tuple
187
187
188 >>> def f(tree):
188 >>> def f(tree):
189 ... print prettyformat(_unnesttemplatelist(tree))
189 ... print prettyformat(_unnesttemplatelist(tree))
190 >>> f(('template', []))
190 >>> f(('template', []))
191 ('string', '')
191 ('string', '')
192 >>> f(('template', [('string', 'foo')]))
192 >>> f(('template', [('string', 'foo')]))
193 ('string', 'foo')
193 ('string', 'foo')
194 >>> f(('template', [('string', 'foo'), ('symbol', 'rev')]))
194 >>> f(('template', [('string', 'foo'), ('symbol', 'rev')]))
195 (template
195 (template
196 ('string', 'foo')
196 ('string', 'foo')
197 ('symbol', 'rev'))
197 ('symbol', 'rev'))
198 >>> f(('template', [('symbol', 'rev')])) # template(rev) -> str
198 >>> f(('template', [('symbol', 'rev')])) # template(rev) -> str
199 (template
199 (template
200 ('symbol', 'rev'))
200 ('symbol', 'rev'))
201 >>> f(('template', [('template', [('string', 'foo')])]))
201 >>> f(('template', [('template', [('string', 'foo')])]))
202 ('string', 'foo')
202 ('string', 'foo')
203 """
203 """
204 if not isinstance(tree, tuple):
204 if not isinstance(tree, tuple):
205 return tree
205 return tree
206 op = tree[0]
206 op = tree[0]
207 if op != 'template':
207 if op != 'template':
208 return (op,) + tuple(_unnesttemplatelist(x) for x in tree[1:])
208 return (op,) + tuple(_unnesttemplatelist(x) for x in tree[1:])
209
209
210 assert len(tree) == 2
210 assert len(tree) == 2
211 xs = tuple(_unnesttemplatelist(x) for x in tree[1])
211 xs = tuple(_unnesttemplatelist(x) for x in tree[1])
212 if not xs:
212 if not xs:
213 return ('string', '') # empty template ""
213 return ('string', '') # empty template ""
214 elif len(xs) == 1 and xs[0][0] == 'string':
214 elif len(xs) == 1 and xs[0][0] == 'string':
215 return xs[0] # fast path for string with no template fragment "x"
215 return xs[0] # fast path for string with no template fragment "x"
216 else:
216 else:
217 return (op,) + xs
217 return (op,) + xs
218
218
219 def parse(tmpl):
219 def parse(tmpl):
220 """Parse template string into tree"""
220 """Parse template string into tree"""
221 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl))
221 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl))
222 assert pos == len(tmpl), 'unquoted template should be consumed'
222 assert pos == len(tmpl), 'unquoted template should be consumed'
223 return _unnesttemplatelist(('template', parsed))
223 return _unnesttemplatelist(('template', parsed))
224
224
225 def _parseexpr(expr):
225 def _parseexpr(expr):
226 """Parse a template expression into tree
226 """Parse a template expression into tree
227
227
228 >>> _parseexpr('"foo"')
228 >>> _parseexpr('"foo"')
229 ('string', 'foo')
229 ('string', 'foo')
230 >>> _parseexpr('foo(bar)')
230 >>> _parseexpr('foo(bar)')
231 ('func', ('symbol', 'foo'), ('symbol', 'bar'))
231 ('func', ('symbol', 'foo'), ('symbol', 'bar'))
232 >>> _parseexpr('foo(')
232 >>> _parseexpr('foo(')
233 Traceback (most recent call last):
233 Traceback (most recent call last):
234 ...
234 ...
235 ParseError: ('not a prefix: end', 4)
235 ParseError: ('not a prefix: end', 4)
236 >>> _parseexpr('"foo" "bar"')
236 >>> _parseexpr('"foo" "bar"')
237 Traceback (most recent call last):
237 Traceback (most recent call last):
238 ...
238 ...
239 ParseError: ('invalid token', 7)
239 ParseError: ('invalid token', 7)
240 """
240 """
241 p = parser.parser(elements)
241 p = parser.parser(elements)
242 tree, pos = p.parse(tokenize(expr, 0, len(expr)))
242 tree, pos = p.parse(tokenize(expr, 0, len(expr)))
243 if pos != len(expr):
243 if pos != len(expr):
244 raise error.ParseError(_('invalid token'), pos)
244 raise error.ParseError(_('invalid token'), pos)
245 return _unnesttemplatelist(tree)
245 return _unnesttemplatelist(tree)
246
246
247 def prettyformat(tree):
247 def prettyformat(tree):
248 return parser.prettyformat(tree, ('integer', 'string', 'symbol'))
248 return parser.prettyformat(tree, ('integer', 'string', 'symbol'))
249
249
250 def compileexp(exp, context, curmethods):
250 def compileexp(exp, context, curmethods):
251 """Compile parsed template tree to (func, data) pair"""
251 """Compile parsed template tree to (func, data) pair"""
252 t = exp[0]
252 t = exp[0]
253 if t in curmethods:
253 if t in curmethods:
254 return curmethods[t](exp, context)
254 return curmethods[t](exp, context)
255 raise error.ParseError(_("unknown method '%s'") % t)
255 raise error.ParseError(_("unknown method '%s'") % t)
256
256
257 # template evaluation
257 # template evaluation
258
258
259 def getsymbol(exp):
259 def getsymbol(exp):
260 if exp[0] == 'symbol':
260 if exp[0] == 'symbol':
261 return exp[1]
261 return exp[1]
262 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
262 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
263
263
264 def getlist(x):
264 def getlist(x):
265 if not x:
265 if not x:
266 return []
266 return []
267 if x[0] == 'list':
267 if x[0] == 'list':
268 return getlist(x[1]) + [x[2]]
268 return getlist(x[1]) + [x[2]]
269 return [x]
269 return [x]
270
270
271 def gettemplate(exp, context):
271 def gettemplate(exp, context):
272 """Compile given template tree or load named template from map file;
272 """Compile given template tree or load named template from map file;
273 returns (func, data) pair"""
273 returns (func, data) pair"""
274 if exp[0] in ('template', 'string'):
274 if exp[0] in ('template', 'string'):
275 return compileexp(exp, context, methods)
275 return compileexp(exp, context, methods)
276 if exp[0] == 'symbol':
276 if exp[0] == 'symbol':
277 # unlike runsymbol(), here 'symbol' is always taken as template name
277 # unlike runsymbol(), here 'symbol' is always taken as template name
278 # even if it exists in mapping. this allows us to override mapping
278 # even if it exists in mapping. this allows us to override mapping
279 # by web templates, e.g. 'changelogtag' is redefined in map file.
279 # by web templates, e.g. 'changelogtag' is redefined in map file.
280 return context._load(exp[1])
280 return context._load(exp[1])
281 raise error.ParseError(_("expected template specifier"))
281 raise error.ParseError(_("expected template specifier"))
282
282
283 def evalfuncarg(context, mapping, arg):
283 def evalfuncarg(context, mapping, arg):
284 func, data = arg
284 func, data = arg
285 # func() may return string, generator of strings or arbitrary object such
285 # func() may return string, generator of strings or arbitrary object such
286 # as date tuple, but filter does not want generator.
286 # as date tuple, but filter does not want generator.
287 thing = func(context, mapping, data)
287 thing = func(context, mapping, data)
288 if isinstance(thing, types.GeneratorType):
288 if isinstance(thing, types.GeneratorType):
289 thing = stringify(thing)
289 thing = stringify(thing)
290 return thing
290 return thing
291
291
292 def evalinteger(context, mapping, arg, err):
292 def evalinteger(context, mapping, arg, err):
293 v = evalfuncarg(context, mapping, arg)
293 v = evalfuncarg(context, mapping, arg)
294 try:
294 try:
295 return int(v)
295 return int(v)
296 except (TypeError, ValueError):
296 except (TypeError, ValueError):
297 raise error.ParseError(err)
297 raise error.ParseError(err)
298
298
299 def evalstring(context, mapping, arg):
299 def evalstring(context, mapping, arg):
300 func, data = arg
300 func, data = arg
301 return stringify(func(context, mapping, data))
301 return stringify(func(context, mapping, data))
302
302
303 def evalstringliteral(context, mapping, arg):
303 def evalstringliteral(context, mapping, arg):
304 """Evaluate given argument as string template, but returns symbol name
304 """Evaluate given argument as string template, but returns symbol name
305 if it is unknown"""
305 if it is unknown"""
306 func, data = arg
306 func, data = arg
307 if func is runsymbol:
307 if func is runsymbol:
308 thing = func(context, mapping, data, default=data)
308 thing = func(context, mapping, data, default=data)
309 else:
309 else:
310 thing = func(context, mapping, data)
310 thing = func(context, mapping, data)
311 return stringify(thing)
311 return stringify(thing)
312
312
313 def runinteger(context, mapping, data):
313 def runinteger(context, mapping, data):
314 return int(data)
314 return int(data)
315
315
316 def runstring(context, mapping, data):
316 def runstring(context, mapping, data):
317 return data
317 return data
318
318
319 def _recursivesymbolblocker(key):
319 def _recursivesymbolblocker(key):
320 def showrecursion(**args):
320 def showrecursion(**args):
321 raise error.Abort(_("recursive reference '%s' in template") % key)
321 raise error.Abort(_("recursive reference '%s' in template") % key)
322 return showrecursion
322 return showrecursion
323
323
324 def _runrecursivesymbol(context, mapping, key):
324 def _runrecursivesymbol(context, mapping, key):
325 raise error.Abort(_("recursive reference '%s' in template") % key)
325 raise error.Abort(_("recursive reference '%s' in template") % key)
326
326
327 def runsymbol(context, mapping, key, default=''):
327 def runsymbol(context, mapping, key, default=''):
328 v = mapping.get(key)
328 v = mapping.get(key)
329 if v is None:
329 if v is None:
330 v = context._defaults.get(key)
330 v = context._defaults.get(key)
331 if v is None:
331 if v is None:
332 # put poison to cut recursion. we can't move this to parsing phase
332 # put poison to cut recursion. we can't move this to parsing phase
333 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
333 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
334 safemapping = mapping.copy()
334 safemapping = mapping.copy()
335 safemapping[key] = _recursivesymbolblocker(key)
335 safemapping[key] = _recursivesymbolblocker(key)
336 try:
336 try:
337 v = context.process(key, safemapping)
337 v = context.process(key, safemapping)
338 except TemplateNotFound:
338 except TemplateNotFound:
339 v = default
339 v = default
340 if callable(v):
340 if callable(v):
341 return v(**mapping)
341 return v(**mapping)
342 return v
342 return v
343
343
344 def buildtemplate(exp, context):
344 def buildtemplate(exp, context):
345 ctmpl = [compileexp(e, context, methods) for e in exp[1:]]
345 ctmpl = [compileexp(e, context, methods) for e in exp[1:]]
346 return (runtemplate, ctmpl)
346 return (runtemplate, ctmpl)
347
347
348 def runtemplate(context, mapping, template):
348 def runtemplate(context, mapping, template):
349 for func, data in template:
349 for func, data in template:
350 yield func(context, mapping, data)
350 yield func(context, mapping, data)
351
351
352 def buildfilter(exp, context):
352 def buildfilter(exp, context):
353 arg = compileexp(exp[1], context, methods)
353 arg = compileexp(exp[1], context, methods)
354 n = getsymbol(exp[2])
354 n = getsymbol(exp[2])
355 if n in context._filters:
355 if n in context._filters:
356 filt = context._filters[n]
356 filt = context._filters[n]
357 return (runfilter, (arg, filt))
357 return (runfilter, (arg, filt))
358 if n in funcs:
358 if n in funcs:
359 f = funcs[n]
359 f = funcs[n]
360 return (f, [arg])
360 return (f, [arg])
361 raise error.ParseError(_("unknown function '%s'") % n)
361 raise error.ParseError(_("unknown function '%s'") % n)
362
362
363 def runfilter(context, mapping, data):
363 def runfilter(context, mapping, data):
364 arg, filt = data
364 arg, filt = data
365 thing = evalfuncarg(context, mapping, arg)
365 thing = evalfuncarg(context, mapping, arg)
366 try:
366 try:
367 return filt(thing)
367 return filt(thing)
368 except (ValueError, AttributeError, TypeError):
368 except (ValueError, AttributeError, TypeError):
369 if isinstance(arg[1], tuple):
369 if isinstance(arg[1], tuple):
370 dt = arg[1][1]
370 dt = arg[1][1]
371 else:
371 else:
372 dt = arg[1]
372 dt = arg[1]
373 raise error.Abort(_("template filter '%s' is not compatible with "
373 raise error.Abort(_("template filter '%s' is not compatible with "
374 "keyword '%s'") % (filt.func_name, dt))
374 "keyword '%s'") % (filt.func_name, dt))
375
375
376 def buildmap(exp, context):
376 def buildmap(exp, context):
377 func, data = compileexp(exp[1], context, methods)
377 func, data = compileexp(exp[1], context, methods)
378 tfunc, tdata = gettemplate(exp[2], context)
378 tfunc, tdata = gettemplate(exp[2], context)
379 return (runmap, (func, data, tfunc, tdata))
379 return (runmap, (func, data, tfunc, tdata))
380
380
381 def runmap(context, mapping, data):
381 def runmap(context, mapping, data):
382 func, data, tfunc, tdata = data
382 func, data, tfunc, tdata = data
383 d = func(context, mapping, data)
383 d = func(context, mapping, data)
384 if util.safehasattr(d, 'itermaps'):
384 if util.safehasattr(d, 'itermaps'):
385 diter = d.itermaps()
385 diter = d.itermaps()
386 else:
386 else:
387 try:
387 try:
388 diter = iter(d)
388 diter = iter(d)
389 except TypeError:
389 except TypeError:
390 if func is runsymbol:
390 if func is runsymbol:
391 raise error.ParseError(_("keyword '%s' is not iterable") % data)
391 raise error.ParseError(_("keyword '%s' is not iterable") % data)
392 else:
392 else:
393 raise error.ParseError(_("%r is not iterable") % d)
393 raise error.ParseError(_("%r is not iterable") % d)
394
394
395 for i in diter:
395 for i in diter:
396 lm = mapping.copy()
396 lm = mapping.copy()
397 if isinstance(i, dict):
397 if isinstance(i, dict):
398 lm.update(i)
398 lm.update(i)
399 lm['originalnode'] = mapping.get('node')
399 lm['originalnode'] = mapping.get('node')
400 yield tfunc(context, lm, tdata)
400 yield tfunc(context, lm, tdata)
401 else:
401 else:
402 # v is not an iterable of dicts, this happen when 'key'
402 # v is not an iterable of dicts, this happen when 'key'
403 # has been fully expanded already and format is useless.
403 # has been fully expanded already and format is useless.
404 # If so, return the expanded value.
404 # If so, return the expanded value.
405 yield i
405 yield i
406
406
407 def buildfunc(exp, context):
407 def buildfunc(exp, context):
408 n = getsymbol(exp[1])
408 n = getsymbol(exp[1])
409 args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
409 args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
410 if n in funcs:
410 if n in funcs:
411 f = funcs[n]
411 f = funcs[n]
412 return (f, args)
412 return (f, args)
413 if n in context._filters:
413 if n in context._filters:
414 if len(args) != 1:
414 if len(args) != 1:
415 raise error.ParseError(_("filter %s expects one argument") % n)
415 raise error.ParseError(_("filter %s expects one argument") % n)
416 f = context._filters[n]
416 f = context._filters[n]
417 return (runfilter, (args[0], f))
417 return (runfilter, (args[0], f))
418 raise error.ParseError(_("unknown function '%s'") % n)
418 raise error.ParseError(_("unknown function '%s'") % n)
419
419
420 # dict of template built-in functions
420 # dict of template built-in functions
421 funcs = {}
421 funcs = {}
422
422
423 templatefunc = registrar.templatefunc(funcs)
423 templatefunc = registrar.templatefunc(funcs)
424
424
425 @templatefunc('date(date[, fmt])')
425 @templatefunc('date(date[, fmt])')
426 def date(context, mapping, args):
426 def date(context, mapping, args):
427 """Format a date. See :hg:`help dates` for formatting
427 """Format a date. See :hg:`help dates` for formatting
428 strings. The default is a Unix date format, including the timezone:
428 strings. The default is a Unix date format, including the timezone:
429 "Mon Sep 04 15:13:13 2006 0700"."""
429 "Mon Sep 04 15:13:13 2006 0700"."""
430 if not (1 <= len(args) <= 2):
430 if not (1 <= len(args) <= 2):
431 # i18n: "date" is a keyword
431 # i18n: "date" is a keyword
432 raise error.ParseError(_("date expects one or two arguments"))
432 raise error.ParseError(_("date expects one or two arguments"))
433
433
434 date = evalfuncarg(context, mapping, args[0])
434 date = evalfuncarg(context, mapping, args[0])
435 fmt = None
435 fmt = None
436 if len(args) == 2:
436 if len(args) == 2:
437 fmt = evalstring(context, mapping, args[1])
437 fmt = evalstring(context, mapping, args[1])
438 try:
438 try:
439 if fmt is None:
439 if fmt is None:
440 return util.datestr(date)
440 return util.datestr(date)
441 else:
441 else:
442 return util.datestr(date, fmt)
442 return util.datestr(date, fmt)
443 except (TypeError, ValueError):
443 except (TypeError, ValueError):
444 # i18n: "date" is a keyword
444 # i18n: "date" is a keyword
445 raise error.ParseError(_("date expects a date information"))
445 raise error.ParseError(_("date expects a date information"))
446
446
447 @templatefunc('diff([includepattern [, excludepattern]])')
447 @templatefunc('diff([includepattern [, excludepattern]])')
448 def diff(context, mapping, args):
448 def diff(context, mapping, args):
449 """Show a diff, optionally
449 """Show a diff, optionally
450 specifying files to include or exclude."""
450 specifying files to include or exclude."""
451 if len(args) > 2:
451 if len(args) > 2:
452 # i18n: "diff" is a keyword
452 # i18n: "diff" is a keyword
453 raise error.ParseError(_("diff expects zero, one, or two arguments"))
453 raise error.ParseError(_("diff expects zero, one, or two arguments"))
454
454
455 def getpatterns(i):
455 def getpatterns(i):
456 if i < len(args):
456 if i < len(args):
457 s = evalstring(context, mapping, args[i]).strip()
457 s = evalstring(context, mapping, args[i]).strip()
458 if s:
458 if s:
459 return [s]
459 return [s]
460 return []
460 return []
461
461
462 ctx = mapping['ctx']
462 ctx = mapping['ctx']
463 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
463 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
464
464
465 return ''.join(chunks)
465 return ''.join(chunks)
466
466
467 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
467 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
468 def fill(context, mapping, args):
468 def fill(context, mapping, args):
469 """Fill many
469 """Fill many
470 paragraphs with optional indentation. See the "fill" filter."""
470 paragraphs with optional indentation. See the "fill" filter."""
471 if not (1 <= len(args) <= 4):
471 if not (1 <= len(args) <= 4):
472 # i18n: "fill" is a keyword
472 # i18n: "fill" is a keyword
473 raise error.ParseError(_("fill expects one to four arguments"))
473 raise error.ParseError(_("fill expects one to four arguments"))
474
474
475 text = evalstring(context, mapping, args[0])
475 text = evalstring(context, mapping, args[0])
476 width = 76
476 width = 76
477 initindent = ''
477 initindent = ''
478 hangindent = ''
478 hangindent = ''
479 if 2 <= len(args) <= 4:
479 if 2 <= len(args) <= 4:
480 width = evalinteger(context, mapping, args[1],
480 width = evalinteger(context, mapping, args[1],
481 # i18n: "fill" is a keyword
481 # i18n: "fill" is a keyword
482 _("fill expects an integer width"))
482 _("fill expects an integer width"))
483 try:
483 try:
484 initindent = evalstring(context, mapping, args[2])
484 initindent = evalstring(context, mapping, args[2])
485 hangindent = evalstring(context, mapping, args[3])
485 hangindent = evalstring(context, mapping, args[3])
486 except IndexError:
486 except IndexError:
487 pass
487 pass
488
488
489 return templatefilters.fill(text, width, initindent, hangindent)
489 return templatefilters.fill(text, width, initindent, hangindent)
490
490
491 @templatefunc('pad(text, width[, fillchar=\' \'[, right=False]])')
491 @templatefunc('pad(text, width[, fillchar=\' \'[, right=False]])')
492 def pad(context, mapping, args):
492 def pad(context, mapping, args):
493 """Pad text with a
493 """Pad text with a
494 fill character."""
494 fill character."""
495 if not (2 <= len(args) <= 4):
495 if not (2 <= len(args) <= 4):
496 # i18n: "pad" is a keyword
496 # i18n: "pad" is a keyword
497 raise error.ParseError(_("pad() expects two to four arguments"))
497 raise error.ParseError(_("pad() expects two to four arguments"))
498
498
499 width = evalinteger(context, mapping, args[1],
499 width = evalinteger(context, mapping, args[1],
500 # i18n: "pad" is a keyword
500 # i18n: "pad" is a keyword
501 _("pad() expects an integer width"))
501 _("pad() expects an integer width"))
502
502
503 text = evalstring(context, mapping, args[0])
503 text = evalstring(context, mapping, args[0])
504
504
505 right = False
505 right = False
506 fillchar = ' '
506 fillchar = ' '
507 if len(args) > 2:
507 if len(args) > 2:
508 fillchar = evalstring(context, mapping, args[2])
508 fillchar = evalstring(context, mapping, args[2])
509 if len(args) > 3:
509 if len(args) > 3:
510 right = util.parsebool(args[3][1])
510 right = util.parsebool(args[3][1])
511
511
512 if right:
512 if right:
513 return text.rjust(width, fillchar)
513 return text.rjust(width, fillchar)
514 else:
514 else:
515 return text.ljust(width, fillchar)
515 return text.ljust(width, fillchar)
516
516
517 @templatefunc('indent(text, indentchars[, firstline])')
517 @templatefunc('indent(text, indentchars[, firstline])')
518 def indent(context, mapping, args):
518 def indent(context, mapping, args):
519 """Indents all non-empty lines
519 """Indents all non-empty lines
520 with the characters given in the indentchars string. An optional
520 with the characters given in the indentchars string. An optional
521 third parameter will override the indent for the first line only
521 third parameter will override the indent for the first line only
522 if present."""
522 if present."""
523 if not (2 <= len(args) <= 3):
523 if not (2 <= len(args) <= 3):
524 # i18n: "indent" is a keyword
524 # i18n: "indent" is a keyword
525 raise error.ParseError(_("indent() expects two or three arguments"))
525 raise error.ParseError(_("indent() expects two or three arguments"))
526
526
527 text = evalstring(context, mapping, args[0])
527 text = evalstring(context, mapping, args[0])
528 indent = evalstring(context, mapping, args[1])
528 indent = evalstring(context, mapping, args[1])
529
529
530 if len(args) == 3:
530 if len(args) == 3:
531 firstline = evalstring(context, mapping, args[2])
531 firstline = evalstring(context, mapping, args[2])
532 else:
532 else:
533 firstline = indent
533 firstline = indent
534
534
535 # the indent function doesn't indent the first line, so we do it here
535 # the indent function doesn't indent the first line, so we do it here
536 return templatefilters.indent(firstline + text, indent)
536 return templatefilters.indent(firstline + text, indent)
537
537
538 @templatefunc('get(dict, key)')
538 @templatefunc('get(dict, key)')
539 def get(context, mapping, args):
539 def get(context, mapping, args):
540 """Get an attribute/key from an object. Some keywords
540 """Get an attribute/key from an object. Some keywords
541 are complex types. This function allows you to obtain the value of an
541 are complex types. This function allows you to obtain the value of an
542 attribute on these types."""
542 attribute on these types."""
543 if len(args) != 2:
543 if len(args) != 2:
544 # i18n: "get" is a keyword
544 # i18n: "get" is a keyword
545 raise error.ParseError(_("get() expects two arguments"))
545 raise error.ParseError(_("get() expects two arguments"))
546
546
547 dictarg = evalfuncarg(context, mapping, args[0])
547 dictarg = evalfuncarg(context, mapping, args[0])
548 if not util.safehasattr(dictarg, 'get'):
548 if not util.safehasattr(dictarg, 'get'):
549 # i18n: "get" is a keyword
549 # i18n: "get" is a keyword
550 raise error.ParseError(_("get() expects a dict as first argument"))
550 raise error.ParseError(_("get() expects a dict as first argument"))
551
551
552 key = evalfuncarg(context, mapping, args[1])
552 key = evalfuncarg(context, mapping, args[1])
553 return dictarg.get(key)
553 return dictarg.get(key)
554
554
555 @templatefunc('if(expr, then[, else])')
555 @templatefunc('if(expr, then[, else])')
556 def if_(context, mapping, args):
556 def if_(context, mapping, args):
557 """Conditionally execute based on the result of
557 """Conditionally execute based on the result of
558 an expression."""
558 an expression."""
559 if not (2 <= len(args) <= 3):
559 if not (2 <= len(args) <= 3):
560 # i18n: "if" is a keyword
560 # i18n: "if" is a keyword
561 raise error.ParseError(_("if expects two or three arguments"))
561 raise error.ParseError(_("if expects two or three arguments"))
562
562
563 test = evalstring(context, mapping, args[0])
563 test = evalstring(context, mapping, args[0])
564 if test:
564 if test:
565 yield args[1][0](context, mapping, args[1][1])
565 yield args[1][0](context, mapping, args[1][1])
566 elif len(args) == 3:
566 elif len(args) == 3:
567 yield args[2][0](context, mapping, args[2][1])
567 yield args[2][0](context, mapping, args[2][1])
568
568
569 @templatefunc('ifcontains(search, thing, then[, else])')
569 @templatefunc('ifcontains(search, thing, then[, else])')
570 def ifcontains(context, mapping, args):
570 def ifcontains(context, mapping, args):
571 """Conditionally execute based
571 """Conditionally execute based
572 on whether the item "search" is in "thing"."""
572 on whether the item "search" is in "thing"."""
573 if not (3 <= len(args) <= 4):
573 if not (3 <= len(args) <= 4):
574 # i18n: "ifcontains" is a keyword
574 # i18n: "ifcontains" is a keyword
575 raise error.ParseError(_("ifcontains expects three or four arguments"))
575 raise error.ParseError(_("ifcontains expects three or four arguments"))
576
576
577 item = evalstring(context, mapping, args[0])
577 item = evalstring(context, mapping, args[0])
578 items = evalfuncarg(context, mapping, args[1])
578 items = evalfuncarg(context, mapping, args[1])
579
579
580 if item in items:
580 if item in items:
581 yield args[2][0](context, mapping, args[2][1])
581 yield args[2][0](context, mapping, args[2][1])
582 elif len(args) == 4:
582 elif len(args) == 4:
583 yield args[3][0](context, mapping, args[3][1])
583 yield args[3][0](context, mapping, args[3][1])
584
584
585 @templatefunc('ifeq(expr1, expr2, then[, else])')
585 @templatefunc('ifeq(expr1, expr2, then[, else])')
586 def ifeq(context, mapping, args):
586 def ifeq(context, mapping, args):
587 """Conditionally execute based on
587 """Conditionally execute based on
588 whether 2 items are equivalent."""
588 whether 2 items are equivalent."""
589 if not (3 <= len(args) <= 4):
589 if not (3 <= len(args) <= 4):
590 # i18n: "ifeq" is a keyword
590 # i18n: "ifeq" is a keyword
591 raise error.ParseError(_("ifeq expects three or four arguments"))
591 raise error.ParseError(_("ifeq expects three or four arguments"))
592
592
593 test = evalstring(context, mapping, args[0])
593 test = evalstring(context, mapping, args[0])
594 match = evalstring(context, mapping, args[1])
594 match = evalstring(context, mapping, args[1])
595 if test == match:
595 if test == match:
596 yield args[2][0](context, mapping, args[2][1])
596 yield args[2][0](context, mapping, args[2][1])
597 elif len(args) == 4:
597 elif len(args) == 4:
598 yield args[3][0](context, mapping, args[3][1])
598 yield args[3][0](context, mapping, args[3][1])
599
599
600 @templatefunc('join(list, sep)')
600 @templatefunc('join(list, sep)')
601 def join(context, mapping, args):
601 def join(context, mapping, args):
602 """Join items in a list with a delimiter."""
602 """Join items in a list with a delimiter."""
603 if not (1 <= len(args) <= 2):
603 if not (1 <= len(args) <= 2):
604 # i18n: "join" is a keyword
604 # i18n: "join" is a keyword
605 raise error.ParseError(_("join expects one or two arguments"))
605 raise error.ParseError(_("join expects one or two arguments"))
606
606
607 joinset = args[0][0](context, mapping, args[0][1])
607 joinset = args[0][0](context, mapping, args[0][1])
608 if util.safehasattr(joinset, 'itermaps'):
608 if util.safehasattr(joinset, 'itermaps'):
609 jf = joinset.joinfmt
609 jf = joinset.joinfmt
610 joinset = [jf(x) for x in joinset.itermaps()]
610 joinset = [jf(x) for x in joinset.itermaps()]
611
611
612 joiner = " "
612 joiner = " "
613 if len(args) > 1:
613 if len(args) > 1:
614 joiner = evalstring(context, mapping, args[1])
614 joiner = evalstring(context, mapping, args[1])
615
615
616 first = True
616 first = True
617 for x in joinset:
617 for x in joinset:
618 if first:
618 if first:
619 first = False
619 first = False
620 else:
620 else:
621 yield joiner
621 yield joiner
622 yield x
622 yield x
623
623
624 @templatefunc('label(label, expr)')
624 @templatefunc('label(label, expr)')
625 def label(context, mapping, args):
625 def label(context, mapping, args):
626 """Apply a label to generated content. Content with
626 """Apply a label to generated content. Content with
627 a label applied can result in additional post-processing, such as
627 a label applied can result in additional post-processing, such as
628 automatic colorization."""
628 automatic colorization."""
629 if len(args) != 2:
629 if len(args) != 2:
630 # i18n: "label" is a keyword
630 # i18n: "label" is a keyword
631 raise error.ParseError(_("label expects two arguments"))
631 raise error.ParseError(_("label expects two arguments"))
632
632
633 ui = mapping['ui']
633 ui = mapping['ui']
634 thing = evalstring(context, mapping, args[1])
634 thing = evalstring(context, mapping, args[1])
635 # preserve unknown symbol as literal so effects like 'red', 'bold',
635 # preserve unknown symbol as literal so effects like 'red', 'bold',
636 # etc. don't need to be quoted
636 # etc. don't need to be quoted
637 label = evalstringliteral(context, mapping, args[0])
637 label = evalstringliteral(context, mapping, args[0])
638
638
639 return ui.label(thing, label)
639 return ui.label(thing, label)
640
640
641 @templatefunc('latesttag([pattern])')
641 @templatefunc('latesttag([pattern])')
642 def latesttag(context, mapping, args):
642 def latesttag(context, mapping, args):
643 """The global tags matching the given pattern on the
643 """The global tags matching the given pattern on the
644 most recent globally tagged ancestor of this changeset."""
644 most recent globally tagged ancestor of this changeset."""
645 if len(args) > 1:
645 if len(args) > 1:
646 # i18n: "latesttag" is a keyword
646 # i18n: "latesttag" is a keyword
647 raise error.ParseError(_("latesttag expects at most one argument"))
647 raise error.ParseError(_("latesttag expects at most one argument"))
648
648
649 pattern = None
649 pattern = None
650 if len(args) == 1:
650 if len(args) == 1:
651 pattern = evalstring(context, mapping, args[0])
651 pattern = evalstring(context, mapping, args[0])
652
652
653 return templatekw.showlatesttags(pattern, **mapping)
653 return templatekw.showlatesttags(pattern, **mapping)
654
654
655 @templatefunc('localdate(date[, tz])')
655 @templatefunc('localdate(date[, tz])')
656 def localdate(context, mapping, args):
656 def localdate(context, mapping, args):
657 """Converts a date to the specified timezone.
657 """Converts a date to the specified timezone.
658 The default is local date."""
658 The default is local date."""
659 if not (1 <= len(args) <= 2):
659 if not (1 <= len(args) <= 2):
660 # i18n: "localdate" is a keyword
660 # i18n: "localdate" is a keyword
661 raise error.ParseError(_("localdate expects one or two arguments"))
661 raise error.ParseError(_("localdate expects one or two arguments"))
662
662
663 date = evalfuncarg(context, mapping, args[0])
663 date = evalfuncarg(context, mapping, args[0])
664 try:
664 try:
665 date = util.parsedate(date)
665 date = util.parsedate(date)
666 except AttributeError: # not str nor date tuple
666 except AttributeError: # not str nor date tuple
667 # i18n: "localdate" is a keyword
667 # i18n: "localdate" is a keyword
668 raise error.ParseError(_("localdate expects a date information"))
668 raise error.ParseError(_("localdate expects a date information"))
669 if len(args) >= 2:
669 if len(args) >= 2:
670 tzoffset = None
670 tzoffset = None
671 tz = evalfuncarg(context, mapping, args[1])
671 tz = evalfuncarg(context, mapping, args[1])
672 if isinstance(tz, str):
672 if isinstance(tz, str):
673 tzoffset = util.parsetimezone(tz)
673 tzoffset, remainder = util.parsetimezone(tz)
674 if remainder:
675 tzoffset = None
674 if tzoffset is None:
676 if tzoffset is None:
675 try:
677 try:
676 tzoffset = int(tz)
678 tzoffset = int(tz)
677 except (TypeError, ValueError):
679 except (TypeError, ValueError):
678 # i18n: "localdate" is a keyword
680 # i18n: "localdate" is a keyword
679 raise error.ParseError(_("localdate expects a timezone"))
681 raise error.ParseError(_("localdate expects a timezone"))
680 else:
682 else:
681 tzoffset = util.makedate()[1]
683 tzoffset = util.makedate()[1]
682 return (date[0], tzoffset)
684 return (date[0], tzoffset)
683
685
684 @templatefunc('revset(query[, formatargs...])')
686 @templatefunc('revset(query[, formatargs...])')
685 def revset(context, mapping, args):
687 def revset(context, mapping, args):
686 """Execute a revision set query. See
688 """Execute a revision set query. See
687 :hg:`help revset`."""
689 :hg:`help revset`."""
688 if not len(args) > 0:
690 if not len(args) > 0:
689 # i18n: "revset" is a keyword
691 # i18n: "revset" is a keyword
690 raise error.ParseError(_("revset expects one or more arguments"))
692 raise error.ParseError(_("revset expects one or more arguments"))
691
693
692 raw = evalstring(context, mapping, args[0])
694 raw = evalstring(context, mapping, args[0])
693 ctx = mapping['ctx']
695 ctx = mapping['ctx']
694 repo = ctx.repo()
696 repo = ctx.repo()
695
697
696 def query(expr):
698 def query(expr):
697 m = revsetmod.match(repo.ui, expr)
699 m = revsetmod.match(repo.ui, expr)
698 return m(repo)
700 return m(repo)
699
701
700 if len(args) > 1:
702 if len(args) > 1:
701 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
703 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
702 revs = query(revsetmod.formatspec(raw, *formatargs))
704 revs = query(revsetmod.formatspec(raw, *formatargs))
703 revs = list(revs)
705 revs = list(revs)
704 else:
706 else:
705 revsetcache = mapping['cache'].setdefault("revsetcache", {})
707 revsetcache = mapping['cache'].setdefault("revsetcache", {})
706 if raw in revsetcache:
708 if raw in revsetcache:
707 revs = revsetcache[raw]
709 revs = revsetcache[raw]
708 else:
710 else:
709 revs = query(raw)
711 revs = query(raw)
710 revs = list(revs)
712 revs = list(revs)
711 revsetcache[raw] = revs
713 revsetcache[raw] = revs
712
714
713 return templatekw.showrevslist("revision", revs, **mapping)
715 return templatekw.showrevslist("revision", revs, **mapping)
714
716
715 @templatefunc('rstdoc(text, style)')
717 @templatefunc('rstdoc(text, style)')
716 def rstdoc(context, mapping, args):
718 def rstdoc(context, mapping, args):
717 """Format ReStructuredText."""
719 """Format ReStructuredText."""
718 if len(args) != 2:
720 if len(args) != 2:
719 # i18n: "rstdoc" is a keyword
721 # i18n: "rstdoc" is a keyword
720 raise error.ParseError(_("rstdoc expects two arguments"))
722 raise error.ParseError(_("rstdoc expects two arguments"))
721
723
722 text = evalstring(context, mapping, args[0])
724 text = evalstring(context, mapping, args[0])
723 style = evalstring(context, mapping, args[1])
725 style = evalstring(context, mapping, args[1])
724
726
725 return minirst.format(text, style=style, keep=['verbose'])
727 return minirst.format(text, style=style, keep=['verbose'])
726
728
727 @templatefunc('separate(sep, args)')
729 @templatefunc('separate(sep, args)')
728 def separate(context, mapping, args):
730 def separate(context, mapping, args):
729 """Add a separator between non-empty arguments."""
731 """Add a separator between non-empty arguments."""
730 if not args:
732 if not args:
731 # i18n: "separate" is a keyword
733 # i18n: "separate" is a keyword
732 raise error.ParseError(_("separate expects at least one argument"))
734 raise error.ParseError(_("separate expects at least one argument"))
733
735
734 sep = evalstring(context, mapping, args[0])
736 sep = evalstring(context, mapping, args[0])
735 first = True
737 first = True
736 for arg in args[1:]:
738 for arg in args[1:]:
737 argstr = evalstring(context, mapping, arg)
739 argstr = evalstring(context, mapping, arg)
738 if not argstr:
740 if not argstr:
739 continue
741 continue
740 if first:
742 if first:
741 first = False
743 first = False
742 else:
744 else:
743 yield sep
745 yield sep
744 yield argstr
746 yield argstr
745
747
746 @templatefunc('shortest(node, minlength=4)')
748 @templatefunc('shortest(node, minlength=4)')
747 def shortest(context, mapping, args):
749 def shortest(context, mapping, args):
748 """Obtain the shortest representation of
750 """Obtain the shortest representation of
749 a node."""
751 a node."""
750 if not (1 <= len(args) <= 2):
752 if not (1 <= len(args) <= 2):
751 # i18n: "shortest" is a keyword
753 # i18n: "shortest" is a keyword
752 raise error.ParseError(_("shortest() expects one or two arguments"))
754 raise error.ParseError(_("shortest() expects one or two arguments"))
753
755
754 node = evalstring(context, mapping, args[0])
756 node = evalstring(context, mapping, args[0])
755
757
756 minlength = 4
758 minlength = 4
757 if len(args) > 1:
759 if len(args) > 1:
758 minlength = evalinteger(context, mapping, args[1],
760 minlength = evalinteger(context, mapping, args[1],
759 # i18n: "shortest" is a keyword
761 # i18n: "shortest" is a keyword
760 _("shortest() expects an integer minlength"))
762 _("shortest() expects an integer minlength"))
761
763
762 cl = mapping['ctx']._repo.changelog
764 cl = mapping['ctx']._repo.changelog
763 def isvalid(test):
765 def isvalid(test):
764 try:
766 try:
765 try:
767 try:
766 cl.index.partialmatch(test)
768 cl.index.partialmatch(test)
767 except AttributeError:
769 except AttributeError:
768 # Pure mercurial doesn't support partialmatch on the index.
770 # Pure mercurial doesn't support partialmatch on the index.
769 # Fallback to the slow way.
771 # Fallback to the slow way.
770 if cl._partialmatch(test) is None:
772 if cl._partialmatch(test) is None:
771 return False
773 return False
772
774
773 try:
775 try:
774 i = int(test)
776 i = int(test)
775 # if we are a pure int, then starting with zero will not be
777 # if we are a pure int, then starting with zero will not be
776 # confused as a rev; or, obviously, if the int is larger than
778 # confused as a rev; or, obviously, if the int is larger than
777 # the value of the tip rev
779 # the value of the tip rev
778 if test[0] == '0' or i > len(cl):
780 if test[0] == '0' or i > len(cl):
779 return True
781 return True
780 return False
782 return False
781 except ValueError:
783 except ValueError:
782 return True
784 return True
783 except error.RevlogError:
785 except error.RevlogError:
784 return False
786 return False
785
787
786 shortest = node
788 shortest = node
787 startlength = max(6, minlength)
789 startlength = max(6, minlength)
788 length = startlength
790 length = startlength
789 while True:
791 while True:
790 test = node[:length]
792 test = node[:length]
791 if isvalid(test):
793 if isvalid(test):
792 shortest = test
794 shortest = test
793 if length == minlength or length > startlength:
795 if length == minlength or length > startlength:
794 return shortest
796 return shortest
795 length -= 1
797 length -= 1
796 else:
798 else:
797 length += 1
799 length += 1
798 if len(shortest) <= length:
800 if len(shortest) <= length:
799 return shortest
801 return shortest
800
802
801 @templatefunc('strip(text[, chars])')
803 @templatefunc('strip(text[, chars])')
802 def strip(context, mapping, args):
804 def strip(context, mapping, args):
803 """Strip characters from a string. By default,
805 """Strip characters from a string. By default,
804 strips all leading and trailing whitespace."""
806 strips all leading and trailing whitespace."""
805 if not (1 <= len(args) <= 2):
807 if not (1 <= len(args) <= 2):
806 # i18n: "strip" is a keyword
808 # i18n: "strip" is a keyword
807 raise error.ParseError(_("strip expects one or two arguments"))
809 raise error.ParseError(_("strip expects one or two arguments"))
808
810
809 text = evalstring(context, mapping, args[0])
811 text = evalstring(context, mapping, args[0])
810 if len(args) == 2:
812 if len(args) == 2:
811 chars = evalstring(context, mapping, args[1])
813 chars = evalstring(context, mapping, args[1])
812 return text.strip(chars)
814 return text.strip(chars)
813 return text.strip()
815 return text.strip()
814
816
815 @templatefunc('sub(pattern, replacement, expression)')
817 @templatefunc('sub(pattern, replacement, expression)')
816 def sub(context, mapping, args):
818 def sub(context, mapping, args):
817 """Perform text substitution
819 """Perform text substitution
818 using regular expressions."""
820 using regular expressions."""
819 if len(args) != 3:
821 if len(args) != 3:
820 # i18n: "sub" is a keyword
822 # i18n: "sub" is a keyword
821 raise error.ParseError(_("sub expects three arguments"))
823 raise error.ParseError(_("sub expects three arguments"))
822
824
823 pat = evalstring(context, mapping, args[0])
825 pat = evalstring(context, mapping, args[0])
824 rpl = evalstring(context, mapping, args[1])
826 rpl = evalstring(context, mapping, args[1])
825 src = evalstring(context, mapping, args[2])
827 src = evalstring(context, mapping, args[2])
826 try:
828 try:
827 patre = re.compile(pat)
829 patre = re.compile(pat)
828 except re.error:
830 except re.error:
829 # i18n: "sub" is a keyword
831 # i18n: "sub" is a keyword
830 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
832 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
831 try:
833 try:
832 yield patre.sub(rpl, src)
834 yield patre.sub(rpl, src)
833 except re.error:
835 except re.error:
834 # i18n: "sub" is a keyword
836 # i18n: "sub" is a keyword
835 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
837 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
836
838
837 @templatefunc('startswith(pattern, text)')
839 @templatefunc('startswith(pattern, text)')
838 def startswith(context, mapping, args):
840 def startswith(context, mapping, args):
839 """Returns the value from the "text" argument
841 """Returns the value from the "text" argument
840 if it begins with the content from the "pattern" argument."""
842 if it begins with the content from the "pattern" argument."""
841 if len(args) != 2:
843 if len(args) != 2:
842 # i18n: "startswith" is a keyword
844 # i18n: "startswith" is a keyword
843 raise error.ParseError(_("startswith expects two arguments"))
845 raise error.ParseError(_("startswith expects two arguments"))
844
846
845 patn = evalstring(context, mapping, args[0])
847 patn = evalstring(context, mapping, args[0])
846 text = evalstring(context, mapping, args[1])
848 text = evalstring(context, mapping, args[1])
847 if text.startswith(patn):
849 if text.startswith(patn):
848 return text
850 return text
849 return ''
851 return ''
850
852
851 @templatefunc('word(number, text[, separator])')
853 @templatefunc('word(number, text[, separator])')
852 def word(context, mapping, args):
854 def word(context, mapping, args):
853 """Return the nth word from a string."""
855 """Return the nth word from a string."""
854 if not (2 <= len(args) <= 3):
856 if not (2 <= len(args) <= 3):
855 # i18n: "word" is a keyword
857 # i18n: "word" is a keyword
856 raise error.ParseError(_("word expects two or three arguments, got %d")
858 raise error.ParseError(_("word expects two or three arguments, got %d")
857 % len(args))
859 % len(args))
858
860
859 num = evalinteger(context, mapping, args[0],
861 num = evalinteger(context, mapping, args[0],
860 # i18n: "word" is a keyword
862 # i18n: "word" is a keyword
861 _("word expects an integer index"))
863 _("word expects an integer index"))
862 text = evalstring(context, mapping, args[1])
864 text = evalstring(context, mapping, args[1])
863 if len(args) == 3:
865 if len(args) == 3:
864 splitter = evalstring(context, mapping, args[2])
866 splitter = evalstring(context, mapping, args[2])
865 else:
867 else:
866 splitter = None
868 splitter = None
867
869
868 tokens = text.split(splitter)
870 tokens = text.split(splitter)
869 if num >= len(tokens) or num < -len(tokens):
871 if num >= len(tokens) or num < -len(tokens):
870 return ''
872 return ''
871 else:
873 else:
872 return tokens[num]
874 return tokens[num]
873
875
874 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
876 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
875 exprmethods = {
877 exprmethods = {
876 "integer": lambda e, c: (runinteger, e[1]),
878 "integer": lambda e, c: (runinteger, e[1]),
877 "string": lambda e, c: (runstring, e[1]),
879 "string": lambda e, c: (runstring, e[1]),
878 "symbol": lambda e, c: (runsymbol, e[1]),
880 "symbol": lambda e, c: (runsymbol, e[1]),
879 "template": buildtemplate,
881 "template": buildtemplate,
880 "group": lambda e, c: compileexp(e[1], c, exprmethods),
882 "group": lambda e, c: compileexp(e[1], c, exprmethods),
881 # ".": buildmember,
883 # ".": buildmember,
882 "|": buildfilter,
884 "|": buildfilter,
883 "%": buildmap,
885 "%": buildmap,
884 "func": buildfunc,
886 "func": buildfunc,
885 }
887 }
886
888
887 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
889 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
888 methods = exprmethods.copy()
890 methods = exprmethods.copy()
889 methods["integer"] = exprmethods["symbol"] # '{1}' as variable
891 methods["integer"] = exprmethods["symbol"] # '{1}' as variable
890
892
891 class _aliasrules(parser.basealiasrules):
893 class _aliasrules(parser.basealiasrules):
892 """Parsing and expansion rule set of template aliases"""
894 """Parsing and expansion rule set of template aliases"""
893 _section = _('template alias')
895 _section = _('template alias')
894 _parse = staticmethod(_parseexpr)
896 _parse = staticmethod(_parseexpr)
895
897
896 @staticmethod
898 @staticmethod
897 def _trygetfunc(tree):
899 def _trygetfunc(tree):
898 """Return (name, args) if tree is func(...) or ...|filter; otherwise
900 """Return (name, args) if tree is func(...) or ...|filter; otherwise
899 None"""
901 None"""
900 if tree[0] == 'func' and tree[1][0] == 'symbol':
902 if tree[0] == 'func' and tree[1][0] == 'symbol':
901 return tree[1][1], getlist(tree[2])
903 return tree[1][1], getlist(tree[2])
902 if tree[0] == '|' and tree[2][0] == 'symbol':
904 if tree[0] == '|' and tree[2][0] == 'symbol':
903 return tree[2][1], [tree[1]]
905 return tree[2][1], [tree[1]]
904
906
905 def expandaliases(tree, aliases):
907 def expandaliases(tree, aliases):
906 """Return new tree of aliases are expanded"""
908 """Return new tree of aliases are expanded"""
907 aliasmap = _aliasrules.buildmap(aliases)
909 aliasmap = _aliasrules.buildmap(aliases)
908 return _aliasrules.expand(aliasmap, tree)
910 return _aliasrules.expand(aliasmap, tree)
909
911
910 # template engine
912 # template engine
911
913
912 stringify = templatefilters.stringify
914 stringify = templatefilters.stringify
913
915
914 def _flatten(thing):
916 def _flatten(thing):
915 '''yield a single stream from a possibly nested set of iterators'''
917 '''yield a single stream from a possibly nested set of iterators'''
916 if isinstance(thing, str):
918 if isinstance(thing, str):
917 yield thing
919 yield thing
918 elif not util.safehasattr(thing, '__iter__'):
920 elif not util.safehasattr(thing, '__iter__'):
919 if thing is not None:
921 if thing is not None:
920 yield str(thing)
922 yield str(thing)
921 else:
923 else:
922 for i in thing:
924 for i in thing:
923 if isinstance(i, str):
925 if isinstance(i, str):
924 yield i
926 yield i
925 elif not util.safehasattr(i, '__iter__'):
927 elif not util.safehasattr(i, '__iter__'):
926 if i is not None:
928 if i is not None:
927 yield str(i)
929 yield str(i)
928 elif i is not None:
930 elif i is not None:
929 for j in _flatten(i):
931 for j in _flatten(i):
930 yield j
932 yield j
931
933
932 def unquotestring(s):
934 def unquotestring(s):
933 '''unwrap quotes if any; otherwise returns unmodified string'''
935 '''unwrap quotes if any; otherwise returns unmodified string'''
934 if len(s) < 2 or s[0] not in "'\"" or s[0] != s[-1]:
936 if len(s) < 2 or s[0] not in "'\"" or s[0] != s[-1]:
935 return s
937 return s
936 return s[1:-1]
938 return s[1:-1]
937
939
938 class engine(object):
940 class engine(object):
939 '''template expansion engine.
941 '''template expansion engine.
940
942
941 template expansion works like this. a map file contains key=value
943 template expansion works like this. a map file contains key=value
942 pairs. if value is quoted, it is treated as string. otherwise, it
944 pairs. if value is quoted, it is treated as string. otherwise, it
943 is treated as name of template file.
945 is treated as name of template file.
944
946
945 templater is asked to expand a key in map. it looks up key, and
947 templater is asked to expand a key in map. it looks up key, and
946 looks for strings like this: {foo}. it expands {foo} by looking up
948 looks for strings like this: {foo}. it expands {foo} by looking up
947 foo in map, and substituting it. expansion is recursive: it stops
949 foo in map, and substituting it. expansion is recursive: it stops
948 when there is no more {foo} to replace.
950 when there is no more {foo} to replace.
949
951
950 expansion also allows formatting and filtering.
952 expansion also allows formatting and filtering.
951
953
952 format uses key to expand each item in list. syntax is
954 format uses key to expand each item in list. syntax is
953 {key%format}.
955 {key%format}.
954
956
955 filter uses function to transform value. syntax is
957 filter uses function to transform value. syntax is
956 {key|filter1|filter2|...}.'''
958 {key|filter1|filter2|...}.'''
957
959
958 def __init__(self, loader, filters=None, defaults=None, aliases=()):
960 def __init__(self, loader, filters=None, defaults=None, aliases=()):
959 self._loader = loader
961 self._loader = loader
960 if filters is None:
962 if filters is None:
961 filters = {}
963 filters = {}
962 self._filters = filters
964 self._filters = filters
963 if defaults is None:
965 if defaults is None:
964 defaults = {}
966 defaults = {}
965 self._defaults = defaults
967 self._defaults = defaults
966 self._aliasmap = _aliasrules.buildmap(aliases)
968 self._aliasmap = _aliasrules.buildmap(aliases)
967 self._cache = {} # key: (func, data)
969 self._cache = {} # key: (func, data)
968
970
969 def _load(self, t):
971 def _load(self, t):
970 '''load, parse, and cache a template'''
972 '''load, parse, and cache a template'''
971 if t not in self._cache:
973 if t not in self._cache:
972 # put poison to cut recursion while compiling 't'
974 # put poison to cut recursion while compiling 't'
973 self._cache[t] = (_runrecursivesymbol, t)
975 self._cache[t] = (_runrecursivesymbol, t)
974 try:
976 try:
975 x = parse(self._loader(t))
977 x = parse(self._loader(t))
976 if self._aliasmap:
978 if self._aliasmap:
977 x = _aliasrules.expand(self._aliasmap, x)
979 x = _aliasrules.expand(self._aliasmap, x)
978 self._cache[t] = compileexp(x, self, methods)
980 self._cache[t] = compileexp(x, self, methods)
979 except: # re-raises
981 except: # re-raises
980 del self._cache[t]
982 del self._cache[t]
981 raise
983 raise
982 return self._cache[t]
984 return self._cache[t]
983
985
984 def process(self, t, mapping):
986 def process(self, t, mapping):
985 '''Perform expansion. t is name of map element to expand.
987 '''Perform expansion. t is name of map element to expand.
986 mapping contains added elements for use during expansion. Is a
988 mapping contains added elements for use during expansion. Is a
987 generator.'''
989 generator.'''
988 func, data = self._load(t)
990 func, data = self._load(t)
989 return _flatten(func(self, mapping, data))
991 return _flatten(func(self, mapping, data))
990
992
991 engines = {'default': engine}
993 engines = {'default': engine}
992
994
993 def stylelist():
995 def stylelist():
994 paths = templatepaths()
996 paths = templatepaths()
995 if not paths:
997 if not paths:
996 return _('no templates found, try `hg debuginstall` for more info')
998 return _('no templates found, try `hg debuginstall` for more info')
997 dirlist = os.listdir(paths[0])
999 dirlist = os.listdir(paths[0])
998 stylelist = []
1000 stylelist = []
999 for file in dirlist:
1001 for file in dirlist:
1000 split = file.split(".")
1002 split = file.split(".")
1001 if split[-1] in ('orig', 'rej'):
1003 if split[-1] in ('orig', 'rej'):
1002 continue
1004 continue
1003 if split[0] == "map-cmdline":
1005 if split[0] == "map-cmdline":
1004 stylelist.append(split[1])
1006 stylelist.append(split[1])
1005 return ", ".join(sorted(stylelist))
1007 return ", ".join(sorted(stylelist))
1006
1008
1007 def _readmapfile(mapfile):
1009 def _readmapfile(mapfile):
1008 """Load template elements from the given map file"""
1010 """Load template elements from the given map file"""
1009 if not os.path.exists(mapfile):
1011 if not os.path.exists(mapfile):
1010 raise error.Abort(_("style '%s' not found") % mapfile,
1012 raise error.Abort(_("style '%s' not found") % mapfile,
1011 hint=_("available styles: %s") % stylelist())
1013 hint=_("available styles: %s") % stylelist())
1012
1014
1013 base = os.path.dirname(mapfile)
1015 base = os.path.dirname(mapfile)
1014 conf = config.config(includepaths=templatepaths())
1016 conf = config.config(includepaths=templatepaths())
1015 conf.read(mapfile)
1017 conf.read(mapfile)
1016
1018
1017 cache = {}
1019 cache = {}
1018 tmap = {}
1020 tmap = {}
1019 for key, val in conf[''].items():
1021 for key, val in conf[''].items():
1020 if not val:
1022 if not val:
1021 raise error.ParseError(_('missing value'), conf.source('', key))
1023 raise error.ParseError(_('missing value'), conf.source('', key))
1022 if val[0] in "'\"":
1024 if val[0] in "'\"":
1023 if val[0] != val[-1]:
1025 if val[0] != val[-1]:
1024 raise error.ParseError(_('unmatched quotes'),
1026 raise error.ParseError(_('unmatched quotes'),
1025 conf.source('', key))
1027 conf.source('', key))
1026 cache[key] = unquotestring(val)
1028 cache[key] = unquotestring(val)
1027 else:
1029 else:
1028 val = 'default', val
1030 val = 'default', val
1029 if ':' in val[1]:
1031 if ':' in val[1]:
1030 val = val[1].split(':', 1)
1032 val = val[1].split(':', 1)
1031 tmap[key] = val[0], os.path.join(base, val[1])
1033 tmap[key] = val[0], os.path.join(base, val[1])
1032 return cache, tmap
1034 return cache, tmap
1033
1035
1034 class TemplateNotFound(error.Abort):
1036 class TemplateNotFound(error.Abort):
1035 pass
1037 pass
1036
1038
1037 class templater(object):
1039 class templater(object):
1038
1040
1039 def __init__(self, filters=None, defaults=None, cache=None, aliases=(),
1041 def __init__(self, filters=None, defaults=None, cache=None, aliases=(),
1040 minchunk=1024, maxchunk=65536):
1042 minchunk=1024, maxchunk=65536):
1041 '''set up template engine.
1043 '''set up template engine.
1042 filters is dict of functions. each transforms a value into another.
1044 filters is dict of functions. each transforms a value into another.
1043 defaults is dict of default map definitions.
1045 defaults is dict of default map definitions.
1044 aliases is list of alias (name, replacement) pairs.
1046 aliases is list of alias (name, replacement) pairs.
1045 '''
1047 '''
1046 if filters is None:
1048 if filters is None:
1047 filters = {}
1049 filters = {}
1048 if defaults is None:
1050 if defaults is None:
1049 defaults = {}
1051 defaults = {}
1050 if cache is None:
1052 if cache is None:
1051 cache = {}
1053 cache = {}
1052 self.cache = cache.copy()
1054 self.cache = cache.copy()
1053 self.map = {}
1055 self.map = {}
1054 self.filters = templatefilters.filters.copy()
1056 self.filters = templatefilters.filters.copy()
1055 self.filters.update(filters)
1057 self.filters.update(filters)
1056 self.defaults = defaults
1058 self.defaults = defaults
1057 self._aliases = aliases
1059 self._aliases = aliases
1058 self.minchunk, self.maxchunk = minchunk, maxchunk
1060 self.minchunk, self.maxchunk = minchunk, maxchunk
1059 self.ecache = {}
1061 self.ecache = {}
1060
1062
1061 @classmethod
1063 @classmethod
1062 def frommapfile(cls, mapfile, filters=None, defaults=None, cache=None,
1064 def frommapfile(cls, mapfile, filters=None, defaults=None, cache=None,
1063 minchunk=1024, maxchunk=65536):
1065 minchunk=1024, maxchunk=65536):
1064 """Create templater from the specified map file"""
1066 """Create templater from the specified map file"""
1065 t = cls(filters, defaults, cache, [], minchunk, maxchunk)
1067 t = cls(filters, defaults, cache, [], minchunk, maxchunk)
1066 cache, tmap = _readmapfile(mapfile)
1068 cache, tmap = _readmapfile(mapfile)
1067 t.cache.update(cache)
1069 t.cache.update(cache)
1068 t.map = tmap
1070 t.map = tmap
1069 return t
1071 return t
1070
1072
1071 def __contains__(self, key):
1073 def __contains__(self, key):
1072 return key in self.cache or key in self.map
1074 return key in self.cache or key in self.map
1073
1075
1074 def load(self, t):
1076 def load(self, t):
1075 '''Get the template for the given template name. Use a local cache.'''
1077 '''Get the template for the given template name. Use a local cache.'''
1076 if t not in self.cache:
1078 if t not in self.cache:
1077 try:
1079 try:
1078 self.cache[t] = util.readfile(self.map[t][1])
1080 self.cache[t] = util.readfile(self.map[t][1])
1079 except KeyError as inst:
1081 except KeyError as inst:
1080 raise TemplateNotFound(_('"%s" not in template map') %
1082 raise TemplateNotFound(_('"%s" not in template map') %
1081 inst.args[0])
1083 inst.args[0])
1082 except IOError as inst:
1084 except IOError as inst:
1083 raise IOError(inst.args[0], _('template file %s: %s') %
1085 raise IOError(inst.args[0], _('template file %s: %s') %
1084 (self.map[t][1], inst.args[1]))
1086 (self.map[t][1], inst.args[1]))
1085 return self.cache[t]
1087 return self.cache[t]
1086
1088
1087 def __call__(self, t, **mapping):
1089 def __call__(self, t, **mapping):
1088 ttype = t in self.map and self.map[t][0] or 'default'
1090 ttype = t in self.map and self.map[t][0] or 'default'
1089 if ttype not in self.ecache:
1091 if ttype not in self.ecache:
1090 try:
1092 try:
1091 ecls = engines[ttype]
1093 ecls = engines[ttype]
1092 except KeyError:
1094 except KeyError:
1093 raise error.Abort(_('invalid template engine: %s') % ttype)
1095 raise error.Abort(_('invalid template engine: %s') % ttype)
1094 self.ecache[ttype] = ecls(self.load, self.filters, self.defaults,
1096 self.ecache[ttype] = ecls(self.load, self.filters, self.defaults,
1095 self._aliases)
1097 self._aliases)
1096 proc = self.ecache[ttype]
1098 proc = self.ecache[ttype]
1097
1099
1098 stream = proc.process(t, mapping)
1100 stream = proc.process(t, mapping)
1099 if self.minchunk:
1101 if self.minchunk:
1100 stream = util.increasingchunks(stream, min=self.minchunk,
1102 stream = util.increasingchunks(stream, min=self.minchunk,
1101 max=self.maxchunk)
1103 max=self.maxchunk)
1102 return stream
1104 return stream
1103
1105
1104 def templatepaths():
1106 def templatepaths():
1105 '''return locations used for template files.'''
1107 '''return locations used for template files.'''
1106 pathsrel = ['templates']
1108 pathsrel = ['templates']
1107 paths = [os.path.normpath(os.path.join(util.datapath, f))
1109 paths = [os.path.normpath(os.path.join(util.datapath, f))
1108 for f in pathsrel]
1110 for f in pathsrel]
1109 return [p for p in paths if os.path.isdir(p)]
1111 return [p for p in paths if os.path.isdir(p)]
1110
1112
1111 def templatepath(name):
1113 def templatepath(name):
1112 '''return location of template file. returns None if not found.'''
1114 '''return location of template file. returns None if not found.'''
1113 for p in templatepaths():
1115 for p in templatepaths():
1114 f = os.path.join(p, name)
1116 f = os.path.join(p, name)
1115 if os.path.exists(f):
1117 if os.path.exists(f):
1116 return f
1118 return f
1117 return None
1119 return None
1118
1120
1119 def stylemap(styles, paths=None):
1121 def stylemap(styles, paths=None):
1120 """Return path to mapfile for a given style.
1122 """Return path to mapfile for a given style.
1121
1123
1122 Searches mapfile in the following locations:
1124 Searches mapfile in the following locations:
1123 1. templatepath/style/map
1125 1. templatepath/style/map
1124 2. templatepath/map-style
1126 2. templatepath/map-style
1125 3. templatepath/map
1127 3. templatepath/map
1126 """
1128 """
1127
1129
1128 if paths is None:
1130 if paths is None:
1129 paths = templatepaths()
1131 paths = templatepaths()
1130 elif isinstance(paths, str):
1132 elif isinstance(paths, str):
1131 paths = [paths]
1133 paths = [paths]
1132
1134
1133 if isinstance(styles, str):
1135 if isinstance(styles, str):
1134 styles = [styles]
1136 styles = [styles]
1135
1137
1136 for style in styles:
1138 for style in styles:
1137 # only plain name is allowed to honor template paths
1139 # only plain name is allowed to honor template paths
1138 if (not style
1140 if (not style
1139 or style in (os.curdir, os.pardir)
1141 or style in (os.curdir, os.pardir)
1140 or os.sep in style
1142 or os.sep in style
1141 or os.altsep and os.altsep in style):
1143 or os.altsep and os.altsep in style):
1142 continue
1144 continue
1143 locations = [os.path.join(style, 'map'), 'map-' + style]
1145 locations = [os.path.join(style, 'map'), 'map-' + style]
1144 locations.append('map')
1146 locations.append('map')
1145
1147
1146 for path in paths:
1148 for path in paths:
1147 for location in locations:
1149 for location in locations:
1148 mapfile = os.path.join(path, location)
1150 mapfile = os.path.join(path, location)
1149 if os.path.isfile(mapfile):
1151 if os.path.isfile(mapfile):
1150 return style, mapfile
1152 return style, mapfile
1151
1153
1152 raise RuntimeError("No hgweb templates found in %r" % paths)
1154 raise RuntimeError("No hgweb templates found in %r" % paths)
1153
1155
1154 def loadfunction(ui, extname, registrarobj):
1156 def loadfunction(ui, extname, registrarobj):
1155 """Load template function from specified registrarobj
1157 """Load template function from specified registrarobj
1156 """
1158 """
1157 for name, func in registrarobj._table.iteritems():
1159 for name, func in registrarobj._table.iteritems():
1158 funcs[name] = func
1160 funcs[name] = func
1159
1161
1160 # tell hggettext to extract docstrings from these functions:
1162 # tell hggettext to extract docstrings from these functions:
1161 i18nfunctions = funcs.values()
1163 i18nfunctions = funcs.values()
@@ -1,2879 +1,2882 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from __future__ import absolute_import
16 from __future__ import absolute_import
17
17
18 import bz2
18 import bz2
19 import calendar
19 import calendar
20 import collections
20 import collections
21 import datetime
21 import datetime
22 import errno
22 import errno
23 import gc
23 import gc
24 import hashlib
24 import hashlib
25 import imp
25 import imp
26 import os
26 import os
27 import re as remod
27 import re as remod
28 import shutil
28 import shutil
29 import signal
29 import signal
30 import socket
30 import socket
31 import subprocess
31 import subprocess
32 import sys
32 import sys
33 import tempfile
33 import tempfile
34 import textwrap
34 import textwrap
35 import time
35 import time
36 import traceback
36 import traceback
37 import zlib
37 import zlib
38
38
39 from . import (
39 from . import (
40 encoding,
40 encoding,
41 error,
41 error,
42 i18n,
42 i18n,
43 osutil,
43 osutil,
44 parsers,
44 parsers,
45 pycompat,
45 pycompat,
46 )
46 )
47
47
48 for attr in (
48 for attr in (
49 'empty',
49 'empty',
50 'httplib',
50 'httplib',
51 'httpserver',
51 'httpserver',
52 'pickle',
52 'pickle',
53 'queue',
53 'queue',
54 'urlerr',
54 'urlerr',
55 'urlparse',
55 'urlparse',
56 # we do import urlreq, but we do it outside the loop
56 # we do import urlreq, but we do it outside the loop
57 #'urlreq',
57 #'urlreq',
58 'stringio',
58 'stringio',
59 'socketserver',
59 'socketserver',
60 'xmlrpclib',
60 'xmlrpclib',
61 ):
61 ):
62 globals()[attr] = getattr(pycompat, attr)
62 globals()[attr] = getattr(pycompat, attr)
63
63
64 # This line is to make pyflakes happy:
64 # This line is to make pyflakes happy:
65 urlreq = pycompat.urlreq
65 urlreq = pycompat.urlreq
66
66
67 if os.name == 'nt':
67 if os.name == 'nt':
68 from . import windows as platform
68 from . import windows as platform
69 else:
69 else:
70 from . import posix as platform
70 from . import posix as platform
71
71
72 _ = i18n._
72 _ = i18n._
73
73
74 bindunixsocket = platform.bindunixsocket
74 bindunixsocket = platform.bindunixsocket
75 cachestat = platform.cachestat
75 cachestat = platform.cachestat
76 checkexec = platform.checkexec
76 checkexec = platform.checkexec
77 checklink = platform.checklink
77 checklink = platform.checklink
78 copymode = platform.copymode
78 copymode = platform.copymode
79 executablepath = platform.executablepath
79 executablepath = platform.executablepath
80 expandglobs = platform.expandglobs
80 expandglobs = platform.expandglobs
81 explainexit = platform.explainexit
81 explainexit = platform.explainexit
82 findexe = platform.findexe
82 findexe = platform.findexe
83 gethgcmd = platform.gethgcmd
83 gethgcmd = platform.gethgcmd
84 getuser = platform.getuser
84 getuser = platform.getuser
85 getpid = os.getpid
85 getpid = os.getpid
86 groupmembers = platform.groupmembers
86 groupmembers = platform.groupmembers
87 groupname = platform.groupname
87 groupname = platform.groupname
88 hidewindow = platform.hidewindow
88 hidewindow = platform.hidewindow
89 isexec = platform.isexec
89 isexec = platform.isexec
90 isowner = platform.isowner
90 isowner = platform.isowner
91 localpath = platform.localpath
91 localpath = platform.localpath
92 lookupreg = platform.lookupreg
92 lookupreg = platform.lookupreg
93 makedir = platform.makedir
93 makedir = platform.makedir
94 nlinks = platform.nlinks
94 nlinks = platform.nlinks
95 normpath = platform.normpath
95 normpath = platform.normpath
96 normcase = platform.normcase
96 normcase = platform.normcase
97 normcasespec = platform.normcasespec
97 normcasespec = platform.normcasespec
98 normcasefallback = platform.normcasefallback
98 normcasefallback = platform.normcasefallback
99 openhardlinks = platform.openhardlinks
99 openhardlinks = platform.openhardlinks
100 oslink = platform.oslink
100 oslink = platform.oslink
101 parsepatchoutput = platform.parsepatchoutput
101 parsepatchoutput = platform.parsepatchoutput
102 pconvert = platform.pconvert
102 pconvert = platform.pconvert
103 poll = platform.poll
103 poll = platform.poll
104 popen = platform.popen
104 popen = platform.popen
105 posixfile = platform.posixfile
105 posixfile = platform.posixfile
106 quotecommand = platform.quotecommand
106 quotecommand = platform.quotecommand
107 readpipe = platform.readpipe
107 readpipe = platform.readpipe
108 rename = platform.rename
108 rename = platform.rename
109 removedirs = platform.removedirs
109 removedirs = platform.removedirs
110 samedevice = platform.samedevice
110 samedevice = platform.samedevice
111 samefile = platform.samefile
111 samefile = platform.samefile
112 samestat = platform.samestat
112 samestat = platform.samestat
113 setbinary = platform.setbinary
113 setbinary = platform.setbinary
114 setflags = platform.setflags
114 setflags = platform.setflags
115 setsignalhandler = platform.setsignalhandler
115 setsignalhandler = platform.setsignalhandler
116 shellquote = platform.shellquote
116 shellquote = platform.shellquote
117 spawndetached = platform.spawndetached
117 spawndetached = platform.spawndetached
118 split = platform.split
118 split = platform.split
119 sshargs = platform.sshargs
119 sshargs = platform.sshargs
120 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
120 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
121 statisexec = platform.statisexec
121 statisexec = platform.statisexec
122 statislink = platform.statislink
122 statislink = platform.statislink
123 termwidth = platform.termwidth
123 termwidth = platform.termwidth
124 testpid = platform.testpid
124 testpid = platform.testpid
125 umask = platform.umask
125 umask = platform.umask
126 unlink = platform.unlink
126 unlink = platform.unlink
127 unlinkpath = platform.unlinkpath
127 unlinkpath = platform.unlinkpath
128 username = platform.username
128 username = platform.username
129
129
130 # Python compatibility
130 # Python compatibility
131
131
132 _notset = object()
132 _notset = object()
133
133
134 # disable Python's problematic floating point timestamps (issue4836)
134 # disable Python's problematic floating point timestamps (issue4836)
135 # (Python hypocritically says you shouldn't change this behavior in
135 # (Python hypocritically says you shouldn't change this behavior in
136 # libraries, and sure enough Mercurial is not a library.)
136 # libraries, and sure enough Mercurial is not a library.)
137 os.stat_float_times(False)
137 os.stat_float_times(False)
138
138
139 def safehasattr(thing, attr):
139 def safehasattr(thing, attr):
140 return getattr(thing, attr, _notset) is not _notset
140 return getattr(thing, attr, _notset) is not _notset
141
141
142 DIGESTS = {
142 DIGESTS = {
143 'md5': hashlib.md5,
143 'md5': hashlib.md5,
144 'sha1': hashlib.sha1,
144 'sha1': hashlib.sha1,
145 'sha512': hashlib.sha512,
145 'sha512': hashlib.sha512,
146 }
146 }
147 # List of digest types from strongest to weakest
147 # List of digest types from strongest to weakest
148 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
148 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
149
149
150 for k in DIGESTS_BY_STRENGTH:
150 for k in DIGESTS_BY_STRENGTH:
151 assert k in DIGESTS
151 assert k in DIGESTS
152
152
153 class digester(object):
153 class digester(object):
154 """helper to compute digests.
154 """helper to compute digests.
155
155
156 This helper can be used to compute one or more digests given their name.
156 This helper can be used to compute one or more digests given their name.
157
157
158 >>> d = digester(['md5', 'sha1'])
158 >>> d = digester(['md5', 'sha1'])
159 >>> d.update('foo')
159 >>> d.update('foo')
160 >>> [k for k in sorted(d)]
160 >>> [k for k in sorted(d)]
161 ['md5', 'sha1']
161 ['md5', 'sha1']
162 >>> d['md5']
162 >>> d['md5']
163 'acbd18db4cc2f85cedef654fccc4a4d8'
163 'acbd18db4cc2f85cedef654fccc4a4d8'
164 >>> d['sha1']
164 >>> d['sha1']
165 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
165 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
166 >>> digester.preferred(['md5', 'sha1'])
166 >>> digester.preferred(['md5', 'sha1'])
167 'sha1'
167 'sha1'
168 """
168 """
169
169
170 def __init__(self, digests, s=''):
170 def __init__(self, digests, s=''):
171 self._hashes = {}
171 self._hashes = {}
172 for k in digests:
172 for k in digests:
173 if k not in DIGESTS:
173 if k not in DIGESTS:
174 raise Abort(_('unknown digest type: %s') % k)
174 raise Abort(_('unknown digest type: %s') % k)
175 self._hashes[k] = DIGESTS[k]()
175 self._hashes[k] = DIGESTS[k]()
176 if s:
176 if s:
177 self.update(s)
177 self.update(s)
178
178
179 def update(self, data):
179 def update(self, data):
180 for h in self._hashes.values():
180 for h in self._hashes.values():
181 h.update(data)
181 h.update(data)
182
182
183 def __getitem__(self, key):
183 def __getitem__(self, key):
184 if key not in DIGESTS:
184 if key not in DIGESTS:
185 raise Abort(_('unknown digest type: %s') % k)
185 raise Abort(_('unknown digest type: %s') % k)
186 return self._hashes[key].hexdigest()
186 return self._hashes[key].hexdigest()
187
187
188 def __iter__(self):
188 def __iter__(self):
189 return iter(self._hashes)
189 return iter(self._hashes)
190
190
191 @staticmethod
191 @staticmethod
192 def preferred(supported):
192 def preferred(supported):
193 """returns the strongest digest type in both supported and DIGESTS."""
193 """returns the strongest digest type in both supported and DIGESTS."""
194
194
195 for k in DIGESTS_BY_STRENGTH:
195 for k in DIGESTS_BY_STRENGTH:
196 if k in supported:
196 if k in supported:
197 return k
197 return k
198 return None
198 return None
199
199
200 class digestchecker(object):
200 class digestchecker(object):
201 """file handle wrapper that additionally checks content against a given
201 """file handle wrapper that additionally checks content against a given
202 size and digests.
202 size and digests.
203
203
204 d = digestchecker(fh, size, {'md5': '...'})
204 d = digestchecker(fh, size, {'md5': '...'})
205
205
206 When multiple digests are given, all of them are validated.
206 When multiple digests are given, all of them are validated.
207 """
207 """
208
208
209 def __init__(self, fh, size, digests):
209 def __init__(self, fh, size, digests):
210 self._fh = fh
210 self._fh = fh
211 self._size = size
211 self._size = size
212 self._got = 0
212 self._got = 0
213 self._digests = dict(digests)
213 self._digests = dict(digests)
214 self._digester = digester(self._digests.keys())
214 self._digester = digester(self._digests.keys())
215
215
216 def read(self, length=-1):
216 def read(self, length=-1):
217 content = self._fh.read(length)
217 content = self._fh.read(length)
218 self._digester.update(content)
218 self._digester.update(content)
219 self._got += len(content)
219 self._got += len(content)
220 return content
220 return content
221
221
222 def validate(self):
222 def validate(self):
223 if self._size != self._got:
223 if self._size != self._got:
224 raise Abort(_('size mismatch: expected %d, got %d') %
224 raise Abort(_('size mismatch: expected %d, got %d') %
225 (self._size, self._got))
225 (self._size, self._got))
226 for k, v in self._digests.items():
226 for k, v in self._digests.items():
227 if v != self._digester[k]:
227 if v != self._digester[k]:
228 # i18n: first parameter is a digest name
228 # i18n: first parameter is a digest name
229 raise Abort(_('%s mismatch: expected %s, got %s') %
229 raise Abort(_('%s mismatch: expected %s, got %s') %
230 (k, v, self._digester[k]))
230 (k, v, self._digester[k]))
231
231
232 try:
232 try:
233 buffer = buffer
233 buffer = buffer
234 except NameError:
234 except NameError:
235 if sys.version_info[0] < 3:
235 if sys.version_info[0] < 3:
236 def buffer(sliceable, offset=0):
236 def buffer(sliceable, offset=0):
237 return sliceable[offset:]
237 return sliceable[offset:]
238 else:
238 else:
239 def buffer(sliceable, offset=0):
239 def buffer(sliceable, offset=0):
240 return memoryview(sliceable)[offset:]
240 return memoryview(sliceable)[offset:]
241
241
242 closefds = os.name == 'posix'
242 closefds = os.name == 'posix'
243
243
244 _chunksize = 4096
244 _chunksize = 4096
245
245
246 class bufferedinputpipe(object):
246 class bufferedinputpipe(object):
247 """a manually buffered input pipe
247 """a manually buffered input pipe
248
248
249 Python will not let us use buffered IO and lazy reading with 'polling' at
249 Python will not let us use buffered IO and lazy reading with 'polling' at
250 the same time. We cannot probe the buffer state and select will not detect
250 the same time. We cannot probe the buffer state and select will not detect
251 that data are ready to read if they are already buffered.
251 that data are ready to read if they are already buffered.
252
252
253 This class let us work around that by implementing its own buffering
253 This class let us work around that by implementing its own buffering
254 (allowing efficient readline) while offering a way to know if the buffer is
254 (allowing efficient readline) while offering a way to know if the buffer is
255 empty from the output (allowing collaboration of the buffer with polling).
255 empty from the output (allowing collaboration of the buffer with polling).
256
256
257 This class lives in the 'util' module because it makes use of the 'os'
257 This class lives in the 'util' module because it makes use of the 'os'
258 module from the python stdlib.
258 module from the python stdlib.
259 """
259 """
260
260
261 def __init__(self, input):
261 def __init__(self, input):
262 self._input = input
262 self._input = input
263 self._buffer = []
263 self._buffer = []
264 self._eof = False
264 self._eof = False
265 self._lenbuf = 0
265 self._lenbuf = 0
266
266
267 @property
267 @property
268 def hasbuffer(self):
268 def hasbuffer(self):
269 """True is any data is currently buffered
269 """True is any data is currently buffered
270
270
271 This will be used externally a pre-step for polling IO. If there is
271 This will be used externally a pre-step for polling IO. If there is
272 already data then no polling should be set in place."""
272 already data then no polling should be set in place."""
273 return bool(self._buffer)
273 return bool(self._buffer)
274
274
275 @property
275 @property
276 def closed(self):
276 def closed(self):
277 return self._input.closed
277 return self._input.closed
278
278
279 def fileno(self):
279 def fileno(self):
280 return self._input.fileno()
280 return self._input.fileno()
281
281
282 def close(self):
282 def close(self):
283 return self._input.close()
283 return self._input.close()
284
284
285 def read(self, size):
285 def read(self, size):
286 while (not self._eof) and (self._lenbuf < size):
286 while (not self._eof) and (self._lenbuf < size):
287 self._fillbuffer()
287 self._fillbuffer()
288 return self._frombuffer(size)
288 return self._frombuffer(size)
289
289
290 def readline(self, *args, **kwargs):
290 def readline(self, *args, **kwargs):
291 if 1 < len(self._buffer):
291 if 1 < len(self._buffer):
292 # this should not happen because both read and readline end with a
292 # this should not happen because both read and readline end with a
293 # _frombuffer call that collapse it.
293 # _frombuffer call that collapse it.
294 self._buffer = [''.join(self._buffer)]
294 self._buffer = [''.join(self._buffer)]
295 self._lenbuf = len(self._buffer[0])
295 self._lenbuf = len(self._buffer[0])
296 lfi = -1
296 lfi = -1
297 if self._buffer:
297 if self._buffer:
298 lfi = self._buffer[-1].find('\n')
298 lfi = self._buffer[-1].find('\n')
299 while (not self._eof) and lfi < 0:
299 while (not self._eof) and lfi < 0:
300 self._fillbuffer()
300 self._fillbuffer()
301 if self._buffer:
301 if self._buffer:
302 lfi = self._buffer[-1].find('\n')
302 lfi = self._buffer[-1].find('\n')
303 size = lfi + 1
303 size = lfi + 1
304 if lfi < 0: # end of file
304 if lfi < 0: # end of file
305 size = self._lenbuf
305 size = self._lenbuf
306 elif 1 < len(self._buffer):
306 elif 1 < len(self._buffer):
307 # we need to take previous chunks into account
307 # we need to take previous chunks into account
308 size += self._lenbuf - len(self._buffer[-1])
308 size += self._lenbuf - len(self._buffer[-1])
309 return self._frombuffer(size)
309 return self._frombuffer(size)
310
310
311 def _frombuffer(self, size):
311 def _frombuffer(self, size):
312 """return at most 'size' data from the buffer
312 """return at most 'size' data from the buffer
313
313
314 The data are removed from the buffer."""
314 The data are removed from the buffer."""
315 if size == 0 or not self._buffer:
315 if size == 0 or not self._buffer:
316 return ''
316 return ''
317 buf = self._buffer[0]
317 buf = self._buffer[0]
318 if 1 < len(self._buffer):
318 if 1 < len(self._buffer):
319 buf = ''.join(self._buffer)
319 buf = ''.join(self._buffer)
320
320
321 data = buf[:size]
321 data = buf[:size]
322 buf = buf[len(data):]
322 buf = buf[len(data):]
323 if buf:
323 if buf:
324 self._buffer = [buf]
324 self._buffer = [buf]
325 self._lenbuf = len(buf)
325 self._lenbuf = len(buf)
326 else:
326 else:
327 self._buffer = []
327 self._buffer = []
328 self._lenbuf = 0
328 self._lenbuf = 0
329 return data
329 return data
330
330
331 def _fillbuffer(self):
331 def _fillbuffer(self):
332 """read data to the buffer"""
332 """read data to the buffer"""
333 data = os.read(self._input.fileno(), _chunksize)
333 data = os.read(self._input.fileno(), _chunksize)
334 if not data:
334 if not data:
335 self._eof = True
335 self._eof = True
336 else:
336 else:
337 self._lenbuf += len(data)
337 self._lenbuf += len(data)
338 self._buffer.append(data)
338 self._buffer.append(data)
339
339
340 def popen2(cmd, env=None, newlines=False):
340 def popen2(cmd, env=None, newlines=False):
341 # Setting bufsize to -1 lets the system decide the buffer size.
341 # Setting bufsize to -1 lets the system decide the buffer size.
342 # The default for bufsize is 0, meaning unbuffered. This leads to
342 # The default for bufsize is 0, meaning unbuffered. This leads to
343 # poor performance on Mac OS X: http://bugs.python.org/issue4194
343 # poor performance on Mac OS X: http://bugs.python.org/issue4194
344 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
344 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
345 close_fds=closefds,
345 close_fds=closefds,
346 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
346 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
347 universal_newlines=newlines,
347 universal_newlines=newlines,
348 env=env)
348 env=env)
349 return p.stdin, p.stdout
349 return p.stdin, p.stdout
350
350
351 def popen3(cmd, env=None, newlines=False):
351 def popen3(cmd, env=None, newlines=False):
352 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
352 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
353 return stdin, stdout, stderr
353 return stdin, stdout, stderr
354
354
355 def popen4(cmd, env=None, newlines=False, bufsize=-1):
355 def popen4(cmd, env=None, newlines=False, bufsize=-1):
356 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
356 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
357 close_fds=closefds,
357 close_fds=closefds,
358 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
358 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
359 stderr=subprocess.PIPE,
359 stderr=subprocess.PIPE,
360 universal_newlines=newlines,
360 universal_newlines=newlines,
361 env=env)
361 env=env)
362 return p.stdin, p.stdout, p.stderr, p
362 return p.stdin, p.stdout, p.stderr, p
363
363
364 def version():
364 def version():
365 """Return version information if available."""
365 """Return version information if available."""
366 try:
366 try:
367 from . import __version__
367 from . import __version__
368 return __version__.version
368 return __version__.version
369 except ImportError:
369 except ImportError:
370 return 'unknown'
370 return 'unknown'
371
371
372 def versiontuple(v=None, n=4):
372 def versiontuple(v=None, n=4):
373 """Parses a Mercurial version string into an N-tuple.
373 """Parses a Mercurial version string into an N-tuple.
374
374
375 The version string to be parsed is specified with the ``v`` argument.
375 The version string to be parsed is specified with the ``v`` argument.
376 If it isn't defined, the current Mercurial version string will be parsed.
376 If it isn't defined, the current Mercurial version string will be parsed.
377
377
378 ``n`` can be 2, 3, or 4. Here is how some version strings map to
378 ``n`` can be 2, 3, or 4. Here is how some version strings map to
379 returned values:
379 returned values:
380
380
381 >>> v = '3.6.1+190-df9b73d2d444'
381 >>> v = '3.6.1+190-df9b73d2d444'
382 >>> versiontuple(v, 2)
382 >>> versiontuple(v, 2)
383 (3, 6)
383 (3, 6)
384 >>> versiontuple(v, 3)
384 >>> versiontuple(v, 3)
385 (3, 6, 1)
385 (3, 6, 1)
386 >>> versiontuple(v, 4)
386 >>> versiontuple(v, 4)
387 (3, 6, 1, '190-df9b73d2d444')
387 (3, 6, 1, '190-df9b73d2d444')
388
388
389 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
389 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
390 (3, 6, 1, '190-df9b73d2d444+20151118')
390 (3, 6, 1, '190-df9b73d2d444+20151118')
391
391
392 >>> v = '3.6'
392 >>> v = '3.6'
393 >>> versiontuple(v, 2)
393 >>> versiontuple(v, 2)
394 (3, 6)
394 (3, 6)
395 >>> versiontuple(v, 3)
395 >>> versiontuple(v, 3)
396 (3, 6, None)
396 (3, 6, None)
397 >>> versiontuple(v, 4)
397 >>> versiontuple(v, 4)
398 (3, 6, None, None)
398 (3, 6, None, None)
399
399
400 >>> v = '3.9-rc'
400 >>> v = '3.9-rc'
401 >>> versiontuple(v, 2)
401 >>> versiontuple(v, 2)
402 (3, 9)
402 (3, 9)
403 >>> versiontuple(v, 3)
403 >>> versiontuple(v, 3)
404 (3, 9, None)
404 (3, 9, None)
405 >>> versiontuple(v, 4)
405 >>> versiontuple(v, 4)
406 (3, 9, None, 'rc')
406 (3, 9, None, 'rc')
407
407
408 >>> v = '3.9-rc+2-02a8fea4289b'
408 >>> v = '3.9-rc+2-02a8fea4289b'
409 >>> versiontuple(v, 2)
409 >>> versiontuple(v, 2)
410 (3, 9)
410 (3, 9)
411 >>> versiontuple(v, 3)
411 >>> versiontuple(v, 3)
412 (3, 9, None)
412 (3, 9, None)
413 >>> versiontuple(v, 4)
413 >>> versiontuple(v, 4)
414 (3, 9, None, 'rc+2-02a8fea4289b')
414 (3, 9, None, 'rc+2-02a8fea4289b')
415 """
415 """
416 if not v:
416 if not v:
417 v = version()
417 v = version()
418 parts = remod.split('[\+-]', v, 1)
418 parts = remod.split('[\+-]', v, 1)
419 if len(parts) == 1:
419 if len(parts) == 1:
420 vparts, extra = parts[0], None
420 vparts, extra = parts[0], None
421 else:
421 else:
422 vparts, extra = parts
422 vparts, extra = parts
423
423
424 vints = []
424 vints = []
425 for i in vparts.split('.'):
425 for i in vparts.split('.'):
426 try:
426 try:
427 vints.append(int(i))
427 vints.append(int(i))
428 except ValueError:
428 except ValueError:
429 break
429 break
430 # (3, 6) -> (3, 6, None)
430 # (3, 6) -> (3, 6, None)
431 while len(vints) < 3:
431 while len(vints) < 3:
432 vints.append(None)
432 vints.append(None)
433
433
434 if n == 2:
434 if n == 2:
435 return (vints[0], vints[1])
435 return (vints[0], vints[1])
436 if n == 3:
436 if n == 3:
437 return (vints[0], vints[1], vints[2])
437 return (vints[0], vints[1], vints[2])
438 if n == 4:
438 if n == 4:
439 return (vints[0], vints[1], vints[2], extra)
439 return (vints[0], vints[1], vints[2], extra)
440
440
441 # used by parsedate
441 # used by parsedate
442 defaultdateformats = (
442 defaultdateformats = (
443 '%Y-%m-%d %H:%M:%S',
443 '%Y-%m-%d %H:%M:%S',
444 '%Y-%m-%d %I:%M:%S%p',
444 '%Y-%m-%d %I:%M:%S%p',
445 '%Y-%m-%d %H:%M',
445 '%Y-%m-%d %H:%M',
446 '%Y-%m-%d %I:%M%p',
446 '%Y-%m-%d %I:%M%p',
447 '%Y-%m-%d',
447 '%Y-%m-%d',
448 '%m-%d',
448 '%m-%d',
449 '%m/%d',
449 '%m/%d',
450 '%m/%d/%y',
450 '%m/%d/%y',
451 '%m/%d/%Y',
451 '%m/%d/%Y',
452 '%a %b %d %H:%M:%S %Y',
452 '%a %b %d %H:%M:%S %Y',
453 '%a %b %d %I:%M:%S%p %Y',
453 '%a %b %d %I:%M:%S%p %Y',
454 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
454 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
455 '%b %d %H:%M:%S %Y',
455 '%b %d %H:%M:%S %Y',
456 '%b %d %I:%M:%S%p %Y',
456 '%b %d %I:%M:%S%p %Y',
457 '%b %d %H:%M:%S',
457 '%b %d %H:%M:%S',
458 '%b %d %I:%M:%S%p',
458 '%b %d %I:%M:%S%p',
459 '%b %d %H:%M',
459 '%b %d %H:%M',
460 '%b %d %I:%M%p',
460 '%b %d %I:%M%p',
461 '%b %d %Y',
461 '%b %d %Y',
462 '%b %d',
462 '%b %d',
463 '%H:%M:%S',
463 '%H:%M:%S',
464 '%I:%M:%S%p',
464 '%I:%M:%S%p',
465 '%H:%M',
465 '%H:%M',
466 '%I:%M%p',
466 '%I:%M%p',
467 )
467 )
468
468
469 extendeddateformats = defaultdateformats + (
469 extendeddateformats = defaultdateformats + (
470 "%Y",
470 "%Y",
471 "%Y-%m",
471 "%Y-%m",
472 "%b",
472 "%b",
473 "%b %Y",
473 "%b %Y",
474 )
474 )
475
475
476 def cachefunc(func):
476 def cachefunc(func):
477 '''cache the result of function calls'''
477 '''cache the result of function calls'''
478 # XXX doesn't handle keywords args
478 # XXX doesn't handle keywords args
479 if func.__code__.co_argcount == 0:
479 if func.__code__.co_argcount == 0:
480 cache = []
480 cache = []
481 def f():
481 def f():
482 if len(cache) == 0:
482 if len(cache) == 0:
483 cache.append(func())
483 cache.append(func())
484 return cache[0]
484 return cache[0]
485 return f
485 return f
486 cache = {}
486 cache = {}
487 if func.__code__.co_argcount == 1:
487 if func.__code__.co_argcount == 1:
488 # we gain a small amount of time because
488 # we gain a small amount of time because
489 # we don't need to pack/unpack the list
489 # we don't need to pack/unpack the list
490 def f(arg):
490 def f(arg):
491 if arg not in cache:
491 if arg not in cache:
492 cache[arg] = func(arg)
492 cache[arg] = func(arg)
493 return cache[arg]
493 return cache[arg]
494 else:
494 else:
495 def f(*args):
495 def f(*args):
496 if args not in cache:
496 if args not in cache:
497 cache[args] = func(*args)
497 cache[args] = func(*args)
498 return cache[args]
498 return cache[args]
499
499
500 return f
500 return f
501
501
502 class sortdict(dict):
502 class sortdict(dict):
503 '''a simple sorted dictionary'''
503 '''a simple sorted dictionary'''
504 def __init__(self, data=None):
504 def __init__(self, data=None):
505 self._list = []
505 self._list = []
506 if data:
506 if data:
507 self.update(data)
507 self.update(data)
508 def copy(self):
508 def copy(self):
509 return sortdict(self)
509 return sortdict(self)
510 def __setitem__(self, key, val):
510 def __setitem__(self, key, val):
511 if key in self:
511 if key in self:
512 self._list.remove(key)
512 self._list.remove(key)
513 self._list.append(key)
513 self._list.append(key)
514 dict.__setitem__(self, key, val)
514 dict.__setitem__(self, key, val)
515 def __iter__(self):
515 def __iter__(self):
516 return self._list.__iter__()
516 return self._list.__iter__()
517 def update(self, src):
517 def update(self, src):
518 if isinstance(src, dict):
518 if isinstance(src, dict):
519 src = src.iteritems()
519 src = src.iteritems()
520 for k, v in src:
520 for k, v in src:
521 self[k] = v
521 self[k] = v
522 def clear(self):
522 def clear(self):
523 dict.clear(self)
523 dict.clear(self)
524 self._list = []
524 self._list = []
525 def items(self):
525 def items(self):
526 return [(k, self[k]) for k in self._list]
526 return [(k, self[k]) for k in self._list]
527 def __delitem__(self, key):
527 def __delitem__(self, key):
528 dict.__delitem__(self, key)
528 dict.__delitem__(self, key)
529 self._list.remove(key)
529 self._list.remove(key)
530 def pop(self, key, *args, **kwargs):
530 def pop(self, key, *args, **kwargs):
531 dict.pop(self, key, *args, **kwargs)
531 dict.pop(self, key, *args, **kwargs)
532 try:
532 try:
533 self._list.remove(key)
533 self._list.remove(key)
534 except ValueError:
534 except ValueError:
535 pass
535 pass
536 def keys(self):
536 def keys(self):
537 return self._list
537 return self._list
538 def iterkeys(self):
538 def iterkeys(self):
539 return self._list.__iter__()
539 return self._list.__iter__()
540 def iteritems(self):
540 def iteritems(self):
541 for k in self._list:
541 for k in self._list:
542 yield k, self[k]
542 yield k, self[k]
543 def insert(self, index, key, val):
543 def insert(self, index, key, val):
544 self._list.insert(index, key)
544 self._list.insert(index, key)
545 dict.__setitem__(self, key, val)
545 dict.__setitem__(self, key, val)
546 def __repr__(self):
546 def __repr__(self):
547 if not self:
547 if not self:
548 return '%s()' % self.__class__.__name__
548 return '%s()' % self.__class__.__name__
549 return '%s(%r)' % (self.__class__.__name__, self.items())
549 return '%s(%r)' % (self.__class__.__name__, self.items())
550
550
551 class _lrucachenode(object):
551 class _lrucachenode(object):
552 """A node in a doubly linked list.
552 """A node in a doubly linked list.
553
553
554 Holds a reference to nodes on either side as well as a key-value
554 Holds a reference to nodes on either side as well as a key-value
555 pair for the dictionary entry.
555 pair for the dictionary entry.
556 """
556 """
557 __slots__ = ('next', 'prev', 'key', 'value')
557 __slots__ = ('next', 'prev', 'key', 'value')
558
558
559 def __init__(self):
559 def __init__(self):
560 self.next = None
560 self.next = None
561 self.prev = None
561 self.prev = None
562
562
563 self.key = _notset
563 self.key = _notset
564 self.value = None
564 self.value = None
565
565
566 def markempty(self):
566 def markempty(self):
567 """Mark the node as emptied."""
567 """Mark the node as emptied."""
568 self.key = _notset
568 self.key = _notset
569
569
570 class lrucachedict(object):
570 class lrucachedict(object):
571 """Dict that caches most recent accesses and sets.
571 """Dict that caches most recent accesses and sets.
572
572
573 The dict consists of an actual backing dict - indexed by original
573 The dict consists of an actual backing dict - indexed by original
574 key - and a doubly linked circular list defining the order of entries in
574 key - and a doubly linked circular list defining the order of entries in
575 the cache.
575 the cache.
576
576
577 The head node is the newest entry in the cache. If the cache is full,
577 The head node is the newest entry in the cache. If the cache is full,
578 we recycle head.prev and make it the new head. Cache accesses result in
578 we recycle head.prev and make it the new head. Cache accesses result in
579 the node being moved to before the existing head and being marked as the
579 the node being moved to before the existing head and being marked as the
580 new head node.
580 new head node.
581 """
581 """
582 def __init__(self, max):
582 def __init__(self, max):
583 self._cache = {}
583 self._cache = {}
584
584
585 self._head = head = _lrucachenode()
585 self._head = head = _lrucachenode()
586 head.prev = head
586 head.prev = head
587 head.next = head
587 head.next = head
588 self._size = 1
588 self._size = 1
589 self._capacity = max
589 self._capacity = max
590
590
591 def __len__(self):
591 def __len__(self):
592 return len(self._cache)
592 return len(self._cache)
593
593
594 def __contains__(self, k):
594 def __contains__(self, k):
595 return k in self._cache
595 return k in self._cache
596
596
597 def __iter__(self):
597 def __iter__(self):
598 # We don't have to iterate in cache order, but why not.
598 # We don't have to iterate in cache order, but why not.
599 n = self._head
599 n = self._head
600 for i in range(len(self._cache)):
600 for i in range(len(self._cache)):
601 yield n.key
601 yield n.key
602 n = n.next
602 n = n.next
603
603
604 def __getitem__(self, k):
604 def __getitem__(self, k):
605 node = self._cache[k]
605 node = self._cache[k]
606 self._movetohead(node)
606 self._movetohead(node)
607 return node.value
607 return node.value
608
608
609 def __setitem__(self, k, v):
609 def __setitem__(self, k, v):
610 node = self._cache.get(k)
610 node = self._cache.get(k)
611 # Replace existing value and mark as newest.
611 # Replace existing value and mark as newest.
612 if node is not None:
612 if node is not None:
613 node.value = v
613 node.value = v
614 self._movetohead(node)
614 self._movetohead(node)
615 return
615 return
616
616
617 if self._size < self._capacity:
617 if self._size < self._capacity:
618 node = self._addcapacity()
618 node = self._addcapacity()
619 else:
619 else:
620 # Grab the last/oldest item.
620 # Grab the last/oldest item.
621 node = self._head.prev
621 node = self._head.prev
622
622
623 # At capacity. Kill the old entry.
623 # At capacity. Kill the old entry.
624 if node.key is not _notset:
624 if node.key is not _notset:
625 del self._cache[node.key]
625 del self._cache[node.key]
626
626
627 node.key = k
627 node.key = k
628 node.value = v
628 node.value = v
629 self._cache[k] = node
629 self._cache[k] = node
630 # And mark it as newest entry. No need to adjust order since it
630 # And mark it as newest entry. No need to adjust order since it
631 # is already self._head.prev.
631 # is already self._head.prev.
632 self._head = node
632 self._head = node
633
633
634 def __delitem__(self, k):
634 def __delitem__(self, k):
635 node = self._cache.pop(k)
635 node = self._cache.pop(k)
636 node.markempty()
636 node.markempty()
637
637
638 # Temporarily mark as newest item before re-adjusting head to make
638 # Temporarily mark as newest item before re-adjusting head to make
639 # this node the oldest item.
639 # this node the oldest item.
640 self._movetohead(node)
640 self._movetohead(node)
641 self._head = node.next
641 self._head = node.next
642
642
643 # Additional dict methods.
643 # Additional dict methods.
644
644
645 def get(self, k, default=None):
645 def get(self, k, default=None):
646 try:
646 try:
647 return self._cache[k]
647 return self._cache[k]
648 except KeyError:
648 except KeyError:
649 return default
649 return default
650
650
651 def clear(self):
651 def clear(self):
652 n = self._head
652 n = self._head
653 while n.key is not _notset:
653 while n.key is not _notset:
654 n.markempty()
654 n.markempty()
655 n = n.next
655 n = n.next
656
656
657 self._cache.clear()
657 self._cache.clear()
658
658
659 def copy(self):
659 def copy(self):
660 result = lrucachedict(self._capacity)
660 result = lrucachedict(self._capacity)
661 n = self._head.prev
661 n = self._head.prev
662 # Iterate in oldest-to-newest order, so the copy has the right ordering
662 # Iterate in oldest-to-newest order, so the copy has the right ordering
663 for i in range(len(self._cache)):
663 for i in range(len(self._cache)):
664 result[n.key] = n.value
664 result[n.key] = n.value
665 n = n.prev
665 n = n.prev
666 return result
666 return result
667
667
668 def _movetohead(self, node):
668 def _movetohead(self, node):
669 """Mark a node as the newest, making it the new head.
669 """Mark a node as the newest, making it the new head.
670
670
671 When a node is accessed, it becomes the freshest entry in the LRU
671 When a node is accessed, it becomes the freshest entry in the LRU
672 list, which is denoted by self._head.
672 list, which is denoted by self._head.
673
673
674 Visually, let's make ``N`` the new head node (* denotes head):
674 Visually, let's make ``N`` the new head node (* denotes head):
675
675
676 previous/oldest <-> head <-> next/next newest
676 previous/oldest <-> head <-> next/next newest
677
677
678 ----<->--- A* ---<->-----
678 ----<->--- A* ---<->-----
679 | |
679 | |
680 E <-> D <-> N <-> C <-> B
680 E <-> D <-> N <-> C <-> B
681
681
682 To:
682 To:
683
683
684 ----<->--- N* ---<->-----
684 ----<->--- N* ---<->-----
685 | |
685 | |
686 E <-> D <-> C <-> B <-> A
686 E <-> D <-> C <-> B <-> A
687
687
688 This requires the following moves:
688 This requires the following moves:
689
689
690 C.next = D (node.prev.next = node.next)
690 C.next = D (node.prev.next = node.next)
691 D.prev = C (node.next.prev = node.prev)
691 D.prev = C (node.next.prev = node.prev)
692 E.next = N (head.prev.next = node)
692 E.next = N (head.prev.next = node)
693 N.prev = E (node.prev = head.prev)
693 N.prev = E (node.prev = head.prev)
694 N.next = A (node.next = head)
694 N.next = A (node.next = head)
695 A.prev = N (head.prev = node)
695 A.prev = N (head.prev = node)
696 """
696 """
697 head = self._head
697 head = self._head
698 # C.next = D
698 # C.next = D
699 node.prev.next = node.next
699 node.prev.next = node.next
700 # D.prev = C
700 # D.prev = C
701 node.next.prev = node.prev
701 node.next.prev = node.prev
702 # N.prev = E
702 # N.prev = E
703 node.prev = head.prev
703 node.prev = head.prev
704 # N.next = A
704 # N.next = A
705 # It is tempting to do just "head" here, however if node is
705 # It is tempting to do just "head" here, however if node is
706 # adjacent to head, this will do bad things.
706 # adjacent to head, this will do bad things.
707 node.next = head.prev.next
707 node.next = head.prev.next
708 # E.next = N
708 # E.next = N
709 node.next.prev = node
709 node.next.prev = node
710 # A.prev = N
710 # A.prev = N
711 node.prev.next = node
711 node.prev.next = node
712
712
713 self._head = node
713 self._head = node
714
714
715 def _addcapacity(self):
715 def _addcapacity(self):
716 """Add a node to the circular linked list.
716 """Add a node to the circular linked list.
717
717
718 The new node is inserted before the head node.
718 The new node is inserted before the head node.
719 """
719 """
720 head = self._head
720 head = self._head
721 node = _lrucachenode()
721 node = _lrucachenode()
722 head.prev.next = node
722 head.prev.next = node
723 node.prev = head.prev
723 node.prev = head.prev
724 node.next = head
724 node.next = head
725 head.prev = node
725 head.prev = node
726 self._size += 1
726 self._size += 1
727 return node
727 return node
728
728
729 def lrucachefunc(func):
729 def lrucachefunc(func):
730 '''cache most recent results of function calls'''
730 '''cache most recent results of function calls'''
731 cache = {}
731 cache = {}
732 order = collections.deque()
732 order = collections.deque()
733 if func.__code__.co_argcount == 1:
733 if func.__code__.co_argcount == 1:
734 def f(arg):
734 def f(arg):
735 if arg not in cache:
735 if arg not in cache:
736 if len(cache) > 20:
736 if len(cache) > 20:
737 del cache[order.popleft()]
737 del cache[order.popleft()]
738 cache[arg] = func(arg)
738 cache[arg] = func(arg)
739 else:
739 else:
740 order.remove(arg)
740 order.remove(arg)
741 order.append(arg)
741 order.append(arg)
742 return cache[arg]
742 return cache[arg]
743 else:
743 else:
744 def f(*args):
744 def f(*args):
745 if args not in cache:
745 if args not in cache:
746 if len(cache) > 20:
746 if len(cache) > 20:
747 del cache[order.popleft()]
747 del cache[order.popleft()]
748 cache[args] = func(*args)
748 cache[args] = func(*args)
749 else:
749 else:
750 order.remove(args)
750 order.remove(args)
751 order.append(args)
751 order.append(args)
752 return cache[args]
752 return cache[args]
753
753
754 return f
754 return f
755
755
756 class propertycache(object):
756 class propertycache(object):
757 def __init__(self, func):
757 def __init__(self, func):
758 self.func = func
758 self.func = func
759 self.name = func.__name__
759 self.name = func.__name__
760 def __get__(self, obj, type=None):
760 def __get__(self, obj, type=None):
761 result = self.func(obj)
761 result = self.func(obj)
762 self.cachevalue(obj, result)
762 self.cachevalue(obj, result)
763 return result
763 return result
764
764
765 def cachevalue(self, obj, value):
765 def cachevalue(self, obj, value):
766 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
766 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
767 obj.__dict__[self.name] = value
767 obj.__dict__[self.name] = value
768
768
769 def pipefilter(s, cmd):
769 def pipefilter(s, cmd):
770 '''filter string S through command CMD, returning its output'''
770 '''filter string S through command CMD, returning its output'''
771 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
771 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
772 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
772 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
773 pout, perr = p.communicate(s)
773 pout, perr = p.communicate(s)
774 return pout
774 return pout
775
775
776 def tempfilter(s, cmd):
776 def tempfilter(s, cmd):
777 '''filter string S through a pair of temporary files with CMD.
777 '''filter string S through a pair of temporary files with CMD.
778 CMD is used as a template to create the real command to be run,
778 CMD is used as a template to create the real command to be run,
779 with the strings INFILE and OUTFILE replaced by the real names of
779 with the strings INFILE and OUTFILE replaced by the real names of
780 the temporary files generated.'''
780 the temporary files generated.'''
781 inname, outname = None, None
781 inname, outname = None, None
782 try:
782 try:
783 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
783 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
784 fp = os.fdopen(infd, 'wb')
784 fp = os.fdopen(infd, 'wb')
785 fp.write(s)
785 fp.write(s)
786 fp.close()
786 fp.close()
787 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
787 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
788 os.close(outfd)
788 os.close(outfd)
789 cmd = cmd.replace('INFILE', inname)
789 cmd = cmd.replace('INFILE', inname)
790 cmd = cmd.replace('OUTFILE', outname)
790 cmd = cmd.replace('OUTFILE', outname)
791 code = os.system(cmd)
791 code = os.system(cmd)
792 if sys.platform == 'OpenVMS' and code & 1:
792 if sys.platform == 'OpenVMS' and code & 1:
793 code = 0
793 code = 0
794 if code:
794 if code:
795 raise Abort(_("command '%s' failed: %s") %
795 raise Abort(_("command '%s' failed: %s") %
796 (cmd, explainexit(code)))
796 (cmd, explainexit(code)))
797 return readfile(outname)
797 return readfile(outname)
798 finally:
798 finally:
799 try:
799 try:
800 if inname:
800 if inname:
801 os.unlink(inname)
801 os.unlink(inname)
802 except OSError:
802 except OSError:
803 pass
803 pass
804 try:
804 try:
805 if outname:
805 if outname:
806 os.unlink(outname)
806 os.unlink(outname)
807 except OSError:
807 except OSError:
808 pass
808 pass
809
809
810 filtertable = {
810 filtertable = {
811 'tempfile:': tempfilter,
811 'tempfile:': tempfilter,
812 'pipe:': pipefilter,
812 'pipe:': pipefilter,
813 }
813 }
814
814
815 def filter(s, cmd):
815 def filter(s, cmd):
816 "filter a string through a command that transforms its input to its output"
816 "filter a string through a command that transforms its input to its output"
817 for name, fn in filtertable.iteritems():
817 for name, fn in filtertable.iteritems():
818 if cmd.startswith(name):
818 if cmd.startswith(name):
819 return fn(s, cmd[len(name):].lstrip())
819 return fn(s, cmd[len(name):].lstrip())
820 return pipefilter(s, cmd)
820 return pipefilter(s, cmd)
821
821
822 def binary(s):
822 def binary(s):
823 """return true if a string is binary data"""
823 """return true if a string is binary data"""
824 return bool(s and '\0' in s)
824 return bool(s and '\0' in s)
825
825
826 def increasingchunks(source, min=1024, max=65536):
826 def increasingchunks(source, min=1024, max=65536):
827 '''return no less than min bytes per chunk while data remains,
827 '''return no less than min bytes per chunk while data remains,
828 doubling min after each chunk until it reaches max'''
828 doubling min after each chunk until it reaches max'''
829 def log2(x):
829 def log2(x):
830 if not x:
830 if not x:
831 return 0
831 return 0
832 i = 0
832 i = 0
833 while x:
833 while x:
834 x >>= 1
834 x >>= 1
835 i += 1
835 i += 1
836 return i - 1
836 return i - 1
837
837
838 buf = []
838 buf = []
839 blen = 0
839 blen = 0
840 for chunk in source:
840 for chunk in source:
841 buf.append(chunk)
841 buf.append(chunk)
842 blen += len(chunk)
842 blen += len(chunk)
843 if blen >= min:
843 if blen >= min:
844 if min < max:
844 if min < max:
845 min = min << 1
845 min = min << 1
846 nmin = 1 << log2(blen)
846 nmin = 1 << log2(blen)
847 if nmin > min:
847 if nmin > min:
848 min = nmin
848 min = nmin
849 if min > max:
849 if min > max:
850 min = max
850 min = max
851 yield ''.join(buf)
851 yield ''.join(buf)
852 blen = 0
852 blen = 0
853 buf = []
853 buf = []
854 if buf:
854 if buf:
855 yield ''.join(buf)
855 yield ''.join(buf)
856
856
857 Abort = error.Abort
857 Abort = error.Abort
858
858
859 def always(fn):
859 def always(fn):
860 return True
860 return True
861
861
862 def never(fn):
862 def never(fn):
863 return False
863 return False
864
864
865 def nogc(func):
865 def nogc(func):
866 """disable garbage collector
866 """disable garbage collector
867
867
868 Python's garbage collector triggers a GC each time a certain number of
868 Python's garbage collector triggers a GC each time a certain number of
869 container objects (the number being defined by gc.get_threshold()) are
869 container objects (the number being defined by gc.get_threshold()) are
870 allocated even when marked not to be tracked by the collector. Tracking has
870 allocated even when marked not to be tracked by the collector. Tracking has
871 no effect on when GCs are triggered, only on what objects the GC looks
871 no effect on when GCs are triggered, only on what objects the GC looks
872 into. As a workaround, disable GC while building complex (huge)
872 into. As a workaround, disable GC while building complex (huge)
873 containers.
873 containers.
874
874
875 This garbage collector issue have been fixed in 2.7.
875 This garbage collector issue have been fixed in 2.7.
876 """
876 """
877 def wrapper(*args, **kwargs):
877 def wrapper(*args, **kwargs):
878 gcenabled = gc.isenabled()
878 gcenabled = gc.isenabled()
879 gc.disable()
879 gc.disable()
880 try:
880 try:
881 return func(*args, **kwargs)
881 return func(*args, **kwargs)
882 finally:
882 finally:
883 if gcenabled:
883 if gcenabled:
884 gc.enable()
884 gc.enable()
885 return wrapper
885 return wrapper
886
886
887 def pathto(root, n1, n2):
887 def pathto(root, n1, n2):
888 '''return the relative path from one place to another.
888 '''return the relative path from one place to another.
889 root should use os.sep to separate directories
889 root should use os.sep to separate directories
890 n1 should use os.sep to separate directories
890 n1 should use os.sep to separate directories
891 n2 should use "/" to separate directories
891 n2 should use "/" to separate directories
892 returns an os.sep-separated path.
892 returns an os.sep-separated path.
893
893
894 If n1 is a relative path, it's assumed it's
894 If n1 is a relative path, it's assumed it's
895 relative to root.
895 relative to root.
896 n2 should always be relative to root.
896 n2 should always be relative to root.
897 '''
897 '''
898 if not n1:
898 if not n1:
899 return localpath(n2)
899 return localpath(n2)
900 if os.path.isabs(n1):
900 if os.path.isabs(n1):
901 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
901 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
902 return os.path.join(root, localpath(n2))
902 return os.path.join(root, localpath(n2))
903 n2 = '/'.join((pconvert(root), n2))
903 n2 = '/'.join((pconvert(root), n2))
904 a, b = splitpath(n1), n2.split('/')
904 a, b = splitpath(n1), n2.split('/')
905 a.reverse()
905 a.reverse()
906 b.reverse()
906 b.reverse()
907 while a and b and a[-1] == b[-1]:
907 while a and b and a[-1] == b[-1]:
908 a.pop()
908 a.pop()
909 b.pop()
909 b.pop()
910 b.reverse()
910 b.reverse()
911 return os.sep.join((['..'] * len(a)) + b) or '.'
911 return os.sep.join((['..'] * len(a)) + b) or '.'
912
912
913 def mainfrozen():
913 def mainfrozen():
914 """return True if we are a frozen executable.
914 """return True if we are a frozen executable.
915
915
916 The code supports py2exe (most common, Windows only) and tools/freeze
916 The code supports py2exe (most common, Windows only) and tools/freeze
917 (portable, not much used).
917 (portable, not much used).
918 """
918 """
919 return (safehasattr(sys, "frozen") or # new py2exe
919 return (safehasattr(sys, "frozen") or # new py2exe
920 safehasattr(sys, "importers") or # old py2exe
920 safehasattr(sys, "importers") or # old py2exe
921 imp.is_frozen("__main__")) # tools/freeze
921 imp.is_frozen("__main__")) # tools/freeze
922
922
923 # the location of data files matching the source code
923 # the location of data files matching the source code
924 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
924 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
925 # executable version (py2exe) doesn't support __file__
925 # executable version (py2exe) doesn't support __file__
926 datapath = os.path.dirname(sys.executable)
926 datapath = os.path.dirname(sys.executable)
927 else:
927 else:
928 datapath = os.path.dirname(__file__)
928 datapath = os.path.dirname(__file__)
929
929
930 i18n.setdatapath(datapath)
930 i18n.setdatapath(datapath)
931
931
932 _hgexecutable = None
932 _hgexecutable = None
933
933
934 def hgexecutable():
934 def hgexecutable():
935 """return location of the 'hg' executable.
935 """return location of the 'hg' executable.
936
936
937 Defaults to $HG or 'hg' in the search path.
937 Defaults to $HG or 'hg' in the search path.
938 """
938 """
939 if _hgexecutable is None:
939 if _hgexecutable is None:
940 hg = os.environ.get('HG')
940 hg = os.environ.get('HG')
941 mainmod = sys.modules['__main__']
941 mainmod = sys.modules['__main__']
942 if hg:
942 if hg:
943 _sethgexecutable(hg)
943 _sethgexecutable(hg)
944 elif mainfrozen():
944 elif mainfrozen():
945 if getattr(sys, 'frozen', None) == 'macosx_app':
945 if getattr(sys, 'frozen', None) == 'macosx_app':
946 # Env variable set by py2app
946 # Env variable set by py2app
947 _sethgexecutable(os.environ['EXECUTABLEPATH'])
947 _sethgexecutable(os.environ['EXECUTABLEPATH'])
948 else:
948 else:
949 _sethgexecutable(sys.executable)
949 _sethgexecutable(sys.executable)
950 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
950 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
951 _sethgexecutable(mainmod.__file__)
951 _sethgexecutable(mainmod.__file__)
952 else:
952 else:
953 exe = findexe('hg') or os.path.basename(sys.argv[0])
953 exe = findexe('hg') or os.path.basename(sys.argv[0])
954 _sethgexecutable(exe)
954 _sethgexecutable(exe)
955 return _hgexecutable
955 return _hgexecutable
956
956
957 def _sethgexecutable(path):
957 def _sethgexecutable(path):
958 """set location of the 'hg' executable"""
958 """set location of the 'hg' executable"""
959 global _hgexecutable
959 global _hgexecutable
960 _hgexecutable = path
960 _hgexecutable = path
961
961
962 def _isstdout(f):
962 def _isstdout(f):
963 fileno = getattr(f, 'fileno', None)
963 fileno = getattr(f, 'fileno', None)
964 return fileno and fileno() == sys.__stdout__.fileno()
964 return fileno and fileno() == sys.__stdout__.fileno()
965
965
966 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
966 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
967 '''enhanced shell command execution.
967 '''enhanced shell command execution.
968 run with environment maybe modified, maybe in different dir.
968 run with environment maybe modified, maybe in different dir.
969
969
970 if command fails and onerr is None, return status, else raise onerr
970 if command fails and onerr is None, return status, else raise onerr
971 object as exception.
971 object as exception.
972
972
973 if out is specified, it is assumed to be a file-like object that has a
973 if out is specified, it is assumed to be a file-like object that has a
974 write() method. stdout and stderr will be redirected to out.'''
974 write() method. stdout and stderr will be redirected to out.'''
975 if environ is None:
975 if environ is None:
976 environ = {}
976 environ = {}
977 try:
977 try:
978 sys.stdout.flush()
978 sys.stdout.flush()
979 except Exception:
979 except Exception:
980 pass
980 pass
981 def py2shell(val):
981 def py2shell(val):
982 'convert python object into string that is useful to shell'
982 'convert python object into string that is useful to shell'
983 if val is None or val is False:
983 if val is None or val is False:
984 return '0'
984 return '0'
985 if val is True:
985 if val is True:
986 return '1'
986 return '1'
987 return str(val)
987 return str(val)
988 origcmd = cmd
988 origcmd = cmd
989 cmd = quotecommand(cmd)
989 cmd = quotecommand(cmd)
990 if sys.platform == 'plan9' and (sys.version_info[0] == 2
990 if sys.platform == 'plan9' and (sys.version_info[0] == 2
991 and sys.version_info[1] < 7):
991 and sys.version_info[1] < 7):
992 # subprocess kludge to work around issues in half-baked Python
992 # subprocess kludge to work around issues in half-baked Python
993 # ports, notably bichued/python:
993 # ports, notably bichued/python:
994 if not cwd is None:
994 if not cwd is None:
995 os.chdir(cwd)
995 os.chdir(cwd)
996 rc = os.system(cmd)
996 rc = os.system(cmd)
997 else:
997 else:
998 env = dict(os.environ)
998 env = dict(os.environ)
999 env.update((k, py2shell(v)) for k, v in environ.iteritems())
999 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1000 env['HG'] = hgexecutable()
1000 env['HG'] = hgexecutable()
1001 if out is None or _isstdout(out):
1001 if out is None or _isstdout(out):
1002 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1002 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1003 env=env, cwd=cwd)
1003 env=env, cwd=cwd)
1004 else:
1004 else:
1005 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1005 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1006 env=env, cwd=cwd, stdout=subprocess.PIPE,
1006 env=env, cwd=cwd, stdout=subprocess.PIPE,
1007 stderr=subprocess.STDOUT)
1007 stderr=subprocess.STDOUT)
1008 while True:
1008 while True:
1009 line = proc.stdout.readline()
1009 line = proc.stdout.readline()
1010 if not line:
1010 if not line:
1011 break
1011 break
1012 out.write(line)
1012 out.write(line)
1013 proc.wait()
1013 proc.wait()
1014 rc = proc.returncode
1014 rc = proc.returncode
1015 if sys.platform == 'OpenVMS' and rc & 1:
1015 if sys.platform == 'OpenVMS' and rc & 1:
1016 rc = 0
1016 rc = 0
1017 if rc and onerr:
1017 if rc and onerr:
1018 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
1018 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
1019 explainexit(rc)[0])
1019 explainexit(rc)[0])
1020 if errprefix:
1020 if errprefix:
1021 errmsg = '%s: %s' % (errprefix, errmsg)
1021 errmsg = '%s: %s' % (errprefix, errmsg)
1022 raise onerr(errmsg)
1022 raise onerr(errmsg)
1023 return rc
1023 return rc
1024
1024
1025 def checksignature(func):
1025 def checksignature(func):
1026 '''wrap a function with code to check for calling errors'''
1026 '''wrap a function with code to check for calling errors'''
1027 def check(*args, **kwargs):
1027 def check(*args, **kwargs):
1028 try:
1028 try:
1029 return func(*args, **kwargs)
1029 return func(*args, **kwargs)
1030 except TypeError:
1030 except TypeError:
1031 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1031 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1032 raise error.SignatureError
1032 raise error.SignatureError
1033 raise
1033 raise
1034
1034
1035 return check
1035 return check
1036
1036
1037 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1037 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1038 '''copy a file, preserving mode and optionally other stat info like
1038 '''copy a file, preserving mode and optionally other stat info like
1039 atime/mtime
1039 atime/mtime
1040
1040
1041 checkambig argument is used with filestat, and is useful only if
1041 checkambig argument is used with filestat, and is useful only if
1042 destination file is guarded by any lock (e.g. repo.lock or
1042 destination file is guarded by any lock (e.g. repo.lock or
1043 repo.wlock).
1043 repo.wlock).
1044
1044
1045 copystat and checkambig should be exclusive.
1045 copystat and checkambig should be exclusive.
1046 '''
1046 '''
1047 assert not (copystat and checkambig)
1047 assert not (copystat and checkambig)
1048 oldstat = None
1048 oldstat = None
1049 if os.path.lexists(dest):
1049 if os.path.lexists(dest):
1050 if checkambig:
1050 if checkambig:
1051 oldstat = checkambig and filestat(dest)
1051 oldstat = checkambig and filestat(dest)
1052 unlink(dest)
1052 unlink(dest)
1053 # hardlinks are problematic on CIFS, quietly ignore this flag
1053 # hardlinks are problematic on CIFS, quietly ignore this flag
1054 # until we find a way to work around it cleanly (issue4546)
1054 # until we find a way to work around it cleanly (issue4546)
1055 if False and hardlink:
1055 if False and hardlink:
1056 try:
1056 try:
1057 oslink(src, dest)
1057 oslink(src, dest)
1058 return
1058 return
1059 except (IOError, OSError):
1059 except (IOError, OSError):
1060 pass # fall back to normal copy
1060 pass # fall back to normal copy
1061 if os.path.islink(src):
1061 if os.path.islink(src):
1062 os.symlink(os.readlink(src), dest)
1062 os.symlink(os.readlink(src), dest)
1063 # copytime is ignored for symlinks, but in general copytime isn't needed
1063 # copytime is ignored for symlinks, but in general copytime isn't needed
1064 # for them anyway
1064 # for them anyway
1065 else:
1065 else:
1066 try:
1066 try:
1067 shutil.copyfile(src, dest)
1067 shutil.copyfile(src, dest)
1068 if copystat:
1068 if copystat:
1069 # copystat also copies mode
1069 # copystat also copies mode
1070 shutil.copystat(src, dest)
1070 shutil.copystat(src, dest)
1071 else:
1071 else:
1072 shutil.copymode(src, dest)
1072 shutil.copymode(src, dest)
1073 if oldstat and oldstat.stat:
1073 if oldstat and oldstat.stat:
1074 newstat = filestat(dest)
1074 newstat = filestat(dest)
1075 if newstat.isambig(oldstat):
1075 if newstat.isambig(oldstat):
1076 # stat of copied file is ambiguous to original one
1076 # stat of copied file is ambiguous to original one
1077 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1077 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1078 os.utime(dest, (advanced, advanced))
1078 os.utime(dest, (advanced, advanced))
1079 except shutil.Error as inst:
1079 except shutil.Error as inst:
1080 raise Abort(str(inst))
1080 raise Abort(str(inst))
1081
1081
1082 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1082 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1083 """Copy a directory tree using hardlinks if possible."""
1083 """Copy a directory tree using hardlinks if possible."""
1084 num = 0
1084 num = 0
1085
1085
1086 if hardlink is None:
1086 if hardlink is None:
1087 hardlink = (os.stat(src).st_dev ==
1087 hardlink = (os.stat(src).st_dev ==
1088 os.stat(os.path.dirname(dst)).st_dev)
1088 os.stat(os.path.dirname(dst)).st_dev)
1089 if hardlink:
1089 if hardlink:
1090 topic = _('linking')
1090 topic = _('linking')
1091 else:
1091 else:
1092 topic = _('copying')
1092 topic = _('copying')
1093
1093
1094 if os.path.isdir(src):
1094 if os.path.isdir(src):
1095 os.mkdir(dst)
1095 os.mkdir(dst)
1096 for name, kind in osutil.listdir(src):
1096 for name, kind in osutil.listdir(src):
1097 srcname = os.path.join(src, name)
1097 srcname = os.path.join(src, name)
1098 dstname = os.path.join(dst, name)
1098 dstname = os.path.join(dst, name)
1099 def nprog(t, pos):
1099 def nprog(t, pos):
1100 if pos is not None:
1100 if pos is not None:
1101 return progress(t, pos + num)
1101 return progress(t, pos + num)
1102 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1102 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1103 num += n
1103 num += n
1104 else:
1104 else:
1105 if hardlink:
1105 if hardlink:
1106 try:
1106 try:
1107 oslink(src, dst)
1107 oslink(src, dst)
1108 except (IOError, OSError):
1108 except (IOError, OSError):
1109 hardlink = False
1109 hardlink = False
1110 shutil.copy(src, dst)
1110 shutil.copy(src, dst)
1111 else:
1111 else:
1112 shutil.copy(src, dst)
1112 shutil.copy(src, dst)
1113 num += 1
1113 num += 1
1114 progress(topic, num)
1114 progress(topic, num)
1115 progress(topic, None)
1115 progress(topic, None)
1116
1116
1117 return hardlink, num
1117 return hardlink, num
1118
1118
1119 _winreservednames = '''con prn aux nul
1119 _winreservednames = '''con prn aux nul
1120 com1 com2 com3 com4 com5 com6 com7 com8 com9
1120 com1 com2 com3 com4 com5 com6 com7 com8 com9
1121 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1121 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1122 _winreservedchars = ':*?"<>|'
1122 _winreservedchars = ':*?"<>|'
1123 def checkwinfilename(path):
1123 def checkwinfilename(path):
1124 r'''Check that the base-relative path is a valid filename on Windows.
1124 r'''Check that the base-relative path is a valid filename on Windows.
1125 Returns None if the path is ok, or a UI string describing the problem.
1125 Returns None if the path is ok, or a UI string describing the problem.
1126
1126
1127 >>> checkwinfilename("just/a/normal/path")
1127 >>> checkwinfilename("just/a/normal/path")
1128 >>> checkwinfilename("foo/bar/con.xml")
1128 >>> checkwinfilename("foo/bar/con.xml")
1129 "filename contains 'con', which is reserved on Windows"
1129 "filename contains 'con', which is reserved on Windows"
1130 >>> checkwinfilename("foo/con.xml/bar")
1130 >>> checkwinfilename("foo/con.xml/bar")
1131 "filename contains 'con', which is reserved on Windows"
1131 "filename contains 'con', which is reserved on Windows"
1132 >>> checkwinfilename("foo/bar/xml.con")
1132 >>> checkwinfilename("foo/bar/xml.con")
1133 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1133 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1134 "filename contains 'AUX', which is reserved on Windows"
1134 "filename contains 'AUX', which is reserved on Windows"
1135 >>> checkwinfilename("foo/bar/bla:.txt")
1135 >>> checkwinfilename("foo/bar/bla:.txt")
1136 "filename contains ':', which is reserved on Windows"
1136 "filename contains ':', which is reserved on Windows"
1137 >>> checkwinfilename("foo/bar/b\07la.txt")
1137 >>> checkwinfilename("foo/bar/b\07la.txt")
1138 "filename contains '\\x07', which is invalid on Windows"
1138 "filename contains '\\x07', which is invalid on Windows"
1139 >>> checkwinfilename("foo/bar/bla ")
1139 >>> checkwinfilename("foo/bar/bla ")
1140 "filename ends with ' ', which is not allowed on Windows"
1140 "filename ends with ' ', which is not allowed on Windows"
1141 >>> checkwinfilename("../bar")
1141 >>> checkwinfilename("../bar")
1142 >>> checkwinfilename("foo\\")
1142 >>> checkwinfilename("foo\\")
1143 "filename ends with '\\', which is invalid on Windows"
1143 "filename ends with '\\', which is invalid on Windows"
1144 >>> checkwinfilename("foo\\/bar")
1144 >>> checkwinfilename("foo\\/bar")
1145 "directory name ends with '\\', which is invalid on Windows"
1145 "directory name ends with '\\', which is invalid on Windows"
1146 '''
1146 '''
1147 if path.endswith('\\'):
1147 if path.endswith('\\'):
1148 return _("filename ends with '\\', which is invalid on Windows")
1148 return _("filename ends with '\\', which is invalid on Windows")
1149 if '\\/' in path:
1149 if '\\/' in path:
1150 return _("directory name ends with '\\', which is invalid on Windows")
1150 return _("directory name ends with '\\', which is invalid on Windows")
1151 for n in path.replace('\\', '/').split('/'):
1151 for n in path.replace('\\', '/').split('/'):
1152 if not n:
1152 if not n:
1153 continue
1153 continue
1154 for c in n:
1154 for c in n:
1155 if c in _winreservedchars:
1155 if c in _winreservedchars:
1156 return _("filename contains '%s', which is reserved "
1156 return _("filename contains '%s', which is reserved "
1157 "on Windows") % c
1157 "on Windows") % c
1158 if ord(c) <= 31:
1158 if ord(c) <= 31:
1159 return _("filename contains %r, which is invalid "
1159 return _("filename contains %r, which is invalid "
1160 "on Windows") % c
1160 "on Windows") % c
1161 base = n.split('.')[0]
1161 base = n.split('.')[0]
1162 if base and base.lower() in _winreservednames:
1162 if base and base.lower() in _winreservednames:
1163 return _("filename contains '%s', which is reserved "
1163 return _("filename contains '%s', which is reserved "
1164 "on Windows") % base
1164 "on Windows") % base
1165 t = n[-1]
1165 t = n[-1]
1166 if t in '. ' and n not in '..':
1166 if t in '. ' and n not in '..':
1167 return _("filename ends with '%s', which is not allowed "
1167 return _("filename ends with '%s', which is not allowed "
1168 "on Windows") % t
1168 "on Windows") % t
1169
1169
1170 if os.name == 'nt':
1170 if os.name == 'nt':
1171 checkosfilename = checkwinfilename
1171 checkosfilename = checkwinfilename
1172 else:
1172 else:
1173 checkosfilename = platform.checkosfilename
1173 checkosfilename = platform.checkosfilename
1174
1174
1175 def makelock(info, pathname):
1175 def makelock(info, pathname):
1176 try:
1176 try:
1177 return os.symlink(info, pathname)
1177 return os.symlink(info, pathname)
1178 except OSError as why:
1178 except OSError as why:
1179 if why.errno == errno.EEXIST:
1179 if why.errno == errno.EEXIST:
1180 raise
1180 raise
1181 except AttributeError: # no symlink in os
1181 except AttributeError: # no symlink in os
1182 pass
1182 pass
1183
1183
1184 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1184 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1185 os.write(ld, info)
1185 os.write(ld, info)
1186 os.close(ld)
1186 os.close(ld)
1187
1187
1188 def readlock(pathname):
1188 def readlock(pathname):
1189 try:
1189 try:
1190 return os.readlink(pathname)
1190 return os.readlink(pathname)
1191 except OSError as why:
1191 except OSError as why:
1192 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1192 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1193 raise
1193 raise
1194 except AttributeError: # no symlink in os
1194 except AttributeError: # no symlink in os
1195 pass
1195 pass
1196 fp = posixfile(pathname)
1196 fp = posixfile(pathname)
1197 r = fp.read()
1197 r = fp.read()
1198 fp.close()
1198 fp.close()
1199 return r
1199 return r
1200
1200
1201 def fstat(fp):
1201 def fstat(fp):
1202 '''stat file object that may not have fileno method.'''
1202 '''stat file object that may not have fileno method.'''
1203 try:
1203 try:
1204 return os.fstat(fp.fileno())
1204 return os.fstat(fp.fileno())
1205 except AttributeError:
1205 except AttributeError:
1206 return os.stat(fp.name)
1206 return os.stat(fp.name)
1207
1207
1208 # File system features
1208 # File system features
1209
1209
1210 def checkcase(path):
1210 def checkcase(path):
1211 """
1211 """
1212 Return true if the given path is on a case-sensitive filesystem
1212 Return true if the given path is on a case-sensitive filesystem
1213
1213
1214 Requires a path (like /foo/.hg) ending with a foldable final
1214 Requires a path (like /foo/.hg) ending with a foldable final
1215 directory component.
1215 directory component.
1216 """
1216 """
1217 s1 = os.lstat(path)
1217 s1 = os.lstat(path)
1218 d, b = os.path.split(path)
1218 d, b = os.path.split(path)
1219 b2 = b.upper()
1219 b2 = b.upper()
1220 if b == b2:
1220 if b == b2:
1221 b2 = b.lower()
1221 b2 = b.lower()
1222 if b == b2:
1222 if b == b2:
1223 return True # no evidence against case sensitivity
1223 return True # no evidence against case sensitivity
1224 p2 = os.path.join(d, b2)
1224 p2 = os.path.join(d, b2)
1225 try:
1225 try:
1226 s2 = os.lstat(p2)
1226 s2 = os.lstat(p2)
1227 if s2 == s1:
1227 if s2 == s1:
1228 return False
1228 return False
1229 return True
1229 return True
1230 except OSError:
1230 except OSError:
1231 return True
1231 return True
1232
1232
1233 try:
1233 try:
1234 import re2
1234 import re2
1235 _re2 = None
1235 _re2 = None
1236 except ImportError:
1236 except ImportError:
1237 _re2 = False
1237 _re2 = False
1238
1238
1239 class _re(object):
1239 class _re(object):
1240 def _checkre2(self):
1240 def _checkre2(self):
1241 global _re2
1241 global _re2
1242 try:
1242 try:
1243 # check if match works, see issue3964
1243 # check if match works, see issue3964
1244 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1244 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1245 except ImportError:
1245 except ImportError:
1246 _re2 = False
1246 _re2 = False
1247
1247
1248 def compile(self, pat, flags=0):
1248 def compile(self, pat, flags=0):
1249 '''Compile a regular expression, using re2 if possible
1249 '''Compile a regular expression, using re2 if possible
1250
1250
1251 For best performance, use only re2-compatible regexp features. The
1251 For best performance, use only re2-compatible regexp features. The
1252 only flags from the re module that are re2-compatible are
1252 only flags from the re module that are re2-compatible are
1253 IGNORECASE and MULTILINE.'''
1253 IGNORECASE and MULTILINE.'''
1254 if _re2 is None:
1254 if _re2 is None:
1255 self._checkre2()
1255 self._checkre2()
1256 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1256 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1257 if flags & remod.IGNORECASE:
1257 if flags & remod.IGNORECASE:
1258 pat = '(?i)' + pat
1258 pat = '(?i)' + pat
1259 if flags & remod.MULTILINE:
1259 if flags & remod.MULTILINE:
1260 pat = '(?m)' + pat
1260 pat = '(?m)' + pat
1261 try:
1261 try:
1262 return re2.compile(pat)
1262 return re2.compile(pat)
1263 except re2.error:
1263 except re2.error:
1264 pass
1264 pass
1265 return remod.compile(pat, flags)
1265 return remod.compile(pat, flags)
1266
1266
1267 @propertycache
1267 @propertycache
1268 def escape(self):
1268 def escape(self):
1269 '''Return the version of escape corresponding to self.compile.
1269 '''Return the version of escape corresponding to self.compile.
1270
1270
1271 This is imperfect because whether re2 or re is used for a particular
1271 This is imperfect because whether re2 or re is used for a particular
1272 function depends on the flags, etc, but it's the best we can do.
1272 function depends on the flags, etc, but it's the best we can do.
1273 '''
1273 '''
1274 global _re2
1274 global _re2
1275 if _re2 is None:
1275 if _re2 is None:
1276 self._checkre2()
1276 self._checkre2()
1277 if _re2:
1277 if _re2:
1278 return re2.escape
1278 return re2.escape
1279 else:
1279 else:
1280 return remod.escape
1280 return remod.escape
1281
1281
1282 re = _re()
1282 re = _re()
1283
1283
1284 _fspathcache = {}
1284 _fspathcache = {}
1285 def fspath(name, root):
1285 def fspath(name, root):
1286 '''Get name in the case stored in the filesystem
1286 '''Get name in the case stored in the filesystem
1287
1287
1288 The name should be relative to root, and be normcase-ed for efficiency.
1288 The name should be relative to root, and be normcase-ed for efficiency.
1289
1289
1290 Note that this function is unnecessary, and should not be
1290 Note that this function is unnecessary, and should not be
1291 called, for case-sensitive filesystems (simply because it's expensive).
1291 called, for case-sensitive filesystems (simply because it's expensive).
1292
1292
1293 The root should be normcase-ed, too.
1293 The root should be normcase-ed, too.
1294 '''
1294 '''
1295 def _makefspathcacheentry(dir):
1295 def _makefspathcacheentry(dir):
1296 return dict((normcase(n), n) for n in os.listdir(dir))
1296 return dict((normcase(n), n) for n in os.listdir(dir))
1297
1297
1298 seps = os.sep
1298 seps = os.sep
1299 if os.altsep:
1299 if os.altsep:
1300 seps = seps + os.altsep
1300 seps = seps + os.altsep
1301 # Protect backslashes. This gets silly very quickly.
1301 # Protect backslashes. This gets silly very quickly.
1302 seps.replace('\\','\\\\')
1302 seps.replace('\\','\\\\')
1303 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1303 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1304 dir = os.path.normpath(root)
1304 dir = os.path.normpath(root)
1305 result = []
1305 result = []
1306 for part, sep in pattern.findall(name):
1306 for part, sep in pattern.findall(name):
1307 if sep:
1307 if sep:
1308 result.append(sep)
1308 result.append(sep)
1309 continue
1309 continue
1310
1310
1311 if dir not in _fspathcache:
1311 if dir not in _fspathcache:
1312 _fspathcache[dir] = _makefspathcacheentry(dir)
1312 _fspathcache[dir] = _makefspathcacheentry(dir)
1313 contents = _fspathcache[dir]
1313 contents = _fspathcache[dir]
1314
1314
1315 found = contents.get(part)
1315 found = contents.get(part)
1316 if not found:
1316 if not found:
1317 # retry "once per directory" per "dirstate.walk" which
1317 # retry "once per directory" per "dirstate.walk" which
1318 # may take place for each patches of "hg qpush", for example
1318 # may take place for each patches of "hg qpush", for example
1319 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1319 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1320 found = contents.get(part)
1320 found = contents.get(part)
1321
1321
1322 result.append(found or part)
1322 result.append(found or part)
1323 dir = os.path.join(dir, part)
1323 dir = os.path.join(dir, part)
1324
1324
1325 return ''.join(result)
1325 return ''.join(result)
1326
1326
1327 def checknlink(testfile):
1327 def checknlink(testfile):
1328 '''check whether hardlink count reporting works properly'''
1328 '''check whether hardlink count reporting works properly'''
1329
1329
1330 # testfile may be open, so we need a separate file for checking to
1330 # testfile may be open, so we need a separate file for checking to
1331 # work around issue2543 (or testfile may get lost on Samba shares)
1331 # work around issue2543 (or testfile may get lost on Samba shares)
1332 f1 = testfile + ".hgtmp1"
1332 f1 = testfile + ".hgtmp1"
1333 if os.path.lexists(f1):
1333 if os.path.lexists(f1):
1334 return False
1334 return False
1335 try:
1335 try:
1336 posixfile(f1, 'w').close()
1336 posixfile(f1, 'w').close()
1337 except IOError:
1337 except IOError:
1338 return False
1338 return False
1339
1339
1340 f2 = testfile + ".hgtmp2"
1340 f2 = testfile + ".hgtmp2"
1341 fd = None
1341 fd = None
1342 try:
1342 try:
1343 oslink(f1, f2)
1343 oslink(f1, f2)
1344 # nlinks() may behave differently for files on Windows shares if
1344 # nlinks() may behave differently for files on Windows shares if
1345 # the file is open.
1345 # the file is open.
1346 fd = posixfile(f2)
1346 fd = posixfile(f2)
1347 return nlinks(f2) > 1
1347 return nlinks(f2) > 1
1348 except OSError:
1348 except OSError:
1349 return False
1349 return False
1350 finally:
1350 finally:
1351 if fd is not None:
1351 if fd is not None:
1352 fd.close()
1352 fd.close()
1353 for f in (f1, f2):
1353 for f in (f1, f2):
1354 try:
1354 try:
1355 os.unlink(f)
1355 os.unlink(f)
1356 except OSError:
1356 except OSError:
1357 pass
1357 pass
1358
1358
1359 def endswithsep(path):
1359 def endswithsep(path):
1360 '''Check path ends with os.sep or os.altsep.'''
1360 '''Check path ends with os.sep or os.altsep.'''
1361 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1361 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1362
1362
1363 def splitpath(path):
1363 def splitpath(path):
1364 '''Split path by os.sep.
1364 '''Split path by os.sep.
1365 Note that this function does not use os.altsep because this is
1365 Note that this function does not use os.altsep because this is
1366 an alternative of simple "xxx.split(os.sep)".
1366 an alternative of simple "xxx.split(os.sep)".
1367 It is recommended to use os.path.normpath() before using this
1367 It is recommended to use os.path.normpath() before using this
1368 function if need.'''
1368 function if need.'''
1369 return path.split(os.sep)
1369 return path.split(os.sep)
1370
1370
1371 def gui():
1371 def gui():
1372 '''Are we running in a GUI?'''
1372 '''Are we running in a GUI?'''
1373 if sys.platform == 'darwin':
1373 if sys.platform == 'darwin':
1374 if 'SSH_CONNECTION' in os.environ:
1374 if 'SSH_CONNECTION' in os.environ:
1375 # handle SSH access to a box where the user is logged in
1375 # handle SSH access to a box where the user is logged in
1376 return False
1376 return False
1377 elif getattr(osutil, 'isgui', None):
1377 elif getattr(osutil, 'isgui', None):
1378 # check if a CoreGraphics session is available
1378 # check if a CoreGraphics session is available
1379 return osutil.isgui()
1379 return osutil.isgui()
1380 else:
1380 else:
1381 # pure build; use a safe default
1381 # pure build; use a safe default
1382 return True
1382 return True
1383 else:
1383 else:
1384 return os.name == "nt" or os.environ.get("DISPLAY")
1384 return os.name == "nt" or os.environ.get("DISPLAY")
1385
1385
1386 def mktempcopy(name, emptyok=False, createmode=None):
1386 def mktempcopy(name, emptyok=False, createmode=None):
1387 """Create a temporary file with the same contents from name
1387 """Create a temporary file with the same contents from name
1388
1388
1389 The permission bits are copied from the original file.
1389 The permission bits are copied from the original file.
1390
1390
1391 If the temporary file is going to be truncated immediately, you
1391 If the temporary file is going to be truncated immediately, you
1392 can use emptyok=True as an optimization.
1392 can use emptyok=True as an optimization.
1393
1393
1394 Returns the name of the temporary file.
1394 Returns the name of the temporary file.
1395 """
1395 """
1396 d, fn = os.path.split(name)
1396 d, fn = os.path.split(name)
1397 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1397 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1398 os.close(fd)
1398 os.close(fd)
1399 # Temporary files are created with mode 0600, which is usually not
1399 # Temporary files are created with mode 0600, which is usually not
1400 # what we want. If the original file already exists, just copy
1400 # what we want. If the original file already exists, just copy
1401 # its mode. Otherwise, manually obey umask.
1401 # its mode. Otherwise, manually obey umask.
1402 copymode(name, temp, createmode)
1402 copymode(name, temp, createmode)
1403 if emptyok:
1403 if emptyok:
1404 return temp
1404 return temp
1405 try:
1405 try:
1406 try:
1406 try:
1407 ifp = posixfile(name, "rb")
1407 ifp = posixfile(name, "rb")
1408 except IOError as inst:
1408 except IOError as inst:
1409 if inst.errno == errno.ENOENT:
1409 if inst.errno == errno.ENOENT:
1410 return temp
1410 return temp
1411 if not getattr(inst, 'filename', None):
1411 if not getattr(inst, 'filename', None):
1412 inst.filename = name
1412 inst.filename = name
1413 raise
1413 raise
1414 ofp = posixfile(temp, "wb")
1414 ofp = posixfile(temp, "wb")
1415 for chunk in filechunkiter(ifp):
1415 for chunk in filechunkiter(ifp):
1416 ofp.write(chunk)
1416 ofp.write(chunk)
1417 ifp.close()
1417 ifp.close()
1418 ofp.close()
1418 ofp.close()
1419 except: # re-raises
1419 except: # re-raises
1420 try: os.unlink(temp)
1420 try: os.unlink(temp)
1421 except OSError: pass
1421 except OSError: pass
1422 raise
1422 raise
1423 return temp
1423 return temp
1424
1424
1425 class filestat(object):
1425 class filestat(object):
1426 """help to exactly detect change of a file
1426 """help to exactly detect change of a file
1427
1427
1428 'stat' attribute is result of 'os.stat()' if specified 'path'
1428 'stat' attribute is result of 'os.stat()' if specified 'path'
1429 exists. Otherwise, it is None. This can avoid preparative
1429 exists. Otherwise, it is None. This can avoid preparative
1430 'exists()' examination on client side of this class.
1430 'exists()' examination on client side of this class.
1431 """
1431 """
1432 def __init__(self, path):
1432 def __init__(self, path):
1433 try:
1433 try:
1434 self.stat = os.stat(path)
1434 self.stat = os.stat(path)
1435 except OSError as err:
1435 except OSError as err:
1436 if err.errno != errno.ENOENT:
1436 if err.errno != errno.ENOENT:
1437 raise
1437 raise
1438 self.stat = None
1438 self.stat = None
1439
1439
1440 __hash__ = object.__hash__
1440 __hash__ = object.__hash__
1441
1441
1442 def __eq__(self, old):
1442 def __eq__(self, old):
1443 try:
1443 try:
1444 # if ambiguity between stat of new and old file is
1444 # if ambiguity between stat of new and old file is
1445 # avoided, comparision of size, ctime and mtime is enough
1445 # avoided, comparision of size, ctime and mtime is enough
1446 # to exactly detect change of a file regardless of platform
1446 # to exactly detect change of a file regardless of platform
1447 return (self.stat.st_size == old.stat.st_size and
1447 return (self.stat.st_size == old.stat.st_size and
1448 self.stat.st_ctime == old.stat.st_ctime and
1448 self.stat.st_ctime == old.stat.st_ctime and
1449 self.stat.st_mtime == old.stat.st_mtime)
1449 self.stat.st_mtime == old.stat.st_mtime)
1450 except AttributeError:
1450 except AttributeError:
1451 return False
1451 return False
1452
1452
1453 def isambig(self, old):
1453 def isambig(self, old):
1454 """Examine whether new (= self) stat is ambiguous against old one
1454 """Examine whether new (= self) stat is ambiguous against old one
1455
1455
1456 "S[N]" below means stat of a file at N-th change:
1456 "S[N]" below means stat of a file at N-th change:
1457
1457
1458 - S[n-1].ctime < S[n].ctime: can detect change of a file
1458 - S[n-1].ctime < S[n].ctime: can detect change of a file
1459 - S[n-1].ctime == S[n].ctime
1459 - S[n-1].ctime == S[n].ctime
1460 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1460 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1461 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1461 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1462 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1462 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1463 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1463 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1464
1464
1465 Case (*2) above means that a file was changed twice or more at
1465 Case (*2) above means that a file was changed twice or more at
1466 same time in sec (= S[n-1].ctime), and comparison of timestamp
1466 same time in sec (= S[n-1].ctime), and comparison of timestamp
1467 is ambiguous.
1467 is ambiguous.
1468
1468
1469 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1469 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1470 timestamp is ambiguous".
1470 timestamp is ambiguous".
1471
1471
1472 But advancing mtime only in case (*2) doesn't work as
1472 But advancing mtime only in case (*2) doesn't work as
1473 expected, because naturally advanced S[n].mtime in case (*1)
1473 expected, because naturally advanced S[n].mtime in case (*1)
1474 might be equal to manually advanced S[n-1 or earlier].mtime.
1474 might be equal to manually advanced S[n-1 or earlier].mtime.
1475
1475
1476 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1476 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1477 treated as ambiguous regardless of mtime, to avoid overlooking
1477 treated as ambiguous regardless of mtime, to avoid overlooking
1478 by confliction between such mtime.
1478 by confliction between such mtime.
1479
1479
1480 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1480 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1481 S[n].mtime", even if size of a file isn't changed.
1481 S[n].mtime", even if size of a file isn't changed.
1482 """
1482 """
1483 try:
1483 try:
1484 return (self.stat.st_ctime == old.stat.st_ctime)
1484 return (self.stat.st_ctime == old.stat.st_ctime)
1485 except AttributeError:
1485 except AttributeError:
1486 return False
1486 return False
1487
1487
1488 def __ne__(self, other):
1488 def __ne__(self, other):
1489 return not self == other
1489 return not self == other
1490
1490
1491 class atomictempfile(object):
1491 class atomictempfile(object):
1492 '''writable file object that atomically updates a file
1492 '''writable file object that atomically updates a file
1493
1493
1494 All writes will go to a temporary copy of the original file. Call
1494 All writes will go to a temporary copy of the original file. Call
1495 close() when you are done writing, and atomictempfile will rename
1495 close() when you are done writing, and atomictempfile will rename
1496 the temporary copy to the original name, making the changes
1496 the temporary copy to the original name, making the changes
1497 visible. If the object is destroyed without being closed, all your
1497 visible. If the object is destroyed without being closed, all your
1498 writes are discarded.
1498 writes are discarded.
1499
1499
1500 checkambig argument of constructor is used with filestat, and is
1500 checkambig argument of constructor is used with filestat, and is
1501 useful only if target file is guarded by any lock (e.g. repo.lock
1501 useful only if target file is guarded by any lock (e.g. repo.lock
1502 or repo.wlock).
1502 or repo.wlock).
1503 '''
1503 '''
1504 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1504 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1505 self.__name = name # permanent name
1505 self.__name = name # permanent name
1506 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1506 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1507 createmode=createmode)
1507 createmode=createmode)
1508 self._fp = posixfile(self._tempname, mode)
1508 self._fp = posixfile(self._tempname, mode)
1509 self._checkambig = checkambig
1509 self._checkambig = checkambig
1510
1510
1511 # delegated methods
1511 # delegated methods
1512 self.read = self._fp.read
1512 self.read = self._fp.read
1513 self.write = self._fp.write
1513 self.write = self._fp.write
1514 self.seek = self._fp.seek
1514 self.seek = self._fp.seek
1515 self.tell = self._fp.tell
1515 self.tell = self._fp.tell
1516 self.fileno = self._fp.fileno
1516 self.fileno = self._fp.fileno
1517
1517
1518 def close(self):
1518 def close(self):
1519 if not self._fp.closed:
1519 if not self._fp.closed:
1520 self._fp.close()
1520 self._fp.close()
1521 filename = localpath(self.__name)
1521 filename = localpath(self.__name)
1522 oldstat = self._checkambig and filestat(filename)
1522 oldstat = self._checkambig and filestat(filename)
1523 if oldstat and oldstat.stat:
1523 if oldstat and oldstat.stat:
1524 rename(self._tempname, filename)
1524 rename(self._tempname, filename)
1525 newstat = filestat(filename)
1525 newstat = filestat(filename)
1526 if newstat.isambig(oldstat):
1526 if newstat.isambig(oldstat):
1527 # stat of changed file is ambiguous to original one
1527 # stat of changed file is ambiguous to original one
1528 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1528 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1529 os.utime(filename, (advanced, advanced))
1529 os.utime(filename, (advanced, advanced))
1530 else:
1530 else:
1531 rename(self._tempname, filename)
1531 rename(self._tempname, filename)
1532
1532
1533 def discard(self):
1533 def discard(self):
1534 if not self._fp.closed:
1534 if not self._fp.closed:
1535 try:
1535 try:
1536 os.unlink(self._tempname)
1536 os.unlink(self._tempname)
1537 except OSError:
1537 except OSError:
1538 pass
1538 pass
1539 self._fp.close()
1539 self._fp.close()
1540
1540
1541 def __del__(self):
1541 def __del__(self):
1542 if safehasattr(self, '_fp'): # constructor actually did something
1542 if safehasattr(self, '_fp'): # constructor actually did something
1543 self.discard()
1543 self.discard()
1544
1544
1545 def __enter__(self):
1545 def __enter__(self):
1546 return self
1546 return self
1547
1547
1548 def __exit__(self, exctype, excvalue, traceback):
1548 def __exit__(self, exctype, excvalue, traceback):
1549 if exctype is not None:
1549 if exctype is not None:
1550 self.discard()
1550 self.discard()
1551 else:
1551 else:
1552 self.close()
1552 self.close()
1553
1553
1554 def makedirs(name, mode=None, notindexed=False):
1554 def makedirs(name, mode=None, notindexed=False):
1555 """recursive directory creation with parent mode inheritance
1555 """recursive directory creation with parent mode inheritance
1556
1556
1557 Newly created directories are marked as "not to be indexed by
1557 Newly created directories are marked as "not to be indexed by
1558 the content indexing service", if ``notindexed`` is specified
1558 the content indexing service", if ``notindexed`` is specified
1559 for "write" mode access.
1559 for "write" mode access.
1560 """
1560 """
1561 try:
1561 try:
1562 makedir(name, notindexed)
1562 makedir(name, notindexed)
1563 except OSError as err:
1563 except OSError as err:
1564 if err.errno == errno.EEXIST:
1564 if err.errno == errno.EEXIST:
1565 return
1565 return
1566 if err.errno != errno.ENOENT or not name:
1566 if err.errno != errno.ENOENT or not name:
1567 raise
1567 raise
1568 parent = os.path.dirname(os.path.abspath(name))
1568 parent = os.path.dirname(os.path.abspath(name))
1569 if parent == name:
1569 if parent == name:
1570 raise
1570 raise
1571 makedirs(parent, mode, notindexed)
1571 makedirs(parent, mode, notindexed)
1572 try:
1572 try:
1573 makedir(name, notindexed)
1573 makedir(name, notindexed)
1574 except OSError as err:
1574 except OSError as err:
1575 # Catch EEXIST to handle races
1575 # Catch EEXIST to handle races
1576 if err.errno == errno.EEXIST:
1576 if err.errno == errno.EEXIST:
1577 return
1577 return
1578 raise
1578 raise
1579 if mode is not None:
1579 if mode is not None:
1580 os.chmod(name, mode)
1580 os.chmod(name, mode)
1581
1581
1582 def readfile(path):
1582 def readfile(path):
1583 with open(path, 'rb') as fp:
1583 with open(path, 'rb') as fp:
1584 return fp.read()
1584 return fp.read()
1585
1585
1586 def writefile(path, text):
1586 def writefile(path, text):
1587 with open(path, 'wb') as fp:
1587 with open(path, 'wb') as fp:
1588 fp.write(text)
1588 fp.write(text)
1589
1589
1590 def appendfile(path, text):
1590 def appendfile(path, text):
1591 with open(path, 'ab') as fp:
1591 with open(path, 'ab') as fp:
1592 fp.write(text)
1592 fp.write(text)
1593
1593
1594 class chunkbuffer(object):
1594 class chunkbuffer(object):
1595 """Allow arbitrary sized chunks of data to be efficiently read from an
1595 """Allow arbitrary sized chunks of data to be efficiently read from an
1596 iterator over chunks of arbitrary size."""
1596 iterator over chunks of arbitrary size."""
1597
1597
1598 def __init__(self, in_iter):
1598 def __init__(self, in_iter):
1599 """in_iter is the iterator that's iterating over the input chunks.
1599 """in_iter is the iterator that's iterating over the input chunks.
1600 targetsize is how big a buffer to try to maintain."""
1600 targetsize is how big a buffer to try to maintain."""
1601 def splitbig(chunks):
1601 def splitbig(chunks):
1602 for chunk in chunks:
1602 for chunk in chunks:
1603 if len(chunk) > 2**20:
1603 if len(chunk) > 2**20:
1604 pos = 0
1604 pos = 0
1605 while pos < len(chunk):
1605 while pos < len(chunk):
1606 end = pos + 2 ** 18
1606 end = pos + 2 ** 18
1607 yield chunk[pos:end]
1607 yield chunk[pos:end]
1608 pos = end
1608 pos = end
1609 else:
1609 else:
1610 yield chunk
1610 yield chunk
1611 self.iter = splitbig(in_iter)
1611 self.iter = splitbig(in_iter)
1612 self._queue = collections.deque()
1612 self._queue = collections.deque()
1613 self._chunkoffset = 0
1613 self._chunkoffset = 0
1614
1614
1615 def read(self, l=None):
1615 def read(self, l=None):
1616 """Read L bytes of data from the iterator of chunks of data.
1616 """Read L bytes of data from the iterator of chunks of data.
1617 Returns less than L bytes if the iterator runs dry.
1617 Returns less than L bytes if the iterator runs dry.
1618
1618
1619 If size parameter is omitted, read everything"""
1619 If size parameter is omitted, read everything"""
1620 if l is None:
1620 if l is None:
1621 return ''.join(self.iter)
1621 return ''.join(self.iter)
1622
1622
1623 left = l
1623 left = l
1624 buf = []
1624 buf = []
1625 queue = self._queue
1625 queue = self._queue
1626 while left > 0:
1626 while left > 0:
1627 # refill the queue
1627 # refill the queue
1628 if not queue:
1628 if not queue:
1629 target = 2**18
1629 target = 2**18
1630 for chunk in self.iter:
1630 for chunk in self.iter:
1631 queue.append(chunk)
1631 queue.append(chunk)
1632 target -= len(chunk)
1632 target -= len(chunk)
1633 if target <= 0:
1633 if target <= 0:
1634 break
1634 break
1635 if not queue:
1635 if not queue:
1636 break
1636 break
1637
1637
1638 # The easy way to do this would be to queue.popleft(), modify the
1638 # The easy way to do this would be to queue.popleft(), modify the
1639 # chunk (if necessary), then queue.appendleft(). However, for cases
1639 # chunk (if necessary), then queue.appendleft(). However, for cases
1640 # where we read partial chunk content, this incurs 2 dequeue
1640 # where we read partial chunk content, this incurs 2 dequeue
1641 # mutations and creates a new str for the remaining chunk in the
1641 # mutations and creates a new str for the remaining chunk in the
1642 # queue. Our code below avoids this overhead.
1642 # queue. Our code below avoids this overhead.
1643
1643
1644 chunk = queue[0]
1644 chunk = queue[0]
1645 chunkl = len(chunk)
1645 chunkl = len(chunk)
1646 offset = self._chunkoffset
1646 offset = self._chunkoffset
1647
1647
1648 # Use full chunk.
1648 # Use full chunk.
1649 if offset == 0 and left >= chunkl:
1649 if offset == 0 and left >= chunkl:
1650 left -= chunkl
1650 left -= chunkl
1651 queue.popleft()
1651 queue.popleft()
1652 buf.append(chunk)
1652 buf.append(chunk)
1653 # self._chunkoffset remains at 0.
1653 # self._chunkoffset remains at 0.
1654 continue
1654 continue
1655
1655
1656 chunkremaining = chunkl - offset
1656 chunkremaining = chunkl - offset
1657
1657
1658 # Use all of unconsumed part of chunk.
1658 # Use all of unconsumed part of chunk.
1659 if left >= chunkremaining:
1659 if left >= chunkremaining:
1660 left -= chunkremaining
1660 left -= chunkremaining
1661 queue.popleft()
1661 queue.popleft()
1662 # offset == 0 is enabled by block above, so this won't merely
1662 # offset == 0 is enabled by block above, so this won't merely
1663 # copy via ``chunk[0:]``.
1663 # copy via ``chunk[0:]``.
1664 buf.append(chunk[offset:])
1664 buf.append(chunk[offset:])
1665 self._chunkoffset = 0
1665 self._chunkoffset = 0
1666
1666
1667 # Partial chunk needed.
1667 # Partial chunk needed.
1668 else:
1668 else:
1669 buf.append(chunk[offset:offset + left])
1669 buf.append(chunk[offset:offset + left])
1670 self._chunkoffset += left
1670 self._chunkoffset += left
1671 left -= chunkremaining
1671 left -= chunkremaining
1672
1672
1673 return ''.join(buf)
1673 return ''.join(buf)
1674
1674
1675 def filechunkiter(f, size=65536, limit=None):
1675 def filechunkiter(f, size=65536, limit=None):
1676 """Create a generator that produces the data in the file size
1676 """Create a generator that produces the data in the file size
1677 (default 65536) bytes at a time, up to optional limit (default is
1677 (default 65536) bytes at a time, up to optional limit (default is
1678 to read all data). Chunks may be less than size bytes if the
1678 to read all data). Chunks may be less than size bytes if the
1679 chunk is the last chunk in the file, or the file is a socket or
1679 chunk is the last chunk in the file, or the file is a socket or
1680 some other type of file that sometimes reads less data than is
1680 some other type of file that sometimes reads less data than is
1681 requested."""
1681 requested."""
1682 assert size >= 0
1682 assert size >= 0
1683 assert limit is None or limit >= 0
1683 assert limit is None or limit >= 0
1684 while True:
1684 while True:
1685 if limit is None:
1685 if limit is None:
1686 nbytes = size
1686 nbytes = size
1687 else:
1687 else:
1688 nbytes = min(limit, size)
1688 nbytes = min(limit, size)
1689 s = nbytes and f.read(nbytes)
1689 s = nbytes and f.read(nbytes)
1690 if not s:
1690 if not s:
1691 break
1691 break
1692 if limit:
1692 if limit:
1693 limit -= len(s)
1693 limit -= len(s)
1694 yield s
1694 yield s
1695
1695
1696 def makedate(timestamp=None):
1696 def makedate(timestamp=None):
1697 '''Return a unix timestamp (or the current time) as a (unixtime,
1697 '''Return a unix timestamp (or the current time) as a (unixtime,
1698 offset) tuple based off the local timezone.'''
1698 offset) tuple based off the local timezone.'''
1699 if timestamp is None:
1699 if timestamp is None:
1700 timestamp = time.time()
1700 timestamp = time.time()
1701 if timestamp < 0:
1701 if timestamp < 0:
1702 hint = _("check your clock")
1702 hint = _("check your clock")
1703 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1703 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1704 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1704 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1705 datetime.datetime.fromtimestamp(timestamp))
1705 datetime.datetime.fromtimestamp(timestamp))
1706 tz = delta.days * 86400 + delta.seconds
1706 tz = delta.days * 86400 + delta.seconds
1707 return timestamp, tz
1707 return timestamp, tz
1708
1708
1709 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1709 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1710 """represent a (unixtime, offset) tuple as a localized time.
1710 """represent a (unixtime, offset) tuple as a localized time.
1711 unixtime is seconds since the epoch, and offset is the time zone's
1711 unixtime is seconds since the epoch, and offset is the time zone's
1712 number of seconds away from UTC.
1712 number of seconds away from UTC.
1713
1713
1714 >>> datestr((0, 0))
1714 >>> datestr((0, 0))
1715 'Thu Jan 01 00:00:00 1970 +0000'
1715 'Thu Jan 01 00:00:00 1970 +0000'
1716 >>> datestr((42, 0))
1716 >>> datestr((42, 0))
1717 'Thu Jan 01 00:00:42 1970 +0000'
1717 'Thu Jan 01 00:00:42 1970 +0000'
1718 >>> datestr((-42, 0))
1718 >>> datestr((-42, 0))
1719 'Wed Dec 31 23:59:18 1969 +0000'
1719 'Wed Dec 31 23:59:18 1969 +0000'
1720 >>> datestr((0x7fffffff, 0))
1720 >>> datestr((0x7fffffff, 0))
1721 'Tue Jan 19 03:14:07 2038 +0000'
1721 'Tue Jan 19 03:14:07 2038 +0000'
1722 >>> datestr((-0x80000000, 0))
1722 >>> datestr((-0x80000000, 0))
1723 'Fri Dec 13 20:45:52 1901 +0000'
1723 'Fri Dec 13 20:45:52 1901 +0000'
1724 """
1724 """
1725 t, tz = date or makedate()
1725 t, tz = date or makedate()
1726 if "%1" in format or "%2" in format or "%z" in format:
1726 if "%1" in format or "%2" in format or "%z" in format:
1727 sign = (tz > 0) and "-" or "+"
1727 sign = (tz > 0) and "-" or "+"
1728 minutes = abs(tz) // 60
1728 minutes = abs(tz) // 60
1729 q, r = divmod(minutes, 60)
1729 q, r = divmod(minutes, 60)
1730 format = format.replace("%z", "%1%2")
1730 format = format.replace("%z", "%1%2")
1731 format = format.replace("%1", "%c%02d" % (sign, q))
1731 format = format.replace("%1", "%c%02d" % (sign, q))
1732 format = format.replace("%2", "%02d" % r)
1732 format = format.replace("%2", "%02d" % r)
1733 d = t - tz
1733 d = t - tz
1734 if d > 0x7fffffff:
1734 if d > 0x7fffffff:
1735 d = 0x7fffffff
1735 d = 0x7fffffff
1736 elif d < -0x80000000:
1736 elif d < -0x80000000:
1737 d = -0x80000000
1737 d = -0x80000000
1738 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1738 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1739 # because they use the gmtime() system call which is buggy on Windows
1739 # because they use the gmtime() system call which is buggy on Windows
1740 # for negative values.
1740 # for negative values.
1741 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1741 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1742 s = t.strftime(format)
1742 s = t.strftime(format)
1743 return s
1743 return s
1744
1744
1745 def shortdate(date=None):
1745 def shortdate(date=None):
1746 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1746 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1747 return datestr(date, format='%Y-%m-%d')
1747 return datestr(date, format='%Y-%m-%d')
1748
1748
1749 def parsetimezone(tz):
1749 def parsetimezone(s):
1750 """parse a timezone string and return an offset integer"""
1750 """find a trailing timezone, if any, in string, and return a
1751 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1751 (offset, remainder) pair"""
1752 sign = (tz[0] == "+") and 1 or -1
1752
1753 hours = int(tz[1:3])
1753 if s.endswith("GMT") or s.endswith("UTC"):
1754 minutes = int(tz[3:5])
1754 return 0, s[:-3].rstrip()
1755 return -sign * (hours * 60 + minutes) * 60
1755
1756 if tz == "GMT" or tz == "UTC":
1756 # Unix-style timezones [+-]hhmm
1757 return 0
1757 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1758 return None
1758 sign = (s[-5] == "+") and 1 or -1
1759 hours = int(s[-4:-2])
1760 minutes = int(s[-2:])
1761 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1762
1763 return None, s
1759
1764
1760 def strdate(string, format, defaults=[]):
1765 def strdate(string, format, defaults=[]):
1761 """parse a localized time string and return a (unixtime, offset) tuple.
1766 """parse a localized time string and return a (unixtime, offset) tuple.
1762 if the string cannot be parsed, ValueError is raised."""
1767 if the string cannot be parsed, ValueError is raised."""
1763 # NOTE: unixtime = localunixtime + offset
1768 # NOTE: unixtime = localunixtime + offset
1764 offset, date = parsetimezone(string.split()[-1]), string
1769 offset, date = parsetimezone(string)
1765 if offset is not None:
1766 date = " ".join(string.split()[:-1])
1767
1770
1768 # add missing elements from defaults
1771 # add missing elements from defaults
1769 usenow = False # default to using biased defaults
1772 usenow = False # default to using biased defaults
1770 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1773 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1771 found = [True for p in part if ("%"+p) in format]
1774 found = [True for p in part if ("%"+p) in format]
1772 if not found:
1775 if not found:
1773 date += "@" + defaults[part][usenow]
1776 date += "@" + defaults[part][usenow]
1774 format += "@%" + part[0]
1777 format += "@%" + part[0]
1775 else:
1778 else:
1776 # We've found a specific time element, less specific time
1779 # We've found a specific time element, less specific time
1777 # elements are relative to today
1780 # elements are relative to today
1778 usenow = True
1781 usenow = True
1779
1782
1780 timetuple = time.strptime(date, format)
1783 timetuple = time.strptime(date, format)
1781 localunixtime = int(calendar.timegm(timetuple))
1784 localunixtime = int(calendar.timegm(timetuple))
1782 if offset is None:
1785 if offset is None:
1783 # local timezone
1786 # local timezone
1784 unixtime = int(time.mktime(timetuple))
1787 unixtime = int(time.mktime(timetuple))
1785 offset = unixtime - localunixtime
1788 offset = unixtime - localunixtime
1786 else:
1789 else:
1787 unixtime = localunixtime + offset
1790 unixtime = localunixtime + offset
1788 return unixtime, offset
1791 return unixtime, offset
1789
1792
1790 def parsedate(date, formats=None, bias=None):
1793 def parsedate(date, formats=None, bias=None):
1791 """parse a localized date/time and return a (unixtime, offset) tuple.
1794 """parse a localized date/time and return a (unixtime, offset) tuple.
1792
1795
1793 The date may be a "unixtime offset" string or in one of the specified
1796 The date may be a "unixtime offset" string or in one of the specified
1794 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1797 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1795
1798
1796 >>> parsedate(' today ') == parsedate(\
1799 >>> parsedate(' today ') == parsedate(\
1797 datetime.date.today().strftime('%b %d'))
1800 datetime.date.today().strftime('%b %d'))
1798 True
1801 True
1799 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1802 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1800 datetime.timedelta(days=1)\
1803 datetime.timedelta(days=1)\
1801 ).strftime('%b %d'))
1804 ).strftime('%b %d'))
1802 True
1805 True
1803 >>> now, tz = makedate()
1806 >>> now, tz = makedate()
1804 >>> strnow, strtz = parsedate('now')
1807 >>> strnow, strtz = parsedate('now')
1805 >>> (strnow - now) < 1
1808 >>> (strnow - now) < 1
1806 True
1809 True
1807 >>> tz == strtz
1810 >>> tz == strtz
1808 True
1811 True
1809 """
1812 """
1810 if bias is None:
1813 if bias is None:
1811 bias = {}
1814 bias = {}
1812 if not date:
1815 if not date:
1813 return 0, 0
1816 return 0, 0
1814 if isinstance(date, tuple) and len(date) == 2:
1817 if isinstance(date, tuple) and len(date) == 2:
1815 return date
1818 return date
1816 if not formats:
1819 if not formats:
1817 formats = defaultdateformats
1820 formats = defaultdateformats
1818 date = date.strip()
1821 date = date.strip()
1819
1822
1820 if date == 'now' or date == _('now'):
1823 if date == 'now' or date == _('now'):
1821 return makedate()
1824 return makedate()
1822 if date == 'today' or date == _('today'):
1825 if date == 'today' or date == _('today'):
1823 date = datetime.date.today().strftime('%b %d')
1826 date = datetime.date.today().strftime('%b %d')
1824 elif date == 'yesterday' or date == _('yesterday'):
1827 elif date == 'yesterday' or date == _('yesterday'):
1825 date = (datetime.date.today() -
1828 date = (datetime.date.today() -
1826 datetime.timedelta(days=1)).strftime('%b %d')
1829 datetime.timedelta(days=1)).strftime('%b %d')
1827
1830
1828 try:
1831 try:
1829 when, offset = map(int, date.split(' '))
1832 when, offset = map(int, date.split(' '))
1830 except ValueError:
1833 except ValueError:
1831 # fill out defaults
1834 # fill out defaults
1832 now = makedate()
1835 now = makedate()
1833 defaults = {}
1836 defaults = {}
1834 for part in ("d", "mb", "yY", "HI", "M", "S"):
1837 for part in ("d", "mb", "yY", "HI", "M", "S"):
1835 # this piece is for rounding the specific end of unknowns
1838 # this piece is for rounding the specific end of unknowns
1836 b = bias.get(part)
1839 b = bias.get(part)
1837 if b is None:
1840 if b is None:
1838 if part[0] in "HMS":
1841 if part[0] in "HMS":
1839 b = "00"
1842 b = "00"
1840 else:
1843 else:
1841 b = "0"
1844 b = "0"
1842
1845
1843 # this piece is for matching the generic end to today's date
1846 # this piece is for matching the generic end to today's date
1844 n = datestr(now, "%" + part[0])
1847 n = datestr(now, "%" + part[0])
1845
1848
1846 defaults[part] = (b, n)
1849 defaults[part] = (b, n)
1847
1850
1848 for format in formats:
1851 for format in formats:
1849 try:
1852 try:
1850 when, offset = strdate(date, format, defaults)
1853 when, offset = strdate(date, format, defaults)
1851 except (ValueError, OverflowError):
1854 except (ValueError, OverflowError):
1852 pass
1855 pass
1853 else:
1856 else:
1854 break
1857 break
1855 else:
1858 else:
1856 raise Abort(_('invalid date: %r') % date)
1859 raise Abort(_('invalid date: %r') % date)
1857 # validate explicit (probably user-specified) date and
1860 # validate explicit (probably user-specified) date and
1858 # time zone offset. values must fit in signed 32 bits for
1861 # time zone offset. values must fit in signed 32 bits for
1859 # current 32-bit linux runtimes. timezones go from UTC-12
1862 # current 32-bit linux runtimes. timezones go from UTC-12
1860 # to UTC+14
1863 # to UTC+14
1861 if when < -0x80000000 or when > 0x7fffffff:
1864 if when < -0x80000000 or when > 0x7fffffff:
1862 raise Abort(_('date exceeds 32 bits: %d') % when)
1865 raise Abort(_('date exceeds 32 bits: %d') % when)
1863 if offset < -50400 or offset > 43200:
1866 if offset < -50400 or offset > 43200:
1864 raise Abort(_('impossible time zone offset: %d') % offset)
1867 raise Abort(_('impossible time zone offset: %d') % offset)
1865 return when, offset
1868 return when, offset
1866
1869
1867 def matchdate(date):
1870 def matchdate(date):
1868 """Return a function that matches a given date match specifier
1871 """Return a function that matches a given date match specifier
1869
1872
1870 Formats include:
1873 Formats include:
1871
1874
1872 '{date}' match a given date to the accuracy provided
1875 '{date}' match a given date to the accuracy provided
1873
1876
1874 '<{date}' on or before a given date
1877 '<{date}' on or before a given date
1875
1878
1876 '>{date}' on or after a given date
1879 '>{date}' on or after a given date
1877
1880
1878 >>> p1 = parsedate("10:29:59")
1881 >>> p1 = parsedate("10:29:59")
1879 >>> p2 = parsedate("10:30:00")
1882 >>> p2 = parsedate("10:30:00")
1880 >>> p3 = parsedate("10:30:59")
1883 >>> p3 = parsedate("10:30:59")
1881 >>> p4 = parsedate("10:31:00")
1884 >>> p4 = parsedate("10:31:00")
1882 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1885 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1883 >>> f = matchdate("10:30")
1886 >>> f = matchdate("10:30")
1884 >>> f(p1[0])
1887 >>> f(p1[0])
1885 False
1888 False
1886 >>> f(p2[0])
1889 >>> f(p2[0])
1887 True
1890 True
1888 >>> f(p3[0])
1891 >>> f(p3[0])
1889 True
1892 True
1890 >>> f(p4[0])
1893 >>> f(p4[0])
1891 False
1894 False
1892 >>> f(p5[0])
1895 >>> f(p5[0])
1893 False
1896 False
1894 """
1897 """
1895
1898
1896 def lower(date):
1899 def lower(date):
1897 d = {'mb': "1", 'd': "1"}
1900 d = {'mb': "1", 'd': "1"}
1898 return parsedate(date, extendeddateformats, d)[0]
1901 return parsedate(date, extendeddateformats, d)[0]
1899
1902
1900 def upper(date):
1903 def upper(date):
1901 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1904 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1902 for days in ("31", "30", "29"):
1905 for days in ("31", "30", "29"):
1903 try:
1906 try:
1904 d["d"] = days
1907 d["d"] = days
1905 return parsedate(date, extendeddateformats, d)[0]
1908 return parsedate(date, extendeddateformats, d)[0]
1906 except Abort:
1909 except Abort:
1907 pass
1910 pass
1908 d["d"] = "28"
1911 d["d"] = "28"
1909 return parsedate(date, extendeddateformats, d)[0]
1912 return parsedate(date, extendeddateformats, d)[0]
1910
1913
1911 date = date.strip()
1914 date = date.strip()
1912
1915
1913 if not date:
1916 if not date:
1914 raise Abort(_("dates cannot consist entirely of whitespace"))
1917 raise Abort(_("dates cannot consist entirely of whitespace"))
1915 elif date[0] == "<":
1918 elif date[0] == "<":
1916 if not date[1:]:
1919 if not date[1:]:
1917 raise Abort(_("invalid day spec, use '<DATE'"))
1920 raise Abort(_("invalid day spec, use '<DATE'"))
1918 when = upper(date[1:])
1921 when = upper(date[1:])
1919 return lambda x: x <= when
1922 return lambda x: x <= when
1920 elif date[0] == ">":
1923 elif date[0] == ">":
1921 if not date[1:]:
1924 if not date[1:]:
1922 raise Abort(_("invalid day spec, use '>DATE'"))
1925 raise Abort(_("invalid day spec, use '>DATE'"))
1923 when = lower(date[1:])
1926 when = lower(date[1:])
1924 return lambda x: x >= when
1927 return lambda x: x >= when
1925 elif date[0] == "-":
1928 elif date[0] == "-":
1926 try:
1929 try:
1927 days = int(date[1:])
1930 days = int(date[1:])
1928 except ValueError:
1931 except ValueError:
1929 raise Abort(_("invalid day spec: %s") % date[1:])
1932 raise Abort(_("invalid day spec: %s") % date[1:])
1930 if days < 0:
1933 if days < 0:
1931 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1934 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1932 % date[1:])
1935 % date[1:])
1933 when = makedate()[0] - days * 3600 * 24
1936 when = makedate()[0] - days * 3600 * 24
1934 return lambda x: x >= when
1937 return lambda x: x >= when
1935 elif " to " in date:
1938 elif " to " in date:
1936 a, b = date.split(" to ")
1939 a, b = date.split(" to ")
1937 start, stop = lower(a), upper(b)
1940 start, stop = lower(a), upper(b)
1938 return lambda x: x >= start and x <= stop
1941 return lambda x: x >= start and x <= stop
1939 else:
1942 else:
1940 start, stop = lower(date), upper(date)
1943 start, stop = lower(date), upper(date)
1941 return lambda x: x >= start and x <= stop
1944 return lambda x: x >= start and x <= stop
1942
1945
1943 def stringmatcher(pattern):
1946 def stringmatcher(pattern):
1944 """
1947 """
1945 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1948 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1946 returns the matcher name, pattern, and matcher function.
1949 returns the matcher name, pattern, and matcher function.
1947 missing or unknown prefixes are treated as literal matches.
1950 missing or unknown prefixes are treated as literal matches.
1948
1951
1949 helper for tests:
1952 helper for tests:
1950 >>> def test(pattern, *tests):
1953 >>> def test(pattern, *tests):
1951 ... kind, pattern, matcher = stringmatcher(pattern)
1954 ... kind, pattern, matcher = stringmatcher(pattern)
1952 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1955 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1953
1956
1954 exact matching (no prefix):
1957 exact matching (no prefix):
1955 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1958 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1956 ('literal', 'abcdefg', [False, False, True])
1959 ('literal', 'abcdefg', [False, False, True])
1957
1960
1958 regex matching ('re:' prefix)
1961 regex matching ('re:' prefix)
1959 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1962 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1960 ('re', 'a.+b', [False, False, True])
1963 ('re', 'a.+b', [False, False, True])
1961
1964
1962 force exact matches ('literal:' prefix)
1965 force exact matches ('literal:' prefix)
1963 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1966 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1964 ('literal', 're:foobar', [False, True])
1967 ('literal', 're:foobar', [False, True])
1965
1968
1966 unknown prefixes are ignored and treated as literals
1969 unknown prefixes are ignored and treated as literals
1967 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1970 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1968 ('literal', 'foo:bar', [False, False, True])
1971 ('literal', 'foo:bar', [False, False, True])
1969 """
1972 """
1970 if pattern.startswith('re:'):
1973 if pattern.startswith('re:'):
1971 pattern = pattern[3:]
1974 pattern = pattern[3:]
1972 try:
1975 try:
1973 regex = remod.compile(pattern)
1976 regex = remod.compile(pattern)
1974 except remod.error as e:
1977 except remod.error as e:
1975 raise error.ParseError(_('invalid regular expression: %s')
1978 raise error.ParseError(_('invalid regular expression: %s')
1976 % e)
1979 % e)
1977 return 're', pattern, regex.search
1980 return 're', pattern, regex.search
1978 elif pattern.startswith('literal:'):
1981 elif pattern.startswith('literal:'):
1979 pattern = pattern[8:]
1982 pattern = pattern[8:]
1980 return 'literal', pattern, pattern.__eq__
1983 return 'literal', pattern, pattern.__eq__
1981
1984
1982 def shortuser(user):
1985 def shortuser(user):
1983 """Return a short representation of a user name or email address."""
1986 """Return a short representation of a user name or email address."""
1984 f = user.find('@')
1987 f = user.find('@')
1985 if f >= 0:
1988 if f >= 0:
1986 user = user[:f]
1989 user = user[:f]
1987 f = user.find('<')
1990 f = user.find('<')
1988 if f >= 0:
1991 if f >= 0:
1989 user = user[f + 1:]
1992 user = user[f + 1:]
1990 f = user.find(' ')
1993 f = user.find(' ')
1991 if f >= 0:
1994 if f >= 0:
1992 user = user[:f]
1995 user = user[:f]
1993 f = user.find('.')
1996 f = user.find('.')
1994 if f >= 0:
1997 if f >= 0:
1995 user = user[:f]
1998 user = user[:f]
1996 return user
1999 return user
1997
2000
1998 def emailuser(user):
2001 def emailuser(user):
1999 """Return the user portion of an email address."""
2002 """Return the user portion of an email address."""
2000 f = user.find('@')
2003 f = user.find('@')
2001 if f >= 0:
2004 if f >= 0:
2002 user = user[:f]
2005 user = user[:f]
2003 f = user.find('<')
2006 f = user.find('<')
2004 if f >= 0:
2007 if f >= 0:
2005 user = user[f + 1:]
2008 user = user[f + 1:]
2006 return user
2009 return user
2007
2010
2008 def email(author):
2011 def email(author):
2009 '''get email of author.'''
2012 '''get email of author.'''
2010 r = author.find('>')
2013 r = author.find('>')
2011 if r == -1:
2014 if r == -1:
2012 r = None
2015 r = None
2013 return author[author.find('<') + 1:r]
2016 return author[author.find('<') + 1:r]
2014
2017
2015 def ellipsis(text, maxlength=400):
2018 def ellipsis(text, maxlength=400):
2016 """Trim string to at most maxlength (default: 400) columns in display."""
2019 """Trim string to at most maxlength (default: 400) columns in display."""
2017 return encoding.trim(text, maxlength, ellipsis='...')
2020 return encoding.trim(text, maxlength, ellipsis='...')
2018
2021
2019 def unitcountfn(*unittable):
2022 def unitcountfn(*unittable):
2020 '''return a function that renders a readable count of some quantity'''
2023 '''return a function that renders a readable count of some quantity'''
2021
2024
2022 def go(count):
2025 def go(count):
2023 for multiplier, divisor, format in unittable:
2026 for multiplier, divisor, format in unittable:
2024 if count >= divisor * multiplier:
2027 if count >= divisor * multiplier:
2025 return format % (count / float(divisor))
2028 return format % (count / float(divisor))
2026 return unittable[-1][2] % count
2029 return unittable[-1][2] % count
2027
2030
2028 return go
2031 return go
2029
2032
2030 bytecount = unitcountfn(
2033 bytecount = unitcountfn(
2031 (100, 1 << 30, _('%.0f GB')),
2034 (100, 1 << 30, _('%.0f GB')),
2032 (10, 1 << 30, _('%.1f GB')),
2035 (10, 1 << 30, _('%.1f GB')),
2033 (1, 1 << 30, _('%.2f GB')),
2036 (1, 1 << 30, _('%.2f GB')),
2034 (100, 1 << 20, _('%.0f MB')),
2037 (100, 1 << 20, _('%.0f MB')),
2035 (10, 1 << 20, _('%.1f MB')),
2038 (10, 1 << 20, _('%.1f MB')),
2036 (1, 1 << 20, _('%.2f MB')),
2039 (1, 1 << 20, _('%.2f MB')),
2037 (100, 1 << 10, _('%.0f KB')),
2040 (100, 1 << 10, _('%.0f KB')),
2038 (10, 1 << 10, _('%.1f KB')),
2041 (10, 1 << 10, _('%.1f KB')),
2039 (1, 1 << 10, _('%.2f KB')),
2042 (1, 1 << 10, _('%.2f KB')),
2040 (1, 1, _('%.0f bytes')),
2043 (1, 1, _('%.0f bytes')),
2041 )
2044 )
2042
2045
2043 def uirepr(s):
2046 def uirepr(s):
2044 # Avoid double backslash in Windows path repr()
2047 # Avoid double backslash in Windows path repr()
2045 return repr(s).replace('\\\\', '\\')
2048 return repr(s).replace('\\\\', '\\')
2046
2049
2047 # delay import of textwrap
2050 # delay import of textwrap
2048 def MBTextWrapper(**kwargs):
2051 def MBTextWrapper(**kwargs):
2049 class tw(textwrap.TextWrapper):
2052 class tw(textwrap.TextWrapper):
2050 """
2053 """
2051 Extend TextWrapper for width-awareness.
2054 Extend TextWrapper for width-awareness.
2052
2055
2053 Neither number of 'bytes' in any encoding nor 'characters' is
2056 Neither number of 'bytes' in any encoding nor 'characters' is
2054 appropriate to calculate terminal columns for specified string.
2057 appropriate to calculate terminal columns for specified string.
2055
2058
2056 Original TextWrapper implementation uses built-in 'len()' directly,
2059 Original TextWrapper implementation uses built-in 'len()' directly,
2057 so overriding is needed to use width information of each characters.
2060 so overriding is needed to use width information of each characters.
2058
2061
2059 In addition, characters classified into 'ambiguous' width are
2062 In addition, characters classified into 'ambiguous' width are
2060 treated as wide in East Asian area, but as narrow in other.
2063 treated as wide in East Asian area, but as narrow in other.
2061
2064
2062 This requires use decision to determine width of such characters.
2065 This requires use decision to determine width of such characters.
2063 """
2066 """
2064 def _cutdown(self, ucstr, space_left):
2067 def _cutdown(self, ucstr, space_left):
2065 l = 0
2068 l = 0
2066 colwidth = encoding.ucolwidth
2069 colwidth = encoding.ucolwidth
2067 for i in xrange(len(ucstr)):
2070 for i in xrange(len(ucstr)):
2068 l += colwidth(ucstr[i])
2071 l += colwidth(ucstr[i])
2069 if space_left < l:
2072 if space_left < l:
2070 return (ucstr[:i], ucstr[i:])
2073 return (ucstr[:i], ucstr[i:])
2071 return ucstr, ''
2074 return ucstr, ''
2072
2075
2073 # overriding of base class
2076 # overriding of base class
2074 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2077 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2075 space_left = max(width - cur_len, 1)
2078 space_left = max(width - cur_len, 1)
2076
2079
2077 if self.break_long_words:
2080 if self.break_long_words:
2078 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2081 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2079 cur_line.append(cut)
2082 cur_line.append(cut)
2080 reversed_chunks[-1] = res
2083 reversed_chunks[-1] = res
2081 elif not cur_line:
2084 elif not cur_line:
2082 cur_line.append(reversed_chunks.pop())
2085 cur_line.append(reversed_chunks.pop())
2083
2086
2084 # this overriding code is imported from TextWrapper of Python 2.6
2087 # this overriding code is imported from TextWrapper of Python 2.6
2085 # to calculate columns of string by 'encoding.ucolwidth()'
2088 # to calculate columns of string by 'encoding.ucolwidth()'
2086 def _wrap_chunks(self, chunks):
2089 def _wrap_chunks(self, chunks):
2087 colwidth = encoding.ucolwidth
2090 colwidth = encoding.ucolwidth
2088
2091
2089 lines = []
2092 lines = []
2090 if self.width <= 0:
2093 if self.width <= 0:
2091 raise ValueError("invalid width %r (must be > 0)" % self.width)
2094 raise ValueError("invalid width %r (must be > 0)" % self.width)
2092
2095
2093 # Arrange in reverse order so items can be efficiently popped
2096 # Arrange in reverse order so items can be efficiently popped
2094 # from a stack of chucks.
2097 # from a stack of chucks.
2095 chunks.reverse()
2098 chunks.reverse()
2096
2099
2097 while chunks:
2100 while chunks:
2098
2101
2099 # Start the list of chunks that will make up the current line.
2102 # Start the list of chunks that will make up the current line.
2100 # cur_len is just the length of all the chunks in cur_line.
2103 # cur_len is just the length of all the chunks in cur_line.
2101 cur_line = []
2104 cur_line = []
2102 cur_len = 0
2105 cur_len = 0
2103
2106
2104 # Figure out which static string will prefix this line.
2107 # Figure out which static string will prefix this line.
2105 if lines:
2108 if lines:
2106 indent = self.subsequent_indent
2109 indent = self.subsequent_indent
2107 else:
2110 else:
2108 indent = self.initial_indent
2111 indent = self.initial_indent
2109
2112
2110 # Maximum width for this line.
2113 # Maximum width for this line.
2111 width = self.width - len(indent)
2114 width = self.width - len(indent)
2112
2115
2113 # First chunk on line is whitespace -- drop it, unless this
2116 # First chunk on line is whitespace -- drop it, unless this
2114 # is the very beginning of the text (i.e. no lines started yet).
2117 # is the very beginning of the text (i.e. no lines started yet).
2115 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2118 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2116 del chunks[-1]
2119 del chunks[-1]
2117
2120
2118 while chunks:
2121 while chunks:
2119 l = colwidth(chunks[-1])
2122 l = colwidth(chunks[-1])
2120
2123
2121 # Can at least squeeze this chunk onto the current line.
2124 # Can at least squeeze this chunk onto the current line.
2122 if cur_len + l <= width:
2125 if cur_len + l <= width:
2123 cur_line.append(chunks.pop())
2126 cur_line.append(chunks.pop())
2124 cur_len += l
2127 cur_len += l
2125
2128
2126 # Nope, this line is full.
2129 # Nope, this line is full.
2127 else:
2130 else:
2128 break
2131 break
2129
2132
2130 # The current line is full, and the next chunk is too big to
2133 # The current line is full, and the next chunk is too big to
2131 # fit on *any* line (not just this one).
2134 # fit on *any* line (not just this one).
2132 if chunks and colwidth(chunks[-1]) > width:
2135 if chunks and colwidth(chunks[-1]) > width:
2133 self._handle_long_word(chunks, cur_line, cur_len, width)
2136 self._handle_long_word(chunks, cur_line, cur_len, width)
2134
2137
2135 # If the last chunk on this line is all whitespace, drop it.
2138 # If the last chunk on this line is all whitespace, drop it.
2136 if (self.drop_whitespace and
2139 if (self.drop_whitespace and
2137 cur_line and cur_line[-1].strip() == ''):
2140 cur_line and cur_line[-1].strip() == ''):
2138 del cur_line[-1]
2141 del cur_line[-1]
2139
2142
2140 # Convert current line back to a string and store it in list
2143 # Convert current line back to a string and store it in list
2141 # of all lines (return value).
2144 # of all lines (return value).
2142 if cur_line:
2145 if cur_line:
2143 lines.append(indent + ''.join(cur_line))
2146 lines.append(indent + ''.join(cur_line))
2144
2147
2145 return lines
2148 return lines
2146
2149
2147 global MBTextWrapper
2150 global MBTextWrapper
2148 MBTextWrapper = tw
2151 MBTextWrapper = tw
2149 return tw(**kwargs)
2152 return tw(**kwargs)
2150
2153
2151 def wrap(line, width, initindent='', hangindent=''):
2154 def wrap(line, width, initindent='', hangindent=''):
2152 maxindent = max(len(hangindent), len(initindent))
2155 maxindent = max(len(hangindent), len(initindent))
2153 if width <= maxindent:
2156 if width <= maxindent:
2154 # adjust for weird terminal size
2157 # adjust for weird terminal size
2155 width = max(78, maxindent + 1)
2158 width = max(78, maxindent + 1)
2156 line = line.decode(encoding.encoding, encoding.encodingmode)
2159 line = line.decode(encoding.encoding, encoding.encodingmode)
2157 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
2160 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
2158 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
2161 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
2159 wrapper = MBTextWrapper(width=width,
2162 wrapper = MBTextWrapper(width=width,
2160 initial_indent=initindent,
2163 initial_indent=initindent,
2161 subsequent_indent=hangindent)
2164 subsequent_indent=hangindent)
2162 return wrapper.fill(line).encode(encoding.encoding)
2165 return wrapper.fill(line).encode(encoding.encoding)
2163
2166
2164 def iterlines(iterator):
2167 def iterlines(iterator):
2165 for chunk in iterator:
2168 for chunk in iterator:
2166 for line in chunk.splitlines():
2169 for line in chunk.splitlines():
2167 yield line
2170 yield line
2168
2171
2169 def expandpath(path):
2172 def expandpath(path):
2170 return os.path.expanduser(os.path.expandvars(path))
2173 return os.path.expanduser(os.path.expandvars(path))
2171
2174
2172 def hgcmd():
2175 def hgcmd():
2173 """Return the command used to execute current hg
2176 """Return the command used to execute current hg
2174
2177
2175 This is different from hgexecutable() because on Windows we want
2178 This is different from hgexecutable() because on Windows we want
2176 to avoid things opening new shell windows like batch files, so we
2179 to avoid things opening new shell windows like batch files, so we
2177 get either the python call or current executable.
2180 get either the python call or current executable.
2178 """
2181 """
2179 if mainfrozen():
2182 if mainfrozen():
2180 if getattr(sys, 'frozen', None) == 'macosx_app':
2183 if getattr(sys, 'frozen', None) == 'macosx_app':
2181 # Env variable set by py2app
2184 # Env variable set by py2app
2182 return [os.environ['EXECUTABLEPATH']]
2185 return [os.environ['EXECUTABLEPATH']]
2183 else:
2186 else:
2184 return [sys.executable]
2187 return [sys.executable]
2185 return gethgcmd()
2188 return gethgcmd()
2186
2189
2187 def rundetached(args, condfn):
2190 def rundetached(args, condfn):
2188 """Execute the argument list in a detached process.
2191 """Execute the argument list in a detached process.
2189
2192
2190 condfn is a callable which is called repeatedly and should return
2193 condfn is a callable which is called repeatedly and should return
2191 True once the child process is known to have started successfully.
2194 True once the child process is known to have started successfully.
2192 At this point, the child process PID is returned. If the child
2195 At this point, the child process PID is returned. If the child
2193 process fails to start or finishes before condfn() evaluates to
2196 process fails to start or finishes before condfn() evaluates to
2194 True, return -1.
2197 True, return -1.
2195 """
2198 """
2196 # Windows case is easier because the child process is either
2199 # Windows case is easier because the child process is either
2197 # successfully starting and validating the condition or exiting
2200 # successfully starting and validating the condition or exiting
2198 # on failure. We just poll on its PID. On Unix, if the child
2201 # on failure. We just poll on its PID. On Unix, if the child
2199 # process fails to start, it will be left in a zombie state until
2202 # process fails to start, it will be left in a zombie state until
2200 # the parent wait on it, which we cannot do since we expect a long
2203 # the parent wait on it, which we cannot do since we expect a long
2201 # running process on success. Instead we listen for SIGCHLD telling
2204 # running process on success. Instead we listen for SIGCHLD telling
2202 # us our child process terminated.
2205 # us our child process terminated.
2203 terminated = set()
2206 terminated = set()
2204 def handler(signum, frame):
2207 def handler(signum, frame):
2205 terminated.add(os.wait())
2208 terminated.add(os.wait())
2206 prevhandler = None
2209 prevhandler = None
2207 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2210 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2208 if SIGCHLD is not None:
2211 if SIGCHLD is not None:
2209 prevhandler = signal.signal(SIGCHLD, handler)
2212 prevhandler = signal.signal(SIGCHLD, handler)
2210 try:
2213 try:
2211 pid = spawndetached(args)
2214 pid = spawndetached(args)
2212 while not condfn():
2215 while not condfn():
2213 if ((pid in terminated or not testpid(pid))
2216 if ((pid in terminated or not testpid(pid))
2214 and not condfn()):
2217 and not condfn()):
2215 return -1
2218 return -1
2216 time.sleep(0.1)
2219 time.sleep(0.1)
2217 return pid
2220 return pid
2218 finally:
2221 finally:
2219 if prevhandler is not None:
2222 if prevhandler is not None:
2220 signal.signal(signal.SIGCHLD, prevhandler)
2223 signal.signal(signal.SIGCHLD, prevhandler)
2221
2224
2222 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2225 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2223 """Return the result of interpolating items in the mapping into string s.
2226 """Return the result of interpolating items in the mapping into string s.
2224
2227
2225 prefix is a single character string, or a two character string with
2228 prefix is a single character string, or a two character string with
2226 a backslash as the first character if the prefix needs to be escaped in
2229 a backslash as the first character if the prefix needs to be escaped in
2227 a regular expression.
2230 a regular expression.
2228
2231
2229 fn is an optional function that will be applied to the replacement text
2232 fn is an optional function that will be applied to the replacement text
2230 just before replacement.
2233 just before replacement.
2231
2234
2232 escape_prefix is an optional flag that allows using doubled prefix for
2235 escape_prefix is an optional flag that allows using doubled prefix for
2233 its escaping.
2236 its escaping.
2234 """
2237 """
2235 fn = fn or (lambda s: s)
2238 fn = fn or (lambda s: s)
2236 patterns = '|'.join(mapping.keys())
2239 patterns = '|'.join(mapping.keys())
2237 if escape_prefix:
2240 if escape_prefix:
2238 patterns += '|' + prefix
2241 patterns += '|' + prefix
2239 if len(prefix) > 1:
2242 if len(prefix) > 1:
2240 prefix_char = prefix[1:]
2243 prefix_char = prefix[1:]
2241 else:
2244 else:
2242 prefix_char = prefix
2245 prefix_char = prefix
2243 mapping[prefix_char] = prefix_char
2246 mapping[prefix_char] = prefix_char
2244 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2247 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2245 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2248 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2246
2249
2247 def getport(port):
2250 def getport(port):
2248 """Return the port for a given network service.
2251 """Return the port for a given network service.
2249
2252
2250 If port is an integer, it's returned as is. If it's a string, it's
2253 If port is an integer, it's returned as is. If it's a string, it's
2251 looked up using socket.getservbyname(). If there's no matching
2254 looked up using socket.getservbyname(). If there's no matching
2252 service, error.Abort is raised.
2255 service, error.Abort is raised.
2253 """
2256 """
2254 try:
2257 try:
2255 return int(port)
2258 return int(port)
2256 except ValueError:
2259 except ValueError:
2257 pass
2260 pass
2258
2261
2259 try:
2262 try:
2260 return socket.getservbyname(port)
2263 return socket.getservbyname(port)
2261 except socket.error:
2264 except socket.error:
2262 raise Abort(_("no port number associated with service '%s'") % port)
2265 raise Abort(_("no port number associated with service '%s'") % port)
2263
2266
2264 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2267 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2265 '0': False, 'no': False, 'false': False, 'off': False,
2268 '0': False, 'no': False, 'false': False, 'off': False,
2266 'never': False}
2269 'never': False}
2267
2270
2268 def parsebool(s):
2271 def parsebool(s):
2269 """Parse s into a boolean.
2272 """Parse s into a boolean.
2270
2273
2271 If s is not a valid boolean, returns None.
2274 If s is not a valid boolean, returns None.
2272 """
2275 """
2273 return _booleans.get(s.lower(), None)
2276 return _booleans.get(s.lower(), None)
2274
2277
2275 _hexdig = '0123456789ABCDEFabcdef'
2278 _hexdig = '0123456789ABCDEFabcdef'
2276 _hextochr = dict((a + b, chr(int(a + b, 16)))
2279 _hextochr = dict((a + b, chr(int(a + b, 16)))
2277 for a in _hexdig for b in _hexdig)
2280 for a in _hexdig for b in _hexdig)
2278
2281
2279 def _urlunquote(s):
2282 def _urlunquote(s):
2280 """Decode HTTP/HTML % encoding.
2283 """Decode HTTP/HTML % encoding.
2281
2284
2282 >>> _urlunquote('abc%20def')
2285 >>> _urlunquote('abc%20def')
2283 'abc def'
2286 'abc def'
2284 """
2287 """
2285 res = s.split('%')
2288 res = s.split('%')
2286 # fastpath
2289 # fastpath
2287 if len(res) == 1:
2290 if len(res) == 1:
2288 return s
2291 return s
2289 s = res[0]
2292 s = res[0]
2290 for item in res[1:]:
2293 for item in res[1:]:
2291 try:
2294 try:
2292 s += _hextochr[item[:2]] + item[2:]
2295 s += _hextochr[item[:2]] + item[2:]
2293 except KeyError:
2296 except KeyError:
2294 s += '%' + item
2297 s += '%' + item
2295 except UnicodeDecodeError:
2298 except UnicodeDecodeError:
2296 s += unichr(int(item[:2], 16)) + item[2:]
2299 s += unichr(int(item[:2], 16)) + item[2:]
2297 return s
2300 return s
2298
2301
2299 class url(object):
2302 class url(object):
2300 r"""Reliable URL parser.
2303 r"""Reliable URL parser.
2301
2304
2302 This parses URLs and provides attributes for the following
2305 This parses URLs and provides attributes for the following
2303 components:
2306 components:
2304
2307
2305 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2308 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2306
2309
2307 Missing components are set to None. The only exception is
2310 Missing components are set to None. The only exception is
2308 fragment, which is set to '' if present but empty.
2311 fragment, which is set to '' if present but empty.
2309
2312
2310 If parsefragment is False, fragment is included in query. If
2313 If parsefragment is False, fragment is included in query. If
2311 parsequery is False, query is included in path. If both are
2314 parsequery is False, query is included in path. If both are
2312 False, both fragment and query are included in path.
2315 False, both fragment and query are included in path.
2313
2316
2314 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2317 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2315
2318
2316 Note that for backward compatibility reasons, bundle URLs do not
2319 Note that for backward compatibility reasons, bundle URLs do not
2317 take host names. That means 'bundle://../' has a path of '../'.
2320 take host names. That means 'bundle://../' has a path of '../'.
2318
2321
2319 Examples:
2322 Examples:
2320
2323
2321 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2324 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2322 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2325 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2323 >>> url('ssh://[::1]:2200//home/joe/repo')
2326 >>> url('ssh://[::1]:2200//home/joe/repo')
2324 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2327 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2325 >>> url('file:///home/joe/repo')
2328 >>> url('file:///home/joe/repo')
2326 <url scheme: 'file', path: '/home/joe/repo'>
2329 <url scheme: 'file', path: '/home/joe/repo'>
2327 >>> url('file:///c:/temp/foo/')
2330 >>> url('file:///c:/temp/foo/')
2328 <url scheme: 'file', path: 'c:/temp/foo/'>
2331 <url scheme: 'file', path: 'c:/temp/foo/'>
2329 >>> url('bundle:foo')
2332 >>> url('bundle:foo')
2330 <url scheme: 'bundle', path: 'foo'>
2333 <url scheme: 'bundle', path: 'foo'>
2331 >>> url('bundle://../foo')
2334 >>> url('bundle://../foo')
2332 <url scheme: 'bundle', path: '../foo'>
2335 <url scheme: 'bundle', path: '../foo'>
2333 >>> url(r'c:\foo\bar')
2336 >>> url(r'c:\foo\bar')
2334 <url path: 'c:\\foo\\bar'>
2337 <url path: 'c:\\foo\\bar'>
2335 >>> url(r'\\blah\blah\blah')
2338 >>> url(r'\\blah\blah\blah')
2336 <url path: '\\\\blah\\blah\\blah'>
2339 <url path: '\\\\blah\\blah\\blah'>
2337 >>> url(r'\\blah\blah\blah#baz')
2340 >>> url(r'\\blah\blah\blah#baz')
2338 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2341 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2339 >>> url(r'file:///C:\users\me')
2342 >>> url(r'file:///C:\users\me')
2340 <url scheme: 'file', path: 'C:\\users\\me'>
2343 <url scheme: 'file', path: 'C:\\users\\me'>
2341
2344
2342 Authentication credentials:
2345 Authentication credentials:
2343
2346
2344 >>> url('ssh://joe:xyz@x/repo')
2347 >>> url('ssh://joe:xyz@x/repo')
2345 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2348 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2346 >>> url('ssh://joe@x/repo')
2349 >>> url('ssh://joe@x/repo')
2347 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2350 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2348
2351
2349 Query strings and fragments:
2352 Query strings and fragments:
2350
2353
2351 >>> url('http://host/a?b#c')
2354 >>> url('http://host/a?b#c')
2352 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2355 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2353 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2356 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2354 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2357 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2355 """
2358 """
2356
2359
2357 _safechars = "!~*'()+"
2360 _safechars = "!~*'()+"
2358 _safepchars = "/!~*'()+:\\"
2361 _safepchars = "/!~*'()+:\\"
2359 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2362 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2360
2363
2361 def __init__(self, path, parsequery=True, parsefragment=True):
2364 def __init__(self, path, parsequery=True, parsefragment=True):
2362 # We slowly chomp away at path until we have only the path left
2365 # We slowly chomp away at path until we have only the path left
2363 self.scheme = self.user = self.passwd = self.host = None
2366 self.scheme = self.user = self.passwd = self.host = None
2364 self.port = self.path = self.query = self.fragment = None
2367 self.port = self.path = self.query = self.fragment = None
2365 self._localpath = True
2368 self._localpath = True
2366 self._hostport = ''
2369 self._hostport = ''
2367 self._origpath = path
2370 self._origpath = path
2368
2371
2369 if parsefragment and '#' in path:
2372 if parsefragment and '#' in path:
2370 path, self.fragment = path.split('#', 1)
2373 path, self.fragment = path.split('#', 1)
2371 if not path:
2374 if not path:
2372 path = None
2375 path = None
2373
2376
2374 # special case for Windows drive letters and UNC paths
2377 # special case for Windows drive letters and UNC paths
2375 if hasdriveletter(path) or path.startswith(r'\\'):
2378 if hasdriveletter(path) or path.startswith(r'\\'):
2376 self.path = path
2379 self.path = path
2377 return
2380 return
2378
2381
2379 # For compatibility reasons, we can't handle bundle paths as
2382 # For compatibility reasons, we can't handle bundle paths as
2380 # normal URLS
2383 # normal URLS
2381 if path.startswith('bundle:'):
2384 if path.startswith('bundle:'):
2382 self.scheme = 'bundle'
2385 self.scheme = 'bundle'
2383 path = path[7:]
2386 path = path[7:]
2384 if path.startswith('//'):
2387 if path.startswith('//'):
2385 path = path[2:]
2388 path = path[2:]
2386 self.path = path
2389 self.path = path
2387 return
2390 return
2388
2391
2389 if self._matchscheme(path):
2392 if self._matchscheme(path):
2390 parts = path.split(':', 1)
2393 parts = path.split(':', 1)
2391 if parts[0]:
2394 if parts[0]:
2392 self.scheme, path = parts
2395 self.scheme, path = parts
2393 self._localpath = False
2396 self._localpath = False
2394
2397
2395 if not path:
2398 if not path:
2396 path = None
2399 path = None
2397 if self._localpath:
2400 if self._localpath:
2398 self.path = ''
2401 self.path = ''
2399 return
2402 return
2400 else:
2403 else:
2401 if self._localpath:
2404 if self._localpath:
2402 self.path = path
2405 self.path = path
2403 return
2406 return
2404
2407
2405 if parsequery and '?' in path:
2408 if parsequery and '?' in path:
2406 path, self.query = path.split('?', 1)
2409 path, self.query = path.split('?', 1)
2407 if not path:
2410 if not path:
2408 path = None
2411 path = None
2409 if not self.query:
2412 if not self.query:
2410 self.query = None
2413 self.query = None
2411
2414
2412 # // is required to specify a host/authority
2415 # // is required to specify a host/authority
2413 if path and path.startswith('//'):
2416 if path and path.startswith('//'):
2414 parts = path[2:].split('/', 1)
2417 parts = path[2:].split('/', 1)
2415 if len(parts) > 1:
2418 if len(parts) > 1:
2416 self.host, path = parts
2419 self.host, path = parts
2417 else:
2420 else:
2418 self.host = parts[0]
2421 self.host = parts[0]
2419 path = None
2422 path = None
2420 if not self.host:
2423 if not self.host:
2421 self.host = None
2424 self.host = None
2422 # path of file:///d is /d
2425 # path of file:///d is /d
2423 # path of file:///d:/ is d:/, not /d:/
2426 # path of file:///d:/ is d:/, not /d:/
2424 if path and not hasdriveletter(path):
2427 if path and not hasdriveletter(path):
2425 path = '/' + path
2428 path = '/' + path
2426
2429
2427 if self.host and '@' in self.host:
2430 if self.host and '@' in self.host:
2428 self.user, self.host = self.host.rsplit('@', 1)
2431 self.user, self.host = self.host.rsplit('@', 1)
2429 if ':' in self.user:
2432 if ':' in self.user:
2430 self.user, self.passwd = self.user.split(':', 1)
2433 self.user, self.passwd = self.user.split(':', 1)
2431 if not self.host:
2434 if not self.host:
2432 self.host = None
2435 self.host = None
2433
2436
2434 # Don't split on colons in IPv6 addresses without ports
2437 # Don't split on colons in IPv6 addresses without ports
2435 if (self.host and ':' in self.host and
2438 if (self.host and ':' in self.host and
2436 not (self.host.startswith('[') and self.host.endswith(']'))):
2439 not (self.host.startswith('[') and self.host.endswith(']'))):
2437 self._hostport = self.host
2440 self._hostport = self.host
2438 self.host, self.port = self.host.rsplit(':', 1)
2441 self.host, self.port = self.host.rsplit(':', 1)
2439 if not self.host:
2442 if not self.host:
2440 self.host = None
2443 self.host = None
2441
2444
2442 if (self.host and self.scheme == 'file' and
2445 if (self.host and self.scheme == 'file' and
2443 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2446 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2444 raise Abort(_('file:// URLs can only refer to localhost'))
2447 raise Abort(_('file:// URLs can only refer to localhost'))
2445
2448
2446 self.path = path
2449 self.path = path
2447
2450
2448 # leave the query string escaped
2451 # leave the query string escaped
2449 for a in ('user', 'passwd', 'host', 'port',
2452 for a in ('user', 'passwd', 'host', 'port',
2450 'path', 'fragment'):
2453 'path', 'fragment'):
2451 v = getattr(self, a)
2454 v = getattr(self, a)
2452 if v is not None:
2455 if v is not None:
2453 setattr(self, a, _urlunquote(v))
2456 setattr(self, a, _urlunquote(v))
2454
2457
2455 def __repr__(self):
2458 def __repr__(self):
2456 attrs = []
2459 attrs = []
2457 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2460 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2458 'query', 'fragment'):
2461 'query', 'fragment'):
2459 v = getattr(self, a)
2462 v = getattr(self, a)
2460 if v is not None:
2463 if v is not None:
2461 attrs.append('%s: %r' % (a, v))
2464 attrs.append('%s: %r' % (a, v))
2462 return '<url %s>' % ', '.join(attrs)
2465 return '<url %s>' % ', '.join(attrs)
2463
2466
2464 def __str__(self):
2467 def __str__(self):
2465 r"""Join the URL's components back into a URL string.
2468 r"""Join the URL's components back into a URL string.
2466
2469
2467 Examples:
2470 Examples:
2468
2471
2469 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2472 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2470 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2473 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2471 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2474 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2472 'http://user:pw@host:80/?foo=bar&baz=42'
2475 'http://user:pw@host:80/?foo=bar&baz=42'
2473 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2476 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2474 'http://user:pw@host:80/?foo=bar%3dbaz'
2477 'http://user:pw@host:80/?foo=bar%3dbaz'
2475 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2478 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2476 'ssh://user:pw@[::1]:2200//home/joe#'
2479 'ssh://user:pw@[::1]:2200//home/joe#'
2477 >>> str(url('http://localhost:80//'))
2480 >>> str(url('http://localhost:80//'))
2478 'http://localhost:80//'
2481 'http://localhost:80//'
2479 >>> str(url('http://localhost:80/'))
2482 >>> str(url('http://localhost:80/'))
2480 'http://localhost:80/'
2483 'http://localhost:80/'
2481 >>> str(url('http://localhost:80'))
2484 >>> str(url('http://localhost:80'))
2482 'http://localhost:80/'
2485 'http://localhost:80/'
2483 >>> str(url('bundle:foo'))
2486 >>> str(url('bundle:foo'))
2484 'bundle:foo'
2487 'bundle:foo'
2485 >>> str(url('bundle://../foo'))
2488 >>> str(url('bundle://../foo'))
2486 'bundle:../foo'
2489 'bundle:../foo'
2487 >>> str(url('path'))
2490 >>> str(url('path'))
2488 'path'
2491 'path'
2489 >>> str(url('file:///tmp/foo/bar'))
2492 >>> str(url('file:///tmp/foo/bar'))
2490 'file:///tmp/foo/bar'
2493 'file:///tmp/foo/bar'
2491 >>> str(url('file:///c:/tmp/foo/bar'))
2494 >>> str(url('file:///c:/tmp/foo/bar'))
2492 'file:///c:/tmp/foo/bar'
2495 'file:///c:/tmp/foo/bar'
2493 >>> print url(r'bundle:foo\bar')
2496 >>> print url(r'bundle:foo\bar')
2494 bundle:foo\bar
2497 bundle:foo\bar
2495 >>> print url(r'file:///D:\data\hg')
2498 >>> print url(r'file:///D:\data\hg')
2496 file:///D:\data\hg
2499 file:///D:\data\hg
2497 """
2500 """
2498 if self._localpath:
2501 if self._localpath:
2499 s = self.path
2502 s = self.path
2500 if self.scheme == 'bundle':
2503 if self.scheme == 'bundle':
2501 s = 'bundle:' + s
2504 s = 'bundle:' + s
2502 if self.fragment:
2505 if self.fragment:
2503 s += '#' + self.fragment
2506 s += '#' + self.fragment
2504 return s
2507 return s
2505
2508
2506 s = self.scheme + ':'
2509 s = self.scheme + ':'
2507 if self.user or self.passwd or self.host:
2510 if self.user or self.passwd or self.host:
2508 s += '//'
2511 s += '//'
2509 elif self.scheme and (not self.path or self.path.startswith('/')
2512 elif self.scheme and (not self.path or self.path.startswith('/')
2510 or hasdriveletter(self.path)):
2513 or hasdriveletter(self.path)):
2511 s += '//'
2514 s += '//'
2512 if hasdriveletter(self.path):
2515 if hasdriveletter(self.path):
2513 s += '/'
2516 s += '/'
2514 if self.user:
2517 if self.user:
2515 s += urlreq.quote(self.user, safe=self._safechars)
2518 s += urlreq.quote(self.user, safe=self._safechars)
2516 if self.passwd:
2519 if self.passwd:
2517 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2520 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2518 if self.user or self.passwd:
2521 if self.user or self.passwd:
2519 s += '@'
2522 s += '@'
2520 if self.host:
2523 if self.host:
2521 if not (self.host.startswith('[') and self.host.endswith(']')):
2524 if not (self.host.startswith('[') and self.host.endswith(']')):
2522 s += urlreq.quote(self.host)
2525 s += urlreq.quote(self.host)
2523 else:
2526 else:
2524 s += self.host
2527 s += self.host
2525 if self.port:
2528 if self.port:
2526 s += ':' + urlreq.quote(self.port)
2529 s += ':' + urlreq.quote(self.port)
2527 if self.host:
2530 if self.host:
2528 s += '/'
2531 s += '/'
2529 if self.path:
2532 if self.path:
2530 # TODO: similar to the query string, we should not unescape the
2533 # TODO: similar to the query string, we should not unescape the
2531 # path when we store it, the path might contain '%2f' = '/',
2534 # path when we store it, the path might contain '%2f' = '/',
2532 # which we should *not* escape.
2535 # which we should *not* escape.
2533 s += urlreq.quote(self.path, safe=self._safepchars)
2536 s += urlreq.quote(self.path, safe=self._safepchars)
2534 if self.query:
2537 if self.query:
2535 # we store the query in escaped form.
2538 # we store the query in escaped form.
2536 s += '?' + self.query
2539 s += '?' + self.query
2537 if self.fragment is not None:
2540 if self.fragment is not None:
2538 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2541 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2539 return s
2542 return s
2540
2543
2541 def authinfo(self):
2544 def authinfo(self):
2542 user, passwd = self.user, self.passwd
2545 user, passwd = self.user, self.passwd
2543 try:
2546 try:
2544 self.user, self.passwd = None, None
2547 self.user, self.passwd = None, None
2545 s = str(self)
2548 s = str(self)
2546 finally:
2549 finally:
2547 self.user, self.passwd = user, passwd
2550 self.user, self.passwd = user, passwd
2548 if not self.user:
2551 if not self.user:
2549 return (s, None)
2552 return (s, None)
2550 # authinfo[1] is passed to urllib2 password manager, and its
2553 # authinfo[1] is passed to urllib2 password manager, and its
2551 # URIs must not contain credentials. The host is passed in the
2554 # URIs must not contain credentials. The host is passed in the
2552 # URIs list because Python < 2.4.3 uses only that to search for
2555 # URIs list because Python < 2.4.3 uses only that to search for
2553 # a password.
2556 # a password.
2554 return (s, (None, (s, self.host),
2557 return (s, (None, (s, self.host),
2555 self.user, self.passwd or ''))
2558 self.user, self.passwd or ''))
2556
2559
2557 def isabs(self):
2560 def isabs(self):
2558 if self.scheme and self.scheme != 'file':
2561 if self.scheme and self.scheme != 'file':
2559 return True # remote URL
2562 return True # remote URL
2560 if hasdriveletter(self.path):
2563 if hasdriveletter(self.path):
2561 return True # absolute for our purposes - can't be joined()
2564 return True # absolute for our purposes - can't be joined()
2562 if self.path.startswith(r'\\'):
2565 if self.path.startswith(r'\\'):
2563 return True # Windows UNC path
2566 return True # Windows UNC path
2564 if self.path.startswith('/'):
2567 if self.path.startswith('/'):
2565 return True # POSIX-style
2568 return True # POSIX-style
2566 return False
2569 return False
2567
2570
2568 def localpath(self):
2571 def localpath(self):
2569 if self.scheme == 'file' or self.scheme == 'bundle':
2572 if self.scheme == 'file' or self.scheme == 'bundle':
2570 path = self.path or '/'
2573 path = self.path or '/'
2571 # For Windows, we need to promote hosts containing drive
2574 # For Windows, we need to promote hosts containing drive
2572 # letters to paths with drive letters.
2575 # letters to paths with drive letters.
2573 if hasdriveletter(self._hostport):
2576 if hasdriveletter(self._hostport):
2574 path = self._hostport + '/' + self.path
2577 path = self._hostport + '/' + self.path
2575 elif (self.host is not None and self.path
2578 elif (self.host is not None and self.path
2576 and not hasdriveletter(path)):
2579 and not hasdriveletter(path)):
2577 path = '/' + path
2580 path = '/' + path
2578 return path
2581 return path
2579 return self._origpath
2582 return self._origpath
2580
2583
2581 def islocal(self):
2584 def islocal(self):
2582 '''whether localpath will return something that posixfile can open'''
2585 '''whether localpath will return something that posixfile can open'''
2583 return (not self.scheme or self.scheme == 'file'
2586 return (not self.scheme or self.scheme == 'file'
2584 or self.scheme == 'bundle')
2587 or self.scheme == 'bundle')
2585
2588
2586 def hasscheme(path):
2589 def hasscheme(path):
2587 return bool(url(path).scheme)
2590 return bool(url(path).scheme)
2588
2591
2589 def hasdriveletter(path):
2592 def hasdriveletter(path):
2590 return path and path[1:2] == ':' and path[0:1].isalpha()
2593 return path and path[1:2] == ':' and path[0:1].isalpha()
2591
2594
2592 def urllocalpath(path):
2595 def urllocalpath(path):
2593 return url(path, parsequery=False, parsefragment=False).localpath()
2596 return url(path, parsequery=False, parsefragment=False).localpath()
2594
2597
2595 def hidepassword(u):
2598 def hidepassword(u):
2596 '''hide user credential in a url string'''
2599 '''hide user credential in a url string'''
2597 u = url(u)
2600 u = url(u)
2598 if u.passwd:
2601 if u.passwd:
2599 u.passwd = '***'
2602 u.passwd = '***'
2600 return str(u)
2603 return str(u)
2601
2604
2602 def removeauth(u):
2605 def removeauth(u):
2603 '''remove all authentication information from a url string'''
2606 '''remove all authentication information from a url string'''
2604 u = url(u)
2607 u = url(u)
2605 u.user = u.passwd = None
2608 u.user = u.passwd = None
2606 return str(u)
2609 return str(u)
2607
2610
2608 def isatty(fp):
2611 def isatty(fp):
2609 try:
2612 try:
2610 return fp.isatty()
2613 return fp.isatty()
2611 except AttributeError:
2614 except AttributeError:
2612 return False
2615 return False
2613
2616
2614 timecount = unitcountfn(
2617 timecount = unitcountfn(
2615 (1, 1e3, _('%.0f s')),
2618 (1, 1e3, _('%.0f s')),
2616 (100, 1, _('%.1f s')),
2619 (100, 1, _('%.1f s')),
2617 (10, 1, _('%.2f s')),
2620 (10, 1, _('%.2f s')),
2618 (1, 1, _('%.3f s')),
2621 (1, 1, _('%.3f s')),
2619 (100, 0.001, _('%.1f ms')),
2622 (100, 0.001, _('%.1f ms')),
2620 (10, 0.001, _('%.2f ms')),
2623 (10, 0.001, _('%.2f ms')),
2621 (1, 0.001, _('%.3f ms')),
2624 (1, 0.001, _('%.3f ms')),
2622 (100, 0.000001, _('%.1f us')),
2625 (100, 0.000001, _('%.1f us')),
2623 (10, 0.000001, _('%.2f us')),
2626 (10, 0.000001, _('%.2f us')),
2624 (1, 0.000001, _('%.3f us')),
2627 (1, 0.000001, _('%.3f us')),
2625 (100, 0.000000001, _('%.1f ns')),
2628 (100, 0.000000001, _('%.1f ns')),
2626 (10, 0.000000001, _('%.2f ns')),
2629 (10, 0.000000001, _('%.2f ns')),
2627 (1, 0.000000001, _('%.3f ns')),
2630 (1, 0.000000001, _('%.3f ns')),
2628 )
2631 )
2629
2632
2630 _timenesting = [0]
2633 _timenesting = [0]
2631
2634
2632 def timed(func):
2635 def timed(func):
2633 '''Report the execution time of a function call to stderr.
2636 '''Report the execution time of a function call to stderr.
2634
2637
2635 During development, use as a decorator when you need to measure
2638 During development, use as a decorator when you need to measure
2636 the cost of a function, e.g. as follows:
2639 the cost of a function, e.g. as follows:
2637
2640
2638 @util.timed
2641 @util.timed
2639 def foo(a, b, c):
2642 def foo(a, b, c):
2640 pass
2643 pass
2641 '''
2644 '''
2642
2645
2643 def wrapper(*args, **kwargs):
2646 def wrapper(*args, **kwargs):
2644 start = time.time()
2647 start = time.time()
2645 indent = 2
2648 indent = 2
2646 _timenesting[0] += indent
2649 _timenesting[0] += indent
2647 try:
2650 try:
2648 return func(*args, **kwargs)
2651 return func(*args, **kwargs)
2649 finally:
2652 finally:
2650 elapsed = time.time() - start
2653 elapsed = time.time() - start
2651 _timenesting[0] -= indent
2654 _timenesting[0] -= indent
2652 sys.stderr.write('%s%s: %s\n' %
2655 sys.stderr.write('%s%s: %s\n' %
2653 (' ' * _timenesting[0], func.__name__,
2656 (' ' * _timenesting[0], func.__name__,
2654 timecount(elapsed)))
2657 timecount(elapsed)))
2655 return wrapper
2658 return wrapper
2656
2659
2657 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2660 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2658 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2661 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2659
2662
2660 def sizetoint(s):
2663 def sizetoint(s):
2661 '''Convert a space specifier to a byte count.
2664 '''Convert a space specifier to a byte count.
2662
2665
2663 >>> sizetoint('30')
2666 >>> sizetoint('30')
2664 30
2667 30
2665 >>> sizetoint('2.2kb')
2668 >>> sizetoint('2.2kb')
2666 2252
2669 2252
2667 >>> sizetoint('6M')
2670 >>> sizetoint('6M')
2668 6291456
2671 6291456
2669 '''
2672 '''
2670 t = s.strip().lower()
2673 t = s.strip().lower()
2671 try:
2674 try:
2672 for k, u in _sizeunits:
2675 for k, u in _sizeunits:
2673 if t.endswith(k):
2676 if t.endswith(k):
2674 return int(float(t[:-len(k)]) * u)
2677 return int(float(t[:-len(k)]) * u)
2675 return int(t)
2678 return int(t)
2676 except ValueError:
2679 except ValueError:
2677 raise error.ParseError(_("couldn't parse size: %s") % s)
2680 raise error.ParseError(_("couldn't parse size: %s") % s)
2678
2681
2679 class hooks(object):
2682 class hooks(object):
2680 '''A collection of hook functions that can be used to extend a
2683 '''A collection of hook functions that can be used to extend a
2681 function's behavior. Hooks are called in lexicographic order,
2684 function's behavior. Hooks are called in lexicographic order,
2682 based on the names of their sources.'''
2685 based on the names of their sources.'''
2683
2686
2684 def __init__(self):
2687 def __init__(self):
2685 self._hooks = []
2688 self._hooks = []
2686
2689
2687 def add(self, source, hook):
2690 def add(self, source, hook):
2688 self._hooks.append((source, hook))
2691 self._hooks.append((source, hook))
2689
2692
2690 def __call__(self, *args):
2693 def __call__(self, *args):
2691 self._hooks.sort(key=lambda x: x[0])
2694 self._hooks.sort(key=lambda x: x[0])
2692 results = []
2695 results = []
2693 for source, hook in self._hooks:
2696 for source, hook in self._hooks:
2694 results.append(hook(*args))
2697 results.append(hook(*args))
2695 return results
2698 return results
2696
2699
2697 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s'):
2700 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s'):
2698 '''Yields lines for a nicely formatted stacktrace.
2701 '''Yields lines for a nicely formatted stacktrace.
2699 Skips the 'skip' last entries.
2702 Skips the 'skip' last entries.
2700 Each file+linenumber is formatted according to fileline.
2703 Each file+linenumber is formatted according to fileline.
2701 Each line is formatted according to line.
2704 Each line is formatted according to line.
2702 If line is None, it yields:
2705 If line is None, it yields:
2703 length of longest filepath+line number,
2706 length of longest filepath+line number,
2704 filepath+linenumber,
2707 filepath+linenumber,
2705 function
2708 function
2706
2709
2707 Not be used in production code but very convenient while developing.
2710 Not be used in production code but very convenient while developing.
2708 '''
2711 '''
2709 entries = [(fileline % (fn, ln), func)
2712 entries = [(fileline % (fn, ln), func)
2710 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2713 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2711 if entries:
2714 if entries:
2712 fnmax = max(len(entry[0]) for entry in entries)
2715 fnmax = max(len(entry[0]) for entry in entries)
2713 for fnln, func in entries:
2716 for fnln, func in entries:
2714 if line is None:
2717 if line is None:
2715 yield (fnmax, fnln, func)
2718 yield (fnmax, fnln, func)
2716 else:
2719 else:
2717 yield line % (fnmax, fnln, func)
2720 yield line % (fnmax, fnln, func)
2718
2721
2719 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2722 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2720 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2723 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2721 Skips the 'skip' last entries. By default it will flush stdout first.
2724 Skips the 'skip' last entries. By default it will flush stdout first.
2722 It can be used everywhere and intentionally does not require an ui object.
2725 It can be used everywhere and intentionally does not require an ui object.
2723 Not be used in production code but very convenient while developing.
2726 Not be used in production code but very convenient while developing.
2724 '''
2727 '''
2725 if otherf:
2728 if otherf:
2726 otherf.flush()
2729 otherf.flush()
2727 f.write('%s at:\n' % msg)
2730 f.write('%s at:\n' % msg)
2728 for line in getstackframes(skip + 1):
2731 for line in getstackframes(skip + 1):
2729 f.write(line)
2732 f.write(line)
2730 f.flush()
2733 f.flush()
2731
2734
2732 class dirs(object):
2735 class dirs(object):
2733 '''a multiset of directory names from a dirstate or manifest'''
2736 '''a multiset of directory names from a dirstate or manifest'''
2734
2737
2735 def __init__(self, map, skip=None):
2738 def __init__(self, map, skip=None):
2736 self._dirs = {}
2739 self._dirs = {}
2737 addpath = self.addpath
2740 addpath = self.addpath
2738 if safehasattr(map, 'iteritems') and skip is not None:
2741 if safehasattr(map, 'iteritems') and skip is not None:
2739 for f, s in map.iteritems():
2742 for f, s in map.iteritems():
2740 if s[0] != skip:
2743 if s[0] != skip:
2741 addpath(f)
2744 addpath(f)
2742 else:
2745 else:
2743 for f in map:
2746 for f in map:
2744 addpath(f)
2747 addpath(f)
2745
2748
2746 def addpath(self, path):
2749 def addpath(self, path):
2747 dirs = self._dirs
2750 dirs = self._dirs
2748 for base in finddirs(path):
2751 for base in finddirs(path):
2749 if base in dirs:
2752 if base in dirs:
2750 dirs[base] += 1
2753 dirs[base] += 1
2751 return
2754 return
2752 dirs[base] = 1
2755 dirs[base] = 1
2753
2756
2754 def delpath(self, path):
2757 def delpath(self, path):
2755 dirs = self._dirs
2758 dirs = self._dirs
2756 for base in finddirs(path):
2759 for base in finddirs(path):
2757 if dirs[base] > 1:
2760 if dirs[base] > 1:
2758 dirs[base] -= 1
2761 dirs[base] -= 1
2759 return
2762 return
2760 del dirs[base]
2763 del dirs[base]
2761
2764
2762 def __iter__(self):
2765 def __iter__(self):
2763 return self._dirs.iterkeys()
2766 return self._dirs.iterkeys()
2764
2767
2765 def __contains__(self, d):
2768 def __contains__(self, d):
2766 return d in self._dirs
2769 return d in self._dirs
2767
2770
2768 if safehasattr(parsers, 'dirs'):
2771 if safehasattr(parsers, 'dirs'):
2769 dirs = parsers.dirs
2772 dirs = parsers.dirs
2770
2773
2771 def finddirs(path):
2774 def finddirs(path):
2772 pos = path.rfind('/')
2775 pos = path.rfind('/')
2773 while pos != -1:
2776 while pos != -1:
2774 yield path[:pos]
2777 yield path[:pos]
2775 pos = path.rfind('/', 0, pos)
2778 pos = path.rfind('/', 0, pos)
2776
2779
2777 # compression utility
2780 # compression utility
2778
2781
2779 class nocompress(object):
2782 class nocompress(object):
2780 def compress(self, x):
2783 def compress(self, x):
2781 return x
2784 return x
2782 def flush(self):
2785 def flush(self):
2783 return ""
2786 return ""
2784
2787
2785 compressors = {
2788 compressors = {
2786 None: nocompress,
2789 None: nocompress,
2787 # lambda to prevent early import
2790 # lambda to prevent early import
2788 'BZ': lambda: bz2.BZ2Compressor(),
2791 'BZ': lambda: bz2.BZ2Compressor(),
2789 'GZ': lambda: zlib.compressobj(),
2792 'GZ': lambda: zlib.compressobj(),
2790 }
2793 }
2791 # also support the old form by courtesies
2794 # also support the old form by courtesies
2792 compressors['UN'] = compressors[None]
2795 compressors['UN'] = compressors[None]
2793
2796
2794 def _makedecompressor(decompcls):
2797 def _makedecompressor(decompcls):
2795 def generator(f):
2798 def generator(f):
2796 d = decompcls()
2799 d = decompcls()
2797 for chunk in filechunkiter(f):
2800 for chunk in filechunkiter(f):
2798 yield d.decompress(chunk)
2801 yield d.decompress(chunk)
2799 def func(fh):
2802 def func(fh):
2800 return chunkbuffer(generator(fh))
2803 return chunkbuffer(generator(fh))
2801 return func
2804 return func
2802
2805
2803 class ctxmanager(object):
2806 class ctxmanager(object):
2804 '''A context manager for use in 'with' blocks to allow multiple
2807 '''A context manager for use in 'with' blocks to allow multiple
2805 contexts to be entered at once. This is both safer and more
2808 contexts to be entered at once. This is both safer and more
2806 flexible than contextlib.nested.
2809 flexible than contextlib.nested.
2807
2810
2808 Once Mercurial supports Python 2.7+, this will become mostly
2811 Once Mercurial supports Python 2.7+, this will become mostly
2809 unnecessary.
2812 unnecessary.
2810 '''
2813 '''
2811
2814
2812 def __init__(self, *args):
2815 def __init__(self, *args):
2813 '''Accepts a list of no-argument functions that return context
2816 '''Accepts a list of no-argument functions that return context
2814 managers. These will be invoked at __call__ time.'''
2817 managers. These will be invoked at __call__ time.'''
2815 self._pending = args
2818 self._pending = args
2816 self._atexit = []
2819 self._atexit = []
2817
2820
2818 def __enter__(self):
2821 def __enter__(self):
2819 return self
2822 return self
2820
2823
2821 def enter(self):
2824 def enter(self):
2822 '''Create and enter context managers in the order in which they were
2825 '''Create and enter context managers in the order in which they were
2823 passed to the constructor.'''
2826 passed to the constructor.'''
2824 values = []
2827 values = []
2825 for func in self._pending:
2828 for func in self._pending:
2826 obj = func()
2829 obj = func()
2827 values.append(obj.__enter__())
2830 values.append(obj.__enter__())
2828 self._atexit.append(obj.__exit__)
2831 self._atexit.append(obj.__exit__)
2829 del self._pending
2832 del self._pending
2830 return values
2833 return values
2831
2834
2832 def atexit(self, func, *args, **kwargs):
2835 def atexit(self, func, *args, **kwargs):
2833 '''Add a function to call when this context manager exits. The
2836 '''Add a function to call when this context manager exits. The
2834 ordering of multiple atexit calls is unspecified, save that
2837 ordering of multiple atexit calls is unspecified, save that
2835 they will happen before any __exit__ functions.'''
2838 they will happen before any __exit__ functions.'''
2836 def wrapper(exc_type, exc_val, exc_tb):
2839 def wrapper(exc_type, exc_val, exc_tb):
2837 func(*args, **kwargs)
2840 func(*args, **kwargs)
2838 self._atexit.append(wrapper)
2841 self._atexit.append(wrapper)
2839 return func
2842 return func
2840
2843
2841 def __exit__(self, exc_type, exc_val, exc_tb):
2844 def __exit__(self, exc_type, exc_val, exc_tb):
2842 '''Context managers are exited in the reverse order from which
2845 '''Context managers are exited in the reverse order from which
2843 they were created.'''
2846 they were created.'''
2844 received = exc_type is not None
2847 received = exc_type is not None
2845 suppressed = False
2848 suppressed = False
2846 pending = None
2849 pending = None
2847 self._atexit.reverse()
2850 self._atexit.reverse()
2848 for exitfunc in self._atexit:
2851 for exitfunc in self._atexit:
2849 try:
2852 try:
2850 if exitfunc(exc_type, exc_val, exc_tb):
2853 if exitfunc(exc_type, exc_val, exc_tb):
2851 suppressed = True
2854 suppressed = True
2852 exc_type = None
2855 exc_type = None
2853 exc_val = None
2856 exc_val = None
2854 exc_tb = None
2857 exc_tb = None
2855 except BaseException:
2858 except BaseException:
2856 pending = sys.exc_info()
2859 pending = sys.exc_info()
2857 exc_type, exc_val, exc_tb = pending = sys.exc_info()
2860 exc_type, exc_val, exc_tb = pending = sys.exc_info()
2858 del self._atexit
2861 del self._atexit
2859 if pending:
2862 if pending:
2860 raise exc_val
2863 raise exc_val
2861 return received and suppressed
2864 return received and suppressed
2862
2865
2863 def _bz2():
2866 def _bz2():
2864 d = bz2.BZ2Decompressor()
2867 d = bz2.BZ2Decompressor()
2865 # Bzip2 stream start with BZ, but we stripped it.
2868 # Bzip2 stream start with BZ, but we stripped it.
2866 # we put it back for good measure.
2869 # we put it back for good measure.
2867 d.decompress('BZ')
2870 d.decompress('BZ')
2868 return d
2871 return d
2869
2872
2870 decompressors = {None: lambda fh: fh,
2873 decompressors = {None: lambda fh: fh,
2871 '_truncatedBZ': _makedecompressor(_bz2),
2874 '_truncatedBZ': _makedecompressor(_bz2),
2872 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2875 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2873 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2876 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2874 }
2877 }
2875 # also support the old form by courtesies
2878 # also support the old form by courtesies
2876 decompressors['UN'] = decompressors[None]
2879 decompressors['UN'] = decompressors[None]
2877
2880
2878 # convenient shortcut
2881 # convenient shortcut
2879 dst = debugstacktrace
2882 dst = debugstacktrace
@@ -1,3864 +1,3867 b''
1 $ hg init a
1 $ hg init a
2 $ cd a
2 $ cd a
3 $ echo a > a
3 $ echo a > a
4 $ hg add a
4 $ hg add a
5 $ echo line 1 > b
5 $ echo line 1 > b
6 $ echo line 2 >> b
6 $ echo line 2 >> b
7 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
7 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
8
8
9 $ hg add b
9 $ hg add b
10 $ echo other 1 > c
10 $ echo other 1 > c
11 $ echo other 2 >> c
11 $ echo other 2 >> c
12 $ echo >> c
12 $ echo >> c
13 $ echo other 3 >> c
13 $ echo other 3 >> c
14 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
14 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
15
15
16 $ hg add c
16 $ hg add c
17 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
17 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
18 $ echo c >> c
18 $ echo c >> c
19 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
19 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
20
20
21 $ echo foo > .hg/branch
21 $ echo foo > .hg/branch
22 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
22 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
23
23
24 $ hg co -q 3
24 $ hg co -q 3
25 $ echo other 4 >> d
25 $ echo other 4 >> d
26 $ hg add d
26 $ hg add d
27 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
27 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
28
28
29 $ hg merge -q foo
29 $ hg merge -q foo
30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
31
31
32 Second branch starting at nullrev:
32 Second branch starting at nullrev:
33
33
34 $ hg update null
34 $ hg update null
35 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
35 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
36 $ echo second > second
36 $ echo second > second
37 $ hg add second
37 $ hg add second
38 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
38 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
39 created new head
39 created new head
40
40
41 $ echo third > third
41 $ echo third > third
42 $ hg add third
42 $ hg add third
43 $ hg mv second fourth
43 $ hg mv second fourth
44 $ hg commit -m third -d "2020-01-01 10:01"
44 $ hg commit -m third -d "2020-01-01 10:01"
45
45
46 $ hg log --template '{join(file_copies, ",\n")}\n' -r .
46 $ hg log --template '{join(file_copies, ",\n")}\n' -r .
47 fourth (second)
47 fourth (second)
48 $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
48 $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
49 second -> fourth
49 second -> fourth
50 $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
50 $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
51 8 t
51 8 t
52 7 f
52 7 f
53
53
54 Working-directory revision has special identifiers, though they are still
54 Working-directory revision has special identifiers, though they are still
55 experimental:
55 experimental:
56
56
57 $ hg log -r 'wdir()' -T '{rev}:{node}\n'
57 $ hg log -r 'wdir()' -T '{rev}:{node}\n'
58 2147483647:ffffffffffffffffffffffffffffffffffffffff
58 2147483647:ffffffffffffffffffffffffffffffffffffffff
59
59
60 Some keywords are invalid for working-directory revision, but they should
60 Some keywords are invalid for working-directory revision, but they should
61 never cause crash:
61 never cause crash:
62
62
63 $ hg log -r 'wdir()' -T '{manifest}\n'
63 $ hg log -r 'wdir()' -T '{manifest}\n'
64
64
65
65
66 Quoting for ui.logtemplate
66 Quoting for ui.logtemplate
67
67
68 $ hg tip --config "ui.logtemplate={rev}\n"
68 $ hg tip --config "ui.logtemplate={rev}\n"
69 8
69 8
70 $ hg tip --config "ui.logtemplate='{rev}\n'"
70 $ hg tip --config "ui.logtemplate='{rev}\n'"
71 8
71 8
72 $ hg tip --config 'ui.logtemplate="{rev}\n"'
72 $ hg tip --config 'ui.logtemplate="{rev}\n"'
73 8
73 8
74 $ hg tip --config 'ui.logtemplate=n{rev}\n'
74 $ hg tip --config 'ui.logtemplate=n{rev}\n'
75 n8
75 n8
76
76
77 Make sure user/global hgrc does not affect tests
77 Make sure user/global hgrc does not affect tests
78
78
79 $ echo '[ui]' > .hg/hgrc
79 $ echo '[ui]' > .hg/hgrc
80 $ echo 'logtemplate =' >> .hg/hgrc
80 $ echo 'logtemplate =' >> .hg/hgrc
81 $ echo 'style =' >> .hg/hgrc
81 $ echo 'style =' >> .hg/hgrc
82
82
83 Add some simple styles to settings
83 Add some simple styles to settings
84
84
85 $ echo '[templates]' >> .hg/hgrc
85 $ echo '[templates]' >> .hg/hgrc
86 $ printf 'simple = "{rev}\\n"\n' >> .hg/hgrc
86 $ printf 'simple = "{rev}\\n"\n' >> .hg/hgrc
87 $ printf 'simple2 = {rev}\\n\n' >> .hg/hgrc
87 $ printf 'simple2 = {rev}\\n\n' >> .hg/hgrc
88
88
89 $ hg log -l1 -Tsimple
89 $ hg log -l1 -Tsimple
90 8
90 8
91 $ hg log -l1 -Tsimple2
91 $ hg log -l1 -Tsimple2
92 8
92 8
93
93
94 Test templates and style maps in files:
94 Test templates and style maps in files:
95
95
96 $ echo "{rev}" > tmpl
96 $ echo "{rev}" > tmpl
97 $ hg log -l1 -T./tmpl
97 $ hg log -l1 -T./tmpl
98 8
98 8
99 $ hg log -l1 -Tblah/blah
99 $ hg log -l1 -Tblah/blah
100 blah/blah (no-eol)
100 blah/blah (no-eol)
101
101
102 $ printf 'changeset = "{rev}\\n"\n' > map-simple
102 $ printf 'changeset = "{rev}\\n"\n' > map-simple
103 $ hg log -l1 -T./map-simple
103 $ hg log -l1 -T./map-simple
104 8
104 8
105
105
106 Template should precede style option
106 Template should precede style option
107
107
108 $ hg log -l1 --style default -T '{rev}\n'
108 $ hg log -l1 --style default -T '{rev}\n'
109 8
109 8
110
110
111 Add a commit with empty description, to ensure that the templates
111 Add a commit with empty description, to ensure that the templates
112 below will omit the description line.
112 below will omit the description line.
113
113
114 $ echo c >> c
114 $ echo c >> c
115 $ hg add c
115 $ hg add c
116 $ hg commit -qm ' '
116 $ hg commit -qm ' '
117
117
118 Default style is like normal output. Phases style should be the same
118 Default style is like normal output. Phases style should be the same
119 as default style, except for extra phase lines.
119 as default style, except for extra phase lines.
120
120
121 $ hg log > log.out
121 $ hg log > log.out
122 $ hg log --style default > style.out
122 $ hg log --style default > style.out
123 $ cmp log.out style.out || diff -u log.out style.out
123 $ cmp log.out style.out || diff -u log.out style.out
124 $ hg log -T phases > phases.out
124 $ hg log -T phases > phases.out
125 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
125 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
126 +phase: draft
126 +phase: draft
127 +phase: draft
127 +phase: draft
128 +phase: draft
128 +phase: draft
129 +phase: draft
129 +phase: draft
130 +phase: draft
130 +phase: draft
131 +phase: draft
131 +phase: draft
132 +phase: draft
132 +phase: draft
133 +phase: draft
133 +phase: draft
134 +phase: draft
134 +phase: draft
135 +phase: draft
135 +phase: draft
136
136
137 $ hg log -v > log.out
137 $ hg log -v > log.out
138 $ hg log -v --style default > style.out
138 $ hg log -v --style default > style.out
139 $ cmp log.out style.out || diff -u log.out style.out
139 $ cmp log.out style.out || diff -u log.out style.out
140 $ hg log -v -T phases > phases.out
140 $ hg log -v -T phases > phases.out
141 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
141 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
142 +phase: draft
142 +phase: draft
143 +phase: draft
143 +phase: draft
144 +phase: draft
144 +phase: draft
145 +phase: draft
145 +phase: draft
146 +phase: draft
146 +phase: draft
147 +phase: draft
147 +phase: draft
148 +phase: draft
148 +phase: draft
149 +phase: draft
149 +phase: draft
150 +phase: draft
150 +phase: draft
151 +phase: draft
151 +phase: draft
152
152
153 $ hg log -q > log.out
153 $ hg log -q > log.out
154 $ hg log -q --style default > style.out
154 $ hg log -q --style default > style.out
155 $ cmp log.out style.out || diff -u log.out style.out
155 $ cmp log.out style.out || diff -u log.out style.out
156 $ hg log -q -T phases > phases.out
156 $ hg log -q -T phases > phases.out
157 $ cmp log.out phases.out || diff -u log.out phases.out
157 $ cmp log.out phases.out || diff -u log.out phases.out
158
158
159 $ hg log --debug > log.out
159 $ hg log --debug > log.out
160 $ hg log --debug --style default > style.out
160 $ hg log --debug --style default > style.out
161 $ cmp log.out style.out || diff -u log.out style.out
161 $ cmp log.out style.out || diff -u log.out style.out
162 $ hg log --debug -T phases > phases.out
162 $ hg log --debug -T phases > phases.out
163 $ cmp log.out phases.out || diff -u log.out phases.out
163 $ cmp log.out phases.out || diff -u log.out phases.out
164
164
165 Default style of working-directory revision should also be the same (but
165 Default style of working-directory revision should also be the same (but
166 date may change while running tests):
166 date may change while running tests):
167
167
168 $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
168 $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
169 $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
169 $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
170 $ cmp log.out style.out || diff -u log.out style.out
170 $ cmp log.out style.out || diff -u log.out style.out
171
171
172 $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
172 $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
173 $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
173 $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
174 $ cmp log.out style.out || diff -u log.out style.out
174 $ cmp log.out style.out || diff -u log.out style.out
175
175
176 $ hg log -r 'wdir()' -q > log.out
176 $ hg log -r 'wdir()' -q > log.out
177 $ hg log -r 'wdir()' -q --style default > style.out
177 $ hg log -r 'wdir()' -q --style default > style.out
178 $ cmp log.out style.out || diff -u log.out style.out
178 $ cmp log.out style.out || diff -u log.out style.out
179
179
180 $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
180 $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
181 $ hg log -r 'wdir()' --debug --style default \
181 $ hg log -r 'wdir()' --debug --style default \
182 > | sed 's|^date:.*|date:|' > style.out
182 > | sed 's|^date:.*|date:|' > style.out
183 $ cmp log.out style.out || diff -u log.out style.out
183 $ cmp log.out style.out || diff -u log.out style.out
184
184
185 Default style should also preserve color information (issue2866):
185 Default style should also preserve color information (issue2866):
186
186
187 $ cp $HGRCPATH $HGRCPATH-bak
187 $ cp $HGRCPATH $HGRCPATH-bak
188 $ cat <<EOF >> $HGRCPATH
188 $ cat <<EOF >> $HGRCPATH
189 > [extensions]
189 > [extensions]
190 > color=
190 > color=
191 > EOF
191 > EOF
192
192
193 $ hg --color=debug log > log.out
193 $ hg --color=debug log > log.out
194 $ hg --color=debug log --style default > style.out
194 $ hg --color=debug log --style default > style.out
195 $ cmp log.out style.out || diff -u log.out style.out
195 $ cmp log.out style.out || diff -u log.out style.out
196 $ hg --color=debug log -T phases > phases.out
196 $ hg --color=debug log -T phases > phases.out
197 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
197 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
198 +[log.phase|phase: draft]
198 +[log.phase|phase: draft]
199 +[log.phase|phase: draft]
199 +[log.phase|phase: draft]
200 +[log.phase|phase: draft]
200 +[log.phase|phase: draft]
201 +[log.phase|phase: draft]
201 +[log.phase|phase: draft]
202 +[log.phase|phase: draft]
202 +[log.phase|phase: draft]
203 +[log.phase|phase: draft]
203 +[log.phase|phase: draft]
204 +[log.phase|phase: draft]
204 +[log.phase|phase: draft]
205 +[log.phase|phase: draft]
205 +[log.phase|phase: draft]
206 +[log.phase|phase: draft]
206 +[log.phase|phase: draft]
207 +[log.phase|phase: draft]
207 +[log.phase|phase: draft]
208
208
209 $ hg --color=debug -v log > log.out
209 $ hg --color=debug -v log > log.out
210 $ hg --color=debug -v log --style default > style.out
210 $ hg --color=debug -v log --style default > style.out
211 $ cmp log.out style.out || diff -u log.out style.out
211 $ cmp log.out style.out || diff -u log.out style.out
212 $ hg --color=debug -v log -T phases > phases.out
212 $ hg --color=debug -v log -T phases > phases.out
213 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
213 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
214 +[log.phase|phase: draft]
214 +[log.phase|phase: draft]
215 +[log.phase|phase: draft]
215 +[log.phase|phase: draft]
216 +[log.phase|phase: draft]
216 +[log.phase|phase: draft]
217 +[log.phase|phase: draft]
217 +[log.phase|phase: draft]
218 +[log.phase|phase: draft]
218 +[log.phase|phase: draft]
219 +[log.phase|phase: draft]
219 +[log.phase|phase: draft]
220 +[log.phase|phase: draft]
220 +[log.phase|phase: draft]
221 +[log.phase|phase: draft]
221 +[log.phase|phase: draft]
222 +[log.phase|phase: draft]
222 +[log.phase|phase: draft]
223 +[log.phase|phase: draft]
223 +[log.phase|phase: draft]
224
224
225 $ hg --color=debug -q log > log.out
225 $ hg --color=debug -q log > log.out
226 $ hg --color=debug -q log --style default > style.out
226 $ hg --color=debug -q log --style default > style.out
227 $ cmp log.out style.out || diff -u log.out style.out
227 $ cmp log.out style.out || diff -u log.out style.out
228 $ hg --color=debug -q log -T phases > phases.out
228 $ hg --color=debug -q log -T phases > phases.out
229 $ cmp log.out phases.out || diff -u log.out phases.out
229 $ cmp log.out phases.out || diff -u log.out phases.out
230
230
231 $ hg --color=debug --debug log > log.out
231 $ hg --color=debug --debug log > log.out
232 $ hg --color=debug --debug log --style default > style.out
232 $ hg --color=debug --debug log --style default > style.out
233 $ cmp log.out style.out || diff -u log.out style.out
233 $ cmp log.out style.out || diff -u log.out style.out
234 $ hg --color=debug --debug log -T phases > phases.out
234 $ hg --color=debug --debug log -T phases > phases.out
235 $ cmp log.out phases.out || diff -u log.out phases.out
235 $ cmp log.out phases.out || diff -u log.out phases.out
236
236
237 $ mv $HGRCPATH-bak $HGRCPATH
237 $ mv $HGRCPATH-bak $HGRCPATH
238
238
239 Remove commit with empty commit message, so as to not pollute further
239 Remove commit with empty commit message, so as to not pollute further
240 tests.
240 tests.
241
241
242 $ hg --config extensions.strip= strip -q .
242 $ hg --config extensions.strip= strip -q .
243
243
244 Revision with no copies (used to print a traceback):
244 Revision with no copies (used to print a traceback):
245
245
246 $ hg tip -v --template '\n'
246 $ hg tip -v --template '\n'
247
247
248
248
249 Compact style works:
249 Compact style works:
250
250
251 $ hg log -Tcompact
251 $ hg log -Tcompact
252 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
252 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
253 third
253 third
254
254
255 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
255 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
256 second
256 second
257
257
258 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
258 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
259 merge
259 merge
260
260
261 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
261 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
262 new head
262 new head
263
263
264 4 bbe44766e73d 1970-01-17 04:53 +0000 person
264 4 bbe44766e73d 1970-01-17 04:53 +0000 person
265 new branch
265 new branch
266
266
267 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
267 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
268 no user, no domain
268 no user, no domain
269
269
270 2 97054abb4ab8 1970-01-14 21:20 +0000 other
270 2 97054abb4ab8 1970-01-14 21:20 +0000 other
271 no person
271 no person
272
272
273 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
273 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
274 other 1
274 other 1
275
275
276 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
276 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
277 line 1
277 line 1
278
278
279
279
280 $ hg log -v --style compact
280 $ hg log -v --style compact
281 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
281 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
282 third
282 third
283
283
284 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
284 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
285 second
285 second
286
286
287 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
287 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
288 merge
288 merge
289
289
290 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
290 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
291 new head
291 new head
292
292
293 4 bbe44766e73d 1970-01-17 04:53 +0000 person
293 4 bbe44766e73d 1970-01-17 04:53 +0000 person
294 new branch
294 new branch
295
295
296 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
296 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
297 no user, no domain
297 no user, no domain
298
298
299 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
299 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
300 no person
300 no person
301
301
302 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
302 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
303 other 1
303 other 1
304 other 2
304 other 2
305
305
306 other 3
306 other 3
307
307
308 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
308 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
309 line 1
309 line 1
310 line 2
310 line 2
311
311
312
312
313 $ hg log --debug --style compact
313 $ hg log --debug --style compact
314 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
314 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
315 third
315 third
316
316
317 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
317 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
318 second
318 second
319
319
320 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
320 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
321 merge
321 merge
322
322
323 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
323 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
324 new head
324 new head
325
325
326 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
326 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
327 new branch
327 new branch
328
328
329 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
329 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
330 no user, no domain
330 no user, no domain
331
331
332 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
332 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
333 no person
333 no person
334
334
335 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
335 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
336 other 1
336 other 1
337 other 2
337 other 2
338
338
339 other 3
339 other 3
340
340
341 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
341 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
342 line 1
342 line 1
343 line 2
343 line 2
344
344
345
345
346 Test xml styles:
346 Test xml styles:
347
347
348 $ hg log --style xml -r 'not all()'
348 $ hg log --style xml -r 'not all()'
349 <?xml version="1.0"?>
349 <?xml version="1.0"?>
350 <log>
350 <log>
351 </log>
351 </log>
352
352
353 $ hg log --style xml
353 $ hg log --style xml
354 <?xml version="1.0"?>
354 <?xml version="1.0"?>
355 <log>
355 <log>
356 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
356 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
357 <tag>tip</tag>
357 <tag>tip</tag>
358 <author email="test">test</author>
358 <author email="test">test</author>
359 <date>2020-01-01T10:01:00+00:00</date>
359 <date>2020-01-01T10:01:00+00:00</date>
360 <msg xml:space="preserve">third</msg>
360 <msg xml:space="preserve">third</msg>
361 </logentry>
361 </logentry>
362 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
362 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
363 <parent revision="-1" node="0000000000000000000000000000000000000000" />
363 <parent revision="-1" node="0000000000000000000000000000000000000000" />
364 <author email="user@hostname">User Name</author>
364 <author email="user@hostname">User Name</author>
365 <date>1970-01-12T13:46:40+00:00</date>
365 <date>1970-01-12T13:46:40+00:00</date>
366 <msg xml:space="preserve">second</msg>
366 <msg xml:space="preserve">second</msg>
367 </logentry>
367 </logentry>
368 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
368 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
369 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
369 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
370 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
370 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
371 <author email="person">person</author>
371 <author email="person">person</author>
372 <date>1970-01-18T08:40:01+00:00</date>
372 <date>1970-01-18T08:40:01+00:00</date>
373 <msg xml:space="preserve">merge</msg>
373 <msg xml:space="preserve">merge</msg>
374 </logentry>
374 </logentry>
375 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
375 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
376 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
376 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
377 <author email="person">person</author>
377 <author email="person">person</author>
378 <date>1970-01-18T08:40:00+00:00</date>
378 <date>1970-01-18T08:40:00+00:00</date>
379 <msg xml:space="preserve">new head</msg>
379 <msg xml:space="preserve">new head</msg>
380 </logentry>
380 </logentry>
381 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
381 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
382 <branch>foo</branch>
382 <branch>foo</branch>
383 <author email="person">person</author>
383 <author email="person">person</author>
384 <date>1970-01-17T04:53:20+00:00</date>
384 <date>1970-01-17T04:53:20+00:00</date>
385 <msg xml:space="preserve">new branch</msg>
385 <msg xml:space="preserve">new branch</msg>
386 </logentry>
386 </logentry>
387 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
387 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
388 <author email="person">person</author>
388 <author email="person">person</author>
389 <date>1970-01-16T01:06:40+00:00</date>
389 <date>1970-01-16T01:06:40+00:00</date>
390 <msg xml:space="preserve">no user, no domain</msg>
390 <msg xml:space="preserve">no user, no domain</msg>
391 </logentry>
391 </logentry>
392 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
392 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
393 <author email="other@place">other</author>
393 <author email="other@place">other</author>
394 <date>1970-01-14T21:20:00+00:00</date>
394 <date>1970-01-14T21:20:00+00:00</date>
395 <msg xml:space="preserve">no person</msg>
395 <msg xml:space="preserve">no person</msg>
396 </logentry>
396 </logentry>
397 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
397 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
398 <author email="other@place">A. N. Other</author>
398 <author email="other@place">A. N. Other</author>
399 <date>1970-01-13T17:33:20+00:00</date>
399 <date>1970-01-13T17:33:20+00:00</date>
400 <msg xml:space="preserve">other 1
400 <msg xml:space="preserve">other 1
401 other 2
401 other 2
402
402
403 other 3</msg>
403 other 3</msg>
404 </logentry>
404 </logentry>
405 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
405 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
406 <author email="user@hostname">User Name</author>
406 <author email="user@hostname">User Name</author>
407 <date>1970-01-12T13:46:40+00:00</date>
407 <date>1970-01-12T13:46:40+00:00</date>
408 <msg xml:space="preserve">line 1
408 <msg xml:space="preserve">line 1
409 line 2</msg>
409 line 2</msg>
410 </logentry>
410 </logentry>
411 </log>
411 </log>
412
412
413 $ hg log -v --style xml
413 $ hg log -v --style xml
414 <?xml version="1.0"?>
414 <?xml version="1.0"?>
415 <log>
415 <log>
416 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
416 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
417 <tag>tip</tag>
417 <tag>tip</tag>
418 <author email="test">test</author>
418 <author email="test">test</author>
419 <date>2020-01-01T10:01:00+00:00</date>
419 <date>2020-01-01T10:01:00+00:00</date>
420 <msg xml:space="preserve">third</msg>
420 <msg xml:space="preserve">third</msg>
421 <paths>
421 <paths>
422 <path action="A">fourth</path>
422 <path action="A">fourth</path>
423 <path action="A">third</path>
423 <path action="A">third</path>
424 <path action="R">second</path>
424 <path action="R">second</path>
425 </paths>
425 </paths>
426 <copies>
426 <copies>
427 <copy source="second">fourth</copy>
427 <copy source="second">fourth</copy>
428 </copies>
428 </copies>
429 </logentry>
429 </logentry>
430 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
430 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
431 <parent revision="-1" node="0000000000000000000000000000000000000000" />
431 <parent revision="-1" node="0000000000000000000000000000000000000000" />
432 <author email="user@hostname">User Name</author>
432 <author email="user@hostname">User Name</author>
433 <date>1970-01-12T13:46:40+00:00</date>
433 <date>1970-01-12T13:46:40+00:00</date>
434 <msg xml:space="preserve">second</msg>
434 <msg xml:space="preserve">second</msg>
435 <paths>
435 <paths>
436 <path action="A">second</path>
436 <path action="A">second</path>
437 </paths>
437 </paths>
438 </logentry>
438 </logentry>
439 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
439 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
440 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
440 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
441 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
441 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
442 <author email="person">person</author>
442 <author email="person">person</author>
443 <date>1970-01-18T08:40:01+00:00</date>
443 <date>1970-01-18T08:40:01+00:00</date>
444 <msg xml:space="preserve">merge</msg>
444 <msg xml:space="preserve">merge</msg>
445 <paths>
445 <paths>
446 </paths>
446 </paths>
447 </logentry>
447 </logentry>
448 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
448 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
449 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
449 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
450 <author email="person">person</author>
450 <author email="person">person</author>
451 <date>1970-01-18T08:40:00+00:00</date>
451 <date>1970-01-18T08:40:00+00:00</date>
452 <msg xml:space="preserve">new head</msg>
452 <msg xml:space="preserve">new head</msg>
453 <paths>
453 <paths>
454 <path action="A">d</path>
454 <path action="A">d</path>
455 </paths>
455 </paths>
456 </logentry>
456 </logentry>
457 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
457 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
458 <branch>foo</branch>
458 <branch>foo</branch>
459 <author email="person">person</author>
459 <author email="person">person</author>
460 <date>1970-01-17T04:53:20+00:00</date>
460 <date>1970-01-17T04:53:20+00:00</date>
461 <msg xml:space="preserve">new branch</msg>
461 <msg xml:space="preserve">new branch</msg>
462 <paths>
462 <paths>
463 </paths>
463 </paths>
464 </logentry>
464 </logentry>
465 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
465 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
466 <author email="person">person</author>
466 <author email="person">person</author>
467 <date>1970-01-16T01:06:40+00:00</date>
467 <date>1970-01-16T01:06:40+00:00</date>
468 <msg xml:space="preserve">no user, no domain</msg>
468 <msg xml:space="preserve">no user, no domain</msg>
469 <paths>
469 <paths>
470 <path action="M">c</path>
470 <path action="M">c</path>
471 </paths>
471 </paths>
472 </logentry>
472 </logentry>
473 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
473 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
474 <author email="other@place">other</author>
474 <author email="other@place">other</author>
475 <date>1970-01-14T21:20:00+00:00</date>
475 <date>1970-01-14T21:20:00+00:00</date>
476 <msg xml:space="preserve">no person</msg>
476 <msg xml:space="preserve">no person</msg>
477 <paths>
477 <paths>
478 <path action="A">c</path>
478 <path action="A">c</path>
479 </paths>
479 </paths>
480 </logentry>
480 </logentry>
481 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
481 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
482 <author email="other@place">A. N. Other</author>
482 <author email="other@place">A. N. Other</author>
483 <date>1970-01-13T17:33:20+00:00</date>
483 <date>1970-01-13T17:33:20+00:00</date>
484 <msg xml:space="preserve">other 1
484 <msg xml:space="preserve">other 1
485 other 2
485 other 2
486
486
487 other 3</msg>
487 other 3</msg>
488 <paths>
488 <paths>
489 <path action="A">b</path>
489 <path action="A">b</path>
490 </paths>
490 </paths>
491 </logentry>
491 </logentry>
492 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
492 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
493 <author email="user@hostname">User Name</author>
493 <author email="user@hostname">User Name</author>
494 <date>1970-01-12T13:46:40+00:00</date>
494 <date>1970-01-12T13:46:40+00:00</date>
495 <msg xml:space="preserve">line 1
495 <msg xml:space="preserve">line 1
496 line 2</msg>
496 line 2</msg>
497 <paths>
497 <paths>
498 <path action="A">a</path>
498 <path action="A">a</path>
499 </paths>
499 </paths>
500 </logentry>
500 </logentry>
501 </log>
501 </log>
502
502
503 $ hg log --debug --style xml
503 $ hg log --debug --style xml
504 <?xml version="1.0"?>
504 <?xml version="1.0"?>
505 <log>
505 <log>
506 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
506 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
507 <tag>tip</tag>
507 <tag>tip</tag>
508 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
508 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
509 <parent revision="-1" node="0000000000000000000000000000000000000000" />
509 <parent revision="-1" node="0000000000000000000000000000000000000000" />
510 <author email="test">test</author>
510 <author email="test">test</author>
511 <date>2020-01-01T10:01:00+00:00</date>
511 <date>2020-01-01T10:01:00+00:00</date>
512 <msg xml:space="preserve">third</msg>
512 <msg xml:space="preserve">third</msg>
513 <paths>
513 <paths>
514 <path action="A">fourth</path>
514 <path action="A">fourth</path>
515 <path action="A">third</path>
515 <path action="A">third</path>
516 <path action="R">second</path>
516 <path action="R">second</path>
517 </paths>
517 </paths>
518 <copies>
518 <copies>
519 <copy source="second">fourth</copy>
519 <copy source="second">fourth</copy>
520 </copies>
520 </copies>
521 <extra key="branch">default</extra>
521 <extra key="branch">default</extra>
522 </logentry>
522 </logentry>
523 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
523 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
524 <parent revision="-1" node="0000000000000000000000000000000000000000" />
524 <parent revision="-1" node="0000000000000000000000000000000000000000" />
525 <parent revision="-1" node="0000000000000000000000000000000000000000" />
525 <parent revision="-1" node="0000000000000000000000000000000000000000" />
526 <author email="user@hostname">User Name</author>
526 <author email="user@hostname">User Name</author>
527 <date>1970-01-12T13:46:40+00:00</date>
527 <date>1970-01-12T13:46:40+00:00</date>
528 <msg xml:space="preserve">second</msg>
528 <msg xml:space="preserve">second</msg>
529 <paths>
529 <paths>
530 <path action="A">second</path>
530 <path action="A">second</path>
531 </paths>
531 </paths>
532 <extra key="branch">default</extra>
532 <extra key="branch">default</extra>
533 </logentry>
533 </logentry>
534 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
534 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
535 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
535 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
536 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
536 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
537 <author email="person">person</author>
537 <author email="person">person</author>
538 <date>1970-01-18T08:40:01+00:00</date>
538 <date>1970-01-18T08:40:01+00:00</date>
539 <msg xml:space="preserve">merge</msg>
539 <msg xml:space="preserve">merge</msg>
540 <paths>
540 <paths>
541 </paths>
541 </paths>
542 <extra key="branch">default</extra>
542 <extra key="branch">default</extra>
543 </logentry>
543 </logentry>
544 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
544 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
545 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
545 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
546 <parent revision="-1" node="0000000000000000000000000000000000000000" />
546 <parent revision="-1" node="0000000000000000000000000000000000000000" />
547 <author email="person">person</author>
547 <author email="person">person</author>
548 <date>1970-01-18T08:40:00+00:00</date>
548 <date>1970-01-18T08:40:00+00:00</date>
549 <msg xml:space="preserve">new head</msg>
549 <msg xml:space="preserve">new head</msg>
550 <paths>
550 <paths>
551 <path action="A">d</path>
551 <path action="A">d</path>
552 </paths>
552 </paths>
553 <extra key="branch">default</extra>
553 <extra key="branch">default</extra>
554 </logentry>
554 </logentry>
555 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
555 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
556 <branch>foo</branch>
556 <branch>foo</branch>
557 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
557 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
558 <parent revision="-1" node="0000000000000000000000000000000000000000" />
558 <parent revision="-1" node="0000000000000000000000000000000000000000" />
559 <author email="person">person</author>
559 <author email="person">person</author>
560 <date>1970-01-17T04:53:20+00:00</date>
560 <date>1970-01-17T04:53:20+00:00</date>
561 <msg xml:space="preserve">new branch</msg>
561 <msg xml:space="preserve">new branch</msg>
562 <paths>
562 <paths>
563 </paths>
563 </paths>
564 <extra key="branch">foo</extra>
564 <extra key="branch">foo</extra>
565 </logentry>
565 </logentry>
566 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
566 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
567 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
567 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
568 <parent revision="-1" node="0000000000000000000000000000000000000000" />
568 <parent revision="-1" node="0000000000000000000000000000000000000000" />
569 <author email="person">person</author>
569 <author email="person">person</author>
570 <date>1970-01-16T01:06:40+00:00</date>
570 <date>1970-01-16T01:06:40+00:00</date>
571 <msg xml:space="preserve">no user, no domain</msg>
571 <msg xml:space="preserve">no user, no domain</msg>
572 <paths>
572 <paths>
573 <path action="M">c</path>
573 <path action="M">c</path>
574 </paths>
574 </paths>
575 <extra key="branch">default</extra>
575 <extra key="branch">default</extra>
576 </logentry>
576 </logentry>
577 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
577 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
578 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
578 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
579 <parent revision="-1" node="0000000000000000000000000000000000000000" />
579 <parent revision="-1" node="0000000000000000000000000000000000000000" />
580 <author email="other@place">other</author>
580 <author email="other@place">other</author>
581 <date>1970-01-14T21:20:00+00:00</date>
581 <date>1970-01-14T21:20:00+00:00</date>
582 <msg xml:space="preserve">no person</msg>
582 <msg xml:space="preserve">no person</msg>
583 <paths>
583 <paths>
584 <path action="A">c</path>
584 <path action="A">c</path>
585 </paths>
585 </paths>
586 <extra key="branch">default</extra>
586 <extra key="branch">default</extra>
587 </logentry>
587 </logentry>
588 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
588 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
589 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
589 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
590 <parent revision="-1" node="0000000000000000000000000000000000000000" />
590 <parent revision="-1" node="0000000000000000000000000000000000000000" />
591 <author email="other@place">A. N. Other</author>
591 <author email="other@place">A. N. Other</author>
592 <date>1970-01-13T17:33:20+00:00</date>
592 <date>1970-01-13T17:33:20+00:00</date>
593 <msg xml:space="preserve">other 1
593 <msg xml:space="preserve">other 1
594 other 2
594 other 2
595
595
596 other 3</msg>
596 other 3</msg>
597 <paths>
597 <paths>
598 <path action="A">b</path>
598 <path action="A">b</path>
599 </paths>
599 </paths>
600 <extra key="branch">default</extra>
600 <extra key="branch">default</extra>
601 </logentry>
601 </logentry>
602 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
602 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
603 <parent revision="-1" node="0000000000000000000000000000000000000000" />
603 <parent revision="-1" node="0000000000000000000000000000000000000000" />
604 <parent revision="-1" node="0000000000000000000000000000000000000000" />
604 <parent revision="-1" node="0000000000000000000000000000000000000000" />
605 <author email="user@hostname">User Name</author>
605 <author email="user@hostname">User Name</author>
606 <date>1970-01-12T13:46:40+00:00</date>
606 <date>1970-01-12T13:46:40+00:00</date>
607 <msg xml:space="preserve">line 1
607 <msg xml:space="preserve">line 1
608 line 2</msg>
608 line 2</msg>
609 <paths>
609 <paths>
610 <path action="A">a</path>
610 <path action="A">a</path>
611 </paths>
611 </paths>
612 <extra key="branch">default</extra>
612 <extra key="branch">default</extra>
613 </logentry>
613 </logentry>
614 </log>
614 </log>
615
615
616
616
617 Test JSON style:
617 Test JSON style:
618
618
619 $ hg log -k nosuch -Tjson
619 $ hg log -k nosuch -Tjson
620 []
620 []
621
621
622 $ hg log -qr . -Tjson
622 $ hg log -qr . -Tjson
623 [
623 [
624 {
624 {
625 "rev": 8,
625 "rev": 8,
626 "node": "95c24699272ef57d062b8bccc32c878bf841784a"
626 "node": "95c24699272ef57d062b8bccc32c878bf841784a"
627 }
627 }
628 ]
628 ]
629
629
630 $ hg log -vpr . -Tjson --stat
630 $ hg log -vpr . -Tjson --stat
631 [
631 [
632 {
632 {
633 "rev": 8,
633 "rev": 8,
634 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
634 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
635 "branch": "default",
635 "branch": "default",
636 "phase": "draft",
636 "phase": "draft",
637 "user": "test",
637 "user": "test",
638 "date": [1577872860, 0],
638 "date": [1577872860, 0],
639 "desc": "third",
639 "desc": "third",
640 "bookmarks": [],
640 "bookmarks": [],
641 "tags": ["tip"],
641 "tags": ["tip"],
642 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
642 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
643 "files": ["fourth", "second", "third"],
643 "files": ["fourth", "second", "third"],
644 "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
644 "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
645 "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"
645 "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"
646 }
646 }
647 ]
647 ]
648
648
649 honor --git but not format-breaking diffopts
649 honor --git but not format-breaking diffopts
650 $ hg --config diff.noprefix=True log --git -vpr . -Tjson
650 $ hg --config diff.noprefix=True log --git -vpr . -Tjson
651 [
651 [
652 {
652 {
653 "rev": 8,
653 "rev": 8,
654 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
654 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
655 "branch": "default",
655 "branch": "default",
656 "phase": "draft",
656 "phase": "draft",
657 "user": "test",
657 "user": "test",
658 "date": [1577872860, 0],
658 "date": [1577872860, 0],
659 "desc": "third",
659 "desc": "third",
660 "bookmarks": [],
660 "bookmarks": [],
661 "tags": ["tip"],
661 "tags": ["tip"],
662 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
662 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
663 "files": ["fourth", "second", "third"],
663 "files": ["fourth", "second", "third"],
664 "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n"
664 "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n"
665 }
665 }
666 ]
666 ]
667
667
668 $ hg log -T json
668 $ hg log -T json
669 [
669 [
670 {
670 {
671 "rev": 8,
671 "rev": 8,
672 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
672 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
673 "branch": "default",
673 "branch": "default",
674 "phase": "draft",
674 "phase": "draft",
675 "user": "test",
675 "user": "test",
676 "date": [1577872860, 0],
676 "date": [1577872860, 0],
677 "desc": "third",
677 "desc": "third",
678 "bookmarks": [],
678 "bookmarks": [],
679 "tags": ["tip"],
679 "tags": ["tip"],
680 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"]
680 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"]
681 },
681 },
682 {
682 {
683 "rev": 7,
683 "rev": 7,
684 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
684 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
685 "branch": "default",
685 "branch": "default",
686 "phase": "draft",
686 "phase": "draft",
687 "user": "User Name <user@hostname>",
687 "user": "User Name <user@hostname>",
688 "date": [1000000, 0],
688 "date": [1000000, 0],
689 "desc": "second",
689 "desc": "second",
690 "bookmarks": [],
690 "bookmarks": [],
691 "tags": [],
691 "tags": [],
692 "parents": ["0000000000000000000000000000000000000000"]
692 "parents": ["0000000000000000000000000000000000000000"]
693 },
693 },
694 {
694 {
695 "rev": 6,
695 "rev": 6,
696 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
696 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
697 "branch": "default",
697 "branch": "default",
698 "phase": "draft",
698 "phase": "draft",
699 "user": "person",
699 "user": "person",
700 "date": [1500001, 0],
700 "date": [1500001, 0],
701 "desc": "merge",
701 "desc": "merge",
702 "bookmarks": [],
702 "bookmarks": [],
703 "tags": [],
703 "tags": [],
704 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"]
704 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"]
705 },
705 },
706 {
706 {
707 "rev": 5,
707 "rev": 5,
708 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
708 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
709 "branch": "default",
709 "branch": "default",
710 "phase": "draft",
710 "phase": "draft",
711 "user": "person",
711 "user": "person",
712 "date": [1500000, 0],
712 "date": [1500000, 0],
713 "desc": "new head",
713 "desc": "new head",
714 "bookmarks": [],
714 "bookmarks": [],
715 "tags": [],
715 "tags": [],
716 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
716 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
717 },
717 },
718 {
718 {
719 "rev": 4,
719 "rev": 4,
720 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
720 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
721 "branch": "foo",
721 "branch": "foo",
722 "phase": "draft",
722 "phase": "draft",
723 "user": "person",
723 "user": "person",
724 "date": [1400000, 0],
724 "date": [1400000, 0],
725 "desc": "new branch",
725 "desc": "new branch",
726 "bookmarks": [],
726 "bookmarks": [],
727 "tags": [],
727 "tags": [],
728 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
728 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
729 },
729 },
730 {
730 {
731 "rev": 3,
731 "rev": 3,
732 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
732 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
733 "branch": "default",
733 "branch": "default",
734 "phase": "draft",
734 "phase": "draft",
735 "user": "person",
735 "user": "person",
736 "date": [1300000, 0],
736 "date": [1300000, 0],
737 "desc": "no user, no domain",
737 "desc": "no user, no domain",
738 "bookmarks": [],
738 "bookmarks": [],
739 "tags": [],
739 "tags": [],
740 "parents": ["97054abb4ab824450e9164180baf491ae0078465"]
740 "parents": ["97054abb4ab824450e9164180baf491ae0078465"]
741 },
741 },
742 {
742 {
743 "rev": 2,
743 "rev": 2,
744 "node": "97054abb4ab824450e9164180baf491ae0078465",
744 "node": "97054abb4ab824450e9164180baf491ae0078465",
745 "branch": "default",
745 "branch": "default",
746 "phase": "draft",
746 "phase": "draft",
747 "user": "other@place",
747 "user": "other@place",
748 "date": [1200000, 0],
748 "date": [1200000, 0],
749 "desc": "no person",
749 "desc": "no person",
750 "bookmarks": [],
750 "bookmarks": [],
751 "tags": [],
751 "tags": [],
752 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"]
752 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"]
753 },
753 },
754 {
754 {
755 "rev": 1,
755 "rev": 1,
756 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
756 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
757 "branch": "default",
757 "branch": "default",
758 "phase": "draft",
758 "phase": "draft",
759 "user": "A. N. Other <other@place>",
759 "user": "A. N. Other <other@place>",
760 "date": [1100000, 0],
760 "date": [1100000, 0],
761 "desc": "other 1\nother 2\n\nother 3",
761 "desc": "other 1\nother 2\n\nother 3",
762 "bookmarks": [],
762 "bookmarks": [],
763 "tags": [],
763 "tags": [],
764 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"]
764 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"]
765 },
765 },
766 {
766 {
767 "rev": 0,
767 "rev": 0,
768 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
768 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
769 "branch": "default",
769 "branch": "default",
770 "phase": "draft",
770 "phase": "draft",
771 "user": "User Name <user@hostname>",
771 "user": "User Name <user@hostname>",
772 "date": [1000000, 0],
772 "date": [1000000, 0],
773 "desc": "line 1\nline 2",
773 "desc": "line 1\nline 2",
774 "bookmarks": [],
774 "bookmarks": [],
775 "tags": [],
775 "tags": [],
776 "parents": ["0000000000000000000000000000000000000000"]
776 "parents": ["0000000000000000000000000000000000000000"]
777 }
777 }
778 ]
778 ]
779
779
780 $ hg heads -v -Tjson
780 $ hg heads -v -Tjson
781 [
781 [
782 {
782 {
783 "rev": 8,
783 "rev": 8,
784 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
784 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
785 "branch": "default",
785 "branch": "default",
786 "phase": "draft",
786 "phase": "draft",
787 "user": "test",
787 "user": "test",
788 "date": [1577872860, 0],
788 "date": [1577872860, 0],
789 "desc": "third",
789 "desc": "third",
790 "bookmarks": [],
790 "bookmarks": [],
791 "tags": ["tip"],
791 "tags": ["tip"],
792 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
792 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
793 "files": ["fourth", "second", "third"]
793 "files": ["fourth", "second", "third"]
794 },
794 },
795 {
795 {
796 "rev": 6,
796 "rev": 6,
797 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
797 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
798 "branch": "default",
798 "branch": "default",
799 "phase": "draft",
799 "phase": "draft",
800 "user": "person",
800 "user": "person",
801 "date": [1500001, 0],
801 "date": [1500001, 0],
802 "desc": "merge",
802 "desc": "merge",
803 "bookmarks": [],
803 "bookmarks": [],
804 "tags": [],
804 "tags": [],
805 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
805 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
806 "files": []
806 "files": []
807 },
807 },
808 {
808 {
809 "rev": 4,
809 "rev": 4,
810 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
810 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
811 "branch": "foo",
811 "branch": "foo",
812 "phase": "draft",
812 "phase": "draft",
813 "user": "person",
813 "user": "person",
814 "date": [1400000, 0],
814 "date": [1400000, 0],
815 "desc": "new branch",
815 "desc": "new branch",
816 "bookmarks": [],
816 "bookmarks": [],
817 "tags": [],
817 "tags": [],
818 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
818 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
819 "files": []
819 "files": []
820 }
820 }
821 ]
821 ]
822
822
823 $ hg log --debug -Tjson
823 $ hg log --debug -Tjson
824 [
824 [
825 {
825 {
826 "rev": 8,
826 "rev": 8,
827 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
827 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
828 "branch": "default",
828 "branch": "default",
829 "phase": "draft",
829 "phase": "draft",
830 "user": "test",
830 "user": "test",
831 "date": [1577872860, 0],
831 "date": [1577872860, 0],
832 "desc": "third",
832 "desc": "third",
833 "bookmarks": [],
833 "bookmarks": [],
834 "tags": ["tip"],
834 "tags": ["tip"],
835 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
835 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
836 "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
836 "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
837 "extra": {"branch": "default"},
837 "extra": {"branch": "default"},
838 "modified": [],
838 "modified": [],
839 "added": ["fourth", "third"],
839 "added": ["fourth", "third"],
840 "removed": ["second"]
840 "removed": ["second"]
841 },
841 },
842 {
842 {
843 "rev": 7,
843 "rev": 7,
844 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
844 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
845 "branch": "default",
845 "branch": "default",
846 "phase": "draft",
846 "phase": "draft",
847 "user": "User Name <user@hostname>",
847 "user": "User Name <user@hostname>",
848 "date": [1000000, 0],
848 "date": [1000000, 0],
849 "desc": "second",
849 "desc": "second",
850 "bookmarks": [],
850 "bookmarks": [],
851 "tags": [],
851 "tags": [],
852 "parents": ["0000000000000000000000000000000000000000"],
852 "parents": ["0000000000000000000000000000000000000000"],
853 "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
853 "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
854 "extra": {"branch": "default"},
854 "extra": {"branch": "default"},
855 "modified": [],
855 "modified": [],
856 "added": ["second"],
856 "added": ["second"],
857 "removed": []
857 "removed": []
858 },
858 },
859 {
859 {
860 "rev": 6,
860 "rev": 6,
861 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
861 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
862 "branch": "default",
862 "branch": "default",
863 "phase": "draft",
863 "phase": "draft",
864 "user": "person",
864 "user": "person",
865 "date": [1500001, 0],
865 "date": [1500001, 0],
866 "desc": "merge",
866 "desc": "merge",
867 "bookmarks": [],
867 "bookmarks": [],
868 "tags": [],
868 "tags": [],
869 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
869 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
870 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
870 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
871 "extra": {"branch": "default"},
871 "extra": {"branch": "default"},
872 "modified": [],
872 "modified": [],
873 "added": [],
873 "added": [],
874 "removed": []
874 "removed": []
875 },
875 },
876 {
876 {
877 "rev": 5,
877 "rev": 5,
878 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
878 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
879 "branch": "default",
879 "branch": "default",
880 "phase": "draft",
880 "phase": "draft",
881 "user": "person",
881 "user": "person",
882 "date": [1500000, 0],
882 "date": [1500000, 0],
883 "desc": "new head",
883 "desc": "new head",
884 "bookmarks": [],
884 "bookmarks": [],
885 "tags": [],
885 "tags": [],
886 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
886 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
887 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
887 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
888 "extra": {"branch": "default"},
888 "extra": {"branch": "default"},
889 "modified": [],
889 "modified": [],
890 "added": ["d"],
890 "added": ["d"],
891 "removed": []
891 "removed": []
892 },
892 },
893 {
893 {
894 "rev": 4,
894 "rev": 4,
895 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
895 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
896 "branch": "foo",
896 "branch": "foo",
897 "phase": "draft",
897 "phase": "draft",
898 "user": "person",
898 "user": "person",
899 "date": [1400000, 0],
899 "date": [1400000, 0],
900 "desc": "new branch",
900 "desc": "new branch",
901 "bookmarks": [],
901 "bookmarks": [],
902 "tags": [],
902 "tags": [],
903 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
903 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
904 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
904 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
905 "extra": {"branch": "foo"},
905 "extra": {"branch": "foo"},
906 "modified": [],
906 "modified": [],
907 "added": [],
907 "added": [],
908 "removed": []
908 "removed": []
909 },
909 },
910 {
910 {
911 "rev": 3,
911 "rev": 3,
912 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
912 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
913 "branch": "default",
913 "branch": "default",
914 "phase": "draft",
914 "phase": "draft",
915 "user": "person",
915 "user": "person",
916 "date": [1300000, 0],
916 "date": [1300000, 0],
917 "desc": "no user, no domain",
917 "desc": "no user, no domain",
918 "bookmarks": [],
918 "bookmarks": [],
919 "tags": [],
919 "tags": [],
920 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
920 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
921 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
921 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
922 "extra": {"branch": "default"},
922 "extra": {"branch": "default"},
923 "modified": ["c"],
923 "modified": ["c"],
924 "added": [],
924 "added": [],
925 "removed": []
925 "removed": []
926 },
926 },
927 {
927 {
928 "rev": 2,
928 "rev": 2,
929 "node": "97054abb4ab824450e9164180baf491ae0078465",
929 "node": "97054abb4ab824450e9164180baf491ae0078465",
930 "branch": "default",
930 "branch": "default",
931 "phase": "draft",
931 "phase": "draft",
932 "user": "other@place",
932 "user": "other@place",
933 "date": [1200000, 0],
933 "date": [1200000, 0],
934 "desc": "no person",
934 "desc": "no person",
935 "bookmarks": [],
935 "bookmarks": [],
936 "tags": [],
936 "tags": [],
937 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
937 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
938 "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
938 "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
939 "extra": {"branch": "default"},
939 "extra": {"branch": "default"},
940 "modified": [],
940 "modified": [],
941 "added": ["c"],
941 "added": ["c"],
942 "removed": []
942 "removed": []
943 },
943 },
944 {
944 {
945 "rev": 1,
945 "rev": 1,
946 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
946 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
947 "branch": "default",
947 "branch": "default",
948 "phase": "draft",
948 "phase": "draft",
949 "user": "A. N. Other <other@place>",
949 "user": "A. N. Other <other@place>",
950 "date": [1100000, 0],
950 "date": [1100000, 0],
951 "desc": "other 1\nother 2\n\nother 3",
951 "desc": "other 1\nother 2\n\nother 3",
952 "bookmarks": [],
952 "bookmarks": [],
953 "tags": [],
953 "tags": [],
954 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
954 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
955 "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
955 "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
956 "extra": {"branch": "default"},
956 "extra": {"branch": "default"},
957 "modified": [],
957 "modified": [],
958 "added": ["b"],
958 "added": ["b"],
959 "removed": []
959 "removed": []
960 },
960 },
961 {
961 {
962 "rev": 0,
962 "rev": 0,
963 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
963 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
964 "branch": "default",
964 "branch": "default",
965 "phase": "draft",
965 "phase": "draft",
966 "user": "User Name <user@hostname>",
966 "user": "User Name <user@hostname>",
967 "date": [1000000, 0],
967 "date": [1000000, 0],
968 "desc": "line 1\nline 2",
968 "desc": "line 1\nline 2",
969 "bookmarks": [],
969 "bookmarks": [],
970 "tags": [],
970 "tags": [],
971 "parents": ["0000000000000000000000000000000000000000"],
971 "parents": ["0000000000000000000000000000000000000000"],
972 "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
972 "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
973 "extra": {"branch": "default"},
973 "extra": {"branch": "default"},
974 "modified": [],
974 "modified": [],
975 "added": ["a"],
975 "added": ["a"],
976 "removed": []
976 "removed": []
977 }
977 }
978 ]
978 ]
979
979
980 Error if style not readable:
980 Error if style not readable:
981
981
982 #if unix-permissions no-root
982 #if unix-permissions no-root
983 $ touch q
983 $ touch q
984 $ chmod 0 q
984 $ chmod 0 q
985 $ hg log --style ./q
985 $ hg log --style ./q
986 abort: Permission denied: ./q
986 abort: Permission denied: ./q
987 [255]
987 [255]
988 #endif
988 #endif
989
989
990 Error if no style:
990 Error if no style:
991
991
992 $ hg log --style notexist
992 $ hg log --style notexist
993 abort: style 'notexist' not found
993 abort: style 'notexist' not found
994 (available styles: bisect, changelog, compact, default, phases, status, xml)
994 (available styles: bisect, changelog, compact, default, phases, status, xml)
995 [255]
995 [255]
996
996
997 $ hg log -T list
997 $ hg log -T list
998 available styles: bisect, changelog, compact, default, phases, status, xml
998 available styles: bisect, changelog, compact, default, phases, status, xml
999 abort: specify a template
999 abort: specify a template
1000 [255]
1000 [255]
1001
1001
1002 Error if style missing key:
1002 Error if style missing key:
1003
1003
1004 $ echo 'q = q' > t
1004 $ echo 'q = q' > t
1005 $ hg log --style ./t
1005 $ hg log --style ./t
1006 abort: "changeset" not in template map
1006 abort: "changeset" not in template map
1007 [255]
1007 [255]
1008
1008
1009 Error if style missing value:
1009 Error if style missing value:
1010
1010
1011 $ echo 'changeset =' > t
1011 $ echo 'changeset =' > t
1012 $ hg log --style t
1012 $ hg log --style t
1013 hg: parse error at t:1: missing value
1013 hg: parse error at t:1: missing value
1014 [255]
1014 [255]
1015
1015
1016 Error if include fails:
1016 Error if include fails:
1017
1017
1018 $ echo 'changeset = q' >> t
1018 $ echo 'changeset = q' >> t
1019 #if unix-permissions no-root
1019 #if unix-permissions no-root
1020 $ hg log --style ./t
1020 $ hg log --style ./t
1021 abort: template file ./q: Permission denied
1021 abort: template file ./q: Permission denied
1022 [255]
1022 [255]
1023 $ rm -f q
1023 $ rm -f q
1024 #endif
1024 #endif
1025
1025
1026 Include works:
1026 Include works:
1027
1027
1028 $ echo '{rev}' > q
1028 $ echo '{rev}' > q
1029 $ hg log --style ./t
1029 $ hg log --style ./t
1030 8
1030 8
1031 7
1031 7
1032 6
1032 6
1033 5
1033 5
1034 4
1034 4
1035 3
1035 3
1036 2
1036 2
1037 1
1037 1
1038 0
1038 0
1039
1039
1040 Check that recursive reference does not fall into RuntimeError (issue4758):
1040 Check that recursive reference does not fall into RuntimeError (issue4758):
1041
1041
1042 common mistake:
1042 common mistake:
1043
1043
1044 $ hg log -T '{changeset}\n'
1044 $ hg log -T '{changeset}\n'
1045 abort: recursive reference 'changeset' in template
1045 abort: recursive reference 'changeset' in template
1046 [255]
1046 [255]
1047
1047
1048 circular reference:
1048 circular reference:
1049
1049
1050 $ cat << EOF > issue4758
1050 $ cat << EOF > issue4758
1051 > changeset = '{foo}'
1051 > changeset = '{foo}'
1052 > foo = '{changeset}'
1052 > foo = '{changeset}'
1053 > EOF
1053 > EOF
1054 $ hg log --style ./issue4758
1054 $ hg log --style ./issue4758
1055 abort: recursive reference 'foo' in template
1055 abort: recursive reference 'foo' in template
1056 [255]
1056 [255]
1057
1057
1058 buildmap() -> gettemplate(), where no thunk was made:
1058 buildmap() -> gettemplate(), where no thunk was made:
1059
1059
1060 $ hg log -T '{files % changeset}\n'
1060 $ hg log -T '{files % changeset}\n'
1061 abort: recursive reference 'changeset' in template
1061 abort: recursive reference 'changeset' in template
1062 [255]
1062 [255]
1063
1063
1064 not a recursion if a keyword of the same name exists:
1064 not a recursion if a keyword of the same name exists:
1065
1065
1066 $ cat << EOF > issue4758
1066 $ cat << EOF > issue4758
1067 > changeset = '{tags % rev}'
1067 > changeset = '{tags % rev}'
1068 > rev = '{rev} {tag}\n'
1068 > rev = '{rev} {tag}\n'
1069 > EOF
1069 > EOF
1070 $ hg log --style ./issue4758 -r tip
1070 $ hg log --style ./issue4758 -r tip
1071 8 tip
1071 8 tip
1072
1072
1073 Check that {phase} works correctly on parents:
1073 Check that {phase} works correctly on parents:
1074
1074
1075 $ cat << EOF > parentphase
1075 $ cat << EOF > parentphase
1076 > changeset_debug = '{rev} ({phase}):{parents}\n'
1076 > changeset_debug = '{rev} ({phase}):{parents}\n'
1077 > parent = ' {rev} ({phase})'
1077 > parent = ' {rev} ({phase})'
1078 > EOF
1078 > EOF
1079 $ hg phase -r 5 --public
1079 $ hg phase -r 5 --public
1080 $ hg phase -r 7 --secret --force
1080 $ hg phase -r 7 --secret --force
1081 $ hg log --debug -G --style ./parentphase
1081 $ hg log --debug -G --style ./parentphase
1082 @ 8 (secret): 7 (secret) -1 (public)
1082 @ 8 (secret): 7 (secret) -1 (public)
1083 |
1083 |
1084 o 7 (secret): -1 (public) -1 (public)
1084 o 7 (secret): -1 (public) -1 (public)
1085
1085
1086 o 6 (draft): 5 (public) 4 (draft)
1086 o 6 (draft): 5 (public) 4 (draft)
1087 |\
1087 |\
1088 | o 5 (public): 3 (public) -1 (public)
1088 | o 5 (public): 3 (public) -1 (public)
1089 | |
1089 | |
1090 o | 4 (draft): 3 (public) -1 (public)
1090 o | 4 (draft): 3 (public) -1 (public)
1091 |/
1091 |/
1092 o 3 (public): 2 (public) -1 (public)
1092 o 3 (public): 2 (public) -1 (public)
1093 |
1093 |
1094 o 2 (public): 1 (public) -1 (public)
1094 o 2 (public): 1 (public) -1 (public)
1095 |
1095 |
1096 o 1 (public): 0 (public) -1 (public)
1096 o 1 (public): 0 (public) -1 (public)
1097 |
1097 |
1098 o 0 (public): -1 (public) -1 (public)
1098 o 0 (public): -1 (public) -1 (public)
1099
1099
1100
1100
1101 Missing non-standard names give no error (backward compatibility):
1101 Missing non-standard names give no error (backward compatibility):
1102
1102
1103 $ echo "changeset = '{c}'" > t
1103 $ echo "changeset = '{c}'" > t
1104 $ hg log --style ./t
1104 $ hg log --style ./t
1105
1105
1106 Defining non-standard name works:
1106 Defining non-standard name works:
1107
1107
1108 $ cat <<EOF > t
1108 $ cat <<EOF > t
1109 > changeset = '{c}'
1109 > changeset = '{c}'
1110 > c = q
1110 > c = q
1111 > EOF
1111 > EOF
1112 $ hg log --style ./t
1112 $ hg log --style ./t
1113 8
1113 8
1114 7
1114 7
1115 6
1115 6
1116 5
1116 5
1117 4
1117 4
1118 3
1118 3
1119 2
1119 2
1120 1
1120 1
1121 0
1121 0
1122
1122
1123 ui.style works:
1123 ui.style works:
1124
1124
1125 $ echo '[ui]' > .hg/hgrc
1125 $ echo '[ui]' > .hg/hgrc
1126 $ echo 'style = t' >> .hg/hgrc
1126 $ echo 'style = t' >> .hg/hgrc
1127 $ hg log
1127 $ hg log
1128 8
1128 8
1129 7
1129 7
1130 6
1130 6
1131 5
1131 5
1132 4
1132 4
1133 3
1133 3
1134 2
1134 2
1135 1
1135 1
1136 0
1136 0
1137
1137
1138
1138
1139 Issue338:
1139 Issue338:
1140
1140
1141 $ hg log --style=changelog > changelog
1141 $ hg log --style=changelog > changelog
1142
1142
1143 $ cat changelog
1143 $ cat changelog
1144 2020-01-01 test <test>
1144 2020-01-01 test <test>
1145
1145
1146 * fourth, second, third:
1146 * fourth, second, third:
1147 third
1147 third
1148 [95c24699272e] [tip]
1148 [95c24699272e] [tip]
1149
1149
1150 1970-01-12 User Name <user@hostname>
1150 1970-01-12 User Name <user@hostname>
1151
1151
1152 * second:
1152 * second:
1153 second
1153 second
1154 [29114dbae42b]
1154 [29114dbae42b]
1155
1155
1156 1970-01-18 person <person>
1156 1970-01-18 person <person>
1157
1157
1158 * merge
1158 * merge
1159 [d41e714fe50d]
1159 [d41e714fe50d]
1160
1160
1161 * d:
1161 * d:
1162 new head
1162 new head
1163 [13207e5a10d9]
1163 [13207e5a10d9]
1164
1164
1165 1970-01-17 person <person>
1165 1970-01-17 person <person>
1166
1166
1167 * new branch
1167 * new branch
1168 [bbe44766e73d] <foo>
1168 [bbe44766e73d] <foo>
1169
1169
1170 1970-01-16 person <person>
1170 1970-01-16 person <person>
1171
1171
1172 * c:
1172 * c:
1173 no user, no domain
1173 no user, no domain
1174 [10e46f2dcbf4]
1174 [10e46f2dcbf4]
1175
1175
1176 1970-01-14 other <other@place>
1176 1970-01-14 other <other@place>
1177
1177
1178 * c:
1178 * c:
1179 no person
1179 no person
1180 [97054abb4ab8]
1180 [97054abb4ab8]
1181
1181
1182 1970-01-13 A. N. Other <other@place>
1182 1970-01-13 A. N. Other <other@place>
1183
1183
1184 * b:
1184 * b:
1185 other 1 other 2
1185 other 1 other 2
1186
1186
1187 other 3
1187 other 3
1188 [b608e9d1a3f0]
1188 [b608e9d1a3f0]
1189
1189
1190 1970-01-12 User Name <user@hostname>
1190 1970-01-12 User Name <user@hostname>
1191
1191
1192 * a:
1192 * a:
1193 line 1 line 2
1193 line 1 line 2
1194 [1e4e1b8f71e0]
1194 [1e4e1b8f71e0]
1195
1195
1196
1196
1197 Issue2130: xml output for 'hg heads' is malformed
1197 Issue2130: xml output for 'hg heads' is malformed
1198
1198
1199 $ hg heads --style changelog
1199 $ hg heads --style changelog
1200 2020-01-01 test <test>
1200 2020-01-01 test <test>
1201
1201
1202 * fourth, second, third:
1202 * fourth, second, third:
1203 third
1203 third
1204 [95c24699272e] [tip]
1204 [95c24699272e] [tip]
1205
1205
1206 1970-01-18 person <person>
1206 1970-01-18 person <person>
1207
1207
1208 * merge
1208 * merge
1209 [d41e714fe50d]
1209 [d41e714fe50d]
1210
1210
1211 1970-01-17 person <person>
1211 1970-01-17 person <person>
1212
1212
1213 * new branch
1213 * new branch
1214 [bbe44766e73d] <foo>
1214 [bbe44766e73d] <foo>
1215
1215
1216
1216
1217 Keys work:
1217 Keys work:
1218
1218
1219 $ for key in author branch branches date desc file_adds file_dels file_mods \
1219 $ for key in author branch branches date desc file_adds file_dels file_mods \
1220 > file_copies file_copies_switch files \
1220 > file_copies file_copies_switch files \
1221 > manifest node parents rev tags diffstat extras \
1221 > manifest node parents rev tags diffstat extras \
1222 > p1rev p2rev p1node p2node; do
1222 > p1rev p2rev p1node p2node; do
1223 > for mode in '' --verbose --debug; do
1223 > for mode in '' --verbose --debug; do
1224 > hg log $mode --template "$key$mode: {$key}\n"
1224 > hg log $mode --template "$key$mode: {$key}\n"
1225 > done
1225 > done
1226 > done
1226 > done
1227 author: test
1227 author: test
1228 author: User Name <user@hostname>
1228 author: User Name <user@hostname>
1229 author: person
1229 author: person
1230 author: person
1230 author: person
1231 author: person
1231 author: person
1232 author: person
1232 author: person
1233 author: other@place
1233 author: other@place
1234 author: A. N. Other <other@place>
1234 author: A. N. Other <other@place>
1235 author: User Name <user@hostname>
1235 author: User Name <user@hostname>
1236 author--verbose: test
1236 author--verbose: test
1237 author--verbose: User Name <user@hostname>
1237 author--verbose: User Name <user@hostname>
1238 author--verbose: person
1238 author--verbose: person
1239 author--verbose: person
1239 author--verbose: person
1240 author--verbose: person
1240 author--verbose: person
1241 author--verbose: person
1241 author--verbose: person
1242 author--verbose: other@place
1242 author--verbose: other@place
1243 author--verbose: A. N. Other <other@place>
1243 author--verbose: A. N. Other <other@place>
1244 author--verbose: User Name <user@hostname>
1244 author--verbose: User Name <user@hostname>
1245 author--debug: test
1245 author--debug: test
1246 author--debug: User Name <user@hostname>
1246 author--debug: User Name <user@hostname>
1247 author--debug: person
1247 author--debug: person
1248 author--debug: person
1248 author--debug: person
1249 author--debug: person
1249 author--debug: person
1250 author--debug: person
1250 author--debug: person
1251 author--debug: other@place
1251 author--debug: other@place
1252 author--debug: A. N. Other <other@place>
1252 author--debug: A. N. Other <other@place>
1253 author--debug: User Name <user@hostname>
1253 author--debug: User Name <user@hostname>
1254 branch: default
1254 branch: default
1255 branch: default
1255 branch: default
1256 branch: default
1256 branch: default
1257 branch: default
1257 branch: default
1258 branch: foo
1258 branch: foo
1259 branch: default
1259 branch: default
1260 branch: default
1260 branch: default
1261 branch: default
1261 branch: default
1262 branch: default
1262 branch: default
1263 branch--verbose: default
1263 branch--verbose: default
1264 branch--verbose: default
1264 branch--verbose: default
1265 branch--verbose: default
1265 branch--verbose: default
1266 branch--verbose: default
1266 branch--verbose: default
1267 branch--verbose: foo
1267 branch--verbose: foo
1268 branch--verbose: default
1268 branch--verbose: default
1269 branch--verbose: default
1269 branch--verbose: default
1270 branch--verbose: default
1270 branch--verbose: default
1271 branch--verbose: default
1271 branch--verbose: default
1272 branch--debug: default
1272 branch--debug: default
1273 branch--debug: default
1273 branch--debug: default
1274 branch--debug: default
1274 branch--debug: default
1275 branch--debug: default
1275 branch--debug: default
1276 branch--debug: foo
1276 branch--debug: foo
1277 branch--debug: default
1277 branch--debug: default
1278 branch--debug: default
1278 branch--debug: default
1279 branch--debug: default
1279 branch--debug: default
1280 branch--debug: default
1280 branch--debug: default
1281 branches:
1281 branches:
1282 branches:
1282 branches:
1283 branches:
1283 branches:
1284 branches:
1284 branches:
1285 branches: foo
1285 branches: foo
1286 branches:
1286 branches:
1287 branches:
1287 branches:
1288 branches:
1288 branches:
1289 branches:
1289 branches:
1290 branches--verbose:
1290 branches--verbose:
1291 branches--verbose:
1291 branches--verbose:
1292 branches--verbose:
1292 branches--verbose:
1293 branches--verbose:
1293 branches--verbose:
1294 branches--verbose: foo
1294 branches--verbose: foo
1295 branches--verbose:
1295 branches--verbose:
1296 branches--verbose:
1296 branches--verbose:
1297 branches--verbose:
1297 branches--verbose:
1298 branches--verbose:
1298 branches--verbose:
1299 branches--debug:
1299 branches--debug:
1300 branches--debug:
1300 branches--debug:
1301 branches--debug:
1301 branches--debug:
1302 branches--debug:
1302 branches--debug:
1303 branches--debug: foo
1303 branches--debug: foo
1304 branches--debug:
1304 branches--debug:
1305 branches--debug:
1305 branches--debug:
1306 branches--debug:
1306 branches--debug:
1307 branches--debug:
1307 branches--debug:
1308 date: 1577872860.00
1308 date: 1577872860.00
1309 date: 1000000.00
1309 date: 1000000.00
1310 date: 1500001.00
1310 date: 1500001.00
1311 date: 1500000.00
1311 date: 1500000.00
1312 date: 1400000.00
1312 date: 1400000.00
1313 date: 1300000.00
1313 date: 1300000.00
1314 date: 1200000.00
1314 date: 1200000.00
1315 date: 1100000.00
1315 date: 1100000.00
1316 date: 1000000.00
1316 date: 1000000.00
1317 date--verbose: 1577872860.00
1317 date--verbose: 1577872860.00
1318 date--verbose: 1000000.00
1318 date--verbose: 1000000.00
1319 date--verbose: 1500001.00
1319 date--verbose: 1500001.00
1320 date--verbose: 1500000.00
1320 date--verbose: 1500000.00
1321 date--verbose: 1400000.00
1321 date--verbose: 1400000.00
1322 date--verbose: 1300000.00
1322 date--verbose: 1300000.00
1323 date--verbose: 1200000.00
1323 date--verbose: 1200000.00
1324 date--verbose: 1100000.00
1324 date--verbose: 1100000.00
1325 date--verbose: 1000000.00
1325 date--verbose: 1000000.00
1326 date--debug: 1577872860.00
1326 date--debug: 1577872860.00
1327 date--debug: 1000000.00
1327 date--debug: 1000000.00
1328 date--debug: 1500001.00
1328 date--debug: 1500001.00
1329 date--debug: 1500000.00
1329 date--debug: 1500000.00
1330 date--debug: 1400000.00
1330 date--debug: 1400000.00
1331 date--debug: 1300000.00
1331 date--debug: 1300000.00
1332 date--debug: 1200000.00
1332 date--debug: 1200000.00
1333 date--debug: 1100000.00
1333 date--debug: 1100000.00
1334 date--debug: 1000000.00
1334 date--debug: 1000000.00
1335 desc: third
1335 desc: third
1336 desc: second
1336 desc: second
1337 desc: merge
1337 desc: merge
1338 desc: new head
1338 desc: new head
1339 desc: new branch
1339 desc: new branch
1340 desc: no user, no domain
1340 desc: no user, no domain
1341 desc: no person
1341 desc: no person
1342 desc: other 1
1342 desc: other 1
1343 other 2
1343 other 2
1344
1344
1345 other 3
1345 other 3
1346 desc: line 1
1346 desc: line 1
1347 line 2
1347 line 2
1348 desc--verbose: third
1348 desc--verbose: third
1349 desc--verbose: second
1349 desc--verbose: second
1350 desc--verbose: merge
1350 desc--verbose: merge
1351 desc--verbose: new head
1351 desc--verbose: new head
1352 desc--verbose: new branch
1352 desc--verbose: new branch
1353 desc--verbose: no user, no domain
1353 desc--verbose: no user, no domain
1354 desc--verbose: no person
1354 desc--verbose: no person
1355 desc--verbose: other 1
1355 desc--verbose: other 1
1356 other 2
1356 other 2
1357
1357
1358 other 3
1358 other 3
1359 desc--verbose: line 1
1359 desc--verbose: line 1
1360 line 2
1360 line 2
1361 desc--debug: third
1361 desc--debug: third
1362 desc--debug: second
1362 desc--debug: second
1363 desc--debug: merge
1363 desc--debug: merge
1364 desc--debug: new head
1364 desc--debug: new head
1365 desc--debug: new branch
1365 desc--debug: new branch
1366 desc--debug: no user, no domain
1366 desc--debug: no user, no domain
1367 desc--debug: no person
1367 desc--debug: no person
1368 desc--debug: other 1
1368 desc--debug: other 1
1369 other 2
1369 other 2
1370
1370
1371 other 3
1371 other 3
1372 desc--debug: line 1
1372 desc--debug: line 1
1373 line 2
1373 line 2
1374 file_adds: fourth third
1374 file_adds: fourth third
1375 file_adds: second
1375 file_adds: second
1376 file_adds:
1376 file_adds:
1377 file_adds: d
1377 file_adds: d
1378 file_adds:
1378 file_adds:
1379 file_adds:
1379 file_adds:
1380 file_adds: c
1380 file_adds: c
1381 file_adds: b
1381 file_adds: b
1382 file_adds: a
1382 file_adds: a
1383 file_adds--verbose: fourth third
1383 file_adds--verbose: fourth third
1384 file_adds--verbose: second
1384 file_adds--verbose: second
1385 file_adds--verbose:
1385 file_adds--verbose:
1386 file_adds--verbose: d
1386 file_adds--verbose: d
1387 file_adds--verbose:
1387 file_adds--verbose:
1388 file_adds--verbose:
1388 file_adds--verbose:
1389 file_adds--verbose: c
1389 file_adds--verbose: c
1390 file_adds--verbose: b
1390 file_adds--verbose: b
1391 file_adds--verbose: a
1391 file_adds--verbose: a
1392 file_adds--debug: fourth third
1392 file_adds--debug: fourth third
1393 file_adds--debug: second
1393 file_adds--debug: second
1394 file_adds--debug:
1394 file_adds--debug:
1395 file_adds--debug: d
1395 file_adds--debug: d
1396 file_adds--debug:
1396 file_adds--debug:
1397 file_adds--debug:
1397 file_adds--debug:
1398 file_adds--debug: c
1398 file_adds--debug: c
1399 file_adds--debug: b
1399 file_adds--debug: b
1400 file_adds--debug: a
1400 file_adds--debug: a
1401 file_dels: second
1401 file_dels: second
1402 file_dels:
1402 file_dels:
1403 file_dels:
1403 file_dels:
1404 file_dels:
1404 file_dels:
1405 file_dels:
1405 file_dels:
1406 file_dels:
1406 file_dels:
1407 file_dels:
1407 file_dels:
1408 file_dels:
1408 file_dels:
1409 file_dels:
1409 file_dels:
1410 file_dels--verbose: second
1410 file_dels--verbose: second
1411 file_dels--verbose:
1411 file_dels--verbose:
1412 file_dels--verbose:
1412 file_dels--verbose:
1413 file_dels--verbose:
1413 file_dels--verbose:
1414 file_dels--verbose:
1414 file_dels--verbose:
1415 file_dels--verbose:
1415 file_dels--verbose:
1416 file_dels--verbose:
1416 file_dels--verbose:
1417 file_dels--verbose:
1417 file_dels--verbose:
1418 file_dels--verbose:
1418 file_dels--verbose:
1419 file_dels--debug: second
1419 file_dels--debug: second
1420 file_dels--debug:
1420 file_dels--debug:
1421 file_dels--debug:
1421 file_dels--debug:
1422 file_dels--debug:
1422 file_dels--debug:
1423 file_dels--debug:
1423 file_dels--debug:
1424 file_dels--debug:
1424 file_dels--debug:
1425 file_dels--debug:
1425 file_dels--debug:
1426 file_dels--debug:
1426 file_dels--debug:
1427 file_dels--debug:
1427 file_dels--debug:
1428 file_mods:
1428 file_mods:
1429 file_mods:
1429 file_mods:
1430 file_mods:
1430 file_mods:
1431 file_mods:
1431 file_mods:
1432 file_mods:
1432 file_mods:
1433 file_mods: c
1433 file_mods: c
1434 file_mods:
1434 file_mods:
1435 file_mods:
1435 file_mods:
1436 file_mods:
1436 file_mods:
1437 file_mods--verbose:
1437 file_mods--verbose:
1438 file_mods--verbose:
1438 file_mods--verbose:
1439 file_mods--verbose:
1439 file_mods--verbose:
1440 file_mods--verbose:
1440 file_mods--verbose:
1441 file_mods--verbose:
1441 file_mods--verbose:
1442 file_mods--verbose: c
1442 file_mods--verbose: c
1443 file_mods--verbose:
1443 file_mods--verbose:
1444 file_mods--verbose:
1444 file_mods--verbose:
1445 file_mods--verbose:
1445 file_mods--verbose:
1446 file_mods--debug:
1446 file_mods--debug:
1447 file_mods--debug:
1447 file_mods--debug:
1448 file_mods--debug:
1448 file_mods--debug:
1449 file_mods--debug:
1449 file_mods--debug:
1450 file_mods--debug:
1450 file_mods--debug:
1451 file_mods--debug: c
1451 file_mods--debug: c
1452 file_mods--debug:
1452 file_mods--debug:
1453 file_mods--debug:
1453 file_mods--debug:
1454 file_mods--debug:
1454 file_mods--debug:
1455 file_copies: fourth (second)
1455 file_copies: fourth (second)
1456 file_copies:
1456 file_copies:
1457 file_copies:
1457 file_copies:
1458 file_copies:
1458 file_copies:
1459 file_copies:
1459 file_copies:
1460 file_copies:
1460 file_copies:
1461 file_copies:
1461 file_copies:
1462 file_copies:
1462 file_copies:
1463 file_copies:
1463 file_copies:
1464 file_copies--verbose: fourth (second)
1464 file_copies--verbose: fourth (second)
1465 file_copies--verbose:
1465 file_copies--verbose:
1466 file_copies--verbose:
1466 file_copies--verbose:
1467 file_copies--verbose:
1467 file_copies--verbose:
1468 file_copies--verbose:
1468 file_copies--verbose:
1469 file_copies--verbose:
1469 file_copies--verbose:
1470 file_copies--verbose:
1470 file_copies--verbose:
1471 file_copies--verbose:
1471 file_copies--verbose:
1472 file_copies--verbose:
1472 file_copies--verbose:
1473 file_copies--debug: fourth (second)
1473 file_copies--debug: fourth (second)
1474 file_copies--debug:
1474 file_copies--debug:
1475 file_copies--debug:
1475 file_copies--debug:
1476 file_copies--debug:
1476 file_copies--debug:
1477 file_copies--debug:
1477 file_copies--debug:
1478 file_copies--debug:
1478 file_copies--debug:
1479 file_copies--debug:
1479 file_copies--debug:
1480 file_copies--debug:
1480 file_copies--debug:
1481 file_copies--debug:
1481 file_copies--debug:
1482 file_copies_switch:
1482 file_copies_switch:
1483 file_copies_switch:
1483 file_copies_switch:
1484 file_copies_switch:
1484 file_copies_switch:
1485 file_copies_switch:
1485 file_copies_switch:
1486 file_copies_switch:
1486 file_copies_switch:
1487 file_copies_switch:
1487 file_copies_switch:
1488 file_copies_switch:
1488 file_copies_switch:
1489 file_copies_switch:
1489 file_copies_switch:
1490 file_copies_switch:
1490 file_copies_switch:
1491 file_copies_switch--verbose:
1491 file_copies_switch--verbose:
1492 file_copies_switch--verbose:
1492 file_copies_switch--verbose:
1493 file_copies_switch--verbose:
1493 file_copies_switch--verbose:
1494 file_copies_switch--verbose:
1494 file_copies_switch--verbose:
1495 file_copies_switch--verbose:
1495 file_copies_switch--verbose:
1496 file_copies_switch--verbose:
1496 file_copies_switch--verbose:
1497 file_copies_switch--verbose:
1497 file_copies_switch--verbose:
1498 file_copies_switch--verbose:
1498 file_copies_switch--verbose:
1499 file_copies_switch--verbose:
1499 file_copies_switch--verbose:
1500 file_copies_switch--debug:
1500 file_copies_switch--debug:
1501 file_copies_switch--debug:
1501 file_copies_switch--debug:
1502 file_copies_switch--debug:
1502 file_copies_switch--debug:
1503 file_copies_switch--debug:
1503 file_copies_switch--debug:
1504 file_copies_switch--debug:
1504 file_copies_switch--debug:
1505 file_copies_switch--debug:
1505 file_copies_switch--debug:
1506 file_copies_switch--debug:
1506 file_copies_switch--debug:
1507 file_copies_switch--debug:
1507 file_copies_switch--debug:
1508 file_copies_switch--debug:
1508 file_copies_switch--debug:
1509 files: fourth second third
1509 files: fourth second third
1510 files: second
1510 files: second
1511 files:
1511 files:
1512 files: d
1512 files: d
1513 files:
1513 files:
1514 files: c
1514 files: c
1515 files: c
1515 files: c
1516 files: b
1516 files: b
1517 files: a
1517 files: a
1518 files--verbose: fourth second third
1518 files--verbose: fourth second third
1519 files--verbose: second
1519 files--verbose: second
1520 files--verbose:
1520 files--verbose:
1521 files--verbose: d
1521 files--verbose: d
1522 files--verbose:
1522 files--verbose:
1523 files--verbose: c
1523 files--verbose: c
1524 files--verbose: c
1524 files--verbose: c
1525 files--verbose: b
1525 files--verbose: b
1526 files--verbose: a
1526 files--verbose: a
1527 files--debug: fourth second third
1527 files--debug: fourth second third
1528 files--debug: second
1528 files--debug: second
1529 files--debug:
1529 files--debug:
1530 files--debug: d
1530 files--debug: d
1531 files--debug:
1531 files--debug:
1532 files--debug: c
1532 files--debug: c
1533 files--debug: c
1533 files--debug: c
1534 files--debug: b
1534 files--debug: b
1535 files--debug: a
1535 files--debug: a
1536 manifest: 6:94961b75a2da
1536 manifest: 6:94961b75a2da
1537 manifest: 5:f2dbc354b94e
1537 manifest: 5:f2dbc354b94e
1538 manifest: 4:4dc3def4f9b4
1538 manifest: 4:4dc3def4f9b4
1539 manifest: 4:4dc3def4f9b4
1539 manifest: 4:4dc3def4f9b4
1540 manifest: 3:cb5a1327723b
1540 manifest: 3:cb5a1327723b
1541 manifest: 3:cb5a1327723b
1541 manifest: 3:cb5a1327723b
1542 manifest: 2:6e0e82995c35
1542 manifest: 2:6e0e82995c35
1543 manifest: 1:4e8d705b1e53
1543 manifest: 1:4e8d705b1e53
1544 manifest: 0:a0c8bcbbb45c
1544 manifest: 0:a0c8bcbbb45c
1545 manifest--verbose: 6:94961b75a2da
1545 manifest--verbose: 6:94961b75a2da
1546 manifest--verbose: 5:f2dbc354b94e
1546 manifest--verbose: 5:f2dbc354b94e
1547 manifest--verbose: 4:4dc3def4f9b4
1547 manifest--verbose: 4:4dc3def4f9b4
1548 manifest--verbose: 4:4dc3def4f9b4
1548 manifest--verbose: 4:4dc3def4f9b4
1549 manifest--verbose: 3:cb5a1327723b
1549 manifest--verbose: 3:cb5a1327723b
1550 manifest--verbose: 3:cb5a1327723b
1550 manifest--verbose: 3:cb5a1327723b
1551 manifest--verbose: 2:6e0e82995c35
1551 manifest--verbose: 2:6e0e82995c35
1552 manifest--verbose: 1:4e8d705b1e53
1552 manifest--verbose: 1:4e8d705b1e53
1553 manifest--verbose: 0:a0c8bcbbb45c
1553 manifest--verbose: 0:a0c8bcbbb45c
1554 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
1554 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
1555 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
1555 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
1556 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1556 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1557 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1557 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1558 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1558 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1559 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1559 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1560 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
1560 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
1561 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
1561 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
1562 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
1562 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
1563 node: 95c24699272ef57d062b8bccc32c878bf841784a
1563 node: 95c24699272ef57d062b8bccc32c878bf841784a
1564 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1564 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1565 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1565 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1566 node: 13207e5a10d9fd28ec424934298e176197f2c67f
1566 node: 13207e5a10d9fd28ec424934298e176197f2c67f
1567 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1567 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1568 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1568 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1569 node: 97054abb4ab824450e9164180baf491ae0078465
1569 node: 97054abb4ab824450e9164180baf491ae0078465
1570 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1570 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1571 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1571 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1572 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
1572 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
1573 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1573 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1574 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1574 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1575 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1575 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1576 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1576 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1577 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1577 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1578 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1578 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1579 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1579 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1580 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1580 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1581 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
1581 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
1582 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1582 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1583 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1583 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1584 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1584 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1585 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1585 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1586 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1586 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1587 node--debug: 97054abb4ab824450e9164180baf491ae0078465
1587 node--debug: 97054abb4ab824450e9164180baf491ae0078465
1588 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1588 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1589 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1589 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1590 parents:
1590 parents:
1591 parents: -1:000000000000
1591 parents: -1:000000000000
1592 parents: 5:13207e5a10d9 4:bbe44766e73d
1592 parents: 5:13207e5a10d9 4:bbe44766e73d
1593 parents: 3:10e46f2dcbf4
1593 parents: 3:10e46f2dcbf4
1594 parents:
1594 parents:
1595 parents:
1595 parents:
1596 parents:
1596 parents:
1597 parents:
1597 parents:
1598 parents:
1598 parents:
1599 parents--verbose:
1599 parents--verbose:
1600 parents--verbose: -1:000000000000
1600 parents--verbose: -1:000000000000
1601 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
1601 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
1602 parents--verbose: 3:10e46f2dcbf4
1602 parents--verbose: 3:10e46f2dcbf4
1603 parents--verbose:
1603 parents--verbose:
1604 parents--verbose:
1604 parents--verbose:
1605 parents--verbose:
1605 parents--verbose:
1606 parents--verbose:
1606 parents--verbose:
1607 parents--verbose:
1607 parents--verbose:
1608 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
1608 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
1609 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1609 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1610 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1610 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1611 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1611 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1612 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1612 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1613 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
1613 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
1614 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
1614 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
1615 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
1615 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
1616 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1616 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1617 rev: 8
1617 rev: 8
1618 rev: 7
1618 rev: 7
1619 rev: 6
1619 rev: 6
1620 rev: 5
1620 rev: 5
1621 rev: 4
1621 rev: 4
1622 rev: 3
1622 rev: 3
1623 rev: 2
1623 rev: 2
1624 rev: 1
1624 rev: 1
1625 rev: 0
1625 rev: 0
1626 rev--verbose: 8
1626 rev--verbose: 8
1627 rev--verbose: 7
1627 rev--verbose: 7
1628 rev--verbose: 6
1628 rev--verbose: 6
1629 rev--verbose: 5
1629 rev--verbose: 5
1630 rev--verbose: 4
1630 rev--verbose: 4
1631 rev--verbose: 3
1631 rev--verbose: 3
1632 rev--verbose: 2
1632 rev--verbose: 2
1633 rev--verbose: 1
1633 rev--verbose: 1
1634 rev--verbose: 0
1634 rev--verbose: 0
1635 rev--debug: 8
1635 rev--debug: 8
1636 rev--debug: 7
1636 rev--debug: 7
1637 rev--debug: 6
1637 rev--debug: 6
1638 rev--debug: 5
1638 rev--debug: 5
1639 rev--debug: 4
1639 rev--debug: 4
1640 rev--debug: 3
1640 rev--debug: 3
1641 rev--debug: 2
1641 rev--debug: 2
1642 rev--debug: 1
1642 rev--debug: 1
1643 rev--debug: 0
1643 rev--debug: 0
1644 tags: tip
1644 tags: tip
1645 tags:
1645 tags:
1646 tags:
1646 tags:
1647 tags:
1647 tags:
1648 tags:
1648 tags:
1649 tags:
1649 tags:
1650 tags:
1650 tags:
1651 tags:
1651 tags:
1652 tags:
1652 tags:
1653 tags--verbose: tip
1653 tags--verbose: tip
1654 tags--verbose:
1654 tags--verbose:
1655 tags--verbose:
1655 tags--verbose:
1656 tags--verbose:
1656 tags--verbose:
1657 tags--verbose:
1657 tags--verbose:
1658 tags--verbose:
1658 tags--verbose:
1659 tags--verbose:
1659 tags--verbose:
1660 tags--verbose:
1660 tags--verbose:
1661 tags--verbose:
1661 tags--verbose:
1662 tags--debug: tip
1662 tags--debug: tip
1663 tags--debug:
1663 tags--debug:
1664 tags--debug:
1664 tags--debug:
1665 tags--debug:
1665 tags--debug:
1666 tags--debug:
1666 tags--debug:
1667 tags--debug:
1667 tags--debug:
1668 tags--debug:
1668 tags--debug:
1669 tags--debug:
1669 tags--debug:
1670 tags--debug:
1670 tags--debug:
1671 diffstat: 3: +2/-1
1671 diffstat: 3: +2/-1
1672 diffstat: 1: +1/-0
1672 diffstat: 1: +1/-0
1673 diffstat: 0: +0/-0
1673 diffstat: 0: +0/-0
1674 diffstat: 1: +1/-0
1674 diffstat: 1: +1/-0
1675 diffstat: 0: +0/-0
1675 diffstat: 0: +0/-0
1676 diffstat: 1: +1/-0
1676 diffstat: 1: +1/-0
1677 diffstat: 1: +4/-0
1677 diffstat: 1: +4/-0
1678 diffstat: 1: +2/-0
1678 diffstat: 1: +2/-0
1679 diffstat: 1: +1/-0
1679 diffstat: 1: +1/-0
1680 diffstat--verbose: 3: +2/-1
1680 diffstat--verbose: 3: +2/-1
1681 diffstat--verbose: 1: +1/-0
1681 diffstat--verbose: 1: +1/-0
1682 diffstat--verbose: 0: +0/-0
1682 diffstat--verbose: 0: +0/-0
1683 diffstat--verbose: 1: +1/-0
1683 diffstat--verbose: 1: +1/-0
1684 diffstat--verbose: 0: +0/-0
1684 diffstat--verbose: 0: +0/-0
1685 diffstat--verbose: 1: +1/-0
1685 diffstat--verbose: 1: +1/-0
1686 diffstat--verbose: 1: +4/-0
1686 diffstat--verbose: 1: +4/-0
1687 diffstat--verbose: 1: +2/-0
1687 diffstat--verbose: 1: +2/-0
1688 diffstat--verbose: 1: +1/-0
1688 diffstat--verbose: 1: +1/-0
1689 diffstat--debug: 3: +2/-1
1689 diffstat--debug: 3: +2/-1
1690 diffstat--debug: 1: +1/-0
1690 diffstat--debug: 1: +1/-0
1691 diffstat--debug: 0: +0/-0
1691 diffstat--debug: 0: +0/-0
1692 diffstat--debug: 1: +1/-0
1692 diffstat--debug: 1: +1/-0
1693 diffstat--debug: 0: +0/-0
1693 diffstat--debug: 0: +0/-0
1694 diffstat--debug: 1: +1/-0
1694 diffstat--debug: 1: +1/-0
1695 diffstat--debug: 1: +4/-0
1695 diffstat--debug: 1: +4/-0
1696 diffstat--debug: 1: +2/-0
1696 diffstat--debug: 1: +2/-0
1697 diffstat--debug: 1: +1/-0
1697 diffstat--debug: 1: +1/-0
1698 extras: branch=default
1698 extras: branch=default
1699 extras: branch=default
1699 extras: branch=default
1700 extras: branch=default
1700 extras: branch=default
1701 extras: branch=default
1701 extras: branch=default
1702 extras: branch=foo
1702 extras: branch=foo
1703 extras: branch=default
1703 extras: branch=default
1704 extras: branch=default
1704 extras: branch=default
1705 extras: branch=default
1705 extras: branch=default
1706 extras: branch=default
1706 extras: branch=default
1707 extras--verbose: branch=default
1707 extras--verbose: branch=default
1708 extras--verbose: branch=default
1708 extras--verbose: branch=default
1709 extras--verbose: branch=default
1709 extras--verbose: branch=default
1710 extras--verbose: branch=default
1710 extras--verbose: branch=default
1711 extras--verbose: branch=foo
1711 extras--verbose: branch=foo
1712 extras--verbose: branch=default
1712 extras--verbose: branch=default
1713 extras--verbose: branch=default
1713 extras--verbose: branch=default
1714 extras--verbose: branch=default
1714 extras--verbose: branch=default
1715 extras--verbose: branch=default
1715 extras--verbose: branch=default
1716 extras--debug: branch=default
1716 extras--debug: branch=default
1717 extras--debug: branch=default
1717 extras--debug: branch=default
1718 extras--debug: branch=default
1718 extras--debug: branch=default
1719 extras--debug: branch=default
1719 extras--debug: branch=default
1720 extras--debug: branch=foo
1720 extras--debug: branch=foo
1721 extras--debug: branch=default
1721 extras--debug: branch=default
1722 extras--debug: branch=default
1722 extras--debug: branch=default
1723 extras--debug: branch=default
1723 extras--debug: branch=default
1724 extras--debug: branch=default
1724 extras--debug: branch=default
1725 p1rev: 7
1725 p1rev: 7
1726 p1rev: -1
1726 p1rev: -1
1727 p1rev: 5
1727 p1rev: 5
1728 p1rev: 3
1728 p1rev: 3
1729 p1rev: 3
1729 p1rev: 3
1730 p1rev: 2
1730 p1rev: 2
1731 p1rev: 1
1731 p1rev: 1
1732 p1rev: 0
1732 p1rev: 0
1733 p1rev: -1
1733 p1rev: -1
1734 p1rev--verbose: 7
1734 p1rev--verbose: 7
1735 p1rev--verbose: -1
1735 p1rev--verbose: -1
1736 p1rev--verbose: 5
1736 p1rev--verbose: 5
1737 p1rev--verbose: 3
1737 p1rev--verbose: 3
1738 p1rev--verbose: 3
1738 p1rev--verbose: 3
1739 p1rev--verbose: 2
1739 p1rev--verbose: 2
1740 p1rev--verbose: 1
1740 p1rev--verbose: 1
1741 p1rev--verbose: 0
1741 p1rev--verbose: 0
1742 p1rev--verbose: -1
1742 p1rev--verbose: -1
1743 p1rev--debug: 7
1743 p1rev--debug: 7
1744 p1rev--debug: -1
1744 p1rev--debug: -1
1745 p1rev--debug: 5
1745 p1rev--debug: 5
1746 p1rev--debug: 3
1746 p1rev--debug: 3
1747 p1rev--debug: 3
1747 p1rev--debug: 3
1748 p1rev--debug: 2
1748 p1rev--debug: 2
1749 p1rev--debug: 1
1749 p1rev--debug: 1
1750 p1rev--debug: 0
1750 p1rev--debug: 0
1751 p1rev--debug: -1
1751 p1rev--debug: -1
1752 p2rev: -1
1752 p2rev: -1
1753 p2rev: -1
1753 p2rev: -1
1754 p2rev: 4
1754 p2rev: 4
1755 p2rev: -1
1755 p2rev: -1
1756 p2rev: -1
1756 p2rev: -1
1757 p2rev: -1
1757 p2rev: -1
1758 p2rev: -1
1758 p2rev: -1
1759 p2rev: -1
1759 p2rev: -1
1760 p2rev: -1
1760 p2rev: -1
1761 p2rev--verbose: -1
1761 p2rev--verbose: -1
1762 p2rev--verbose: -1
1762 p2rev--verbose: -1
1763 p2rev--verbose: 4
1763 p2rev--verbose: 4
1764 p2rev--verbose: -1
1764 p2rev--verbose: -1
1765 p2rev--verbose: -1
1765 p2rev--verbose: -1
1766 p2rev--verbose: -1
1766 p2rev--verbose: -1
1767 p2rev--verbose: -1
1767 p2rev--verbose: -1
1768 p2rev--verbose: -1
1768 p2rev--verbose: -1
1769 p2rev--verbose: -1
1769 p2rev--verbose: -1
1770 p2rev--debug: -1
1770 p2rev--debug: -1
1771 p2rev--debug: -1
1771 p2rev--debug: -1
1772 p2rev--debug: 4
1772 p2rev--debug: 4
1773 p2rev--debug: -1
1773 p2rev--debug: -1
1774 p2rev--debug: -1
1774 p2rev--debug: -1
1775 p2rev--debug: -1
1775 p2rev--debug: -1
1776 p2rev--debug: -1
1776 p2rev--debug: -1
1777 p2rev--debug: -1
1777 p2rev--debug: -1
1778 p2rev--debug: -1
1778 p2rev--debug: -1
1779 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1779 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1780 p1node: 0000000000000000000000000000000000000000
1780 p1node: 0000000000000000000000000000000000000000
1781 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
1781 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
1782 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1782 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1783 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1783 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1784 p1node: 97054abb4ab824450e9164180baf491ae0078465
1784 p1node: 97054abb4ab824450e9164180baf491ae0078465
1785 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1785 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1786 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1786 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1787 p1node: 0000000000000000000000000000000000000000
1787 p1node: 0000000000000000000000000000000000000000
1788 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1788 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1789 p1node--verbose: 0000000000000000000000000000000000000000
1789 p1node--verbose: 0000000000000000000000000000000000000000
1790 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1790 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1791 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1791 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1792 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1792 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1793 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1793 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1794 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1794 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1795 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1795 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1796 p1node--verbose: 0000000000000000000000000000000000000000
1796 p1node--verbose: 0000000000000000000000000000000000000000
1797 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1797 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1798 p1node--debug: 0000000000000000000000000000000000000000
1798 p1node--debug: 0000000000000000000000000000000000000000
1799 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1799 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1800 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1800 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1801 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1801 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1802 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
1802 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
1803 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1803 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1804 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1804 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1805 p1node--debug: 0000000000000000000000000000000000000000
1805 p1node--debug: 0000000000000000000000000000000000000000
1806 p2node: 0000000000000000000000000000000000000000
1806 p2node: 0000000000000000000000000000000000000000
1807 p2node: 0000000000000000000000000000000000000000
1807 p2node: 0000000000000000000000000000000000000000
1808 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1808 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1809 p2node: 0000000000000000000000000000000000000000
1809 p2node: 0000000000000000000000000000000000000000
1810 p2node: 0000000000000000000000000000000000000000
1810 p2node: 0000000000000000000000000000000000000000
1811 p2node: 0000000000000000000000000000000000000000
1811 p2node: 0000000000000000000000000000000000000000
1812 p2node: 0000000000000000000000000000000000000000
1812 p2node: 0000000000000000000000000000000000000000
1813 p2node: 0000000000000000000000000000000000000000
1813 p2node: 0000000000000000000000000000000000000000
1814 p2node: 0000000000000000000000000000000000000000
1814 p2node: 0000000000000000000000000000000000000000
1815 p2node--verbose: 0000000000000000000000000000000000000000
1815 p2node--verbose: 0000000000000000000000000000000000000000
1816 p2node--verbose: 0000000000000000000000000000000000000000
1816 p2node--verbose: 0000000000000000000000000000000000000000
1817 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1817 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1818 p2node--verbose: 0000000000000000000000000000000000000000
1818 p2node--verbose: 0000000000000000000000000000000000000000
1819 p2node--verbose: 0000000000000000000000000000000000000000
1819 p2node--verbose: 0000000000000000000000000000000000000000
1820 p2node--verbose: 0000000000000000000000000000000000000000
1820 p2node--verbose: 0000000000000000000000000000000000000000
1821 p2node--verbose: 0000000000000000000000000000000000000000
1821 p2node--verbose: 0000000000000000000000000000000000000000
1822 p2node--verbose: 0000000000000000000000000000000000000000
1822 p2node--verbose: 0000000000000000000000000000000000000000
1823 p2node--verbose: 0000000000000000000000000000000000000000
1823 p2node--verbose: 0000000000000000000000000000000000000000
1824 p2node--debug: 0000000000000000000000000000000000000000
1824 p2node--debug: 0000000000000000000000000000000000000000
1825 p2node--debug: 0000000000000000000000000000000000000000
1825 p2node--debug: 0000000000000000000000000000000000000000
1826 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1826 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1827 p2node--debug: 0000000000000000000000000000000000000000
1827 p2node--debug: 0000000000000000000000000000000000000000
1828 p2node--debug: 0000000000000000000000000000000000000000
1828 p2node--debug: 0000000000000000000000000000000000000000
1829 p2node--debug: 0000000000000000000000000000000000000000
1829 p2node--debug: 0000000000000000000000000000000000000000
1830 p2node--debug: 0000000000000000000000000000000000000000
1830 p2node--debug: 0000000000000000000000000000000000000000
1831 p2node--debug: 0000000000000000000000000000000000000000
1831 p2node--debug: 0000000000000000000000000000000000000000
1832 p2node--debug: 0000000000000000000000000000000000000000
1832 p2node--debug: 0000000000000000000000000000000000000000
1833
1833
1834 Filters work:
1834 Filters work:
1835
1835
1836 $ hg log --template '{author|domain}\n'
1836 $ hg log --template '{author|domain}\n'
1837
1837
1838 hostname
1838 hostname
1839
1839
1840
1840
1841
1841
1842
1842
1843 place
1843 place
1844 place
1844 place
1845 hostname
1845 hostname
1846
1846
1847 $ hg log --template '{author|person}\n'
1847 $ hg log --template '{author|person}\n'
1848 test
1848 test
1849 User Name
1849 User Name
1850 person
1850 person
1851 person
1851 person
1852 person
1852 person
1853 person
1853 person
1854 other
1854 other
1855 A. N. Other
1855 A. N. Other
1856 User Name
1856 User Name
1857
1857
1858 $ hg log --template '{author|user}\n'
1858 $ hg log --template '{author|user}\n'
1859 test
1859 test
1860 user
1860 user
1861 person
1861 person
1862 person
1862 person
1863 person
1863 person
1864 person
1864 person
1865 other
1865 other
1866 other
1866 other
1867 user
1867 user
1868
1868
1869 $ hg log --template '{date|date}\n'
1869 $ hg log --template '{date|date}\n'
1870 Wed Jan 01 10:01:00 2020 +0000
1870 Wed Jan 01 10:01:00 2020 +0000
1871 Mon Jan 12 13:46:40 1970 +0000
1871 Mon Jan 12 13:46:40 1970 +0000
1872 Sun Jan 18 08:40:01 1970 +0000
1872 Sun Jan 18 08:40:01 1970 +0000
1873 Sun Jan 18 08:40:00 1970 +0000
1873 Sun Jan 18 08:40:00 1970 +0000
1874 Sat Jan 17 04:53:20 1970 +0000
1874 Sat Jan 17 04:53:20 1970 +0000
1875 Fri Jan 16 01:06:40 1970 +0000
1875 Fri Jan 16 01:06:40 1970 +0000
1876 Wed Jan 14 21:20:00 1970 +0000
1876 Wed Jan 14 21:20:00 1970 +0000
1877 Tue Jan 13 17:33:20 1970 +0000
1877 Tue Jan 13 17:33:20 1970 +0000
1878 Mon Jan 12 13:46:40 1970 +0000
1878 Mon Jan 12 13:46:40 1970 +0000
1879
1879
1880 $ hg log --template '{date|isodate}\n'
1880 $ hg log --template '{date|isodate}\n'
1881 2020-01-01 10:01 +0000
1881 2020-01-01 10:01 +0000
1882 1970-01-12 13:46 +0000
1882 1970-01-12 13:46 +0000
1883 1970-01-18 08:40 +0000
1883 1970-01-18 08:40 +0000
1884 1970-01-18 08:40 +0000
1884 1970-01-18 08:40 +0000
1885 1970-01-17 04:53 +0000
1885 1970-01-17 04:53 +0000
1886 1970-01-16 01:06 +0000
1886 1970-01-16 01:06 +0000
1887 1970-01-14 21:20 +0000
1887 1970-01-14 21:20 +0000
1888 1970-01-13 17:33 +0000
1888 1970-01-13 17:33 +0000
1889 1970-01-12 13:46 +0000
1889 1970-01-12 13:46 +0000
1890
1890
1891 $ hg log --template '{date|isodatesec}\n'
1891 $ hg log --template '{date|isodatesec}\n'
1892 2020-01-01 10:01:00 +0000
1892 2020-01-01 10:01:00 +0000
1893 1970-01-12 13:46:40 +0000
1893 1970-01-12 13:46:40 +0000
1894 1970-01-18 08:40:01 +0000
1894 1970-01-18 08:40:01 +0000
1895 1970-01-18 08:40:00 +0000
1895 1970-01-18 08:40:00 +0000
1896 1970-01-17 04:53:20 +0000
1896 1970-01-17 04:53:20 +0000
1897 1970-01-16 01:06:40 +0000
1897 1970-01-16 01:06:40 +0000
1898 1970-01-14 21:20:00 +0000
1898 1970-01-14 21:20:00 +0000
1899 1970-01-13 17:33:20 +0000
1899 1970-01-13 17:33:20 +0000
1900 1970-01-12 13:46:40 +0000
1900 1970-01-12 13:46:40 +0000
1901
1901
1902 $ hg log --template '{date|rfc822date}\n'
1902 $ hg log --template '{date|rfc822date}\n'
1903 Wed, 01 Jan 2020 10:01:00 +0000
1903 Wed, 01 Jan 2020 10:01:00 +0000
1904 Mon, 12 Jan 1970 13:46:40 +0000
1904 Mon, 12 Jan 1970 13:46:40 +0000
1905 Sun, 18 Jan 1970 08:40:01 +0000
1905 Sun, 18 Jan 1970 08:40:01 +0000
1906 Sun, 18 Jan 1970 08:40:00 +0000
1906 Sun, 18 Jan 1970 08:40:00 +0000
1907 Sat, 17 Jan 1970 04:53:20 +0000
1907 Sat, 17 Jan 1970 04:53:20 +0000
1908 Fri, 16 Jan 1970 01:06:40 +0000
1908 Fri, 16 Jan 1970 01:06:40 +0000
1909 Wed, 14 Jan 1970 21:20:00 +0000
1909 Wed, 14 Jan 1970 21:20:00 +0000
1910 Tue, 13 Jan 1970 17:33:20 +0000
1910 Tue, 13 Jan 1970 17:33:20 +0000
1911 Mon, 12 Jan 1970 13:46:40 +0000
1911 Mon, 12 Jan 1970 13:46:40 +0000
1912
1912
1913 $ hg log --template '{desc|firstline}\n'
1913 $ hg log --template '{desc|firstline}\n'
1914 third
1914 third
1915 second
1915 second
1916 merge
1916 merge
1917 new head
1917 new head
1918 new branch
1918 new branch
1919 no user, no domain
1919 no user, no domain
1920 no person
1920 no person
1921 other 1
1921 other 1
1922 line 1
1922 line 1
1923
1923
1924 $ hg log --template '{node|short}\n'
1924 $ hg log --template '{node|short}\n'
1925 95c24699272e
1925 95c24699272e
1926 29114dbae42b
1926 29114dbae42b
1927 d41e714fe50d
1927 d41e714fe50d
1928 13207e5a10d9
1928 13207e5a10d9
1929 bbe44766e73d
1929 bbe44766e73d
1930 10e46f2dcbf4
1930 10e46f2dcbf4
1931 97054abb4ab8
1931 97054abb4ab8
1932 b608e9d1a3f0
1932 b608e9d1a3f0
1933 1e4e1b8f71e0
1933 1e4e1b8f71e0
1934
1934
1935 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
1935 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
1936 <changeset author="test"/>
1936 <changeset author="test"/>
1937 <changeset author="User Name &lt;user@hostname&gt;"/>
1937 <changeset author="User Name &lt;user@hostname&gt;"/>
1938 <changeset author="person"/>
1938 <changeset author="person"/>
1939 <changeset author="person"/>
1939 <changeset author="person"/>
1940 <changeset author="person"/>
1940 <changeset author="person"/>
1941 <changeset author="person"/>
1941 <changeset author="person"/>
1942 <changeset author="other@place"/>
1942 <changeset author="other@place"/>
1943 <changeset author="A. N. Other &lt;other@place&gt;"/>
1943 <changeset author="A. N. Other &lt;other@place&gt;"/>
1944 <changeset author="User Name &lt;user@hostname&gt;"/>
1944 <changeset author="User Name &lt;user@hostname&gt;"/>
1945
1945
1946 $ hg log --template '{rev}: {children}\n'
1946 $ hg log --template '{rev}: {children}\n'
1947 8:
1947 8:
1948 7: 8:95c24699272e
1948 7: 8:95c24699272e
1949 6:
1949 6:
1950 5: 6:d41e714fe50d
1950 5: 6:d41e714fe50d
1951 4: 6:d41e714fe50d
1951 4: 6:d41e714fe50d
1952 3: 4:bbe44766e73d 5:13207e5a10d9
1952 3: 4:bbe44766e73d 5:13207e5a10d9
1953 2: 3:10e46f2dcbf4
1953 2: 3:10e46f2dcbf4
1954 1: 2:97054abb4ab8
1954 1: 2:97054abb4ab8
1955 0: 1:b608e9d1a3f0
1955 0: 1:b608e9d1a3f0
1956
1956
1957 Formatnode filter works:
1957 Formatnode filter works:
1958
1958
1959 $ hg -q log -r 0 --template '{node|formatnode}\n'
1959 $ hg -q log -r 0 --template '{node|formatnode}\n'
1960 1e4e1b8f71e0
1960 1e4e1b8f71e0
1961
1961
1962 $ hg log -r 0 --template '{node|formatnode}\n'
1962 $ hg log -r 0 --template '{node|formatnode}\n'
1963 1e4e1b8f71e0
1963 1e4e1b8f71e0
1964
1964
1965 $ hg -v log -r 0 --template '{node|formatnode}\n'
1965 $ hg -v log -r 0 --template '{node|formatnode}\n'
1966 1e4e1b8f71e0
1966 1e4e1b8f71e0
1967
1967
1968 $ hg --debug log -r 0 --template '{node|formatnode}\n'
1968 $ hg --debug log -r 0 --template '{node|formatnode}\n'
1969 1e4e1b8f71e05681d422154f5421e385fec3454f
1969 1e4e1b8f71e05681d422154f5421e385fec3454f
1970
1970
1971 Age filter:
1971 Age filter:
1972
1972
1973 $ hg init unstable-hash
1973 $ hg init unstable-hash
1974 $ cd unstable-hash
1974 $ cd unstable-hash
1975 $ hg log --template '{date|age}\n' > /dev/null || exit 1
1975 $ hg log --template '{date|age}\n' > /dev/null || exit 1
1976
1976
1977 >>> from datetime import datetime, timedelta
1977 >>> from datetime import datetime, timedelta
1978 >>> fp = open('a', 'w')
1978 >>> fp = open('a', 'w')
1979 >>> n = datetime.now() + timedelta(366 * 7)
1979 >>> n = datetime.now() + timedelta(366 * 7)
1980 >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
1980 >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
1981 >>> fp.close()
1981 >>> fp.close()
1982 $ hg add a
1982 $ hg add a
1983 $ hg commit -m future -d "`cat a`"
1983 $ hg commit -m future -d "`cat a`"
1984
1984
1985 $ hg log -l1 --template '{date|age}\n'
1985 $ hg log -l1 --template '{date|age}\n'
1986 7 years from now
1986 7 years from now
1987
1987
1988 $ cd ..
1988 $ cd ..
1989 $ rm -rf unstable-hash
1989 $ rm -rf unstable-hash
1990
1990
1991 Add a dummy commit to make up for the instability of the above:
1991 Add a dummy commit to make up for the instability of the above:
1992
1992
1993 $ echo a > a
1993 $ echo a > a
1994 $ hg add a
1994 $ hg add a
1995 $ hg ci -m future
1995 $ hg ci -m future
1996
1996
1997 Count filter:
1997 Count filter:
1998
1998
1999 $ hg log -l1 --template '{node|count} {node|short|count}\n'
1999 $ hg log -l1 --template '{node|count} {node|short|count}\n'
2000 40 12
2000 40 12
2001
2001
2002 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
2002 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
2003 0 1 4
2003 0 1 4
2004
2004
2005 $ hg log -G --template '{rev}: children: {children|count}, \
2005 $ hg log -G --template '{rev}: children: {children|count}, \
2006 > tags: {tags|count}, file_adds: {file_adds|count}, \
2006 > tags: {tags|count}, file_adds: {file_adds|count}, \
2007 > ancestors: {revset("ancestors(%s)", rev)|count}'
2007 > ancestors: {revset("ancestors(%s)", rev)|count}'
2008 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
2008 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
2009 |
2009 |
2010 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
2010 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
2011 |
2011 |
2012 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
2012 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
2013
2013
2014 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
2014 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
2015 |\
2015 |\
2016 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
2016 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
2017 | |
2017 | |
2018 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
2018 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
2019 |/
2019 |/
2020 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
2020 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
2021 |
2021 |
2022 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
2022 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
2023 |
2023 |
2024 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
2024 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
2025 |
2025 |
2026 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
2026 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
2027
2027
2028
2028
2029 Upper/lower filters:
2029 Upper/lower filters:
2030
2030
2031 $ hg log -r0 --template '{branch|upper}\n'
2031 $ hg log -r0 --template '{branch|upper}\n'
2032 DEFAULT
2032 DEFAULT
2033 $ hg log -r0 --template '{author|lower}\n'
2033 $ hg log -r0 --template '{author|lower}\n'
2034 user name <user@hostname>
2034 user name <user@hostname>
2035 $ hg log -r0 --template '{date|upper}\n'
2035 $ hg log -r0 --template '{date|upper}\n'
2036 abort: template filter 'upper' is not compatible with keyword 'date'
2036 abort: template filter 'upper' is not compatible with keyword 'date'
2037 [255]
2037 [255]
2038
2038
2039 Add a commit that does all possible modifications at once
2039 Add a commit that does all possible modifications at once
2040
2040
2041 $ echo modify >> third
2041 $ echo modify >> third
2042 $ touch b
2042 $ touch b
2043 $ hg add b
2043 $ hg add b
2044 $ hg mv fourth fifth
2044 $ hg mv fourth fifth
2045 $ hg rm a
2045 $ hg rm a
2046 $ hg ci -m "Modify, add, remove, rename"
2046 $ hg ci -m "Modify, add, remove, rename"
2047
2047
2048 Check the status template
2048 Check the status template
2049
2049
2050 $ cat <<EOF >> $HGRCPATH
2050 $ cat <<EOF >> $HGRCPATH
2051 > [extensions]
2051 > [extensions]
2052 > color=
2052 > color=
2053 > EOF
2053 > EOF
2054
2054
2055 $ hg log -T status -r 10
2055 $ hg log -T status -r 10
2056 changeset: 10:0f9759ec227a
2056 changeset: 10:0f9759ec227a
2057 tag: tip
2057 tag: tip
2058 user: test
2058 user: test
2059 date: Thu Jan 01 00:00:00 1970 +0000
2059 date: Thu Jan 01 00:00:00 1970 +0000
2060 summary: Modify, add, remove, rename
2060 summary: Modify, add, remove, rename
2061 files:
2061 files:
2062 M third
2062 M third
2063 A b
2063 A b
2064 A fifth
2064 A fifth
2065 R a
2065 R a
2066 R fourth
2066 R fourth
2067
2067
2068 $ hg log -T status -C -r 10
2068 $ hg log -T status -C -r 10
2069 changeset: 10:0f9759ec227a
2069 changeset: 10:0f9759ec227a
2070 tag: tip
2070 tag: tip
2071 user: test
2071 user: test
2072 date: Thu Jan 01 00:00:00 1970 +0000
2072 date: Thu Jan 01 00:00:00 1970 +0000
2073 summary: Modify, add, remove, rename
2073 summary: Modify, add, remove, rename
2074 files:
2074 files:
2075 M third
2075 M third
2076 A b
2076 A b
2077 A fifth
2077 A fifth
2078 fourth
2078 fourth
2079 R a
2079 R a
2080 R fourth
2080 R fourth
2081
2081
2082 $ hg log -T status -C -r 10 -v
2082 $ hg log -T status -C -r 10 -v
2083 changeset: 10:0f9759ec227a
2083 changeset: 10:0f9759ec227a
2084 tag: tip
2084 tag: tip
2085 user: test
2085 user: test
2086 date: Thu Jan 01 00:00:00 1970 +0000
2086 date: Thu Jan 01 00:00:00 1970 +0000
2087 description:
2087 description:
2088 Modify, add, remove, rename
2088 Modify, add, remove, rename
2089
2089
2090 files:
2090 files:
2091 M third
2091 M third
2092 A b
2092 A b
2093 A fifth
2093 A fifth
2094 fourth
2094 fourth
2095 R a
2095 R a
2096 R fourth
2096 R fourth
2097
2097
2098 $ hg log -T status -C -r 10 --debug
2098 $ hg log -T status -C -r 10 --debug
2099 changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c
2099 changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c
2100 tag: tip
2100 tag: tip
2101 phase: secret
2101 phase: secret
2102 parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066
2102 parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066
2103 parent: -1:0000000000000000000000000000000000000000
2103 parent: -1:0000000000000000000000000000000000000000
2104 manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567
2104 manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567
2105 user: test
2105 user: test
2106 date: Thu Jan 01 00:00:00 1970 +0000
2106 date: Thu Jan 01 00:00:00 1970 +0000
2107 extra: branch=default
2107 extra: branch=default
2108 description:
2108 description:
2109 Modify, add, remove, rename
2109 Modify, add, remove, rename
2110
2110
2111 files:
2111 files:
2112 M third
2112 M third
2113 A b
2113 A b
2114 A fifth
2114 A fifth
2115 fourth
2115 fourth
2116 R a
2116 R a
2117 R fourth
2117 R fourth
2118
2118
2119 $ hg log -T status -C -r 10 --quiet
2119 $ hg log -T status -C -r 10 --quiet
2120 10:0f9759ec227a
2120 10:0f9759ec227a
2121 $ hg --color=debug log -T status -r 10
2121 $ hg --color=debug log -T status -r 10
2122 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2122 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2123 [log.tag|tag: tip]
2123 [log.tag|tag: tip]
2124 [log.user|user: test]
2124 [log.user|user: test]
2125 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2125 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2126 [log.summary|summary: Modify, add, remove, rename]
2126 [log.summary|summary: Modify, add, remove, rename]
2127 [ui.note log.files|files:]
2127 [ui.note log.files|files:]
2128 [status.modified|M third]
2128 [status.modified|M third]
2129 [status.added|A b]
2129 [status.added|A b]
2130 [status.added|A fifth]
2130 [status.added|A fifth]
2131 [status.removed|R a]
2131 [status.removed|R a]
2132 [status.removed|R fourth]
2132 [status.removed|R fourth]
2133
2133
2134 $ hg --color=debug log -T status -C -r 10
2134 $ hg --color=debug log -T status -C -r 10
2135 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2135 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2136 [log.tag|tag: tip]
2136 [log.tag|tag: tip]
2137 [log.user|user: test]
2137 [log.user|user: test]
2138 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2138 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2139 [log.summary|summary: Modify, add, remove, rename]
2139 [log.summary|summary: Modify, add, remove, rename]
2140 [ui.note log.files|files:]
2140 [ui.note log.files|files:]
2141 [status.modified|M third]
2141 [status.modified|M third]
2142 [status.added|A b]
2142 [status.added|A b]
2143 [status.added|A fifth]
2143 [status.added|A fifth]
2144 [status.copied| fourth]
2144 [status.copied| fourth]
2145 [status.removed|R a]
2145 [status.removed|R a]
2146 [status.removed|R fourth]
2146 [status.removed|R fourth]
2147
2147
2148 $ hg --color=debug log -T status -C -r 10 -v
2148 $ hg --color=debug log -T status -C -r 10 -v
2149 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2149 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2150 [log.tag|tag: tip]
2150 [log.tag|tag: tip]
2151 [log.user|user: test]
2151 [log.user|user: test]
2152 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2152 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2153 [ui.note log.description|description:]
2153 [ui.note log.description|description:]
2154 [ui.note log.description|Modify, add, remove, rename]
2154 [ui.note log.description|Modify, add, remove, rename]
2155
2155
2156 [ui.note log.files|files:]
2156 [ui.note log.files|files:]
2157 [status.modified|M third]
2157 [status.modified|M third]
2158 [status.added|A b]
2158 [status.added|A b]
2159 [status.added|A fifth]
2159 [status.added|A fifth]
2160 [status.copied| fourth]
2160 [status.copied| fourth]
2161 [status.removed|R a]
2161 [status.removed|R a]
2162 [status.removed|R fourth]
2162 [status.removed|R fourth]
2163
2163
2164 $ hg --color=debug log -T status -C -r 10 --debug
2164 $ hg --color=debug log -T status -C -r 10 --debug
2165 [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
2165 [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
2166 [log.tag|tag: tip]
2166 [log.tag|tag: tip]
2167 [log.phase|phase: secret]
2167 [log.phase|phase: secret]
2168 [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066]
2168 [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066]
2169 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2169 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2170 [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567]
2170 [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567]
2171 [log.user|user: test]
2171 [log.user|user: test]
2172 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2172 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2173 [ui.debug log.extra|extra: branch=default]
2173 [ui.debug log.extra|extra: branch=default]
2174 [ui.note log.description|description:]
2174 [ui.note log.description|description:]
2175 [ui.note log.description|Modify, add, remove, rename]
2175 [ui.note log.description|Modify, add, remove, rename]
2176
2176
2177 [ui.note log.files|files:]
2177 [ui.note log.files|files:]
2178 [status.modified|M third]
2178 [status.modified|M third]
2179 [status.added|A b]
2179 [status.added|A b]
2180 [status.added|A fifth]
2180 [status.added|A fifth]
2181 [status.copied| fourth]
2181 [status.copied| fourth]
2182 [status.removed|R a]
2182 [status.removed|R a]
2183 [status.removed|R fourth]
2183 [status.removed|R fourth]
2184
2184
2185 $ hg --color=debug log -T status -C -r 10 --quiet
2185 $ hg --color=debug log -T status -C -r 10 --quiet
2186 [log.node|10:0f9759ec227a]
2186 [log.node|10:0f9759ec227a]
2187
2187
2188 Check the bisect template
2188 Check the bisect template
2189
2189
2190 $ hg bisect -g 1
2190 $ hg bisect -g 1
2191 $ hg bisect -b 3 --noupdate
2191 $ hg bisect -b 3 --noupdate
2192 Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
2192 Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
2193 $ hg log -T bisect -r 0:4
2193 $ hg log -T bisect -r 0:4
2194 changeset: 0:1e4e1b8f71e0
2194 changeset: 0:1e4e1b8f71e0
2195 bisect: good (implicit)
2195 bisect: good (implicit)
2196 user: User Name <user@hostname>
2196 user: User Name <user@hostname>
2197 date: Mon Jan 12 13:46:40 1970 +0000
2197 date: Mon Jan 12 13:46:40 1970 +0000
2198 summary: line 1
2198 summary: line 1
2199
2199
2200 changeset: 1:b608e9d1a3f0
2200 changeset: 1:b608e9d1a3f0
2201 bisect: good
2201 bisect: good
2202 user: A. N. Other <other@place>
2202 user: A. N. Other <other@place>
2203 date: Tue Jan 13 17:33:20 1970 +0000
2203 date: Tue Jan 13 17:33:20 1970 +0000
2204 summary: other 1
2204 summary: other 1
2205
2205
2206 changeset: 2:97054abb4ab8
2206 changeset: 2:97054abb4ab8
2207 bisect: untested
2207 bisect: untested
2208 user: other@place
2208 user: other@place
2209 date: Wed Jan 14 21:20:00 1970 +0000
2209 date: Wed Jan 14 21:20:00 1970 +0000
2210 summary: no person
2210 summary: no person
2211
2211
2212 changeset: 3:10e46f2dcbf4
2212 changeset: 3:10e46f2dcbf4
2213 bisect: bad
2213 bisect: bad
2214 user: person
2214 user: person
2215 date: Fri Jan 16 01:06:40 1970 +0000
2215 date: Fri Jan 16 01:06:40 1970 +0000
2216 summary: no user, no domain
2216 summary: no user, no domain
2217
2217
2218 changeset: 4:bbe44766e73d
2218 changeset: 4:bbe44766e73d
2219 bisect: bad (implicit)
2219 bisect: bad (implicit)
2220 branch: foo
2220 branch: foo
2221 user: person
2221 user: person
2222 date: Sat Jan 17 04:53:20 1970 +0000
2222 date: Sat Jan 17 04:53:20 1970 +0000
2223 summary: new branch
2223 summary: new branch
2224
2224
2225 $ hg log --debug -T bisect -r 0:4
2225 $ hg log --debug -T bisect -r 0:4
2226 changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2226 changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2227 bisect: good (implicit)
2227 bisect: good (implicit)
2228 phase: public
2228 phase: public
2229 parent: -1:0000000000000000000000000000000000000000
2229 parent: -1:0000000000000000000000000000000000000000
2230 parent: -1:0000000000000000000000000000000000000000
2230 parent: -1:0000000000000000000000000000000000000000
2231 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
2231 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
2232 user: User Name <user@hostname>
2232 user: User Name <user@hostname>
2233 date: Mon Jan 12 13:46:40 1970 +0000
2233 date: Mon Jan 12 13:46:40 1970 +0000
2234 files+: a
2234 files+: a
2235 extra: branch=default
2235 extra: branch=default
2236 description:
2236 description:
2237 line 1
2237 line 1
2238 line 2
2238 line 2
2239
2239
2240
2240
2241 changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2241 changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2242 bisect: good
2242 bisect: good
2243 phase: public
2243 phase: public
2244 parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2244 parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2245 parent: -1:0000000000000000000000000000000000000000
2245 parent: -1:0000000000000000000000000000000000000000
2246 manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
2246 manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
2247 user: A. N. Other <other@place>
2247 user: A. N. Other <other@place>
2248 date: Tue Jan 13 17:33:20 1970 +0000
2248 date: Tue Jan 13 17:33:20 1970 +0000
2249 files+: b
2249 files+: b
2250 extra: branch=default
2250 extra: branch=default
2251 description:
2251 description:
2252 other 1
2252 other 1
2253 other 2
2253 other 2
2254
2254
2255 other 3
2255 other 3
2256
2256
2257
2257
2258 changeset: 2:97054abb4ab824450e9164180baf491ae0078465
2258 changeset: 2:97054abb4ab824450e9164180baf491ae0078465
2259 bisect: untested
2259 bisect: untested
2260 phase: public
2260 phase: public
2261 parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2261 parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2262 parent: -1:0000000000000000000000000000000000000000
2262 parent: -1:0000000000000000000000000000000000000000
2263 manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
2263 manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
2264 user: other@place
2264 user: other@place
2265 date: Wed Jan 14 21:20:00 1970 +0000
2265 date: Wed Jan 14 21:20:00 1970 +0000
2266 files+: c
2266 files+: c
2267 extra: branch=default
2267 extra: branch=default
2268 description:
2268 description:
2269 no person
2269 no person
2270
2270
2271
2271
2272 changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2272 changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2273 bisect: bad
2273 bisect: bad
2274 phase: public
2274 phase: public
2275 parent: 2:97054abb4ab824450e9164180baf491ae0078465
2275 parent: 2:97054abb4ab824450e9164180baf491ae0078465
2276 parent: -1:0000000000000000000000000000000000000000
2276 parent: -1:0000000000000000000000000000000000000000
2277 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2277 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2278 user: person
2278 user: person
2279 date: Fri Jan 16 01:06:40 1970 +0000
2279 date: Fri Jan 16 01:06:40 1970 +0000
2280 files: c
2280 files: c
2281 extra: branch=default
2281 extra: branch=default
2282 description:
2282 description:
2283 no user, no domain
2283 no user, no domain
2284
2284
2285
2285
2286 changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
2286 changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
2287 bisect: bad (implicit)
2287 bisect: bad (implicit)
2288 branch: foo
2288 branch: foo
2289 phase: draft
2289 phase: draft
2290 parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2290 parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2291 parent: -1:0000000000000000000000000000000000000000
2291 parent: -1:0000000000000000000000000000000000000000
2292 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2292 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2293 user: person
2293 user: person
2294 date: Sat Jan 17 04:53:20 1970 +0000
2294 date: Sat Jan 17 04:53:20 1970 +0000
2295 extra: branch=foo
2295 extra: branch=foo
2296 description:
2296 description:
2297 new branch
2297 new branch
2298
2298
2299
2299
2300 $ hg log -v -T bisect -r 0:4
2300 $ hg log -v -T bisect -r 0:4
2301 changeset: 0:1e4e1b8f71e0
2301 changeset: 0:1e4e1b8f71e0
2302 bisect: good (implicit)
2302 bisect: good (implicit)
2303 user: User Name <user@hostname>
2303 user: User Name <user@hostname>
2304 date: Mon Jan 12 13:46:40 1970 +0000
2304 date: Mon Jan 12 13:46:40 1970 +0000
2305 files: a
2305 files: a
2306 description:
2306 description:
2307 line 1
2307 line 1
2308 line 2
2308 line 2
2309
2309
2310
2310
2311 changeset: 1:b608e9d1a3f0
2311 changeset: 1:b608e9d1a3f0
2312 bisect: good
2312 bisect: good
2313 user: A. N. Other <other@place>
2313 user: A. N. Other <other@place>
2314 date: Tue Jan 13 17:33:20 1970 +0000
2314 date: Tue Jan 13 17:33:20 1970 +0000
2315 files: b
2315 files: b
2316 description:
2316 description:
2317 other 1
2317 other 1
2318 other 2
2318 other 2
2319
2319
2320 other 3
2320 other 3
2321
2321
2322
2322
2323 changeset: 2:97054abb4ab8
2323 changeset: 2:97054abb4ab8
2324 bisect: untested
2324 bisect: untested
2325 user: other@place
2325 user: other@place
2326 date: Wed Jan 14 21:20:00 1970 +0000
2326 date: Wed Jan 14 21:20:00 1970 +0000
2327 files: c
2327 files: c
2328 description:
2328 description:
2329 no person
2329 no person
2330
2330
2331
2331
2332 changeset: 3:10e46f2dcbf4
2332 changeset: 3:10e46f2dcbf4
2333 bisect: bad
2333 bisect: bad
2334 user: person
2334 user: person
2335 date: Fri Jan 16 01:06:40 1970 +0000
2335 date: Fri Jan 16 01:06:40 1970 +0000
2336 files: c
2336 files: c
2337 description:
2337 description:
2338 no user, no domain
2338 no user, no domain
2339
2339
2340
2340
2341 changeset: 4:bbe44766e73d
2341 changeset: 4:bbe44766e73d
2342 bisect: bad (implicit)
2342 bisect: bad (implicit)
2343 branch: foo
2343 branch: foo
2344 user: person
2344 user: person
2345 date: Sat Jan 17 04:53:20 1970 +0000
2345 date: Sat Jan 17 04:53:20 1970 +0000
2346 description:
2346 description:
2347 new branch
2347 new branch
2348
2348
2349
2349
2350 $ hg --color=debug log -T bisect -r 0:4
2350 $ hg --color=debug log -T bisect -r 0:4
2351 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2351 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2352 [log.bisect bisect.good|bisect: good (implicit)]
2352 [log.bisect bisect.good|bisect: good (implicit)]
2353 [log.user|user: User Name <user@hostname>]
2353 [log.user|user: User Name <user@hostname>]
2354 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2354 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2355 [log.summary|summary: line 1]
2355 [log.summary|summary: line 1]
2356
2356
2357 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2357 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2358 [log.bisect bisect.good|bisect: good]
2358 [log.bisect bisect.good|bisect: good]
2359 [log.user|user: A. N. Other <other@place>]
2359 [log.user|user: A. N. Other <other@place>]
2360 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2360 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2361 [log.summary|summary: other 1]
2361 [log.summary|summary: other 1]
2362
2362
2363 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2363 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2364 [log.bisect bisect.untested|bisect: untested]
2364 [log.bisect bisect.untested|bisect: untested]
2365 [log.user|user: other@place]
2365 [log.user|user: other@place]
2366 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2366 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2367 [log.summary|summary: no person]
2367 [log.summary|summary: no person]
2368
2368
2369 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2369 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2370 [log.bisect bisect.bad|bisect: bad]
2370 [log.bisect bisect.bad|bisect: bad]
2371 [log.user|user: person]
2371 [log.user|user: person]
2372 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2372 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2373 [log.summary|summary: no user, no domain]
2373 [log.summary|summary: no user, no domain]
2374
2374
2375 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2375 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2376 [log.bisect bisect.bad|bisect: bad (implicit)]
2376 [log.bisect bisect.bad|bisect: bad (implicit)]
2377 [log.branch|branch: foo]
2377 [log.branch|branch: foo]
2378 [log.user|user: person]
2378 [log.user|user: person]
2379 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2379 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2380 [log.summary|summary: new branch]
2380 [log.summary|summary: new branch]
2381
2381
2382 $ hg --color=debug log --debug -T bisect -r 0:4
2382 $ hg --color=debug log --debug -T bisect -r 0:4
2383 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2383 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2384 [log.bisect bisect.good|bisect: good (implicit)]
2384 [log.bisect bisect.good|bisect: good (implicit)]
2385 [log.phase|phase: public]
2385 [log.phase|phase: public]
2386 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2386 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2387 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2387 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2388 [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
2388 [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
2389 [log.user|user: User Name <user@hostname>]
2389 [log.user|user: User Name <user@hostname>]
2390 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2390 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2391 [ui.debug log.files|files+: a]
2391 [ui.debug log.files|files+: a]
2392 [ui.debug log.extra|extra: branch=default]
2392 [ui.debug log.extra|extra: branch=default]
2393 [ui.note log.description|description:]
2393 [ui.note log.description|description:]
2394 [ui.note log.description|line 1
2394 [ui.note log.description|line 1
2395 line 2]
2395 line 2]
2396
2396
2397
2397
2398 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2398 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2399 [log.bisect bisect.good|bisect: good]
2399 [log.bisect bisect.good|bisect: good]
2400 [log.phase|phase: public]
2400 [log.phase|phase: public]
2401 [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2401 [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2402 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2402 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2403 [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
2403 [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
2404 [log.user|user: A. N. Other <other@place>]
2404 [log.user|user: A. N. Other <other@place>]
2405 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2405 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2406 [ui.debug log.files|files+: b]
2406 [ui.debug log.files|files+: b]
2407 [ui.debug log.extra|extra: branch=default]
2407 [ui.debug log.extra|extra: branch=default]
2408 [ui.note log.description|description:]
2408 [ui.note log.description|description:]
2409 [ui.note log.description|other 1
2409 [ui.note log.description|other 1
2410 other 2
2410 other 2
2411
2411
2412 other 3]
2412 other 3]
2413
2413
2414
2414
2415 [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465]
2415 [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465]
2416 [log.bisect bisect.untested|bisect: untested]
2416 [log.bisect bisect.untested|bisect: untested]
2417 [log.phase|phase: public]
2417 [log.phase|phase: public]
2418 [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2418 [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2419 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2419 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2420 [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
2420 [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
2421 [log.user|user: other@place]
2421 [log.user|user: other@place]
2422 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2422 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2423 [ui.debug log.files|files+: c]
2423 [ui.debug log.files|files+: c]
2424 [ui.debug log.extra|extra: branch=default]
2424 [ui.debug log.extra|extra: branch=default]
2425 [ui.note log.description|description:]
2425 [ui.note log.description|description:]
2426 [ui.note log.description|no person]
2426 [ui.note log.description|no person]
2427
2427
2428
2428
2429 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2429 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2430 [log.bisect bisect.bad|bisect: bad]
2430 [log.bisect bisect.bad|bisect: bad]
2431 [log.phase|phase: public]
2431 [log.phase|phase: public]
2432 [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465]
2432 [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465]
2433 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2433 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2434 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2434 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2435 [log.user|user: person]
2435 [log.user|user: person]
2436 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2436 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2437 [ui.debug log.files|files: c]
2437 [ui.debug log.files|files: c]
2438 [ui.debug log.extra|extra: branch=default]
2438 [ui.debug log.extra|extra: branch=default]
2439 [ui.note log.description|description:]
2439 [ui.note log.description|description:]
2440 [ui.note log.description|no user, no domain]
2440 [ui.note log.description|no user, no domain]
2441
2441
2442
2442
2443 [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
2443 [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
2444 [log.bisect bisect.bad|bisect: bad (implicit)]
2444 [log.bisect bisect.bad|bisect: bad (implicit)]
2445 [log.branch|branch: foo]
2445 [log.branch|branch: foo]
2446 [log.phase|phase: draft]
2446 [log.phase|phase: draft]
2447 [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2447 [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2448 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2448 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2449 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2449 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2450 [log.user|user: person]
2450 [log.user|user: person]
2451 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2451 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2452 [ui.debug log.extra|extra: branch=foo]
2452 [ui.debug log.extra|extra: branch=foo]
2453 [ui.note log.description|description:]
2453 [ui.note log.description|description:]
2454 [ui.note log.description|new branch]
2454 [ui.note log.description|new branch]
2455
2455
2456
2456
2457 $ hg --color=debug log -v -T bisect -r 0:4
2457 $ hg --color=debug log -v -T bisect -r 0:4
2458 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2458 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2459 [log.bisect bisect.good|bisect: good (implicit)]
2459 [log.bisect bisect.good|bisect: good (implicit)]
2460 [log.user|user: User Name <user@hostname>]
2460 [log.user|user: User Name <user@hostname>]
2461 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2461 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2462 [ui.note log.files|files: a]
2462 [ui.note log.files|files: a]
2463 [ui.note log.description|description:]
2463 [ui.note log.description|description:]
2464 [ui.note log.description|line 1
2464 [ui.note log.description|line 1
2465 line 2]
2465 line 2]
2466
2466
2467
2467
2468 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2468 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2469 [log.bisect bisect.good|bisect: good]
2469 [log.bisect bisect.good|bisect: good]
2470 [log.user|user: A. N. Other <other@place>]
2470 [log.user|user: A. N. Other <other@place>]
2471 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2471 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2472 [ui.note log.files|files: b]
2472 [ui.note log.files|files: b]
2473 [ui.note log.description|description:]
2473 [ui.note log.description|description:]
2474 [ui.note log.description|other 1
2474 [ui.note log.description|other 1
2475 other 2
2475 other 2
2476
2476
2477 other 3]
2477 other 3]
2478
2478
2479
2479
2480 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2480 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2481 [log.bisect bisect.untested|bisect: untested]
2481 [log.bisect bisect.untested|bisect: untested]
2482 [log.user|user: other@place]
2482 [log.user|user: other@place]
2483 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2483 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2484 [ui.note log.files|files: c]
2484 [ui.note log.files|files: c]
2485 [ui.note log.description|description:]
2485 [ui.note log.description|description:]
2486 [ui.note log.description|no person]
2486 [ui.note log.description|no person]
2487
2487
2488
2488
2489 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2489 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2490 [log.bisect bisect.bad|bisect: bad]
2490 [log.bisect bisect.bad|bisect: bad]
2491 [log.user|user: person]
2491 [log.user|user: person]
2492 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2492 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2493 [ui.note log.files|files: c]
2493 [ui.note log.files|files: c]
2494 [ui.note log.description|description:]
2494 [ui.note log.description|description:]
2495 [ui.note log.description|no user, no domain]
2495 [ui.note log.description|no user, no domain]
2496
2496
2497
2497
2498 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2498 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2499 [log.bisect bisect.bad|bisect: bad (implicit)]
2499 [log.bisect bisect.bad|bisect: bad (implicit)]
2500 [log.branch|branch: foo]
2500 [log.branch|branch: foo]
2501 [log.user|user: person]
2501 [log.user|user: person]
2502 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2502 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2503 [ui.note log.description|description:]
2503 [ui.note log.description|description:]
2504 [ui.note log.description|new branch]
2504 [ui.note log.description|new branch]
2505
2505
2506
2506
2507 $ hg bisect --reset
2507 $ hg bisect --reset
2508
2508
2509 Error on syntax:
2509 Error on syntax:
2510
2510
2511 $ echo 'x = "f' >> t
2511 $ echo 'x = "f' >> t
2512 $ hg log
2512 $ hg log
2513 hg: parse error at t:3: unmatched quotes
2513 hg: parse error at t:3: unmatched quotes
2514 [255]
2514 [255]
2515
2515
2516 $ hg log -T '{date'
2516 $ hg log -T '{date'
2517 hg: parse error at 1: unterminated template expansion
2517 hg: parse error at 1: unterminated template expansion
2518 [255]
2518 [255]
2519
2519
2520 Behind the scenes, this will throw TypeError
2520 Behind the scenes, this will throw TypeError
2521
2521
2522 $ hg log -l 3 --template '{date|obfuscate}\n'
2522 $ hg log -l 3 --template '{date|obfuscate}\n'
2523 abort: template filter 'obfuscate' is not compatible with keyword 'date'
2523 abort: template filter 'obfuscate' is not compatible with keyword 'date'
2524 [255]
2524 [255]
2525
2525
2526 Behind the scenes, this will throw a ValueError
2526 Behind the scenes, this will throw a ValueError
2527
2527
2528 $ hg log -l 3 --template 'line: {desc|shortdate}\n'
2528 $ hg log -l 3 --template 'line: {desc|shortdate}\n'
2529 abort: template filter 'shortdate' is not compatible with keyword 'desc'
2529 abort: template filter 'shortdate' is not compatible with keyword 'desc'
2530 [255]
2530 [255]
2531
2531
2532 Behind the scenes, this will throw AttributeError
2532 Behind the scenes, this will throw AttributeError
2533
2533
2534 $ hg log -l 3 --template 'line: {date|escape}\n'
2534 $ hg log -l 3 --template 'line: {date|escape}\n'
2535 abort: template filter 'escape' is not compatible with keyword 'date'
2535 abort: template filter 'escape' is not compatible with keyword 'date'
2536 [255]
2536 [255]
2537
2537
2538 $ hg log -l 3 --template 'line: {extras|localdate}\n'
2538 $ hg log -l 3 --template 'line: {extras|localdate}\n'
2539 hg: parse error: localdate expects a date information
2539 hg: parse error: localdate expects a date information
2540 [255]
2540 [255]
2541
2541
2542 Behind the scenes, this will throw ValueError
2542 Behind the scenes, this will throw ValueError
2543
2543
2544 $ hg tip --template '{author|email|date}\n'
2544 $ hg tip --template '{author|email|date}\n'
2545 hg: parse error: date expects a date information
2545 hg: parse error: date expects a date information
2546 [255]
2546 [255]
2547
2547
2548 Error in nested template:
2548 Error in nested template:
2549
2549
2550 $ hg log -T '{"date'
2550 $ hg log -T '{"date'
2551 hg: parse error at 2: unterminated string
2551 hg: parse error at 2: unterminated string
2552 [255]
2552 [255]
2553
2553
2554 $ hg log -T '{"foo{date|=}"}'
2554 $ hg log -T '{"foo{date|=}"}'
2555 hg: parse error at 11: syntax error
2555 hg: parse error at 11: syntax error
2556 [255]
2556 [255]
2557
2557
2558 Thrown an error if a template function doesn't exist
2558 Thrown an error if a template function doesn't exist
2559
2559
2560 $ hg tip --template '{foo()}\n'
2560 $ hg tip --template '{foo()}\n'
2561 hg: parse error: unknown function 'foo'
2561 hg: parse error: unknown function 'foo'
2562 [255]
2562 [255]
2563
2563
2564 Pass generator object created by template function to filter
2564 Pass generator object created by template function to filter
2565
2565
2566 $ hg log -l 1 --template '{if(author, author)|user}\n'
2566 $ hg log -l 1 --template '{if(author, author)|user}\n'
2567 test
2567 test
2568
2568
2569 Test diff function:
2569 Test diff function:
2570
2570
2571 $ hg diff -c 8
2571 $ hg diff -c 8
2572 diff -r 29114dbae42b -r 95c24699272e fourth
2572 diff -r 29114dbae42b -r 95c24699272e fourth
2573 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2573 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2574 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2574 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2575 @@ -0,0 +1,1 @@
2575 @@ -0,0 +1,1 @@
2576 +second
2576 +second
2577 diff -r 29114dbae42b -r 95c24699272e second
2577 diff -r 29114dbae42b -r 95c24699272e second
2578 --- a/second Mon Jan 12 13:46:40 1970 +0000
2578 --- a/second Mon Jan 12 13:46:40 1970 +0000
2579 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2579 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2580 @@ -1,1 +0,0 @@
2580 @@ -1,1 +0,0 @@
2581 -second
2581 -second
2582 diff -r 29114dbae42b -r 95c24699272e third
2582 diff -r 29114dbae42b -r 95c24699272e third
2583 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2583 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2584 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2584 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2585 @@ -0,0 +1,1 @@
2585 @@ -0,0 +1,1 @@
2586 +third
2586 +third
2587
2587
2588 $ hg log -r 8 -T "{diff()}"
2588 $ hg log -r 8 -T "{diff()}"
2589 diff -r 29114dbae42b -r 95c24699272e fourth
2589 diff -r 29114dbae42b -r 95c24699272e fourth
2590 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2590 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2591 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2591 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2592 @@ -0,0 +1,1 @@
2592 @@ -0,0 +1,1 @@
2593 +second
2593 +second
2594 diff -r 29114dbae42b -r 95c24699272e second
2594 diff -r 29114dbae42b -r 95c24699272e second
2595 --- a/second Mon Jan 12 13:46:40 1970 +0000
2595 --- a/second Mon Jan 12 13:46:40 1970 +0000
2596 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2596 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2597 @@ -1,1 +0,0 @@
2597 @@ -1,1 +0,0 @@
2598 -second
2598 -second
2599 diff -r 29114dbae42b -r 95c24699272e third
2599 diff -r 29114dbae42b -r 95c24699272e third
2600 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2600 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2601 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2601 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2602 @@ -0,0 +1,1 @@
2602 @@ -0,0 +1,1 @@
2603 +third
2603 +third
2604
2604
2605 $ hg log -r 8 -T "{diff('glob:f*')}"
2605 $ hg log -r 8 -T "{diff('glob:f*')}"
2606 diff -r 29114dbae42b -r 95c24699272e fourth
2606 diff -r 29114dbae42b -r 95c24699272e fourth
2607 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2607 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2608 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2608 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2609 @@ -0,0 +1,1 @@
2609 @@ -0,0 +1,1 @@
2610 +second
2610 +second
2611
2611
2612 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
2612 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
2613 diff -r 29114dbae42b -r 95c24699272e second
2613 diff -r 29114dbae42b -r 95c24699272e second
2614 --- a/second Mon Jan 12 13:46:40 1970 +0000
2614 --- a/second Mon Jan 12 13:46:40 1970 +0000
2615 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2615 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2616 @@ -1,1 +0,0 @@
2616 @@ -1,1 +0,0 @@
2617 -second
2617 -second
2618 diff -r 29114dbae42b -r 95c24699272e third
2618 diff -r 29114dbae42b -r 95c24699272e third
2619 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2619 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2620 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2620 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2621 @@ -0,0 +1,1 @@
2621 @@ -0,0 +1,1 @@
2622 +third
2622 +third
2623
2623
2624 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
2624 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
2625 diff -r 29114dbae42b -r 95c24699272e fourth
2625 diff -r 29114dbae42b -r 95c24699272e fourth
2626 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2626 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2627 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2627 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2628 @@ -0,0 +1,1 @@
2628 @@ -0,0 +1,1 @@
2629 +second
2629 +second
2630
2630
2631 $ cd ..
2631 $ cd ..
2632
2632
2633
2633
2634 latesttag:
2634 latesttag:
2635
2635
2636 $ hg init latesttag
2636 $ hg init latesttag
2637 $ cd latesttag
2637 $ cd latesttag
2638
2638
2639 $ echo a > file
2639 $ echo a > file
2640 $ hg ci -Am a -d '0 0'
2640 $ hg ci -Am a -d '0 0'
2641 adding file
2641 adding file
2642
2642
2643 $ echo b >> file
2643 $ echo b >> file
2644 $ hg ci -m b -d '1 0'
2644 $ hg ci -m b -d '1 0'
2645
2645
2646 $ echo c >> head1
2646 $ echo c >> head1
2647 $ hg ci -Am h1c -d '2 0'
2647 $ hg ci -Am h1c -d '2 0'
2648 adding head1
2648 adding head1
2649
2649
2650 $ hg update -q 1
2650 $ hg update -q 1
2651 $ echo d >> head2
2651 $ echo d >> head2
2652 $ hg ci -Am h2d -d '3 0'
2652 $ hg ci -Am h2d -d '3 0'
2653 adding head2
2653 adding head2
2654 created new head
2654 created new head
2655
2655
2656 $ echo e >> head2
2656 $ echo e >> head2
2657 $ hg ci -m h2e -d '4 0'
2657 $ hg ci -m h2e -d '4 0'
2658
2658
2659 $ hg merge -q
2659 $ hg merge -q
2660 $ hg ci -m merge -d '5 -3600'
2660 $ hg ci -m merge -d '5 -3600'
2661
2661
2662 No tag set:
2662 No tag set:
2663
2663
2664 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2664 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2665 5: null+5
2665 5: null+5
2666 4: null+4
2666 4: null+4
2667 3: null+3
2667 3: null+3
2668 2: null+3
2668 2: null+3
2669 1: null+2
2669 1: null+2
2670 0: null+1
2670 0: null+1
2671
2671
2672 One common tag: longest path wins:
2672 One common tag: longest path wins:
2673
2673
2674 $ hg tag -r 1 -m t1 -d '6 0' t1
2674 $ hg tag -r 1 -m t1 -d '6 0' t1
2675 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2675 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2676 6: t1+4
2676 6: t1+4
2677 5: t1+3
2677 5: t1+3
2678 4: t1+2
2678 4: t1+2
2679 3: t1+1
2679 3: t1+1
2680 2: t1+1
2680 2: t1+1
2681 1: t1+0
2681 1: t1+0
2682 0: null+1
2682 0: null+1
2683
2683
2684 One ancestor tag: more recent wins:
2684 One ancestor tag: more recent wins:
2685
2685
2686 $ hg tag -r 2 -m t2 -d '7 0' t2
2686 $ hg tag -r 2 -m t2 -d '7 0' t2
2687 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2687 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2688 7: t2+3
2688 7: t2+3
2689 6: t2+2
2689 6: t2+2
2690 5: t2+1
2690 5: t2+1
2691 4: t1+2
2691 4: t1+2
2692 3: t1+1
2692 3: t1+1
2693 2: t2+0
2693 2: t2+0
2694 1: t1+0
2694 1: t1+0
2695 0: null+1
2695 0: null+1
2696
2696
2697 Two branch tags: more recent wins:
2697 Two branch tags: more recent wins:
2698
2698
2699 $ hg tag -r 3 -m t3 -d '8 0' t3
2699 $ hg tag -r 3 -m t3 -d '8 0' t3
2700 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2700 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2701 8: t3+5
2701 8: t3+5
2702 7: t3+4
2702 7: t3+4
2703 6: t3+3
2703 6: t3+3
2704 5: t3+2
2704 5: t3+2
2705 4: t3+1
2705 4: t3+1
2706 3: t3+0
2706 3: t3+0
2707 2: t2+0
2707 2: t2+0
2708 1: t1+0
2708 1: t1+0
2709 0: null+1
2709 0: null+1
2710
2710
2711 Merged tag overrides:
2711 Merged tag overrides:
2712
2712
2713 $ hg tag -r 5 -m t5 -d '9 0' t5
2713 $ hg tag -r 5 -m t5 -d '9 0' t5
2714 $ hg tag -r 3 -m at3 -d '10 0' at3
2714 $ hg tag -r 3 -m at3 -d '10 0' at3
2715 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2715 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2716 10: t5+5
2716 10: t5+5
2717 9: t5+4
2717 9: t5+4
2718 8: t5+3
2718 8: t5+3
2719 7: t5+2
2719 7: t5+2
2720 6: t5+1
2720 6: t5+1
2721 5: t5+0
2721 5: t5+0
2722 4: at3:t3+1
2722 4: at3:t3+1
2723 3: at3:t3+0
2723 3: at3:t3+0
2724 2: t2+0
2724 2: t2+0
2725 1: t1+0
2725 1: t1+0
2726 0: null+1
2726 0: null+1
2727
2727
2728 $ hg log --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
2728 $ hg log --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
2729 10: t5+5,5
2729 10: t5+5,5
2730 9: t5+4,4
2730 9: t5+4,4
2731 8: t5+3,3
2731 8: t5+3,3
2732 7: t5+2,2
2732 7: t5+2,2
2733 6: t5+1,1
2733 6: t5+1,1
2734 5: t5+0,0
2734 5: t5+0,0
2735 4: at3+1,1 t3+1,1
2735 4: at3+1,1 t3+1,1
2736 3: at3+0,0 t3+0,0
2736 3: at3+0,0 t3+0,0
2737 2: t2+0,0
2737 2: t2+0,0
2738 1: t1+0,0
2738 1: t1+0,0
2739 0: null+1,1
2739 0: null+1,1
2740
2740
2741 $ hg log --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
2741 $ hg log --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
2742 10: t3, C: 8, D: 7
2742 10: t3, C: 8, D: 7
2743 9: t3, C: 7, D: 6
2743 9: t3, C: 7, D: 6
2744 8: t3, C: 6, D: 5
2744 8: t3, C: 6, D: 5
2745 7: t3, C: 5, D: 4
2745 7: t3, C: 5, D: 4
2746 6: t3, C: 4, D: 3
2746 6: t3, C: 4, D: 3
2747 5: t3, C: 3, D: 2
2747 5: t3, C: 3, D: 2
2748 4: t3, C: 1, D: 1
2748 4: t3, C: 1, D: 1
2749 3: t3, C: 0, D: 0
2749 3: t3, C: 0, D: 0
2750 2: t1, C: 1, D: 1
2750 2: t1, C: 1, D: 1
2751 1: t1, C: 0, D: 0
2751 1: t1, C: 0, D: 0
2752 0: null, C: 1, D: 1
2752 0: null, C: 1, D: 1
2753
2753
2754 $ cd ..
2754 $ cd ..
2755
2755
2756
2756
2757 Style path expansion: issue1948 - ui.style option doesn't work on OSX
2757 Style path expansion: issue1948 - ui.style option doesn't work on OSX
2758 if it is a relative path
2758 if it is a relative path
2759
2759
2760 $ mkdir -p home/styles
2760 $ mkdir -p home/styles
2761
2761
2762 $ cat > home/styles/teststyle <<EOF
2762 $ cat > home/styles/teststyle <<EOF
2763 > changeset = 'test {rev}:{node|short}\n'
2763 > changeset = 'test {rev}:{node|short}\n'
2764 > EOF
2764 > EOF
2765
2765
2766 $ HOME=`pwd`/home; export HOME
2766 $ HOME=`pwd`/home; export HOME
2767
2767
2768 $ cat > latesttag/.hg/hgrc <<EOF
2768 $ cat > latesttag/.hg/hgrc <<EOF
2769 > [ui]
2769 > [ui]
2770 > style = ~/styles/teststyle
2770 > style = ~/styles/teststyle
2771 > EOF
2771 > EOF
2772
2772
2773 $ hg -R latesttag tip
2773 $ hg -R latesttag tip
2774 test 10:9b4a630e5f5f
2774 test 10:9b4a630e5f5f
2775
2775
2776 Test recursive showlist template (issue1989):
2776 Test recursive showlist template (issue1989):
2777
2777
2778 $ cat > style1989 <<EOF
2778 $ cat > style1989 <<EOF
2779 > changeset = '{file_mods}{manifest}{extras}'
2779 > changeset = '{file_mods}{manifest}{extras}'
2780 > file_mod = 'M|{author|person}\n'
2780 > file_mod = 'M|{author|person}\n'
2781 > manifest = '{rev},{author}\n'
2781 > manifest = '{rev},{author}\n'
2782 > extra = '{key}: {author}\n'
2782 > extra = '{key}: {author}\n'
2783 > EOF
2783 > EOF
2784
2784
2785 $ hg -R latesttag log -r tip --style=style1989
2785 $ hg -R latesttag log -r tip --style=style1989
2786 M|test
2786 M|test
2787 10,test
2787 10,test
2788 branch: test
2788 branch: test
2789
2789
2790 Test new-style inline templating:
2790 Test new-style inline templating:
2791
2791
2792 $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
2792 $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
2793 modified files: .hgtags
2793 modified files: .hgtags
2794
2794
2795
2795
2796 $ hg log -R latesttag -r tip -T '{rev % "a"}\n'
2796 $ hg log -R latesttag -r tip -T '{rev % "a"}\n'
2797 hg: parse error: keyword 'rev' is not iterable
2797 hg: parse error: keyword 'rev' is not iterable
2798 [255]
2798 [255]
2799 $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "a"}\n'
2799 $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "a"}\n'
2800 hg: parse error: None is not iterable
2800 hg: parse error: None is not iterable
2801 [255]
2801 [255]
2802
2802
2803 Test the sub function of templating for expansion:
2803 Test the sub function of templating for expansion:
2804
2804
2805 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
2805 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
2806 xx
2806 xx
2807
2807
2808 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
2808 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
2809 hg: parse error: sub got an invalid pattern: [
2809 hg: parse error: sub got an invalid pattern: [
2810 [255]
2810 [255]
2811 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
2811 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
2812 hg: parse error: sub got an invalid replacement: \1
2812 hg: parse error: sub got an invalid replacement: \1
2813 [255]
2813 [255]
2814
2814
2815 Test the strip function with chars specified:
2815 Test the strip function with chars specified:
2816
2816
2817 $ hg log -R latesttag --template '{desc}\n'
2817 $ hg log -R latesttag --template '{desc}\n'
2818 at3
2818 at3
2819 t5
2819 t5
2820 t3
2820 t3
2821 t2
2821 t2
2822 t1
2822 t1
2823 merge
2823 merge
2824 h2e
2824 h2e
2825 h2d
2825 h2d
2826 h1c
2826 h1c
2827 b
2827 b
2828 a
2828 a
2829
2829
2830 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
2830 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
2831 at3
2831 at3
2832 5
2832 5
2833 3
2833 3
2834 2
2834 2
2835 1
2835 1
2836 merg
2836 merg
2837 h2
2837 h2
2838 h2d
2838 h2d
2839 h1c
2839 h1c
2840 b
2840 b
2841 a
2841 a
2842
2842
2843 Test date format:
2843 Test date format:
2844
2844
2845 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
2845 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
2846 date: 70 01 01 10 +0000
2846 date: 70 01 01 10 +0000
2847 date: 70 01 01 09 +0000
2847 date: 70 01 01 09 +0000
2848 date: 70 01 01 08 +0000
2848 date: 70 01 01 08 +0000
2849 date: 70 01 01 07 +0000
2849 date: 70 01 01 07 +0000
2850 date: 70 01 01 06 +0000
2850 date: 70 01 01 06 +0000
2851 date: 70 01 01 05 +0100
2851 date: 70 01 01 05 +0100
2852 date: 70 01 01 04 +0000
2852 date: 70 01 01 04 +0000
2853 date: 70 01 01 03 +0000
2853 date: 70 01 01 03 +0000
2854 date: 70 01 01 02 +0000
2854 date: 70 01 01 02 +0000
2855 date: 70 01 01 01 +0000
2855 date: 70 01 01 01 +0000
2856 date: 70 01 01 00 +0000
2856 date: 70 01 01 00 +0000
2857
2857
2858 Test invalid date:
2858 Test invalid date:
2859
2859
2860 $ hg log -R latesttag -T '{date(rev)}\n'
2860 $ hg log -R latesttag -T '{date(rev)}\n'
2861 hg: parse error: date expects a date information
2861 hg: parse error: date expects a date information
2862 [255]
2862 [255]
2863
2863
2864 Test integer literal:
2864 Test integer literal:
2865
2865
2866 $ hg debugtemplate -v '{(0)}\n'
2866 $ hg debugtemplate -v '{(0)}\n'
2867 (template
2867 (template
2868 (group
2868 (group
2869 ('integer', '0'))
2869 ('integer', '0'))
2870 ('string', '\n'))
2870 ('string', '\n'))
2871 0
2871 0
2872 $ hg debugtemplate -v '{(123)}\n'
2872 $ hg debugtemplate -v '{(123)}\n'
2873 (template
2873 (template
2874 (group
2874 (group
2875 ('integer', '123'))
2875 ('integer', '123'))
2876 ('string', '\n'))
2876 ('string', '\n'))
2877 123
2877 123
2878 $ hg debugtemplate -v '{(-4)}\n'
2878 $ hg debugtemplate -v '{(-4)}\n'
2879 (template
2879 (template
2880 (group
2880 (group
2881 ('integer', '-4'))
2881 ('integer', '-4'))
2882 ('string', '\n'))
2882 ('string', '\n'))
2883 -4
2883 -4
2884 $ hg debugtemplate '{(-)}\n'
2884 $ hg debugtemplate '{(-)}\n'
2885 hg: parse error at 2: integer literal without digits
2885 hg: parse error at 2: integer literal without digits
2886 [255]
2886 [255]
2887 $ hg debugtemplate '{(-a)}\n'
2887 $ hg debugtemplate '{(-a)}\n'
2888 hg: parse error at 2: integer literal without digits
2888 hg: parse error at 2: integer literal without digits
2889 [255]
2889 [255]
2890
2890
2891 top-level integer literal is interpreted as symbol (i.e. variable name):
2891 top-level integer literal is interpreted as symbol (i.e. variable name):
2892
2892
2893 $ hg debugtemplate -D 1=one -v '{1}\n'
2893 $ hg debugtemplate -D 1=one -v '{1}\n'
2894 (template
2894 (template
2895 ('integer', '1')
2895 ('integer', '1')
2896 ('string', '\n'))
2896 ('string', '\n'))
2897 one
2897 one
2898 $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
2898 $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
2899 (template
2899 (template
2900 (func
2900 (func
2901 ('symbol', 'if')
2901 ('symbol', 'if')
2902 (list
2902 (list
2903 ('string', 't')
2903 ('string', 't')
2904 (template
2904 (template
2905 ('integer', '1'))))
2905 ('integer', '1'))))
2906 ('string', '\n'))
2906 ('string', '\n'))
2907 one
2907 one
2908 $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
2908 $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
2909 (template
2909 (template
2910 (|
2910 (|
2911 ('integer', '1')
2911 ('integer', '1')
2912 ('symbol', 'stringify'))
2912 ('symbol', 'stringify'))
2913 ('string', '\n'))
2913 ('string', '\n'))
2914 one
2914 one
2915
2915
2916 unless explicit symbol is expected:
2916 unless explicit symbol is expected:
2917
2917
2918 $ hg log -Ra -r0 -T '{desc|1}\n'
2918 $ hg log -Ra -r0 -T '{desc|1}\n'
2919 hg: parse error: expected a symbol, got 'integer'
2919 hg: parse error: expected a symbol, got 'integer'
2920 [255]
2920 [255]
2921 $ hg log -Ra -r0 -T '{1()}\n'
2921 $ hg log -Ra -r0 -T '{1()}\n'
2922 hg: parse error: expected a symbol, got 'integer'
2922 hg: parse error: expected a symbol, got 'integer'
2923 [255]
2923 [255]
2924
2924
2925 Test string literal:
2925 Test string literal:
2926
2926
2927 $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
2927 $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
2928 (template
2928 (template
2929 ('string', 'string with no template fragment')
2929 ('string', 'string with no template fragment')
2930 ('string', '\n'))
2930 ('string', '\n'))
2931 string with no template fragment
2931 string with no template fragment
2932 $ hg debugtemplate -Ra -r0 -v '{"template: {rev}"}\n'
2932 $ hg debugtemplate -Ra -r0 -v '{"template: {rev}"}\n'
2933 (template
2933 (template
2934 (template
2934 (template
2935 ('string', 'template: ')
2935 ('string', 'template: ')
2936 ('symbol', 'rev'))
2936 ('symbol', 'rev'))
2937 ('string', '\n'))
2937 ('string', '\n'))
2938 template: 0
2938 template: 0
2939 $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
2939 $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
2940 (template
2940 (template
2941 ('string', 'rawstring: {rev}')
2941 ('string', 'rawstring: {rev}')
2942 ('string', '\n'))
2942 ('string', '\n'))
2943 rawstring: {rev}
2943 rawstring: {rev}
2944 $ hg debugtemplate -Ra -r0 -v '{files % r"rawstring: {file}"}\n'
2944 $ hg debugtemplate -Ra -r0 -v '{files % r"rawstring: {file}"}\n'
2945 (template
2945 (template
2946 (%
2946 (%
2947 ('symbol', 'files')
2947 ('symbol', 'files')
2948 ('string', 'rawstring: {file}'))
2948 ('string', 'rawstring: {file}'))
2949 ('string', '\n'))
2949 ('string', '\n'))
2950 rawstring: {file}
2950 rawstring: {file}
2951
2951
2952 Test string escaping:
2952 Test string escaping:
2953
2953
2954 $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2954 $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2955 >
2955 >
2956 <>\n<[>
2956 <>\n<[>
2957 <>\n<]>
2957 <>\n<]>
2958 <>\n<
2958 <>\n<
2959
2959
2960 $ hg log -R latesttag -r 0 \
2960 $ hg log -R latesttag -r 0 \
2961 > --config ui.logtemplate='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2961 > --config ui.logtemplate='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2962 >
2962 >
2963 <>\n<[>
2963 <>\n<[>
2964 <>\n<]>
2964 <>\n<]>
2965 <>\n<
2965 <>\n<
2966
2966
2967 $ hg log -R latesttag -r 0 -T esc \
2967 $ hg log -R latesttag -r 0 -T esc \
2968 > --config templates.esc='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2968 > --config templates.esc='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2969 >
2969 >
2970 <>\n<[>
2970 <>\n<[>
2971 <>\n<]>
2971 <>\n<]>
2972 <>\n<
2972 <>\n<
2973
2973
2974 $ cat <<'EOF' > esctmpl
2974 $ cat <<'EOF' > esctmpl
2975 > changeset = '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2975 > changeset = '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2976 > EOF
2976 > EOF
2977 $ hg log -R latesttag -r 0 --style ./esctmpl
2977 $ hg log -R latesttag -r 0 --style ./esctmpl
2978 >
2978 >
2979 <>\n<[>
2979 <>\n<[>
2980 <>\n<]>
2980 <>\n<]>
2981 <>\n<
2981 <>\n<
2982
2982
2983 Test string escaping of quotes:
2983 Test string escaping of quotes:
2984
2984
2985 $ hg log -Ra -r0 -T '{"\""}\n'
2985 $ hg log -Ra -r0 -T '{"\""}\n'
2986 "
2986 "
2987 $ hg log -Ra -r0 -T '{"\\\""}\n'
2987 $ hg log -Ra -r0 -T '{"\\\""}\n'
2988 \"
2988 \"
2989 $ hg log -Ra -r0 -T '{r"\""}\n'
2989 $ hg log -Ra -r0 -T '{r"\""}\n'
2990 \"
2990 \"
2991 $ hg log -Ra -r0 -T '{r"\\\""}\n'
2991 $ hg log -Ra -r0 -T '{r"\\\""}\n'
2992 \\\"
2992 \\\"
2993
2993
2994
2994
2995 $ hg log -Ra -r0 -T '{"\""}\n'
2995 $ hg log -Ra -r0 -T '{"\""}\n'
2996 "
2996 "
2997 $ hg log -Ra -r0 -T '{"\\\""}\n'
2997 $ hg log -Ra -r0 -T '{"\\\""}\n'
2998 \"
2998 \"
2999 $ hg log -Ra -r0 -T '{r"\""}\n'
2999 $ hg log -Ra -r0 -T '{r"\""}\n'
3000 \"
3000 \"
3001 $ hg log -Ra -r0 -T '{r"\\\""}\n'
3001 $ hg log -Ra -r0 -T '{r"\\\""}\n'
3002 \\\"
3002 \\\"
3003
3003
3004 Test exception in quoted template. single backslash before quotation mark is
3004 Test exception in quoted template. single backslash before quotation mark is
3005 stripped before parsing:
3005 stripped before parsing:
3006
3006
3007 $ cat <<'EOF' > escquotetmpl
3007 $ cat <<'EOF' > escquotetmpl
3008 > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n"
3008 > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n"
3009 > EOF
3009 > EOF
3010 $ cd latesttag
3010 $ cd latesttag
3011 $ hg log -r 2 --style ../escquotetmpl
3011 $ hg log -r 2 --style ../escquotetmpl
3012 " \" \" \\" head1
3012 " \" \" \\" head1
3013
3013
3014 $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"'
3014 $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"'
3015 valid
3015 valid
3016 $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'"
3016 $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'"
3017 valid
3017 valid
3018
3018
3019 Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested
3019 Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested
3020 _evalifliteral() templates (issue4733):
3020 _evalifliteral() templates (issue4733):
3021
3021
3022 $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n'
3022 $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n'
3023 "2
3023 "2
3024 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n'
3024 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n'
3025 "2
3025 "2
3026 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\"{rev}\\\")}\")}")}\n'
3026 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\"{rev}\\\")}\")}")}\n'
3027 "2
3027 "2
3028
3028
3029 $ hg log -r 2 -T '{if(rev, "\\\"")}\n'
3029 $ hg log -r 2 -T '{if(rev, "\\\"")}\n'
3030 \"
3030 \"
3031 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\\\\\"\")}")}\n'
3031 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\\\\\"\")}")}\n'
3032 \"
3032 \"
3033 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3033 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3034 \"
3034 \"
3035
3035
3036 $ hg log -r 2 -T '{if(rev, r"\\\"")}\n'
3036 $ hg log -r 2 -T '{if(rev, r"\\\"")}\n'
3037 \\\"
3037 \\\"
3038 $ hg log -r 2 -T '{if(rev, "{if(rev, r\"\\\\\\\"\")}")}\n'
3038 $ hg log -r 2 -T '{if(rev, "{if(rev, r\"\\\\\\\"\")}")}\n'
3039 \\\"
3039 \\\"
3040 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, r\\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3040 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, r\\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3041 \\\"
3041 \\\"
3042
3042
3043 escaped single quotes and errors:
3043 escaped single quotes and errors:
3044
3044
3045 $ hg log -r 2 -T "{if(rev, '{if(rev, \'foo\')}')}"'\n'
3045 $ hg log -r 2 -T "{if(rev, '{if(rev, \'foo\')}')}"'\n'
3046 foo
3046 foo
3047 $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n'
3047 $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n'
3048 foo
3048 foo
3049 $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
3049 $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
3050 hg: parse error at 21: unterminated string
3050 hg: parse error at 21: unterminated string
3051 [255]
3051 [255]
3052 $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
3052 $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
3053 hg: parse error: trailing \ in string
3053 hg: parse error: trailing \ in string
3054 [255]
3054 [255]
3055 $ hg log -r 2 -T '{if(rev, r\"\\"")}\n'
3055 $ hg log -r 2 -T '{if(rev, r\"\\"")}\n'
3056 hg: parse error: trailing \ in string
3056 hg: parse error: trailing \ in string
3057 [255]
3057 [255]
3058
3058
3059 $ cd ..
3059 $ cd ..
3060
3060
3061 Test leading backslashes:
3061 Test leading backslashes:
3062
3062
3063 $ cd latesttag
3063 $ cd latesttag
3064 $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n'
3064 $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n'
3065 {rev} {file}
3065 {rev} {file}
3066 $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n'
3066 $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n'
3067 \2 \head1
3067 \2 \head1
3068 $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n'
3068 $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n'
3069 \{rev} \{file}
3069 \{rev} \{file}
3070 $ cd ..
3070 $ cd ..
3071
3071
3072 Test leading backslashes in "if" expression (issue4714):
3072 Test leading backslashes in "if" expression (issue4714):
3073
3073
3074 $ cd latesttag
3074 $ cd latesttag
3075 $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n'
3075 $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n'
3076 {rev} \{rev}
3076 {rev} \{rev}
3077 $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n'
3077 $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n'
3078 \2 \\{rev}
3078 \2 \\{rev}
3079 $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n'
3079 $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n'
3080 \{rev} \\\{rev}
3080 \{rev} \\\{rev}
3081 $ cd ..
3081 $ cd ..
3082
3082
3083 "string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
3083 "string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
3084
3084
3085 $ hg log -R a -r 0 --template '{if("1", "\x5c\x786e", "NG")}\n'
3085 $ hg log -R a -r 0 --template '{if("1", "\x5c\x786e", "NG")}\n'
3086 \x6e
3086 \x6e
3087 $ hg log -R a -r 0 --template '{if("1", r"\x5c\x786e", "NG")}\n'
3087 $ hg log -R a -r 0 --template '{if("1", r"\x5c\x786e", "NG")}\n'
3088 \x5c\x786e
3088 \x5c\x786e
3089 $ hg log -R a -r 0 --template '{if("", "NG", "\x5c\x786e")}\n'
3089 $ hg log -R a -r 0 --template '{if("", "NG", "\x5c\x786e")}\n'
3090 \x6e
3090 \x6e
3091 $ hg log -R a -r 0 --template '{if("", "NG", r"\x5c\x786e")}\n'
3091 $ hg log -R a -r 0 --template '{if("", "NG", r"\x5c\x786e")}\n'
3092 \x5c\x786e
3092 \x5c\x786e
3093
3093
3094 $ hg log -R a -r 2 --template '{ifeq("no perso\x6e", desc, "\x5c\x786e", "NG")}\n'
3094 $ hg log -R a -r 2 --template '{ifeq("no perso\x6e", desc, "\x5c\x786e", "NG")}\n'
3095 \x6e
3095 \x6e
3096 $ hg log -R a -r 2 --template '{ifeq(r"no perso\x6e", desc, "NG", r"\x5c\x786e")}\n'
3096 $ hg log -R a -r 2 --template '{ifeq(r"no perso\x6e", desc, "NG", r"\x5c\x786e")}\n'
3097 \x5c\x786e
3097 \x5c\x786e
3098 $ hg log -R a -r 2 --template '{ifeq(desc, "no perso\x6e", "\x5c\x786e", "NG")}\n'
3098 $ hg log -R a -r 2 --template '{ifeq(desc, "no perso\x6e", "\x5c\x786e", "NG")}\n'
3099 \x6e
3099 \x6e
3100 $ hg log -R a -r 2 --template '{ifeq(desc, r"no perso\x6e", "NG", r"\x5c\x786e")}\n'
3100 $ hg log -R a -r 2 --template '{ifeq(desc, r"no perso\x6e", "NG", r"\x5c\x786e")}\n'
3101 \x5c\x786e
3101 \x5c\x786e
3102
3102
3103 $ hg log -R a -r 8 --template '{join(files, "\n")}\n'
3103 $ hg log -R a -r 8 --template '{join(files, "\n")}\n'
3104 fourth
3104 fourth
3105 second
3105 second
3106 third
3106 third
3107 $ hg log -R a -r 8 --template '{join(files, r"\n")}\n'
3107 $ hg log -R a -r 8 --template '{join(files, r"\n")}\n'
3108 fourth\nsecond\nthird
3108 fourth\nsecond\nthird
3109
3109
3110 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", "htm\x6c")}'
3110 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", "htm\x6c")}'
3111 <p>
3111 <p>
3112 1st
3112 1st
3113 </p>
3113 </p>
3114 <p>
3114 <p>
3115 2nd
3115 2nd
3116 </p>
3116 </p>
3117 $ hg log -R a -r 2 --template '{rstdoc(r"1st\n\n2nd", "html")}'
3117 $ hg log -R a -r 2 --template '{rstdoc(r"1st\n\n2nd", "html")}'
3118 <p>
3118 <p>
3119 1st\n\n2nd
3119 1st\n\n2nd
3120 </p>
3120 </p>
3121 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", r"htm\x6c")}'
3121 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", r"htm\x6c")}'
3122 1st
3122 1st
3123
3123
3124 2nd
3124 2nd
3125
3125
3126 $ hg log -R a -r 2 --template '{strip(desc, "\x6e")}\n'
3126 $ hg log -R a -r 2 --template '{strip(desc, "\x6e")}\n'
3127 o perso
3127 o perso
3128 $ hg log -R a -r 2 --template '{strip(desc, r"\x6e")}\n'
3128 $ hg log -R a -r 2 --template '{strip(desc, r"\x6e")}\n'
3129 no person
3129 no person
3130 $ hg log -R a -r 2 --template '{strip("no perso\x6e", "\x6e")}\n'
3130 $ hg log -R a -r 2 --template '{strip("no perso\x6e", "\x6e")}\n'
3131 o perso
3131 o perso
3132 $ hg log -R a -r 2 --template '{strip(r"no perso\x6e", r"\x6e")}\n'
3132 $ hg log -R a -r 2 --template '{strip(r"no perso\x6e", r"\x6e")}\n'
3133 no perso
3133 no perso
3134
3134
3135 $ hg log -R a -r 2 --template '{sub("\\x6e", "\x2d", desc)}\n'
3135 $ hg log -R a -r 2 --template '{sub("\\x6e", "\x2d", desc)}\n'
3136 -o perso-
3136 -o perso-
3137 $ hg log -R a -r 2 --template '{sub(r"\\x6e", "-", desc)}\n'
3137 $ hg log -R a -r 2 --template '{sub(r"\\x6e", "-", desc)}\n'
3138 no person
3138 no person
3139 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", desc)}\n'
3139 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", desc)}\n'
3140 \x2do perso\x2d
3140 \x2do perso\x2d
3141 $ hg log -R a -r 2 --template '{sub("n", "\x2d", "no perso\x6e")}\n'
3141 $ hg log -R a -r 2 --template '{sub("n", "\x2d", "no perso\x6e")}\n'
3142 -o perso-
3142 -o perso-
3143 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", r"no perso\x6e")}\n'
3143 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", r"no perso\x6e")}\n'
3144 \x2do perso\x6e
3144 \x2do perso\x6e
3145
3145
3146 $ hg log -R a -r 8 --template '{files % "{file}\n"}'
3146 $ hg log -R a -r 8 --template '{files % "{file}\n"}'
3147 fourth
3147 fourth
3148 second
3148 second
3149 third
3149 third
3150
3150
3151 Test string escaping in nested expression:
3151 Test string escaping in nested expression:
3152
3152
3153 $ hg log -R a -r 8 --template '{ifeq(r"\x6e", if("1", "\x5c\x786e"), join(files, "\x5c\x786e"))}\n'
3153 $ hg log -R a -r 8 --template '{ifeq(r"\x6e", if("1", "\x5c\x786e"), join(files, "\x5c\x786e"))}\n'
3154 fourth\x6esecond\x6ethird
3154 fourth\x6esecond\x6ethird
3155 $ hg log -R a -r 8 --template '{ifeq(if("1", r"\x6e"), "\x5c\x786e", join(files, "\x5c\x786e"))}\n'
3155 $ hg log -R a -r 8 --template '{ifeq(if("1", r"\x6e"), "\x5c\x786e", join(files, "\x5c\x786e"))}\n'
3156 fourth\x6esecond\x6ethird
3156 fourth\x6esecond\x6ethird
3157
3157
3158 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", "\x5c\x786e"))}\n'
3158 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", "\x5c\x786e"))}\n'
3159 fourth\x6esecond\x6ethird
3159 fourth\x6esecond\x6ethird
3160 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", r"\x5c\x786e"))}\n'
3160 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", r"\x5c\x786e"))}\n'
3161 fourth\x5c\x786esecond\x5c\x786ethird
3161 fourth\x5c\x786esecond\x5c\x786ethird
3162
3162
3163 $ hg log -R a -r 3:4 --template '{rev}:{sub(if("1", "\x6e"), ifeq(branch, "foo", r"\x5c\x786e", "\x5c\x786e"), desc)}\n'
3163 $ hg log -R a -r 3:4 --template '{rev}:{sub(if("1", "\x6e"), ifeq(branch, "foo", r"\x5c\x786e", "\x5c\x786e"), desc)}\n'
3164 3:\x6eo user, \x6eo domai\x6e
3164 3:\x6eo user, \x6eo domai\x6e
3165 4:\x5c\x786eew bra\x5c\x786ech
3165 4:\x5c\x786eew bra\x5c\x786ech
3166
3166
3167 Test quotes in nested expression are evaluated just like a $(command)
3167 Test quotes in nested expression are evaluated just like a $(command)
3168 substitution in POSIX shells:
3168 substitution in POSIX shells:
3169
3169
3170 $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n'
3170 $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n'
3171 8:95c24699272e
3171 8:95c24699272e
3172 $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n'
3172 $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n'
3173 {8} "95c24699272e"
3173 {8} "95c24699272e"
3174
3174
3175 Test recursive evaluation:
3175 Test recursive evaluation:
3176
3176
3177 $ hg init r
3177 $ hg init r
3178 $ cd r
3178 $ cd r
3179 $ echo a > a
3179 $ echo a > a
3180 $ hg ci -Am '{rev}'
3180 $ hg ci -Am '{rev}'
3181 adding a
3181 adding a
3182 $ hg log -r 0 --template '{if(rev, desc)}\n'
3182 $ hg log -r 0 --template '{if(rev, desc)}\n'
3183 {rev}
3183 {rev}
3184 $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
3184 $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
3185 test 0
3185 test 0
3186
3186
3187 $ hg branch -q 'text.{rev}'
3187 $ hg branch -q 'text.{rev}'
3188 $ echo aa >> aa
3188 $ echo aa >> aa
3189 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
3189 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
3190
3190
3191 $ hg log -l1 --template '{fill(desc, "20", author, branch)}'
3191 $ hg log -l1 --template '{fill(desc, "20", author, branch)}'
3192 {node|short}desc to
3192 {node|short}desc to
3193 text.{rev}be wrapped
3193 text.{rev}be wrapped
3194 text.{rev}desc to be
3194 text.{rev}desc to be
3195 text.{rev}wrapped (no-eol)
3195 text.{rev}wrapped (no-eol)
3196 $ hg log -l1 --template '{fill(desc, "20", "{node|short}:", "text.{rev}:")}'
3196 $ hg log -l1 --template '{fill(desc, "20", "{node|short}:", "text.{rev}:")}'
3197 bcc7ff960b8e:desc to
3197 bcc7ff960b8e:desc to
3198 text.1:be wrapped
3198 text.1:be wrapped
3199 text.1:desc to be
3199 text.1:desc to be
3200 text.1:wrapped (no-eol)
3200 text.1:wrapped (no-eol)
3201 $ hg log -l1 -T '{fill(desc, date, "", "")}\n'
3201 $ hg log -l1 -T '{fill(desc, date, "", "")}\n'
3202 hg: parse error: fill expects an integer width
3202 hg: parse error: fill expects an integer width
3203 [255]
3203 [255]
3204
3204
3205 $ hg log -l 1 --template '{sub(r"[0-9]", "-", author)}'
3205 $ hg log -l 1 --template '{sub(r"[0-9]", "-", author)}'
3206 {node|short} (no-eol)
3206 {node|short} (no-eol)
3207 $ hg log -l 1 --template '{sub(r"[0-9]", "-", "{node|short}")}'
3207 $ hg log -l 1 --template '{sub(r"[0-9]", "-", "{node|short}")}'
3208 bcc-ff---b-e (no-eol)
3208 bcc-ff---b-e (no-eol)
3209
3209
3210 $ cat >> .hg/hgrc <<EOF
3210 $ cat >> .hg/hgrc <<EOF
3211 > [extensions]
3211 > [extensions]
3212 > color=
3212 > color=
3213 > [color]
3213 > [color]
3214 > mode=ansi
3214 > mode=ansi
3215 > text.{rev} = red
3215 > text.{rev} = red
3216 > text.1 = green
3216 > text.1 = green
3217 > EOF
3217 > EOF
3218 $ hg log --color=always -l 1 --template '{label(branch, "text\n")}'
3218 $ hg log --color=always -l 1 --template '{label(branch, "text\n")}'
3219 \x1b[0;31mtext\x1b[0m (esc)
3219 \x1b[0;31mtext\x1b[0m (esc)
3220 $ hg log --color=always -l 1 --template '{label("text.{rev}", "text\n")}'
3220 $ hg log --color=always -l 1 --template '{label("text.{rev}", "text\n")}'
3221 \x1b[0;32mtext\x1b[0m (esc)
3221 \x1b[0;32mtext\x1b[0m (esc)
3222
3222
3223 color effect can be specified without quoting:
3223 color effect can be specified without quoting:
3224
3224
3225 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
3225 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
3226 \x1b[0;31mtext\x1b[0m (esc)
3226 \x1b[0;31mtext\x1b[0m (esc)
3227
3227
3228 label should be no-op if color is disabled:
3228 label should be no-op if color is disabled:
3229
3229
3230 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
3230 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
3231 text
3231 text
3232 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
3232 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
3233 text
3233 text
3234
3234
3235 Test branches inside if statement:
3235 Test branches inside if statement:
3236
3236
3237 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
3237 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
3238 no
3238 no
3239
3239
3240 Test get function:
3240 Test get function:
3241
3241
3242 $ hg log -r 0 --template '{get(extras, "branch")}\n'
3242 $ hg log -r 0 --template '{get(extras, "branch")}\n'
3243 default
3243 default
3244 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
3244 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
3245 default
3245 default
3246 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
3246 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
3247 hg: parse error: get() expects a dict as first argument
3247 hg: parse error: get() expects a dict as first argument
3248 [255]
3248 [255]
3249
3249
3250 Test localdate(date, tz) function:
3250 Test localdate(date, tz) function:
3251
3251
3252 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
3252 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
3253 1970-01-01 09:00 +0900
3253 1970-01-01 09:00 +0900
3254 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
3254 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
3255 1970-01-01 00:00 +0000
3255 1970-01-01 00:00 +0000
3256 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
3257 hg: parse error: localdate expects a timezone
3258 [255]
3256 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
3259 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
3257 1970-01-01 02:00 +0200
3260 1970-01-01 02:00 +0200
3258 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
3261 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
3259 1970-01-01 00:00 +0000
3262 1970-01-01 00:00 +0000
3260 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
3263 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
3261 1970-01-01 00:00 +0000
3264 1970-01-01 00:00 +0000
3262 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
3265 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
3263 hg: parse error: localdate expects a timezone
3266 hg: parse error: localdate expects a timezone
3264 [255]
3267 [255]
3265 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
3268 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
3266 hg: parse error: localdate expects a timezone
3269 hg: parse error: localdate expects a timezone
3267 [255]
3270 [255]
3268
3271
3269 Test shortest(node) function:
3272 Test shortest(node) function:
3270
3273
3271 $ echo b > b
3274 $ echo b > b
3272 $ hg ci -qAm b
3275 $ hg ci -qAm b
3273 $ hg log --template '{shortest(node)}\n'
3276 $ hg log --template '{shortest(node)}\n'
3274 e777
3277 e777
3275 bcc7
3278 bcc7
3276 f776
3279 f776
3277 $ hg log --template '{shortest(node, 10)}\n'
3280 $ hg log --template '{shortest(node, 10)}\n'
3278 e777603221
3281 e777603221
3279 bcc7ff960b
3282 bcc7ff960b
3280 f7769ec2ab
3283 f7769ec2ab
3281 $ hg log --template '{node|shortest}\n' -l1
3284 $ hg log --template '{node|shortest}\n' -l1
3282 e777
3285 e777
3283
3286
3284 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
3287 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
3285 f7769ec2ab
3288 f7769ec2ab
3286 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
3289 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
3287 hg: parse error: shortest() expects an integer minlength
3290 hg: parse error: shortest() expects an integer minlength
3288 [255]
3291 [255]
3289
3292
3290 Test pad function
3293 Test pad function
3291
3294
3292 $ hg log --template '{pad(rev, 20)} {author|user}\n'
3295 $ hg log --template '{pad(rev, 20)} {author|user}\n'
3293 2 test
3296 2 test
3294 1 {node|short}
3297 1 {node|short}
3295 0 test
3298 0 test
3296
3299
3297 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
3300 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
3298 2 test
3301 2 test
3299 1 {node|short}
3302 1 {node|short}
3300 0 test
3303 0 test
3301
3304
3302 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
3305 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
3303 2------------------- test
3306 2------------------- test
3304 1------------------- {node|short}
3307 1------------------- {node|short}
3305 0------------------- test
3308 0------------------- test
3306
3309
3307 Test template string in pad function
3310 Test template string in pad function
3308
3311
3309 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
3312 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
3310 {0} test
3313 {0} test
3311
3314
3312 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
3315 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
3313 \{rev} test
3316 \{rev} test
3314
3317
3315 Test width argument passed to pad function
3318 Test width argument passed to pad function
3316
3319
3317 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
3320 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
3318 0 test
3321 0 test
3319 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
3322 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
3320 hg: parse error: pad() expects an integer width
3323 hg: parse error: pad() expects an integer width
3321 [255]
3324 [255]
3322
3325
3323 Test separate function
3326 Test separate function
3324
3327
3325 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
3328 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
3326 a-b-c
3329 a-b-c
3327 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
3330 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
3328 0:f7769ec2ab97 test default
3331 0:f7769ec2ab97 test default
3329 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
3332 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
3330 a \x1b[0;31mb\x1b[0m c d (esc)
3333 a \x1b[0;31mb\x1b[0m c d (esc)
3331
3334
3332 Test ifcontains function
3335 Test ifcontains function
3333
3336
3334 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
3337 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
3335 2 is in the string
3338 2 is in the string
3336 1 is not
3339 1 is not
3337 0 is in the string
3340 0 is in the string
3338
3341
3339 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
3342 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
3340 2 is in the string
3343 2 is in the string
3341 1 is not
3344 1 is not
3342 0 is in the string
3345 0 is in the string
3343
3346
3344 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
3347 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
3345 2 did not add a
3348 2 did not add a
3346 1 did not add a
3349 1 did not add a
3347 0 added a
3350 0 added a
3348
3351
3349 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
3352 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
3350 2 is parent of 1
3353 2 is parent of 1
3351 1
3354 1
3352 0
3355 0
3353
3356
3354 Test revset function
3357 Test revset function
3355
3358
3356 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
3359 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
3357 2 current rev
3360 2 current rev
3358 1 not current rev
3361 1 not current rev
3359 0 not current rev
3362 0 not current rev
3360
3363
3361 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
3364 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
3362 2 match rev
3365 2 match rev
3363 1 match rev
3366 1 match rev
3364 0 not match rev
3367 0 not match rev
3365
3368
3366 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
3369 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
3367 2 Parents: 1
3370 2 Parents: 1
3368 1 Parents: 0
3371 1 Parents: 0
3369 0 Parents:
3372 0 Parents:
3370
3373
3371 $ cat >> .hg/hgrc <<EOF
3374 $ cat >> .hg/hgrc <<EOF
3372 > [revsetalias]
3375 > [revsetalias]
3373 > myparents(\$1) = parents(\$1)
3376 > myparents(\$1) = parents(\$1)
3374 > EOF
3377 > EOF
3375 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
3378 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
3376 2 Parents: 1
3379 2 Parents: 1
3377 1 Parents: 0
3380 1 Parents: 0
3378 0 Parents:
3381 0 Parents:
3379
3382
3380 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
3383 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
3381 Rev: 2
3384 Rev: 2
3382 Ancestor: 0
3385 Ancestor: 0
3383 Ancestor: 1
3386 Ancestor: 1
3384 Ancestor: 2
3387 Ancestor: 2
3385
3388
3386 Rev: 1
3389 Rev: 1
3387 Ancestor: 0
3390 Ancestor: 0
3388 Ancestor: 1
3391 Ancestor: 1
3389
3392
3390 Rev: 0
3393 Rev: 0
3391 Ancestor: 0
3394 Ancestor: 0
3392
3395
3393 $ hg log --template '{revset("TIP"|lower)}\n' -l1
3396 $ hg log --template '{revset("TIP"|lower)}\n' -l1
3394 2
3397 2
3395
3398
3396 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
3399 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
3397 2
3400 2
3398
3401
3399 a list template is evaluated for each item of revset/parents
3402 a list template is evaluated for each item of revset/parents
3400
3403
3401 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
3404 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
3402 2 p: 1:bcc7ff960b8e
3405 2 p: 1:bcc7ff960b8e
3403 1 p: 0:f7769ec2ab97
3406 1 p: 0:f7769ec2ab97
3404 0 p:
3407 0 p:
3405
3408
3406 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
3409 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
3407 2 p: 1:bcc7ff960b8e -1:000000000000
3410 2 p: 1:bcc7ff960b8e -1:000000000000
3408 1 p: 0:f7769ec2ab97 -1:000000000000
3411 1 p: 0:f7769ec2ab97 -1:000000000000
3409 0 p: -1:000000000000 -1:000000000000
3412 0 p: -1:000000000000 -1:000000000000
3410
3413
3411 therefore, 'revcache' should be recreated for each rev
3414 therefore, 'revcache' should be recreated for each rev
3412
3415
3413 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
3416 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
3414 2 aa b
3417 2 aa b
3415 p
3418 p
3416 1
3419 1
3417 p a
3420 p a
3418 0 a
3421 0 a
3419 p
3422 p
3420
3423
3421 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
3424 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
3422 2 aa b
3425 2 aa b
3423 p
3426 p
3424 1
3427 1
3425 p a
3428 p a
3426 0 a
3429 0 a
3427 p
3430 p
3428
3431
3429 a revset item must be evaluated as an integer revision, not an offset from tip
3432 a revset item must be evaluated as an integer revision, not an offset from tip
3430
3433
3431 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
3434 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
3432 -1:000000000000
3435 -1:000000000000
3433 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
3436 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
3434 -1:000000000000
3437 -1:000000000000
3435
3438
3436 join() should pick '{rev}' from revset items:
3439 join() should pick '{rev}' from revset items:
3437
3440
3438 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
3441 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
3439 4, 5
3442 4, 5
3440
3443
3441 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
3444 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
3442 default. join() should agree with the default formatting:
3445 default. join() should agree with the default formatting:
3443
3446
3444 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
3447 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
3445 5:13207e5a10d9, 4:bbe44766e73d
3448 5:13207e5a10d9, 4:bbe44766e73d
3446
3449
3447 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
3450 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
3448 5:13207e5a10d9fd28ec424934298e176197f2c67f,
3451 5:13207e5a10d9fd28ec424934298e176197f2c67f,
3449 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
3452 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
3450
3453
3451 Test active bookmark templating
3454 Test active bookmark templating
3452
3455
3453 $ hg book foo
3456 $ hg book foo
3454 $ hg book bar
3457 $ hg book bar
3455 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
3458 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
3456 2 bar* foo
3459 2 bar* foo
3457 1
3460 1
3458 0
3461 0
3459 $ hg log --template "{rev} {activebookmark}\n"
3462 $ hg log --template "{rev} {activebookmark}\n"
3460 2 bar
3463 2 bar
3461 1
3464 1
3462 0
3465 0
3463 $ hg bookmarks --inactive bar
3466 $ hg bookmarks --inactive bar
3464 $ hg log --template "{rev} {activebookmark}\n"
3467 $ hg log --template "{rev} {activebookmark}\n"
3465 2
3468 2
3466 1
3469 1
3467 0
3470 0
3468 $ hg book -r1 baz
3471 $ hg book -r1 baz
3469 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
3472 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
3470 2 bar foo
3473 2 bar foo
3471 1 baz
3474 1 baz
3472 0
3475 0
3473 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
3476 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
3474 2 t
3477 2 t
3475 1 f
3478 1 f
3476 0 f
3479 0 f
3477
3480
3478 Test namespaces dict
3481 Test namespaces dict
3479
3482
3480 $ hg log -T '{rev}{namespaces % " {namespace}={join(names, ",")}"}\n'
3483 $ hg log -T '{rev}{namespaces % " {namespace}={join(names, ",")}"}\n'
3481 2 bookmarks=bar,foo tags=tip branches=text.{rev}
3484 2 bookmarks=bar,foo tags=tip branches=text.{rev}
3482 1 bookmarks=baz tags= branches=text.{rev}
3485 1 bookmarks=baz tags= branches=text.{rev}
3483 0 bookmarks= tags= branches=default
3486 0 bookmarks= tags= branches=default
3484 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
3487 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
3485 bookmarks: bar foo
3488 bookmarks: bar foo
3486 tags: tip
3489 tags: tip
3487 branches: text.{rev}
3490 branches: text.{rev}
3488 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
3491 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
3489 bookmarks:
3492 bookmarks:
3490 bar
3493 bar
3491 foo
3494 foo
3492 tags:
3495 tags:
3493 tip
3496 tip
3494 branches:
3497 branches:
3495 text.{rev}
3498 text.{rev}
3496 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
3499 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
3497 bar
3500 bar
3498 foo
3501 foo
3499
3502
3500 Test stringify on sub expressions
3503 Test stringify on sub expressions
3501
3504
3502 $ cd ..
3505 $ cd ..
3503 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
3506 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
3504 fourth, second, third
3507 fourth, second, third
3505 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
3508 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
3506 abc
3509 abc
3507
3510
3508 Test splitlines
3511 Test splitlines
3509
3512
3510 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
3513 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
3511 @ foo Modify, add, remove, rename
3514 @ foo Modify, add, remove, rename
3512 |
3515 |
3513 o foo future
3516 o foo future
3514 |
3517 |
3515 o foo third
3518 o foo third
3516 |
3519 |
3517 o foo second
3520 o foo second
3518
3521
3519 o foo merge
3522 o foo merge
3520 |\
3523 |\
3521 | o foo new head
3524 | o foo new head
3522 | |
3525 | |
3523 o | foo new branch
3526 o | foo new branch
3524 |/
3527 |/
3525 o foo no user, no domain
3528 o foo no user, no domain
3526 |
3529 |
3527 o foo no person
3530 o foo no person
3528 |
3531 |
3529 o foo other 1
3532 o foo other 1
3530 | foo other 2
3533 | foo other 2
3531 | foo
3534 | foo
3532 | foo other 3
3535 | foo other 3
3533 o foo line 1
3536 o foo line 1
3534 foo line 2
3537 foo line 2
3535
3538
3536 Test startswith
3539 Test startswith
3537 $ hg log -Gv -R a --template "{startswith(desc)}"
3540 $ hg log -Gv -R a --template "{startswith(desc)}"
3538 hg: parse error: startswith expects two arguments
3541 hg: parse error: startswith expects two arguments
3539 [255]
3542 [255]
3540
3543
3541 $ hg log -Gv -R a --template "{startswith('line', desc)}"
3544 $ hg log -Gv -R a --template "{startswith('line', desc)}"
3542 @
3545 @
3543 |
3546 |
3544 o
3547 o
3545 |
3548 |
3546 o
3549 o
3547 |
3550 |
3548 o
3551 o
3549
3552
3550 o
3553 o
3551 |\
3554 |\
3552 | o
3555 | o
3553 | |
3556 | |
3554 o |
3557 o |
3555 |/
3558 |/
3556 o
3559 o
3557 |
3560 |
3558 o
3561 o
3559 |
3562 |
3560 o
3563 o
3561 |
3564 |
3562 o line 1
3565 o line 1
3563 line 2
3566 line 2
3564
3567
3565 Test bad template with better error message
3568 Test bad template with better error message
3566
3569
3567 $ hg log -Gv -R a --template '{desc|user()}'
3570 $ hg log -Gv -R a --template '{desc|user()}'
3568 hg: parse error: expected a symbol, got 'func'
3571 hg: parse error: expected a symbol, got 'func'
3569 [255]
3572 [255]
3570
3573
3571 Test word function (including index out of bounds graceful failure)
3574 Test word function (including index out of bounds graceful failure)
3572
3575
3573 $ hg log -Gv -R a --template "{word('1', desc)}"
3576 $ hg log -Gv -R a --template "{word('1', desc)}"
3574 @ add,
3577 @ add,
3575 |
3578 |
3576 o
3579 o
3577 |
3580 |
3578 o
3581 o
3579 |
3582 |
3580 o
3583 o
3581
3584
3582 o
3585 o
3583 |\
3586 |\
3584 | o head
3587 | o head
3585 | |
3588 | |
3586 o | branch
3589 o | branch
3587 |/
3590 |/
3588 o user,
3591 o user,
3589 |
3592 |
3590 o person
3593 o person
3591 |
3594 |
3592 o 1
3595 o 1
3593 |
3596 |
3594 o 1
3597 o 1
3595
3598
3596
3599
3597 Test word third parameter used as splitter
3600 Test word third parameter used as splitter
3598
3601
3599 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
3602 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
3600 @ M
3603 @ M
3601 |
3604 |
3602 o future
3605 o future
3603 |
3606 |
3604 o third
3607 o third
3605 |
3608 |
3606 o sec
3609 o sec
3607
3610
3608 o merge
3611 o merge
3609 |\
3612 |\
3610 | o new head
3613 | o new head
3611 | |
3614 | |
3612 o | new branch
3615 o | new branch
3613 |/
3616 |/
3614 o n
3617 o n
3615 |
3618 |
3616 o n
3619 o n
3617 |
3620 |
3618 o
3621 o
3619 |
3622 |
3620 o line 1
3623 o line 1
3621 line 2
3624 line 2
3622
3625
3623 Test word error messages for not enough and too many arguments
3626 Test word error messages for not enough and too many arguments
3624
3627
3625 $ hg log -Gv -R a --template "{word('0')}"
3628 $ hg log -Gv -R a --template "{word('0')}"
3626 hg: parse error: word expects two or three arguments, got 1
3629 hg: parse error: word expects two or three arguments, got 1
3627 [255]
3630 [255]
3628
3631
3629 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
3632 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
3630 hg: parse error: word expects two or three arguments, got 7
3633 hg: parse error: word expects two or three arguments, got 7
3631 [255]
3634 [255]
3632
3635
3633 Test word for integer literal
3636 Test word for integer literal
3634
3637
3635 $ hg log -R a --template "{word(2, desc)}\n" -r0
3638 $ hg log -R a --template "{word(2, desc)}\n" -r0
3636 line
3639 line
3637
3640
3638 Test word for invalid numbers
3641 Test word for invalid numbers
3639
3642
3640 $ hg log -Gv -R a --template "{word('a', desc)}"
3643 $ hg log -Gv -R a --template "{word('a', desc)}"
3641 hg: parse error: word expects an integer index
3644 hg: parse error: word expects an integer index
3642 [255]
3645 [255]
3643
3646
3644 Test word for out of range
3647 Test word for out of range
3645
3648
3646 $ hg log -R a --template "{word(10000, desc)}"
3649 $ hg log -R a --template "{word(10000, desc)}"
3647 $ hg log -R a --template "{word(-10000, desc)}"
3650 $ hg log -R a --template "{word(-10000, desc)}"
3648
3651
3649 Test indent and not adding to empty lines
3652 Test indent and not adding to empty lines
3650
3653
3651 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
3654 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
3652 -----
3655 -----
3653 > line 1
3656 > line 1
3654 >> line 2
3657 >> line 2
3655 -----
3658 -----
3656 > other 1
3659 > other 1
3657 >> other 2
3660 >> other 2
3658
3661
3659 >> other 3
3662 >> other 3
3660
3663
3661 Test with non-strings like dates
3664 Test with non-strings like dates
3662
3665
3663 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
3666 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
3664 1200000.00
3667 1200000.00
3665 1300000.00
3668 1300000.00
3666
3669
3667 Test broken string escapes:
3670 Test broken string escapes:
3668
3671
3669 $ hg log -T "bogus\\" -R a
3672 $ hg log -T "bogus\\" -R a
3670 hg: parse error: trailing \ in string
3673 hg: parse error: trailing \ in string
3671 [255]
3674 [255]
3672 $ hg log -T "\\xy" -R a
3675 $ hg log -T "\\xy" -R a
3673 hg: parse error: invalid \x escape
3676 hg: parse error: invalid \x escape
3674 [255]
3677 [255]
3675
3678
3676 json filter should escape HTML tags so that the output can be embedded in hgweb:
3679 json filter should escape HTML tags so that the output can be embedded in hgweb:
3677
3680
3678 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
3681 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
3679 "\u003cfoo@example.org\u003e"
3682 "\u003cfoo@example.org\u003e"
3680
3683
3681 Templater supports aliases of symbol and func() styles:
3684 Templater supports aliases of symbol and func() styles:
3682
3685
3683 $ hg clone -q a aliases
3686 $ hg clone -q a aliases
3684 $ cd aliases
3687 $ cd aliases
3685 $ cat <<EOF >> .hg/hgrc
3688 $ cat <<EOF >> .hg/hgrc
3686 > [templatealias]
3689 > [templatealias]
3687 > r = rev
3690 > r = rev
3688 > rn = "{r}:{node|short}"
3691 > rn = "{r}:{node|short}"
3689 > status(c, files) = files % "{c} {file}\n"
3692 > status(c, files) = files % "{c} {file}\n"
3690 > utcdate(d) = localdate(d, "UTC")
3693 > utcdate(d) = localdate(d, "UTC")
3691 > EOF
3694 > EOF
3692
3695
3693 $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
3696 $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
3694 (template
3697 (template
3695 ('symbol', 'rn')
3698 ('symbol', 'rn')
3696 ('string', ' ')
3699 ('string', ' ')
3697 (|
3700 (|
3698 (func
3701 (func
3699 ('symbol', 'utcdate')
3702 ('symbol', 'utcdate')
3700 ('symbol', 'date'))
3703 ('symbol', 'date'))
3701 ('symbol', 'isodate'))
3704 ('symbol', 'isodate'))
3702 ('string', '\n'))
3705 ('string', '\n'))
3703 * expanded:
3706 * expanded:
3704 (template
3707 (template
3705 (template
3708 (template
3706 ('symbol', 'rev')
3709 ('symbol', 'rev')
3707 ('string', ':')
3710 ('string', ':')
3708 (|
3711 (|
3709 ('symbol', 'node')
3712 ('symbol', 'node')
3710 ('symbol', 'short')))
3713 ('symbol', 'short')))
3711 ('string', ' ')
3714 ('string', ' ')
3712 (|
3715 (|
3713 (func
3716 (func
3714 ('symbol', 'localdate')
3717 ('symbol', 'localdate')
3715 (list
3718 (list
3716 ('symbol', 'date')
3719 ('symbol', 'date')
3717 ('string', 'UTC')))
3720 ('string', 'UTC')))
3718 ('symbol', 'isodate'))
3721 ('symbol', 'isodate'))
3719 ('string', '\n'))
3722 ('string', '\n'))
3720 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
3723 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
3721
3724
3722 $ hg debugtemplate -vr0 '{status("A", file_adds)}'
3725 $ hg debugtemplate -vr0 '{status("A", file_adds)}'
3723 (template
3726 (template
3724 (func
3727 (func
3725 ('symbol', 'status')
3728 ('symbol', 'status')
3726 (list
3729 (list
3727 ('string', 'A')
3730 ('string', 'A')
3728 ('symbol', 'file_adds'))))
3731 ('symbol', 'file_adds'))))
3729 * expanded:
3732 * expanded:
3730 (template
3733 (template
3731 (%
3734 (%
3732 ('symbol', 'file_adds')
3735 ('symbol', 'file_adds')
3733 (template
3736 (template
3734 ('string', 'A')
3737 ('string', 'A')
3735 ('string', ' ')
3738 ('string', ' ')
3736 ('symbol', 'file')
3739 ('symbol', 'file')
3737 ('string', '\n'))))
3740 ('string', '\n'))))
3738 A a
3741 A a
3739
3742
3740 A unary function alias can be called as a filter:
3743 A unary function alias can be called as a filter:
3741
3744
3742 $ hg debugtemplate -vr0 '{date|utcdate|isodate}\n'
3745 $ hg debugtemplate -vr0 '{date|utcdate|isodate}\n'
3743 (template
3746 (template
3744 (|
3747 (|
3745 (|
3748 (|
3746 ('symbol', 'date')
3749 ('symbol', 'date')
3747 ('symbol', 'utcdate'))
3750 ('symbol', 'utcdate'))
3748 ('symbol', 'isodate'))
3751 ('symbol', 'isodate'))
3749 ('string', '\n'))
3752 ('string', '\n'))
3750 * expanded:
3753 * expanded:
3751 (template
3754 (template
3752 (|
3755 (|
3753 (func
3756 (func
3754 ('symbol', 'localdate')
3757 ('symbol', 'localdate')
3755 (list
3758 (list
3756 ('symbol', 'date')
3759 ('symbol', 'date')
3757 ('string', 'UTC')))
3760 ('string', 'UTC')))
3758 ('symbol', 'isodate'))
3761 ('symbol', 'isodate'))
3759 ('string', '\n'))
3762 ('string', '\n'))
3760 1970-01-12 13:46 +0000
3763 1970-01-12 13:46 +0000
3761
3764
3762 Aliases should be applied only to command arguments and templates in hgrc.
3765 Aliases should be applied only to command arguments and templates in hgrc.
3763 Otherwise, our stock styles and web templates could be corrupted:
3766 Otherwise, our stock styles and web templates could be corrupted:
3764
3767
3765 $ hg log -r0 -T '{rn} {utcdate(date)|isodate}\n'
3768 $ hg log -r0 -T '{rn} {utcdate(date)|isodate}\n'
3766 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
3769 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
3767
3770
3768 $ hg log -r0 --config ui.logtemplate='"{rn} {utcdate(date)|isodate}\n"'
3771 $ hg log -r0 --config ui.logtemplate='"{rn} {utcdate(date)|isodate}\n"'
3769 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
3772 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
3770
3773
3771 $ cat <<EOF > tmpl
3774 $ cat <<EOF > tmpl
3772 > changeset = 'nothing expanded:{rn}\n'
3775 > changeset = 'nothing expanded:{rn}\n'
3773 > EOF
3776 > EOF
3774 $ hg log -r0 --style ./tmpl
3777 $ hg log -r0 --style ./tmpl
3775 nothing expanded:
3778 nothing expanded:
3776
3779
3777 Aliases in formatter:
3780 Aliases in formatter:
3778
3781
3779 $ hg branches -T '{pad(branch, 7)} {rn}\n'
3782 $ hg branches -T '{pad(branch, 7)} {rn}\n'
3780 default 6:d41e714fe50d
3783 default 6:d41e714fe50d
3781 foo 4:bbe44766e73d
3784 foo 4:bbe44766e73d
3782
3785
3783 Aliases should honor HGPLAIN:
3786 Aliases should honor HGPLAIN:
3784
3787
3785 $ HGPLAIN= hg log -r0 -T 'nothing expanded:{rn}\n'
3788 $ HGPLAIN= hg log -r0 -T 'nothing expanded:{rn}\n'
3786 nothing expanded:
3789 nothing expanded:
3787 $ HGPLAINEXCEPT=templatealias hg log -r0 -T '{rn}\n'
3790 $ HGPLAINEXCEPT=templatealias hg log -r0 -T '{rn}\n'
3788 0:1e4e1b8f71e0
3791 0:1e4e1b8f71e0
3789
3792
3790 Unparsable alias:
3793 Unparsable alias:
3791
3794
3792 $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
3795 $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
3793 (template
3796 (template
3794 ('symbol', 'bad'))
3797 ('symbol', 'bad'))
3795 abort: bad definition of template alias "bad": at 2: not a prefix: end
3798 abort: bad definition of template alias "bad": at 2: not a prefix: end
3796 [255]
3799 [255]
3797 $ hg log --config templatealias.bad='x(' -T '{bad}'
3800 $ hg log --config templatealias.bad='x(' -T '{bad}'
3798 abort: bad definition of template alias "bad": at 2: not a prefix: end
3801 abort: bad definition of template alias "bad": at 2: not a prefix: end
3799 [255]
3802 [255]
3800
3803
3801 $ cd ..
3804 $ cd ..
3802
3805
3803 Set up repository for non-ascii encoding tests:
3806 Set up repository for non-ascii encoding tests:
3804
3807
3805 $ hg init nonascii
3808 $ hg init nonascii
3806 $ cd nonascii
3809 $ cd nonascii
3807 $ python <<EOF
3810 $ python <<EOF
3808 > open('latin1', 'w').write('\xe9')
3811 > open('latin1', 'w').write('\xe9')
3809 > open('utf-8', 'w').write('\xc3\xa9')
3812 > open('utf-8', 'w').write('\xc3\xa9')
3810 > EOF
3813 > EOF
3811 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
3814 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
3812 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
3815 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
3813
3816
3814 json filter should try round-trip conversion to utf-8:
3817 json filter should try round-trip conversion to utf-8:
3815
3818
3816 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
3819 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
3817 "\u00e9"
3820 "\u00e9"
3818 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
3821 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
3819 "non-ascii branch: \u00e9"
3822 "non-ascii branch: \u00e9"
3820
3823
3821 json filter takes input as utf-8b:
3824 json filter takes input as utf-8b:
3822
3825
3823 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
3826 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
3824 "\u00e9"
3827 "\u00e9"
3825 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
3828 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
3826 "\udce9"
3829 "\udce9"
3827
3830
3828 utf8 filter:
3831 utf8 filter:
3829
3832
3830 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
3833 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
3831 round-trip: c3a9
3834 round-trip: c3a9
3832 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
3835 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
3833 decoded: c3a9
3836 decoded: c3a9
3834 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
3837 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
3835 abort: decoding near * (glob)
3838 abort: decoding near * (glob)
3836 [255]
3839 [255]
3837 $ hg log -T "invalid type: {rev|utf8}\n" -r0
3840 $ hg log -T "invalid type: {rev|utf8}\n" -r0
3838 abort: template filter 'utf8' is not compatible with keyword 'rev'
3841 abort: template filter 'utf8' is not compatible with keyword 'rev'
3839 [255]
3842 [255]
3840
3843
3841 $ cd ..
3844 $ cd ..
3842
3845
3843 Test that template function in extension is registered as expected
3846 Test that template function in extension is registered as expected
3844
3847
3845 $ cd a
3848 $ cd a
3846
3849
3847 $ cat <<EOF > $TESTTMP/customfunc.py
3850 $ cat <<EOF > $TESTTMP/customfunc.py
3848 > from mercurial import registrar
3851 > from mercurial import registrar
3849 >
3852 >
3850 > templatefunc = registrar.templatefunc()
3853 > templatefunc = registrar.templatefunc()
3851 >
3854 >
3852 > @templatefunc('custom()')
3855 > @templatefunc('custom()')
3853 > def custom(context, mapping, args):
3856 > def custom(context, mapping, args):
3854 > return 'custom'
3857 > return 'custom'
3855 > EOF
3858 > EOF
3856 $ cat <<EOF > .hg/hgrc
3859 $ cat <<EOF > .hg/hgrc
3857 > [extensions]
3860 > [extensions]
3858 > customfunc = $TESTTMP/customfunc.py
3861 > customfunc = $TESTTMP/customfunc.py
3859 > EOF
3862 > EOF
3860
3863
3861 $ hg log -r . -T "{custom()}\n" --config customfunc.enabled=true
3864 $ hg log -r . -T "{custom()}\n" --config customfunc.enabled=true
3862 custom
3865 custom
3863
3866
3864 $ cd ..
3867 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now