##// END OF EJS Templates
templater: fix if() to not evaluate False as bool('False')...
Yuya Nishihara -
r29816:034412ca default
parent child Browse files
Show More
@@ -1,1175 +1,1184 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 evalboolean(context, mapping, arg):
293 func, data = arg
294 thing = func(context, mapping, data)
295 if isinstance(thing, bool):
296 return thing
297 # other objects are evaluated as strings, which means 0 is True, but
298 # empty dict/list should be False as they are expected to be ''
299 return bool(stringify(thing))
300
292 def evalinteger(context, mapping, arg, err):
301 def evalinteger(context, mapping, arg, err):
293 v = evalfuncarg(context, mapping, arg)
302 v = evalfuncarg(context, mapping, arg)
294 try:
303 try:
295 return int(v)
304 return int(v)
296 except (TypeError, ValueError):
305 except (TypeError, ValueError):
297 raise error.ParseError(err)
306 raise error.ParseError(err)
298
307
299 def evalstring(context, mapping, arg):
308 def evalstring(context, mapping, arg):
300 func, data = arg
309 func, data = arg
301 return stringify(func(context, mapping, data))
310 return stringify(func(context, mapping, data))
302
311
303 def evalstringliteral(context, mapping, arg):
312 def evalstringliteral(context, mapping, arg):
304 """Evaluate given argument as string template, but returns symbol name
313 """Evaluate given argument as string template, but returns symbol name
305 if it is unknown"""
314 if it is unknown"""
306 func, data = arg
315 func, data = arg
307 if func is runsymbol:
316 if func is runsymbol:
308 thing = func(context, mapping, data, default=data)
317 thing = func(context, mapping, data, default=data)
309 else:
318 else:
310 thing = func(context, mapping, data)
319 thing = func(context, mapping, data)
311 return stringify(thing)
320 return stringify(thing)
312
321
313 def runinteger(context, mapping, data):
322 def runinteger(context, mapping, data):
314 return int(data)
323 return int(data)
315
324
316 def runstring(context, mapping, data):
325 def runstring(context, mapping, data):
317 return data
326 return data
318
327
319 def _recursivesymbolblocker(key):
328 def _recursivesymbolblocker(key):
320 def showrecursion(**args):
329 def showrecursion(**args):
321 raise error.Abort(_("recursive reference '%s' in template") % key)
330 raise error.Abort(_("recursive reference '%s' in template") % key)
322 return showrecursion
331 return showrecursion
323
332
324 def _runrecursivesymbol(context, mapping, key):
333 def _runrecursivesymbol(context, mapping, key):
325 raise error.Abort(_("recursive reference '%s' in template") % key)
334 raise error.Abort(_("recursive reference '%s' in template") % key)
326
335
327 def runsymbol(context, mapping, key, default=''):
336 def runsymbol(context, mapping, key, default=''):
328 v = mapping.get(key)
337 v = mapping.get(key)
329 if v is None:
338 if v is None:
330 v = context._defaults.get(key)
339 v = context._defaults.get(key)
331 if v is None:
340 if v is None:
332 # put poison to cut recursion. we can't move this to parsing phase
341 # 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)
342 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
334 safemapping = mapping.copy()
343 safemapping = mapping.copy()
335 safemapping[key] = _recursivesymbolblocker(key)
344 safemapping[key] = _recursivesymbolblocker(key)
336 try:
345 try:
337 v = context.process(key, safemapping)
346 v = context.process(key, safemapping)
338 except TemplateNotFound:
347 except TemplateNotFound:
339 v = default
348 v = default
340 if callable(v):
349 if callable(v):
341 return v(**mapping)
350 return v(**mapping)
342 return v
351 return v
343
352
344 def buildtemplate(exp, context):
353 def buildtemplate(exp, context):
345 ctmpl = [compileexp(e, context, methods) for e in exp[1:]]
354 ctmpl = [compileexp(e, context, methods) for e in exp[1:]]
346 return (runtemplate, ctmpl)
355 return (runtemplate, ctmpl)
347
356
348 def runtemplate(context, mapping, template):
357 def runtemplate(context, mapping, template):
349 for func, data in template:
358 for func, data in template:
350 yield func(context, mapping, data)
359 yield func(context, mapping, data)
351
360
352 def buildfilter(exp, context):
361 def buildfilter(exp, context):
353 arg = compileexp(exp[1], context, methods)
362 arg = compileexp(exp[1], context, methods)
354 n = getsymbol(exp[2])
363 n = getsymbol(exp[2])
355 if n in context._filters:
364 if n in context._filters:
356 filt = context._filters[n]
365 filt = context._filters[n]
357 return (runfilter, (arg, filt))
366 return (runfilter, (arg, filt))
358 if n in funcs:
367 if n in funcs:
359 f = funcs[n]
368 f = funcs[n]
360 return (f, [arg])
369 return (f, [arg])
361 raise error.ParseError(_("unknown function '%s'") % n)
370 raise error.ParseError(_("unknown function '%s'") % n)
362
371
363 def runfilter(context, mapping, data):
372 def runfilter(context, mapping, data):
364 arg, filt = data
373 arg, filt = data
365 thing = evalfuncarg(context, mapping, arg)
374 thing = evalfuncarg(context, mapping, arg)
366 try:
375 try:
367 return filt(thing)
376 return filt(thing)
368 except (ValueError, AttributeError, TypeError):
377 except (ValueError, AttributeError, TypeError):
369 if isinstance(arg[1], tuple):
378 if isinstance(arg[1], tuple):
370 dt = arg[1][1]
379 dt = arg[1][1]
371 else:
380 else:
372 dt = arg[1]
381 dt = arg[1]
373 raise error.Abort(_("template filter '%s' is not compatible with "
382 raise error.Abort(_("template filter '%s' is not compatible with "
374 "keyword '%s'") % (filt.func_name, dt))
383 "keyword '%s'") % (filt.func_name, dt))
375
384
376 def buildmap(exp, context):
385 def buildmap(exp, context):
377 func, data = compileexp(exp[1], context, methods)
386 func, data = compileexp(exp[1], context, methods)
378 tfunc, tdata = gettemplate(exp[2], context)
387 tfunc, tdata = gettemplate(exp[2], context)
379 return (runmap, (func, data, tfunc, tdata))
388 return (runmap, (func, data, tfunc, tdata))
380
389
381 def runmap(context, mapping, data):
390 def runmap(context, mapping, data):
382 func, data, tfunc, tdata = data
391 func, data, tfunc, tdata = data
383 d = func(context, mapping, data)
392 d = func(context, mapping, data)
384 if util.safehasattr(d, 'itermaps'):
393 if util.safehasattr(d, 'itermaps'):
385 diter = d.itermaps()
394 diter = d.itermaps()
386 else:
395 else:
387 try:
396 try:
388 diter = iter(d)
397 diter = iter(d)
389 except TypeError:
398 except TypeError:
390 if func is runsymbol:
399 if func is runsymbol:
391 raise error.ParseError(_("keyword '%s' is not iterable") % data)
400 raise error.ParseError(_("keyword '%s' is not iterable") % data)
392 else:
401 else:
393 raise error.ParseError(_("%r is not iterable") % d)
402 raise error.ParseError(_("%r is not iterable") % d)
394
403
395 for i in diter:
404 for i in diter:
396 lm = mapping.copy()
405 lm = mapping.copy()
397 if isinstance(i, dict):
406 if isinstance(i, dict):
398 lm.update(i)
407 lm.update(i)
399 lm['originalnode'] = mapping.get('node')
408 lm['originalnode'] = mapping.get('node')
400 yield tfunc(context, lm, tdata)
409 yield tfunc(context, lm, tdata)
401 else:
410 else:
402 # v is not an iterable of dicts, this happen when 'key'
411 # v is not an iterable of dicts, this happen when 'key'
403 # has been fully expanded already and format is useless.
412 # has been fully expanded already and format is useless.
404 # If so, return the expanded value.
413 # If so, return the expanded value.
405 yield i
414 yield i
406
415
407 def buildfunc(exp, context):
416 def buildfunc(exp, context):
408 n = getsymbol(exp[1])
417 n = getsymbol(exp[1])
409 args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
418 args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
410 if n in funcs:
419 if n in funcs:
411 f = funcs[n]
420 f = funcs[n]
412 return (f, args)
421 return (f, args)
413 if n in context._filters:
422 if n in context._filters:
414 if len(args) != 1:
423 if len(args) != 1:
415 raise error.ParseError(_("filter %s expects one argument") % n)
424 raise error.ParseError(_("filter %s expects one argument") % n)
416 f = context._filters[n]
425 f = context._filters[n]
417 return (runfilter, (args[0], f))
426 return (runfilter, (args[0], f))
418 raise error.ParseError(_("unknown function '%s'") % n)
427 raise error.ParseError(_("unknown function '%s'") % n)
419
428
420 # dict of template built-in functions
429 # dict of template built-in functions
421 funcs = {}
430 funcs = {}
422
431
423 templatefunc = registrar.templatefunc(funcs)
432 templatefunc = registrar.templatefunc(funcs)
424
433
425 @templatefunc('date(date[, fmt])')
434 @templatefunc('date(date[, fmt])')
426 def date(context, mapping, args):
435 def date(context, mapping, args):
427 """Format a date. See :hg:`help dates` for formatting
436 """Format a date. See :hg:`help dates` for formatting
428 strings. The default is a Unix date format, including the timezone:
437 strings. The default is a Unix date format, including the timezone:
429 "Mon Sep 04 15:13:13 2006 0700"."""
438 "Mon Sep 04 15:13:13 2006 0700"."""
430 if not (1 <= len(args) <= 2):
439 if not (1 <= len(args) <= 2):
431 # i18n: "date" is a keyword
440 # i18n: "date" is a keyword
432 raise error.ParseError(_("date expects one or two arguments"))
441 raise error.ParseError(_("date expects one or two arguments"))
433
442
434 date = evalfuncarg(context, mapping, args[0])
443 date = evalfuncarg(context, mapping, args[0])
435 fmt = None
444 fmt = None
436 if len(args) == 2:
445 if len(args) == 2:
437 fmt = evalstring(context, mapping, args[1])
446 fmt = evalstring(context, mapping, args[1])
438 try:
447 try:
439 if fmt is None:
448 if fmt is None:
440 return util.datestr(date)
449 return util.datestr(date)
441 else:
450 else:
442 return util.datestr(date, fmt)
451 return util.datestr(date, fmt)
443 except (TypeError, ValueError):
452 except (TypeError, ValueError):
444 # i18n: "date" is a keyword
453 # i18n: "date" is a keyword
445 raise error.ParseError(_("date expects a date information"))
454 raise error.ParseError(_("date expects a date information"))
446
455
447 @templatefunc('diff([includepattern [, excludepattern]])')
456 @templatefunc('diff([includepattern [, excludepattern]])')
448 def diff(context, mapping, args):
457 def diff(context, mapping, args):
449 """Show a diff, optionally
458 """Show a diff, optionally
450 specifying files to include or exclude."""
459 specifying files to include or exclude."""
451 if len(args) > 2:
460 if len(args) > 2:
452 # i18n: "diff" is a keyword
461 # i18n: "diff" is a keyword
453 raise error.ParseError(_("diff expects zero, one, or two arguments"))
462 raise error.ParseError(_("diff expects zero, one, or two arguments"))
454
463
455 def getpatterns(i):
464 def getpatterns(i):
456 if i < len(args):
465 if i < len(args):
457 s = evalstring(context, mapping, args[i]).strip()
466 s = evalstring(context, mapping, args[i]).strip()
458 if s:
467 if s:
459 return [s]
468 return [s]
460 return []
469 return []
461
470
462 ctx = mapping['ctx']
471 ctx = mapping['ctx']
463 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
472 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
464
473
465 return ''.join(chunks)
474 return ''.join(chunks)
466
475
467 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
476 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
468 def fill(context, mapping, args):
477 def fill(context, mapping, args):
469 """Fill many
478 """Fill many
470 paragraphs with optional indentation. See the "fill" filter."""
479 paragraphs with optional indentation. See the "fill" filter."""
471 if not (1 <= len(args) <= 4):
480 if not (1 <= len(args) <= 4):
472 # i18n: "fill" is a keyword
481 # i18n: "fill" is a keyword
473 raise error.ParseError(_("fill expects one to four arguments"))
482 raise error.ParseError(_("fill expects one to four arguments"))
474
483
475 text = evalstring(context, mapping, args[0])
484 text = evalstring(context, mapping, args[0])
476 width = 76
485 width = 76
477 initindent = ''
486 initindent = ''
478 hangindent = ''
487 hangindent = ''
479 if 2 <= len(args) <= 4:
488 if 2 <= len(args) <= 4:
480 width = evalinteger(context, mapping, args[1],
489 width = evalinteger(context, mapping, args[1],
481 # i18n: "fill" is a keyword
490 # i18n: "fill" is a keyword
482 _("fill expects an integer width"))
491 _("fill expects an integer width"))
483 try:
492 try:
484 initindent = evalstring(context, mapping, args[2])
493 initindent = evalstring(context, mapping, args[2])
485 hangindent = evalstring(context, mapping, args[3])
494 hangindent = evalstring(context, mapping, args[3])
486 except IndexError:
495 except IndexError:
487 pass
496 pass
488
497
489 return templatefilters.fill(text, width, initindent, hangindent)
498 return templatefilters.fill(text, width, initindent, hangindent)
490
499
491 @templatefunc('pad(text, width[, fillchar=\' \'[, right=False]])')
500 @templatefunc('pad(text, width[, fillchar=\' \'[, right=False]])')
492 def pad(context, mapping, args):
501 def pad(context, mapping, args):
493 """Pad text with a
502 """Pad text with a
494 fill character."""
503 fill character."""
495 if not (2 <= len(args) <= 4):
504 if not (2 <= len(args) <= 4):
496 # i18n: "pad" is a keyword
505 # i18n: "pad" is a keyword
497 raise error.ParseError(_("pad() expects two to four arguments"))
506 raise error.ParseError(_("pad() expects two to four arguments"))
498
507
499 width = evalinteger(context, mapping, args[1],
508 width = evalinteger(context, mapping, args[1],
500 # i18n: "pad" is a keyword
509 # i18n: "pad" is a keyword
501 _("pad() expects an integer width"))
510 _("pad() expects an integer width"))
502
511
503 text = evalstring(context, mapping, args[0])
512 text = evalstring(context, mapping, args[0])
504
513
505 right = False
514 right = False
506 fillchar = ' '
515 fillchar = ' '
507 if len(args) > 2:
516 if len(args) > 2:
508 fillchar = evalstring(context, mapping, args[2])
517 fillchar = evalstring(context, mapping, args[2])
509 if len(args) > 3:
518 if len(args) > 3:
510 right = util.parsebool(args[3][1])
519 right = util.parsebool(args[3][1])
511
520
512 if right:
521 if right:
513 return text.rjust(width, fillchar)
522 return text.rjust(width, fillchar)
514 else:
523 else:
515 return text.ljust(width, fillchar)
524 return text.ljust(width, fillchar)
516
525
517 @templatefunc('indent(text, indentchars[, firstline])')
526 @templatefunc('indent(text, indentchars[, firstline])')
518 def indent(context, mapping, args):
527 def indent(context, mapping, args):
519 """Indents all non-empty lines
528 """Indents all non-empty lines
520 with the characters given in the indentchars string. An optional
529 with the characters given in the indentchars string. An optional
521 third parameter will override the indent for the first line only
530 third parameter will override the indent for the first line only
522 if present."""
531 if present."""
523 if not (2 <= len(args) <= 3):
532 if not (2 <= len(args) <= 3):
524 # i18n: "indent" is a keyword
533 # i18n: "indent" is a keyword
525 raise error.ParseError(_("indent() expects two or three arguments"))
534 raise error.ParseError(_("indent() expects two or three arguments"))
526
535
527 text = evalstring(context, mapping, args[0])
536 text = evalstring(context, mapping, args[0])
528 indent = evalstring(context, mapping, args[1])
537 indent = evalstring(context, mapping, args[1])
529
538
530 if len(args) == 3:
539 if len(args) == 3:
531 firstline = evalstring(context, mapping, args[2])
540 firstline = evalstring(context, mapping, args[2])
532 else:
541 else:
533 firstline = indent
542 firstline = indent
534
543
535 # the indent function doesn't indent the first line, so we do it here
544 # the indent function doesn't indent the first line, so we do it here
536 return templatefilters.indent(firstline + text, indent)
545 return templatefilters.indent(firstline + text, indent)
537
546
538 @templatefunc('get(dict, key)')
547 @templatefunc('get(dict, key)')
539 def get(context, mapping, args):
548 def get(context, mapping, args):
540 """Get an attribute/key from an object. Some keywords
549 """Get an attribute/key from an object. Some keywords
541 are complex types. This function allows you to obtain the value of an
550 are complex types. This function allows you to obtain the value of an
542 attribute on these types."""
551 attribute on these types."""
543 if len(args) != 2:
552 if len(args) != 2:
544 # i18n: "get" is a keyword
553 # i18n: "get" is a keyword
545 raise error.ParseError(_("get() expects two arguments"))
554 raise error.ParseError(_("get() expects two arguments"))
546
555
547 dictarg = evalfuncarg(context, mapping, args[0])
556 dictarg = evalfuncarg(context, mapping, args[0])
548 if not util.safehasattr(dictarg, 'get'):
557 if not util.safehasattr(dictarg, 'get'):
549 # i18n: "get" is a keyword
558 # i18n: "get" is a keyword
550 raise error.ParseError(_("get() expects a dict as first argument"))
559 raise error.ParseError(_("get() expects a dict as first argument"))
551
560
552 key = evalfuncarg(context, mapping, args[1])
561 key = evalfuncarg(context, mapping, args[1])
553 return dictarg.get(key)
562 return dictarg.get(key)
554
563
555 @templatefunc('if(expr, then[, else])')
564 @templatefunc('if(expr, then[, else])')
556 def if_(context, mapping, args):
565 def if_(context, mapping, args):
557 """Conditionally execute based on the result of
566 """Conditionally execute based on the result of
558 an expression."""
567 an expression."""
559 if not (2 <= len(args) <= 3):
568 if not (2 <= len(args) <= 3):
560 # i18n: "if" is a keyword
569 # i18n: "if" is a keyword
561 raise error.ParseError(_("if expects two or three arguments"))
570 raise error.ParseError(_("if expects two or three arguments"))
562
571
563 test = evalstring(context, mapping, args[0])
572 test = evalboolean(context, mapping, args[0])
564 if test:
573 if test:
565 yield args[1][0](context, mapping, args[1][1])
574 yield args[1][0](context, mapping, args[1][1])
566 elif len(args) == 3:
575 elif len(args) == 3:
567 yield args[2][0](context, mapping, args[2][1])
576 yield args[2][0](context, mapping, args[2][1])
568
577
569 @templatefunc('ifcontains(search, thing, then[, else])')
578 @templatefunc('ifcontains(search, thing, then[, else])')
570 def ifcontains(context, mapping, args):
579 def ifcontains(context, mapping, args):
571 """Conditionally execute based
580 """Conditionally execute based
572 on whether the item "search" is in "thing"."""
581 on whether the item "search" is in "thing"."""
573 if not (3 <= len(args) <= 4):
582 if not (3 <= len(args) <= 4):
574 # i18n: "ifcontains" is a keyword
583 # i18n: "ifcontains" is a keyword
575 raise error.ParseError(_("ifcontains expects three or four arguments"))
584 raise error.ParseError(_("ifcontains expects three or four arguments"))
576
585
577 item = evalstring(context, mapping, args[0])
586 item = evalstring(context, mapping, args[0])
578 items = evalfuncarg(context, mapping, args[1])
587 items = evalfuncarg(context, mapping, args[1])
579
588
580 if item in items:
589 if item in items:
581 yield args[2][0](context, mapping, args[2][1])
590 yield args[2][0](context, mapping, args[2][1])
582 elif len(args) == 4:
591 elif len(args) == 4:
583 yield args[3][0](context, mapping, args[3][1])
592 yield args[3][0](context, mapping, args[3][1])
584
593
585 @templatefunc('ifeq(expr1, expr2, then[, else])')
594 @templatefunc('ifeq(expr1, expr2, then[, else])')
586 def ifeq(context, mapping, args):
595 def ifeq(context, mapping, args):
587 """Conditionally execute based on
596 """Conditionally execute based on
588 whether 2 items are equivalent."""
597 whether 2 items are equivalent."""
589 if not (3 <= len(args) <= 4):
598 if not (3 <= len(args) <= 4):
590 # i18n: "ifeq" is a keyword
599 # i18n: "ifeq" is a keyword
591 raise error.ParseError(_("ifeq expects three or four arguments"))
600 raise error.ParseError(_("ifeq expects three or four arguments"))
592
601
593 test = evalstring(context, mapping, args[0])
602 test = evalstring(context, mapping, args[0])
594 match = evalstring(context, mapping, args[1])
603 match = evalstring(context, mapping, args[1])
595 if test == match:
604 if test == match:
596 yield args[2][0](context, mapping, args[2][1])
605 yield args[2][0](context, mapping, args[2][1])
597 elif len(args) == 4:
606 elif len(args) == 4:
598 yield args[3][0](context, mapping, args[3][1])
607 yield args[3][0](context, mapping, args[3][1])
599
608
600 @templatefunc('join(list, sep)')
609 @templatefunc('join(list, sep)')
601 def join(context, mapping, args):
610 def join(context, mapping, args):
602 """Join items in a list with a delimiter."""
611 """Join items in a list with a delimiter."""
603 if not (1 <= len(args) <= 2):
612 if not (1 <= len(args) <= 2):
604 # i18n: "join" is a keyword
613 # i18n: "join" is a keyword
605 raise error.ParseError(_("join expects one or two arguments"))
614 raise error.ParseError(_("join expects one or two arguments"))
606
615
607 joinset = args[0][0](context, mapping, args[0][1])
616 joinset = args[0][0](context, mapping, args[0][1])
608 if util.safehasattr(joinset, 'itermaps'):
617 if util.safehasattr(joinset, 'itermaps'):
609 jf = joinset.joinfmt
618 jf = joinset.joinfmt
610 joinset = [jf(x) for x in joinset.itermaps()]
619 joinset = [jf(x) for x in joinset.itermaps()]
611
620
612 joiner = " "
621 joiner = " "
613 if len(args) > 1:
622 if len(args) > 1:
614 joiner = evalstring(context, mapping, args[1])
623 joiner = evalstring(context, mapping, args[1])
615
624
616 first = True
625 first = True
617 for x in joinset:
626 for x in joinset:
618 if first:
627 if first:
619 first = False
628 first = False
620 else:
629 else:
621 yield joiner
630 yield joiner
622 yield x
631 yield x
623
632
624 @templatefunc('label(label, expr)')
633 @templatefunc('label(label, expr)')
625 def label(context, mapping, args):
634 def label(context, mapping, args):
626 """Apply a label to generated content. Content with
635 """Apply a label to generated content. Content with
627 a label applied can result in additional post-processing, such as
636 a label applied can result in additional post-processing, such as
628 automatic colorization."""
637 automatic colorization."""
629 if len(args) != 2:
638 if len(args) != 2:
630 # i18n: "label" is a keyword
639 # i18n: "label" is a keyword
631 raise error.ParseError(_("label expects two arguments"))
640 raise error.ParseError(_("label expects two arguments"))
632
641
633 ui = mapping['ui']
642 ui = mapping['ui']
634 thing = evalstring(context, mapping, args[1])
643 thing = evalstring(context, mapping, args[1])
635 # preserve unknown symbol as literal so effects like 'red', 'bold',
644 # preserve unknown symbol as literal so effects like 'red', 'bold',
636 # etc. don't need to be quoted
645 # etc. don't need to be quoted
637 label = evalstringliteral(context, mapping, args[0])
646 label = evalstringliteral(context, mapping, args[0])
638
647
639 return ui.label(thing, label)
648 return ui.label(thing, label)
640
649
641 @templatefunc('latesttag([pattern])')
650 @templatefunc('latesttag([pattern])')
642 def latesttag(context, mapping, args):
651 def latesttag(context, mapping, args):
643 """The global tags matching the given pattern on the
652 """The global tags matching the given pattern on the
644 most recent globally tagged ancestor of this changeset."""
653 most recent globally tagged ancestor of this changeset."""
645 if len(args) > 1:
654 if len(args) > 1:
646 # i18n: "latesttag" is a keyword
655 # i18n: "latesttag" is a keyword
647 raise error.ParseError(_("latesttag expects at most one argument"))
656 raise error.ParseError(_("latesttag expects at most one argument"))
648
657
649 pattern = None
658 pattern = None
650 if len(args) == 1:
659 if len(args) == 1:
651 pattern = evalstring(context, mapping, args[0])
660 pattern = evalstring(context, mapping, args[0])
652
661
653 return templatekw.showlatesttags(pattern, **mapping)
662 return templatekw.showlatesttags(pattern, **mapping)
654
663
655 @templatefunc('localdate(date[, tz])')
664 @templatefunc('localdate(date[, tz])')
656 def localdate(context, mapping, args):
665 def localdate(context, mapping, args):
657 """Converts a date to the specified timezone.
666 """Converts a date to the specified timezone.
658 The default is local date."""
667 The default is local date."""
659 if not (1 <= len(args) <= 2):
668 if not (1 <= len(args) <= 2):
660 # i18n: "localdate" is a keyword
669 # i18n: "localdate" is a keyword
661 raise error.ParseError(_("localdate expects one or two arguments"))
670 raise error.ParseError(_("localdate expects one or two arguments"))
662
671
663 date = evalfuncarg(context, mapping, args[0])
672 date = evalfuncarg(context, mapping, args[0])
664 try:
673 try:
665 date = util.parsedate(date)
674 date = util.parsedate(date)
666 except AttributeError: # not str nor date tuple
675 except AttributeError: # not str nor date tuple
667 # i18n: "localdate" is a keyword
676 # i18n: "localdate" is a keyword
668 raise error.ParseError(_("localdate expects a date information"))
677 raise error.ParseError(_("localdate expects a date information"))
669 if len(args) >= 2:
678 if len(args) >= 2:
670 tzoffset = None
679 tzoffset = None
671 tz = evalfuncarg(context, mapping, args[1])
680 tz = evalfuncarg(context, mapping, args[1])
672 if isinstance(tz, str):
681 if isinstance(tz, str):
673 tzoffset, remainder = util.parsetimezone(tz)
682 tzoffset, remainder = util.parsetimezone(tz)
674 if remainder:
683 if remainder:
675 tzoffset = None
684 tzoffset = None
676 if tzoffset is None:
685 if tzoffset is None:
677 try:
686 try:
678 tzoffset = int(tz)
687 tzoffset = int(tz)
679 except (TypeError, ValueError):
688 except (TypeError, ValueError):
680 # i18n: "localdate" is a keyword
689 # i18n: "localdate" is a keyword
681 raise error.ParseError(_("localdate expects a timezone"))
690 raise error.ParseError(_("localdate expects a timezone"))
682 else:
691 else:
683 tzoffset = util.makedate()[1]
692 tzoffset = util.makedate()[1]
684 return (date[0], tzoffset)
693 return (date[0], tzoffset)
685
694
686 @templatefunc('revset(query[, formatargs...])')
695 @templatefunc('revset(query[, formatargs...])')
687 def revset(context, mapping, args):
696 def revset(context, mapping, args):
688 """Execute a revision set query. See
697 """Execute a revision set query. See
689 :hg:`help revset`."""
698 :hg:`help revset`."""
690 if not len(args) > 0:
699 if not len(args) > 0:
691 # i18n: "revset" is a keyword
700 # i18n: "revset" is a keyword
692 raise error.ParseError(_("revset expects one or more arguments"))
701 raise error.ParseError(_("revset expects one or more arguments"))
693
702
694 raw = evalstring(context, mapping, args[0])
703 raw = evalstring(context, mapping, args[0])
695 ctx = mapping['ctx']
704 ctx = mapping['ctx']
696 repo = ctx.repo()
705 repo = ctx.repo()
697
706
698 def query(expr):
707 def query(expr):
699 m = revsetmod.match(repo.ui, expr)
708 m = revsetmod.match(repo.ui, expr)
700 return m(repo)
709 return m(repo)
701
710
702 if len(args) > 1:
711 if len(args) > 1:
703 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
712 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
704 revs = query(revsetmod.formatspec(raw, *formatargs))
713 revs = query(revsetmod.formatspec(raw, *formatargs))
705 revs = list(revs)
714 revs = list(revs)
706 else:
715 else:
707 revsetcache = mapping['cache'].setdefault("revsetcache", {})
716 revsetcache = mapping['cache'].setdefault("revsetcache", {})
708 if raw in revsetcache:
717 if raw in revsetcache:
709 revs = revsetcache[raw]
718 revs = revsetcache[raw]
710 else:
719 else:
711 revs = query(raw)
720 revs = query(raw)
712 revs = list(revs)
721 revs = list(revs)
713 revsetcache[raw] = revs
722 revsetcache[raw] = revs
714
723
715 return templatekw.showrevslist("revision", revs, **mapping)
724 return templatekw.showrevslist("revision", revs, **mapping)
716
725
717 @templatefunc('rstdoc(text, style)')
726 @templatefunc('rstdoc(text, style)')
718 def rstdoc(context, mapping, args):
727 def rstdoc(context, mapping, args):
719 """Format ReStructuredText."""
728 """Format ReStructuredText."""
720 if len(args) != 2:
729 if len(args) != 2:
721 # i18n: "rstdoc" is a keyword
730 # i18n: "rstdoc" is a keyword
722 raise error.ParseError(_("rstdoc expects two arguments"))
731 raise error.ParseError(_("rstdoc expects two arguments"))
723
732
724 text = evalstring(context, mapping, args[0])
733 text = evalstring(context, mapping, args[0])
725 style = evalstring(context, mapping, args[1])
734 style = evalstring(context, mapping, args[1])
726
735
727 return minirst.format(text, style=style, keep=['verbose'])
736 return minirst.format(text, style=style, keep=['verbose'])
728
737
729 @templatefunc('separate(sep, args)')
738 @templatefunc('separate(sep, args)')
730 def separate(context, mapping, args):
739 def separate(context, mapping, args):
731 """Add a separator between non-empty arguments."""
740 """Add a separator between non-empty arguments."""
732 if not args:
741 if not args:
733 # i18n: "separate" is a keyword
742 # i18n: "separate" is a keyword
734 raise error.ParseError(_("separate expects at least one argument"))
743 raise error.ParseError(_("separate expects at least one argument"))
735
744
736 sep = evalstring(context, mapping, args[0])
745 sep = evalstring(context, mapping, args[0])
737 first = True
746 first = True
738 for arg in args[1:]:
747 for arg in args[1:]:
739 argstr = evalstring(context, mapping, arg)
748 argstr = evalstring(context, mapping, arg)
740 if not argstr:
749 if not argstr:
741 continue
750 continue
742 if first:
751 if first:
743 first = False
752 first = False
744 else:
753 else:
745 yield sep
754 yield sep
746 yield argstr
755 yield argstr
747
756
748 @templatefunc('shortest(node, minlength=4)')
757 @templatefunc('shortest(node, minlength=4)')
749 def shortest(context, mapping, args):
758 def shortest(context, mapping, args):
750 """Obtain the shortest representation of
759 """Obtain the shortest representation of
751 a node."""
760 a node."""
752 if not (1 <= len(args) <= 2):
761 if not (1 <= len(args) <= 2):
753 # i18n: "shortest" is a keyword
762 # i18n: "shortest" is a keyword
754 raise error.ParseError(_("shortest() expects one or two arguments"))
763 raise error.ParseError(_("shortest() expects one or two arguments"))
755
764
756 node = evalstring(context, mapping, args[0])
765 node = evalstring(context, mapping, args[0])
757
766
758 minlength = 4
767 minlength = 4
759 if len(args) > 1:
768 if len(args) > 1:
760 minlength = evalinteger(context, mapping, args[1],
769 minlength = evalinteger(context, mapping, args[1],
761 # i18n: "shortest" is a keyword
770 # i18n: "shortest" is a keyword
762 _("shortest() expects an integer minlength"))
771 _("shortest() expects an integer minlength"))
763
772
764 cl = mapping['ctx']._repo.changelog
773 cl = mapping['ctx']._repo.changelog
765 def isvalid(test):
774 def isvalid(test):
766 try:
775 try:
767 try:
776 try:
768 cl.index.partialmatch(test)
777 cl.index.partialmatch(test)
769 except AttributeError:
778 except AttributeError:
770 # Pure mercurial doesn't support partialmatch on the index.
779 # Pure mercurial doesn't support partialmatch on the index.
771 # Fallback to the slow way.
780 # Fallback to the slow way.
772 if cl._partialmatch(test) is None:
781 if cl._partialmatch(test) is None:
773 return False
782 return False
774
783
775 try:
784 try:
776 i = int(test)
785 i = int(test)
777 # if we are a pure int, then starting with zero will not be
786 # if we are a pure int, then starting with zero will not be
778 # confused as a rev; or, obviously, if the int is larger than
787 # confused as a rev; or, obviously, if the int is larger than
779 # the value of the tip rev
788 # the value of the tip rev
780 if test[0] == '0' or i > len(cl):
789 if test[0] == '0' or i > len(cl):
781 return True
790 return True
782 return False
791 return False
783 except ValueError:
792 except ValueError:
784 return True
793 return True
785 except error.RevlogError:
794 except error.RevlogError:
786 return False
795 return False
787
796
788 shortest = node
797 shortest = node
789 startlength = max(6, minlength)
798 startlength = max(6, minlength)
790 length = startlength
799 length = startlength
791 while True:
800 while True:
792 test = node[:length]
801 test = node[:length]
793 if isvalid(test):
802 if isvalid(test):
794 shortest = test
803 shortest = test
795 if length == minlength or length > startlength:
804 if length == minlength or length > startlength:
796 return shortest
805 return shortest
797 length -= 1
806 length -= 1
798 else:
807 else:
799 length += 1
808 length += 1
800 if len(shortest) <= length:
809 if len(shortest) <= length:
801 return shortest
810 return shortest
802
811
803 @templatefunc('strip(text[, chars])')
812 @templatefunc('strip(text[, chars])')
804 def strip(context, mapping, args):
813 def strip(context, mapping, args):
805 """Strip characters from a string. By default,
814 """Strip characters from a string. By default,
806 strips all leading and trailing whitespace."""
815 strips all leading and trailing whitespace."""
807 if not (1 <= len(args) <= 2):
816 if not (1 <= len(args) <= 2):
808 # i18n: "strip" is a keyword
817 # i18n: "strip" is a keyword
809 raise error.ParseError(_("strip expects one or two arguments"))
818 raise error.ParseError(_("strip expects one or two arguments"))
810
819
811 text = evalstring(context, mapping, args[0])
820 text = evalstring(context, mapping, args[0])
812 if len(args) == 2:
821 if len(args) == 2:
813 chars = evalstring(context, mapping, args[1])
822 chars = evalstring(context, mapping, args[1])
814 return text.strip(chars)
823 return text.strip(chars)
815 return text.strip()
824 return text.strip()
816
825
817 @templatefunc('sub(pattern, replacement, expression)')
826 @templatefunc('sub(pattern, replacement, expression)')
818 def sub(context, mapping, args):
827 def sub(context, mapping, args):
819 """Perform text substitution
828 """Perform text substitution
820 using regular expressions."""
829 using regular expressions."""
821 if len(args) != 3:
830 if len(args) != 3:
822 # i18n: "sub" is a keyword
831 # i18n: "sub" is a keyword
823 raise error.ParseError(_("sub expects three arguments"))
832 raise error.ParseError(_("sub expects three arguments"))
824
833
825 pat = evalstring(context, mapping, args[0])
834 pat = evalstring(context, mapping, args[0])
826 rpl = evalstring(context, mapping, args[1])
835 rpl = evalstring(context, mapping, args[1])
827 src = evalstring(context, mapping, args[2])
836 src = evalstring(context, mapping, args[2])
828 try:
837 try:
829 patre = re.compile(pat)
838 patre = re.compile(pat)
830 except re.error:
839 except re.error:
831 # i18n: "sub" is a keyword
840 # i18n: "sub" is a keyword
832 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
841 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
833 try:
842 try:
834 yield patre.sub(rpl, src)
843 yield patre.sub(rpl, src)
835 except re.error:
844 except re.error:
836 # i18n: "sub" is a keyword
845 # i18n: "sub" is a keyword
837 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
846 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
838
847
839 @templatefunc('startswith(pattern, text)')
848 @templatefunc('startswith(pattern, text)')
840 def startswith(context, mapping, args):
849 def startswith(context, mapping, args):
841 """Returns the value from the "text" argument
850 """Returns the value from the "text" argument
842 if it begins with the content from the "pattern" argument."""
851 if it begins with the content from the "pattern" argument."""
843 if len(args) != 2:
852 if len(args) != 2:
844 # i18n: "startswith" is a keyword
853 # i18n: "startswith" is a keyword
845 raise error.ParseError(_("startswith expects two arguments"))
854 raise error.ParseError(_("startswith expects two arguments"))
846
855
847 patn = evalstring(context, mapping, args[0])
856 patn = evalstring(context, mapping, args[0])
848 text = evalstring(context, mapping, args[1])
857 text = evalstring(context, mapping, args[1])
849 if text.startswith(patn):
858 if text.startswith(patn):
850 return text
859 return text
851 return ''
860 return ''
852
861
853 @templatefunc('word(number, text[, separator])')
862 @templatefunc('word(number, text[, separator])')
854 def word(context, mapping, args):
863 def word(context, mapping, args):
855 """Return the nth word from a string."""
864 """Return the nth word from a string."""
856 if not (2 <= len(args) <= 3):
865 if not (2 <= len(args) <= 3):
857 # i18n: "word" is a keyword
866 # i18n: "word" is a keyword
858 raise error.ParseError(_("word expects two or three arguments, got %d")
867 raise error.ParseError(_("word expects two or three arguments, got %d")
859 % len(args))
868 % len(args))
860
869
861 num = evalinteger(context, mapping, args[0],
870 num = evalinteger(context, mapping, args[0],
862 # i18n: "word" is a keyword
871 # i18n: "word" is a keyword
863 _("word expects an integer index"))
872 _("word expects an integer index"))
864 text = evalstring(context, mapping, args[1])
873 text = evalstring(context, mapping, args[1])
865 if len(args) == 3:
874 if len(args) == 3:
866 splitter = evalstring(context, mapping, args[2])
875 splitter = evalstring(context, mapping, args[2])
867 else:
876 else:
868 splitter = None
877 splitter = None
869
878
870 tokens = text.split(splitter)
879 tokens = text.split(splitter)
871 if num >= len(tokens) or num < -len(tokens):
880 if num >= len(tokens) or num < -len(tokens):
872 return ''
881 return ''
873 else:
882 else:
874 return tokens[num]
883 return tokens[num]
875
884
876 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
885 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
877 exprmethods = {
886 exprmethods = {
878 "integer": lambda e, c: (runinteger, e[1]),
887 "integer": lambda e, c: (runinteger, e[1]),
879 "string": lambda e, c: (runstring, e[1]),
888 "string": lambda e, c: (runstring, e[1]),
880 "symbol": lambda e, c: (runsymbol, e[1]),
889 "symbol": lambda e, c: (runsymbol, e[1]),
881 "template": buildtemplate,
890 "template": buildtemplate,
882 "group": lambda e, c: compileexp(e[1], c, exprmethods),
891 "group": lambda e, c: compileexp(e[1], c, exprmethods),
883 # ".": buildmember,
892 # ".": buildmember,
884 "|": buildfilter,
893 "|": buildfilter,
885 "%": buildmap,
894 "%": buildmap,
886 "func": buildfunc,
895 "func": buildfunc,
887 }
896 }
888
897
889 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
898 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
890 methods = exprmethods.copy()
899 methods = exprmethods.copy()
891 methods["integer"] = exprmethods["symbol"] # '{1}' as variable
900 methods["integer"] = exprmethods["symbol"] # '{1}' as variable
892
901
893 class _aliasrules(parser.basealiasrules):
902 class _aliasrules(parser.basealiasrules):
894 """Parsing and expansion rule set of template aliases"""
903 """Parsing and expansion rule set of template aliases"""
895 _section = _('template alias')
904 _section = _('template alias')
896 _parse = staticmethod(_parseexpr)
905 _parse = staticmethod(_parseexpr)
897
906
898 @staticmethod
907 @staticmethod
899 def _trygetfunc(tree):
908 def _trygetfunc(tree):
900 """Return (name, args) if tree is func(...) or ...|filter; otherwise
909 """Return (name, args) if tree is func(...) or ...|filter; otherwise
901 None"""
910 None"""
902 if tree[0] == 'func' and tree[1][0] == 'symbol':
911 if tree[0] == 'func' and tree[1][0] == 'symbol':
903 return tree[1][1], getlist(tree[2])
912 return tree[1][1], getlist(tree[2])
904 if tree[0] == '|' and tree[2][0] == 'symbol':
913 if tree[0] == '|' and tree[2][0] == 'symbol':
905 return tree[2][1], [tree[1]]
914 return tree[2][1], [tree[1]]
906
915
907 def expandaliases(tree, aliases):
916 def expandaliases(tree, aliases):
908 """Return new tree of aliases are expanded"""
917 """Return new tree of aliases are expanded"""
909 aliasmap = _aliasrules.buildmap(aliases)
918 aliasmap = _aliasrules.buildmap(aliases)
910 return _aliasrules.expand(aliasmap, tree)
919 return _aliasrules.expand(aliasmap, tree)
911
920
912 # template engine
921 # template engine
913
922
914 stringify = templatefilters.stringify
923 stringify = templatefilters.stringify
915
924
916 def _flatten(thing):
925 def _flatten(thing):
917 '''yield a single stream from a possibly nested set of iterators'''
926 '''yield a single stream from a possibly nested set of iterators'''
918 if isinstance(thing, str):
927 if isinstance(thing, str):
919 yield thing
928 yield thing
920 elif thing is None:
929 elif thing is None:
921 pass
930 pass
922 elif not util.safehasattr(thing, '__iter__'):
931 elif not util.safehasattr(thing, '__iter__'):
923 yield str(thing)
932 yield str(thing)
924 else:
933 else:
925 for i in thing:
934 for i in thing:
926 if isinstance(i, str):
935 if isinstance(i, str):
927 yield i
936 yield i
928 elif i is None:
937 elif i is None:
929 pass
938 pass
930 elif not util.safehasattr(i, '__iter__'):
939 elif not util.safehasattr(i, '__iter__'):
931 yield str(i)
940 yield str(i)
932 else:
941 else:
933 for j in _flatten(i):
942 for j in _flatten(i):
934 yield j
943 yield j
935
944
936 def unquotestring(s):
945 def unquotestring(s):
937 '''unwrap quotes if any; otherwise returns unmodified string'''
946 '''unwrap quotes if any; otherwise returns unmodified string'''
938 if len(s) < 2 or s[0] not in "'\"" or s[0] != s[-1]:
947 if len(s) < 2 or s[0] not in "'\"" or s[0] != s[-1]:
939 return s
948 return s
940 return s[1:-1]
949 return s[1:-1]
941
950
942 class engine(object):
951 class engine(object):
943 '''template expansion engine.
952 '''template expansion engine.
944
953
945 template expansion works like this. a map file contains key=value
954 template expansion works like this. a map file contains key=value
946 pairs. if value is quoted, it is treated as string. otherwise, it
955 pairs. if value is quoted, it is treated as string. otherwise, it
947 is treated as name of template file.
956 is treated as name of template file.
948
957
949 templater is asked to expand a key in map. it looks up key, and
958 templater is asked to expand a key in map. it looks up key, and
950 looks for strings like this: {foo}. it expands {foo} by looking up
959 looks for strings like this: {foo}. it expands {foo} by looking up
951 foo in map, and substituting it. expansion is recursive: it stops
960 foo in map, and substituting it. expansion is recursive: it stops
952 when there is no more {foo} to replace.
961 when there is no more {foo} to replace.
953
962
954 expansion also allows formatting and filtering.
963 expansion also allows formatting and filtering.
955
964
956 format uses key to expand each item in list. syntax is
965 format uses key to expand each item in list. syntax is
957 {key%format}.
966 {key%format}.
958
967
959 filter uses function to transform value. syntax is
968 filter uses function to transform value. syntax is
960 {key|filter1|filter2|...}.'''
969 {key|filter1|filter2|...}.'''
961
970
962 def __init__(self, loader, filters=None, defaults=None, aliases=()):
971 def __init__(self, loader, filters=None, defaults=None, aliases=()):
963 self._loader = loader
972 self._loader = loader
964 if filters is None:
973 if filters is None:
965 filters = {}
974 filters = {}
966 self._filters = filters
975 self._filters = filters
967 if defaults is None:
976 if defaults is None:
968 defaults = {}
977 defaults = {}
969 self._defaults = defaults
978 self._defaults = defaults
970 self._aliasmap = _aliasrules.buildmap(aliases)
979 self._aliasmap = _aliasrules.buildmap(aliases)
971 self._cache = {} # key: (func, data)
980 self._cache = {} # key: (func, data)
972
981
973 def _load(self, t):
982 def _load(self, t):
974 '''load, parse, and cache a template'''
983 '''load, parse, and cache a template'''
975 if t not in self._cache:
984 if t not in self._cache:
976 # put poison to cut recursion while compiling 't'
985 # put poison to cut recursion while compiling 't'
977 self._cache[t] = (_runrecursivesymbol, t)
986 self._cache[t] = (_runrecursivesymbol, t)
978 try:
987 try:
979 x = parse(self._loader(t))
988 x = parse(self._loader(t))
980 if self._aliasmap:
989 if self._aliasmap:
981 x = _aliasrules.expand(self._aliasmap, x)
990 x = _aliasrules.expand(self._aliasmap, x)
982 self._cache[t] = compileexp(x, self, methods)
991 self._cache[t] = compileexp(x, self, methods)
983 except: # re-raises
992 except: # re-raises
984 del self._cache[t]
993 del self._cache[t]
985 raise
994 raise
986 return self._cache[t]
995 return self._cache[t]
987
996
988 def process(self, t, mapping):
997 def process(self, t, mapping):
989 '''Perform expansion. t is name of map element to expand.
998 '''Perform expansion. t is name of map element to expand.
990 mapping contains added elements for use during expansion. Is a
999 mapping contains added elements for use during expansion. Is a
991 generator.'''
1000 generator.'''
992 func, data = self._load(t)
1001 func, data = self._load(t)
993 return _flatten(func(self, mapping, data))
1002 return _flatten(func(self, mapping, data))
994
1003
995 engines = {'default': engine}
1004 engines = {'default': engine}
996
1005
997 def stylelist():
1006 def stylelist():
998 paths = templatepaths()
1007 paths = templatepaths()
999 if not paths:
1008 if not paths:
1000 return _('no templates found, try `hg debuginstall` for more info')
1009 return _('no templates found, try `hg debuginstall` for more info')
1001 dirlist = os.listdir(paths[0])
1010 dirlist = os.listdir(paths[0])
1002 stylelist = []
1011 stylelist = []
1003 for file in dirlist:
1012 for file in dirlist:
1004 split = file.split(".")
1013 split = file.split(".")
1005 if split[-1] in ('orig', 'rej'):
1014 if split[-1] in ('orig', 'rej'):
1006 continue
1015 continue
1007 if split[0] == "map-cmdline":
1016 if split[0] == "map-cmdline":
1008 stylelist.append(split[1])
1017 stylelist.append(split[1])
1009 return ", ".join(sorted(stylelist))
1018 return ", ".join(sorted(stylelist))
1010
1019
1011 def _readmapfile(mapfile):
1020 def _readmapfile(mapfile):
1012 """Load template elements from the given map file"""
1021 """Load template elements from the given map file"""
1013 if not os.path.exists(mapfile):
1022 if not os.path.exists(mapfile):
1014 raise error.Abort(_("style '%s' not found") % mapfile,
1023 raise error.Abort(_("style '%s' not found") % mapfile,
1015 hint=_("available styles: %s") % stylelist())
1024 hint=_("available styles: %s") % stylelist())
1016
1025
1017 base = os.path.dirname(mapfile)
1026 base = os.path.dirname(mapfile)
1018 conf = config.config(includepaths=templatepaths())
1027 conf = config.config(includepaths=templatepaths())
1019 conf.read(mapfile)
1028 conf.read(mapfile)
1020
1029
1021 cache = {}
1030 cache = {}
1022 tmap = {}
1031 tmap = {}
1023 for key, val in conf[''].items():
1032 for key, val in conf[''].items():
1024 if not val:
1033 if not val:
1025 raise error.ParseError(_('missing value'), conf.source('', key))
1034 raise error.ParseError(_('missing value'), conf.source('', key))
1026 if val[0] in "'\"":
1035 if val[0] in "'\"":
1027 if val[0] != val[-1]:
1036 if val[0] != val[-1]:
1028 raise error.ParseError(_('unmatched quotes'),
1037 raise error.ParseError(_('unmatched quotes'),
1029 conf.source('', key))
1038 conf.source('', key))
1030 cache[key] = unquotestring(val)
1039 cache[key] = unquotestring(val)
1031 elif key == "__base__":
1040 elif key == "__base__":
1032 # treat as a pointer to a base class for this style
1041 # treat as a pointer to a base class for this style
1033 path = util.normpath(os.path.join(base, val))
1042 path = util.normpath(os.path.join(base, val))
1034 bcache, btmap = _readmapfile(path)
1043 bcache, btmap = _readmapfile(path)
1035 for k in bcache:
1044 for k in bcache:
1036 if k not in cache:
1045 if k not in cache:
1037 cache[k] = bcache[k]
1046 cache[k] = bcache[k]
1038 for k in btmap:
1047 for k in btmap:
1039 if k not in tmap:
1048 if k not in tmap:
1040 tmap[k] = btmap[k]
1049 tmap[k] = btmap[k]
1041 else:
1050 else:
1042 val = 'default', val
1051 val = 'default', val
1043 if ':' in val[1]:
1052 if ':' in val[1]:
1044 val = val[1].split(':', 1)
1053 val = val[1].split(':', 1)
1045 tmap[key] = val[0], os.path.join(base, val[1])
1054 tmap[key] = val[0], os.path.join(base, val[1])
1046 return cache, tmap
1055 return cache, tmap
1047
1056
1048 class TemplateNotFound(error.Abort):
1057 class TemplateNotFound(error.Abort):
1049 pass
1058 pass
1050
1059
1051 class templater(object):
1060 class templater(object):
1052
1061
1053 def __init__(self, filters=None, defaults=None, cache=None, aliases=(),
1062 def __init__(self, filters=None, defaults=None, cache=None, aliases=(),
1054 minchunk=1024, maxchunk=65536):
1063 minchunk=1024, maxchunk=65536):
1055 '''set up template engine.
1064 '''set up template engine.
1056 filters is dict of functions. each transforms a value into another.
1065 filters is dict of functions. each transforms a value into another.
1057 defaults is dict of default map definitions.
1066 defaults is dict of default map definitions.
1058 aliases is list of alias (name, replacement) pairs.
1067 aliases is list of alias (name, replacement) pairs.
1059 '''
1068 '''
1060 if filters is None:
1069 if filters is None:
1061 filters = {}
1070 filters = {}
1062 if defaults is None:
1071 if defaults is None:
1063 defaults = {}
1072 defaults = {}
1064 if cache is None:
1073 if cache is None:
1065 cache = {}
1074 cache = {}
1066 self.cache = cache.copy()
1075 self.cache = cache.copy()
1067 self.map = {}
1076 self.map = {}
1068 self.filters = templatefilters.filters.copy()
1077 self.filters = templatefilters.filters.copy()
1069 self.filters.update(filters)
1078 self.filters.update(filters)
1070 self.defaults = defaults
1079 self.defaults = defaults
1071 self._aliases = aliases
1080 self._aliases = aliases
1072 self.minchunk, self.maxchunk = minchunk, maxchunk
1081 self.minchunk, self.maxchunk = minchunk, maxchunk
1073 self.ecache = {}
1082 self.ecache = {}
1074
1083
1075 @classmethod
1084 @classmethod
1076 def frommapfile(cls, mapfile, filters=None, defaults=None, cache=None,
1085 def frommapfile(cls, mapfile, filters=None, defaults=None, cache=None,
1077 minchunk=1024, maxchunk=65536):
1086 minchunk=1024, maxchunk=65536):
1078 """Create templater from the specified map file"""
1087 """Create templater from the specified map file"""
1079 t = cls(filters, defaults, cache, [], minchunk, maxchunk)
1088 t = cls(filters, defaults, cache, [], minchunk, maxchunk)
1080 cache, tmap = _readmapfile(mapfile)
1089 cache, tmap = _readmapfile(mapfile)
1081 t.cache.update(cache)
1090 t.cache.update(cache)
1082 t.map = tmap
1091 t.map = tmap
1083 return t
1092 return t
1084
1093
1085 def __contains__(self, key):
1094 def __contains__(self, key):
1086 return key in self.cache or key in self.map
1095 return key in self.cache or key in self.map
1087
1096
1088 def load(self, t):
1097 def load(self, t):
1089 '''Get the template for the given template name. Use a local cache.'''
1098 '''Get the template for the given template name. Use a local cache.'''
1090 if t not in self.cache:
1099 if t not in self.cache:
1091 try:
1100 try:
1092 self.cache[t] = util.readfile(self.map[t][1])
1101 self.cache[t] = util.readfile(self.map[t][1])
1093 except KeyError as inst:
1102 except KeyError as inst:
1094 raise TemplateNotFound(_('"%s" not in template map') %
1103 raise TemplateNotFound(_('"%s" not in template map') %
1095 inst.args[0])
1104 inst.args[0])
1096 except IOError as inst:
1105 except IOError as inst:
1097 raise IOError(inst.args[0], _('template file %s: %s') %
1106 raise IOError(inst.args[0], _('template file %s: %s') %
1098 (self.map[t][1], inst.args[1]))
1107 (self.map[t][1], inst.args[1]))
1099 return self.cache[t]
1108 return self.cache[t]
1100
1109
1101 def __call__(self, t, **mapping):
1110 def __call__(self, t, **mapping):
1102 ttype = t in self.map and self.map[t][0] or 'default'
1111 ttype = t in self.map and self.map[t][0] or 'default'
1103 if ttype not in self.ecache:
1112 if ttype not in self.ecache:
1104 try:
1113 try:
1105 ecls = engines[ttype]
1114 ecls = engines[ttype]
1106 except KeyError:
1115 except KeyError:
1107 raise error.Abort(_('invalid template engine: %s') % ttype)
1116 raise error.Abort(_('invalid template engine: %s') % ttype)
1108 self.ecache[ttype] = ecls(self.load, self.filters, self.defaults,
1117 self.ecache[ttype] = ecls(self.load, self.filters, self.defaults,
1109 self._aliases)
1118 self._aliases)
1110 proc = self.ecache[ttype]
1119 proc = self.ecache[ttype]
1111
1120
1112 stream = proc.process(t, mapping)
1121 stream = proc.process(t, mapping)
1113 if self.minchunk:
1122 if self.minchunk:
1114 stream = util.increasingchunks(stream, min=self.minchunk,
1123 stream = util.increasingchunks(stream, min=self.minchunk,
1115 max=self.maxchunk)
1124 max=self.maxchunk)
1116 return stream
1125 return stream
1117
1126
1118 def templatepaths():
1127 def templatepaths():
1119 '''return locations used for template files.'''
1128 '''return locations used for template files.'''
1120 pathsrel = ['templates']
1129 pathsrel = ['templates']
1121 paths = [os.path.normpath(os.path.join(util.datapath, f))
1130 paths = [os.path.normpath(os.path.join(util.datapath, f))
1122 for f in pathsrel]
1131 for f in pathsrel]
1123 return [p for p in paths if os.path.isdir(p)]
1132 return [p for p in paths if os.path.isdir(p)]
1124
1133
1125 def templatepath(name):
1134 def templatepath(name):
1126 '''return location of template file. returns None if not found.'''
1135 '''return location of template file. returns None if not found.'''
1127 for p in templatepaths():
1136 for p in templatepaths():
1128 f = os.path.join(p, name)
1137 f = os.path.join(p, name)
1129 if os.path.exists(f):
1138 if os.path.exists(f):
1130 return f
1139 return f
1131 return None
1140 return None
1132
1141
1133 def stylemap(styles, paths=None):
1142 def stylemap(styles, paths=None):
1134 """Return path to mapfile for a given style.
1143 """Return path to mapfile for a given style.
1135
1144
1136 Searches mapfile in the following locations:
1145 Searches mapfile in the following locations:
1137 1. templatepath/style/map
1146 1. templatepath/style/map
1138 2. templatepath/map-style
1147 2. templatepath/map-style
1139 3. templatepath/map
1148 3. templatepath/map
1140 """
1149 """
1141
1150
1142 if paths is None:
1151 if paths is None:
1143 paths = templatepaths()
1152 paths = templatepaths()
1144 elif isinstance(paths, str):
1153 elif isinstance(paths, str):
1145 paths = [paths]
1154 paths = [paths]
1146
1155
1147 if isinstance(styles, str):
1156 if isinstance(styles, str):
1148 styles = [styles]
1157 styles = [styles]
1149
1158
1150 for style in styles:
1159 for style in styles:
1151 # only plain name is allowed to honor template paths
1160 # only plain name is allowed to honor template paths
1152 if (not style
1161 if (not style
1153 or style in (os.curdir, os.pardir)
1162 or style in (os.curdir, os.pardir)
1154 or os.sep in style
1163 or os.sep in style
1155 or os.altsep and os.altsep in style):
1164 or os.altsep and os.altsep in style):
1156 continue
1165 continue
1157 locations = [os.path.join(style, 'map'), 'map-' + style]
1166 locations = [os.path.join(style, 'map'), 'map-' + style]
1158 locations.append('map')
1167 locations.append('map')
1159
1168
1160 for path in paths:
1169 for path in paths:
1161 for location in locations:
1170 for location in locations:
1162 mapfile = os.path.join(path, location)
1171 mapfile = os.path.join(path, location)
1163 if os.path.isfile(mapfile):
1172 if os.path.isfile(mapfile):
1164 return style, mapfile
1173 return style, mapfile
1165
1174
1166 raise RuntimeError("No hgweb templates found in %r" % paths)
1175 raise RuntimeError("No hgweb templates found in %r" % paths)
1167
1176
1168 def loadfunction(ui, extname, registrarobj):
1177 def loadfunction(ui, extname, registrarobj):
1169 """Load template function from specified registrarobj
1178 """Load template function from specified registrarobj
1170 """
1179 """
1171 for name, func in registrarobj._table.iteritems():
1180 for name, func in registrarobj._table.iteritems():
1172 funcs[name] = func
1181 funcs[name] = func
1173
1182
1174 # tell hggettext to extract docstrings from these functions:
1183 # tell hggettext to extract docstrings from these functions:
1175 i18nfunctions = funcs.values()
1184 i18nfunctions = funcs.values()
@@ -1,714 +1,716 b''
1 $ hg init a
1 $ hg init a
2 $ cd a
2 $ cd a
3 $ echo 'root' >root
3 $ echo 'root' >root
4 $ hg add root
4 $ hg add root
5 $ hg commit -d '0 0' -m "Adding root node"
5 $ hg commit -d '0 0' -m "Adding root node"
6
6
7 $ echo 'a' >a
7 $ echo 'a' >a
8 $ hg add a
8 $ hg add a
9 $ hg branch a
9 $ hg branch a
10 marked working directory as branch a
10 marked working directory as branch a
11 (branches are permanent and global, did you want a bookmark?)
11 (branches are permanent and global, did you want a bookmark?)
12 $ hg commit -d '1 0' -m "Adding a branch"
12 $ hg commit -d '1 0' -m "Adding a branch"
13
13
14 $ hg branch q
14 $ hg branch q
15 marked working directory as branch q
15 marked working directory as branch q
16 $ echo 'aa' >a
16 $ echo 'aa' >a
17 $ hg branch -C
17 $ hg branch -C
18 reset working directory to branch a
18 reset working directory to branch a
19 $ hg commit -d '2 0' -m "Adding to a branch"
19 $ hg commit -d '2 0' -m "Adding to a branch"
20
20
21 $ hg update -C 0
21 $ hg update -C 0
22 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
22 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
23 $ echo 'b' >b
23 $ echo 'b' >b
24 $ hg add b
24 $ hg add b
25 $ hg branch b
25 $ hg branch b
26 marked working directory as branch b
26 marked working directory as branch b
27 $ hg commit -d '2 0' -m "Adding b branch"
27 $ hg commit -d '2 0' -m "Adding b branch"
28
28
29 $ echo 'bh1' >bh1
29 $ echo 'bh1' >bh1
30 $ hg add bh1
30 $ hg add bh1
31 $ hg commit -d '3 0' -m "Adding b branch head 1"
31 $ hg commit -d '3 0' -m "Adding b branch head 1"
32
32
33 $ hg update -C 2
33 $ hg update -C 2
34 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
34 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
35 $ echo 'bh2' >bh2
35 $ echo 'bh2' >bh2
36 $ hg add bh2
36 $ hg add bh2
37 $ hg commit -d '4 0' -m "Adding b branch head 2"
37 $ hg commit -d '4 0' -m "Adding b branch head 2"
38
38
39 $ echo 'c' >c
39 $ echo 'c' >c
40 $ hg add c
40 $ hg add c
41 $ hg branch c
41 $ hg branch c
42 marked working directory as branch c
42 marked working directory as branch c
43 $ hg commit -d '5 0' -m "Adding c branch"
43 $ hg commit -d '5 0' -m "Adding c branch"
44
44
45 reserved names
45 reserved names
46
46
47 $ hg branch tip
47 $ hg branch tip
48 abort: the name 'tip' is reserved
48 abort: the name 'tip' is reserved
49 [255]
49 [255]
50 $ hg branch null
50 $ hg branch null
51 abort: the name 'null' is reserved
51 abort: the name 'null' is reserved
52 [255]
52 [255]
53 $ hg branch .
53 $ hg branch .
54 abort: the name '.' is reserved
54 abort: the name '.' is reserved
55 [255]
55 [255]
56
56
57 invalid characters
57 invalid characters
58
58
59 $ hg branch 'foo:bar'
59 $ hg branch 'foo:bar'
60 abort: ':' cannot be used in a name
60 abort: ':' cannot be used in a name
61 [255]
61 [255]
62
62
63 $ hg branch 'foo
63 $ hg branch 'foo
64 > bar'
64 > bar'
65 abort: '\n' cannot be used in a name
65 abort: '\n' cannot be used in a name
66 [255]
66 [255]
67
67
68 trailing or leading spaces should be stripped before testing duplicates
68 trailing or leading spaces should be stripped before testing duplicates
69
69
70 $ hg branch 'b '
70 $ hg branch 'b '
71 abort: a branch of the same name already exists
71 abort: a branch of the same name already exists
72 (use 'hg update' to switch to it)
72 (use 'hg update' to switch to it)
73 [255]
73 [255]
74
74
75 $ hg branch ' b'
75 $ hg branch ' b'
76 abort: a branch of the same name already exists
76 abort: a branch of the same name already exists
77 (use 'hg update' to switch to it)
77 (use 'hg update' to switch to it)
78 [255]
78 [255]
79
79
80 verify update will accept invalid legacy branch names
80 verify update will accept invalid legacy branch names
81
81
82 $ hg init test-invalid-branch-name
82 $ hg init test-invalid-branch-name
83 $ cd test-invalid-branch-name
83 $ cd test-invalid-branch-name
84 $ hg pull -u "$TESTDIR"/bundles/test-invalid-branch-name.hg
84 $ hg pull -u "$TESTDIR"/bundles/test-invalid-branch-name.hg
85 pulling from *test-invalid-branch-name.hg (glob)
85 pulling from *test-invalid-branch-name.hg (glob)
86 requesting all changes
86 requesting all changes
87 adding changesets
87 adding changesets
88 adding manifests
88 adding manifests
89 adding file changes
89 adding file changes
90 added 3 changesets with 3 changes to 2 files
90 added 3 changesets with 3 changes to 2 files
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92
92
93 $ hg update '"colon:test"'
93 $ hg update '"colon:test"'
94 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 $ cd ..
95 $ cd ..
96
96
97 $ echo 'd' >d
97 $ echo 'd' >d
98 $ hg add d
98 $ hg add d
99 $ hg branch 'a branch name much longer than the default justification used by branches'
99 $ hg branch 'a branch name much longer than the default justification used by branches'
100 marked working directory as branch a branch name much longer than the default justification used by branches
100 marked working directory as branch a branch name much longer than the default justification used by branches
101 $ hg commit -d '6 0' -m "Adding d branch"
101 $ hg commit -d '6 0' -m "Adding d branch"
102
102
103 $ hg branches
103 $ hg branches
104 a branch name much longer than the default justification used by branches 7:10ff5895aa57
104 a branch name much longer than the default justification used by branches 7:10ff5895aa57
105 b 4:aee39cd168d0
105 b 4:aee39cd168d0
106 c 6:589736a22561 (inactive)
106 c 6:589736a22561 (inactive)
107 a 5:d8cbc61dbaa6 (inactive)
107 a 5:d8cbc61dbaa6 (inactive)
108 default 0:19709c5a4e75 (inactive)
108 default 0:19709c5a4e75 (inactive)
109
109
110 -------
110 -------
111
111
112 $ hg branches -a
112 $ hg branches -a
113 a branch name much longer than the default justification used by branches 7:10ff5895aa57
113 a branch name much longer than the default justification used by branches 7:10ff5895aa57
114 b 4:aee39cd168d0
114 b 4:aee39cd168d0
115
115
116 --- Branch a
116 --- Branch a
117
117
118 $ hg log -b a
118 $ hg log -b a
119 changeset: 5:d8cbc61dbaa6
119 changeset: 5:d8cbc61dbaa6
120 branch: a
120 branch: a
121 parent: 2:881fe2b92ad0
121 parent: 2:881fe2b92ad0
122 user: test
122 user: test
123 date: Thu Jan 01 00:00:04 1970 +0000
123 date: Thu Jan 01 00:00:04 1970 +0000
124 summary: Adding b branch head 2
124 summary: Adding b branch head 2
125
125
126 changeset: 2:881fe2b92ad0
126 changeset: 2:881fe2b92ad0
127 branch: a
127 branch: a
128 user: test
128 user: test
129 date: Thu Jan 01 00:00:02 1970 +0000
129 date: Thu Jan 01 00:00:02 1970 +0000
130 summary: Adding to a branch
130 summary: Adding to a branch
131
131
132 changeset: 1:dd6b440dd85a
132 changeset: 1:dd6b440dd85a
133 branch: a
133 branch: a
134 user: test
134 user: test
135 date: Thu Jan 01 00:00:01 1970 +0000
135 date: Thu Jan 01 00:00:01 1970 +0000
136 summary: Adding a branch
136 summary: Adding a branch
137
137
138
138
139 ---- Branch b
139 ---- Branch b
140
140
141 $ hg log -b b
141 $ hg log -b b
142 changeset: 4:aee39cd168d0
142 changeset: 4:aee39cd168d0
143 branch: b
143 branch: b
144 user: test
144 user: test
145 date: Thu Jan 01 00:00:03 1970 +0000
145 date: Thu Jan 01 00:00:03 1970 +0000
146 summary: Adding b branch head 1
146 summary: Adding b branch head 1
147
147
148 changeset: 3:ac22033332d1
148 changeset: 3:ac22033332d1
149 branch: b
149 branch: b
150 parent: 0:19709c5a4e75
150 parent: 0:19709c5a4e75
151 user: test
151 user: test
152 date: Thu Jan 01 00:00:02 1970 +0000
152 date: Thu Jan 01 00:00:02 1970 +0000
153 summary: Adding b branch
153 summary: Adding b branch
154
154
155
155
156 ---- going to test branch closing
156 ---- going to test branch closing
157
157
158 $ hg branches
158 $ hg branches
159 a branch name much longer than the default justification used by branches 7:10ff5895aa57
159 a branch name much longer than the default justification used by branches 7:10ff5895aa57
160 b 4:aee39cd168d0
160 b 4:aee39cd168d0
161 c 6:589736a22561 (inactive)
161 c 6:589736a22561 (inactive)
162 a 5:d8cbc61dbaa6 (inactive)
162 a 5:d8cbc61dbaa6 (inactive)
163 default 0:19709c5a4e75 (inactive)
163 default 0:19709c5a4e75 (inactive)
164 $ hg up -C b
164 $ hg up -C b
165 2 files updated, 0 files merged, 4 files removed, 0 files unresolved
165 2 files updated, 0 files merged, 4 files removed, 0 files unresolved
166 $ echo 'xxx1' >> b
166 $ echo 'xxx1' >> b
167 $ hg commit -d '7 0' -m 'adding cset to branch b'
167 $ hg commit -d '7 0' -m 'adding cset to branch b'
168 $ hg up -C aee39cd168d0
168 $ hg up -C aee39cd168d0
169 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
169 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
170 $ echo 'xxx2' >> b
170 $ echo 'xxx2' >> b
171 $ hg commit -d '8 0' -m 'adding head to branch b'
171 $ hg commit -d '8 0' -m 'adding head to branch b'
172 created new head
172 created new head
173 $ echo 'xxx3' >> b
173 $ echo 'xxx3' >> b
174 $ hg commit -d '9 0' -m 'adding another cset to branch b'
174 $ hg commit -d '9 0' -m 'adding another cset to branch b'
175 $ hg branches
175 $ hg branches
176 b 10:bfbe841b666e
176 b 10:bfbe841b666e
177 a branch name much longer than the default justification used by branches 7:10ff5895aa57
177 a branch name much longer than the default justification used by branches 7:10ff5895aa57
178 c 6:589736a22561 (inactive)
178 c 6:589736a22561 (inactive)
179 a 5:d8cbc61dbaa6 (inactive)
179 a 5:d8cbc61dbaa6 (inactive)
180 default 0:19709c5a4e75 (inactive)
180 default 0:19709c5a4e75 (inactive)
181 $ hg heads --closed
181 $ hg heads --closed
182 changeset: 10:bfbe841b666e
182 changeset: 10:bfbe841b666e
183 branch: b
183 branch: b
184 tag: tip
184 tag: tip
185 user: test
185 user: test
186 date: Thu Jan 01 00:00:09 1970 +0000
186 date: Thu Jan 01 00:00:09 1970 +0000
187 summary: adding another cset to branch b
187 summary: adding another cset to branch b
188
188
189 changeset: 8:eebb944467c9
189 changeset: 8:eebb944467c9
190 branch: b
190 branch: b
191 parent: 4:aee39cd168d0
191 parent: 4:aee39cd168d0
192 user: test
192 user: test
193 date: Thu Jan 01 00:00:07 1970 +0000
193 date: Thu Jan 01 00:00:07 1970 +0000
194 summary: adding cset to branch b
194 summary: adding cset to branch b
195
195
196 changeset: 7:10ff5895aa57
196 changeset: 7:10ff5895aa57
197 branch: a branch name much longer than the default justification used by branches
197 branch: a branch name much longer than the default justification used by branches
198 user: test
198 user: test
199 date: Thu Jan 01 00:00:06 1970 +0000
199 date: Thu Jan 01 00:00:06 1970 +0000
200 summary: Adding d branch
200 summary: Adding d branch
201
201
202 changeset: 6:589736a22561
202 changeset: 6:589736a22561
203 branch: c
203 branch: c
204 user: test
204 user: test
205 date: Thu Jan 01 00:00:05 1970 +0000
205 date: Thu Jan 01 00:00:05 1970 +0000
206 summary: Adding c branch
206 summary: Adding c branch
207
207
208 changeset: 5:d8cbc61dbaa6
208 changeset: 5:d8cbc61dbaa6
209 branch: a
209 branch: a
210 parent: 2:881fe2b92ad0
210 parent: 2:881fe2b92ad0
211 user: test
211 user: test
212 date: Thu Jan 01 00:00:04 1970 +0000
212 date: Thu Jan 01 00:00:04 1970 +0000
213 summary: Adding b branch head 2
213 summary: Adding b branch head 2
214
214
215 changeset: 0:19709c5a4e75
215 changeset: 0:19709c5a4e75
216 user: test
216 user: test
217 date: Thu Jan 01 00:00:00 1970 +0000
217 date: Thu Jan 01 00:00:00 1970 +0000
218 summary: Adding root node
218 summary: Adding root node
219
219
220 $ hg heads
220 $ hg heads
221 changeset: 10:bfbe841b666e
221 changeset: 10:bfbe841b666e
222 branch: b
222 branch: b
223 tag: tip
223 tag: tip
224 user: test
224 user: test
225 date: Thu Jan 01 00:00:09 1970 +0000
225 date: Thu Jan 01 00:00:09 1970 +0000
226 summary: adding another cset to branch b
226 summary: adding another cset to branch b
227
227
228 changeset: 8:eebb944467c9
228 changeset: 8:eebb944467c9
229 branch: b
229 branch: b
230 parent: 4:aee39cd168d0
230 parent: 4:aee39cd168d0
231 user: test
231 user: test
232 date: Thu Jan 01 00:00:07 1970 +0000
232 date: Thu Jan 01 00:00:07 1970 +0000
233 summary: adding cset to branch b
233 summary: adding cset to branch b
234
234
235 changeset: 7:10ff5895aa57
235 changeset: 7:10ff5895aa57
236 branch: a branch name much longer than the default justification used by branches
236 branch: a branch name much longer than the default justification used by branches
237 user: test
237 user: test
238 date: Thu Jan 01 00:00:06 1970 +0000
238 date: Thu Jan 01 00:00:06 1970 +0000
239 summary: Adding d branch
239 summary: Adding d branch
240
240
241 changeset: 6:589736a22561
241 changeset: 6:589736a22561
242 branch: c
242 branch: c
243 user: test
243 user: test
244 date: Thu Jan 01 00:00:05 1970 +0000
244 date: Thu Jan 01 00:00:05 1970 +0000
245 summary: Adding c branch
245 summary: Adding c branch
246
246
247 changeset: 5:d8cbc61dbaa6
247 changeset: 5:d8cbc61dbaa6
248 branch: a
248 branch: a
249 parent: 2:881fe2b92ad0
249 parent: 2:881fe2b92ad0
250 user: test
250 user: test
251 date: Thu Jan 01 00:00:04 1970 +0000
251 date: Thu Jan 01 00:00:04 1970 +0000
252 summary: Adding b branch head 2
252 summary: Adding b branch head 2
253
253
254 changeset: 0:19709c5a4e75
254 changeset: 0:19709c5a4e75
255 user: test
255 user: test
256 date: Thu Jan 01 00:00:00 1970 +0000
256 date: Thu Jan 01 00:00:00 1970 +0000
257 summary: Adding root node
257 summary: Adding root node
258
258
259 $ hg commit -d '9 0' --close-branch -m 'prune bad branch'
259 $ hg commit -d '9 0' --close-branch -m 'prune bad branch'
260 $ hg branches -a
260 $ hg branches -a
261 b 8:eebb944467c9
261 b 8:eebb944467c9
262 a branch name much longer than the default justification used by branches 7:10ff5895aa57
262 a branch name much longer than the default justification used by branches 7:10ff5895aa57
263 $ hg up -C b
263 $ hg up -C b
264 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
264 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
265 $ hg commit -d '9 0' --close-branch -m 'close this part branch too'
265 $ hg commit -d '9 0' --close-branch -m 'close this part branch too'
266 $ hg commit -d '9 0' --close-branch -m 're-closing this branch'
266 $ hg commit -d '9 0' --close-branch -m 're-closing this branch'
267 abort: can only close branch heads
267 abort: can only close branch heads
268 [255]
268 [255]
269
269
270 $ hg log -r tip --debug
270 $ hg log -r tip --debug
271 changeset: 12:e3d49c0575d8fc2cb1cd6859c747c14f5f6d499f
271 changeset: 12:e3d49c0575d8fc2cb1cd6859c747c14f5f6d499f
272 branch: b
272 branch: b
273 tag: tip
273 tag: tip
274 phase: draft
274 phase: draft
275 parent: 8:eebb944467c9fb9651ed232aeaf31b3c0a7fc6c1
275 parent: 8:eebb944467c9fb9651ed232aeaf31b3c0a7fc6c1
276 parent: -1:0000000000000000000000000000000000000000
276 parent: -1:0000000000000000000000000000000000000000
277 manifest: 8:6f9ed32d2b310e391a4f107d5f0f071df785bfee
277 manifest: 8:6f9ed32d2b310e391a4f107d5f0f071df785bfee
278 user: test
278 user: test
279 date: Thu Jan 01 00:00:09 1970 +0000
279 date: Thu Jan 01 00:00:09 1970 +0000
280 extra: branch=b
280 extra: branch=b
281 extra: close=1
281 extra: close=1
282 description:
282 description:
283 close this part branch too
283 close this part branch too
284
284
285
285
286 --- b branch should be inactive
286 --- b branch should be inactive
287
287
288 $ hg branches
288 $ hg branches
289 a branch name much longer than the default justification used by branches 7:10ff5895aa57
289 a branch name much longer than the default justification used by branches 7:10ff5895aa57
290 c 6:589736a22561 (inactive)
290 c 6:589736a22561 (inactive)
291 a 5:d8cbc61dbaa6 (inactive)
291 a 5:d8cbc61dbaa6 (inactive)
292 default 0:19709c5a4e75 (inactive)
292 default 0:19709c5a4e75 (inactive)
293 $ hg branches -c
293 $ hg branches -c
294 a branch name much longer than the default justification used by branches 7:10ff5895aa57
294 a branch name much longer than the default justification used by branches 7:10ff5895aa57
295 b 12:e3d49c0575d8 (closed)
295 b 12:e3d49c0575d8 (closed)
296 c 6:589736a22561 (inactive)
296 c 6:589736a22561 (inactive)
297 a 5:d8cbc61dbaa6 (inactive)
297 a 5:d8cbc61dbaa6 (inactive)
298 default 0:19709c5a4e75 (inactive)
298 default 0:19709c5a4e75 (inactive)
299 $ hg branches -a
299 $ hg branches -a
300 a branch name much longer than the default justification used by branches 7:10ff5895aa57
300 a branch name much longer than the default justification used by branches 7:10ff5895aa57
301 $ hg branches -q
301 $ hg branches -q
302 a branch name much longer than the default justification used by branches
302 a branch name much longer than the default justification used by branches
303 c
303 c
304 a
304 a
305 default
305 default
306 $ hg heads b
306 $ hg heads b
307 no open branch heads found on branches b
307 no open branch heads found on branches b
308 [1]
308 [1]
309 $ hg heads --closed b
309 $ hg heads --closed b
310 changeset: 12:e3d49c0575d8
310 changeset: 12:e3d49c0575d8
311 branch: b
311 branch: b
312 tag: tip
312 tag: tip
313 parent: 8:eebb944467c9
313 parent: 8:eebb944467c9
314 user: test
314 user: test
315 date: Thu Jan 01 00:00:09 1970 +0000
315 date: Thu Jan 01 00:00:09 1970 +0000
316 summary: close this part branch too
316 summary: close this part branch too
317
317
318 changeset: 11:d3f163457ebf
318 changeset: 11:d3f163457ebf
319 branch: b
319 branch: b
320 user: test
320 user: test
321 date: Thu Jan 01 00:00:09 1970 +0000
321 date: Thu Jan 01 00:00:09 1970 +0000
322 summary: prune bad branch
322 summary: prune bad branch
323
323
324 $ echo 'xxx4' >> b
324 $ echo 'xxx4' >> b
325 $ hg commit -d '9 0' -m 'reopen branch with a change'
325 $ hg commit -d '9 0' -m 'reopen branch with a change'
326 reopening closed branch head 12
326 reopening closed branch head 12
327
327
328 --- branch b is back in action
328 --- branch b is back in action
329
329
330 $ hg branches -a
330 $ hg branches -a
331 b 13:e23b5505d1ad
331 b 13:e23b5505d1ad
332 a branch name much longer than the default justification used by branches 7:10ff5895aa57
332 a branch name much longer than the default justification used by branches 7:10ff5895aa57
333
333
334 ---- test heads listings
334 ---- test heads listings
335
335
336 $ hg heads
336 $ hg heads
337 changeset: 13:e23b5505d1ad
337 changeset: 13:e23b5505d1ad
338 branch: b
338 branch: b
339 tag: tip
339 tag: tip
340 user: test
340 user: test
341 date: Thu Jan 01 00:00:09 1970 +0000
341 date: Thu Jan 01 00:00:09 1970 +0000
342 summary: reopen branch with a change
342 summary: reopen branch with a change
343
343
344 changeset: 7:10ff5895aa57
344 changeset: 7:10ff5895aa57
345 branch: a branch name much longer than the default justification used by branches
345 branch: a branch name much longer than the default justification used by branches
346 user: test
346 user: test
347 date: Thu Jan 01 00:00:06 1970 +0000
347 date: Thu Jan 01 00:00:06 1970 +0000
348 summary: Adding d branch
348 summary: Adding d branch
349
349
350 changeset: 6:589736a22561
350 changeset: 6:589736a22561
351 branch: c
351 branch: c
352 user: test
352 user: test
353 date: Thu Jan 01 00:00:05 1970 +0000
353 date: Thu Jan 01 00:00:05 1970 +0000
354 summary: Adding c branch
354 summary: Adding c branch
355
355
356 changeset: 5:d8cbc61dbaa6
356 changeset: 5:d8cbc61dbaa6
357 branch: a
357 branch: a
358 parent: 2:881fe2b92ad0
358 parent: 2:881fe2b92ad0
359 user: test
359 user: test
360 date: Thu Jan 01 00:00:04 1970 +0000
360 date: Thu Jan 01 00:00:04 1970 +0000
361 summary: Adding b branch head 2
361 summary: Adding b branch head 2
362
362
363 changeset: 0:19709c5a4e75
363 changeset: 0:19709c5a4e75
364 user: test
364 user: test
365 date: Thu Jan 01 00:00:00 1970 +0000
365 date: Thu Jan 01 00:00:00 1970 +0000
366 summary: Adding root node
366 summary: Adding root node
367
367
368
368
369 branch default
369 branch default
370
370
371 $ hg heads default
371 $ hg heads default
372 changeset: 0:19709c5a4e75
372 changeset: 0:19709c5a4e75
373 user: test
373 user: test
374 date: Thu Jan 01 00:00:00 1970 +0000
374 date: Thu Jan 01 00:00:00 1970 +0000
375 summary: Adding root node
375 summary: Adding root node
376
376
377
377
378 branch a
378 branch a
379
379
380 $ hg heads a
380 $ hg heads a
381 changeset: 5:d8cbc61dbaa6
381 changeset: 5:d8cbc61dbaa6
382 branch: a
382 branch: a
383 parent: 2:881fe2b92ad0
383 parent: 2:881fe2b92ad0
384 user: test
384 user: test
385 date: Thu Jan 01 00:00:04 1970 +0000
385 date: Thu Jan 01 00:00:04 1970 +0000
386 summary: Adding b branch head 2
386 summary: Adding b branch head 2
387
387
388 $ hg heads --active a
388 $ hg heads --active a
389 no open branch heads found on branches a
389 no open branch heads found on branches a
390 [1]
390 [1]
391
391
392 branch b
392 branch b
393
393
394 $ hg heads b
394 $ hg heads b
395 changeset: 13:e23b5505d1ad
395 changeset: 13:e23b5505d1ad
396 branch: b
396 branch: b
397 tag: tip
397 tag: tip
398 user: test
398 user: test
399 date: Thu Jan 01 00:00:09 1970 +0000
399 date: Thu Jan 01 00:00:09 1970 +0000
400 summary: reopen branch with a change
400 summary: reopen branch with a change
401
401
402 $ hg heads --closed b
402 $ hg heads --closed b
403 changeset: 13:e23b5505d1ad
403 changeset: 13:e23b5505d1ad
404 branch: b
404 branch: b
405 tag: tip
405 tag: tip
406 user: test
406 user: test
407 date: Thu Jan 01 00:00:09 1970 +0000
407 date: Thu Jan 01 00:00:09 1970 +0000
408 summary: reopen branch with a change
408 summary: reopen branch with a change
409
409
410 changeset: 11:d3f163457ebf
410 changeset: 11:d3f163457ebf
411 branch: b
411 branch: b
412 user: test
412 user: test
413 date: Thu Jan 01 00:00:09 1970 +0000
413 date: Thu Jan 01 00:00:09 1970 +0000
414 summary: prune bad branch
414 summary: prune bad branch
415
415
416 default branch colors:
416 default branch colors:
417
417
418 $ cat <<EOF >> $HGRCPATH
418 $ cat <<EOF >> $HGRCPATH
419 > [extensions]
419 > [extensions]
420 > color =
420 > color =
421 > [color]
421 > [color]
422 > mode = ansi
422 > mode = ansi
423 > EOF
423 > EOF
424
424
425 $ hg up -C c
425 $ hg up -C c
426 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
426 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
427 $ hg commit -d '9 0' --close-branch -m 'reclosing this branch'
427 $ hg commit -d '9 0' --close-branch -m 'reclosing this branch'
428 $ hg up -C b
428 $ hg up -C b
429 2 files updated, 0 files merged, 3 files removed, 0 files unresolved
429 2 files updated, 0 files merged, 3 files removed, 0 files unresolved
430 $ hg branches --color=always
430 $ hg branches --color=always
431 \x1b[0;32mb\x1b[0m\x1b[0;33m 13:e23b5505d1ad\x1b[0m (esc)
431 \x1b[0;32mb\x1b[0m\x1b[0;33m 13:e23b5505d1ad\x1b[0m (esc)
432 \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
432 \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
433 \x1b[0;0ma\x1b[0m\x1b[0;33m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
433 \x1b[0;0ma\x1b[0m\x1b[0;33m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
434 \x1b[0;0mdefault\x1b[0m\x1b[0;33m 0:19709c5a4e75\x1b[0m (inactive) (esc)
434 \x1b[0;0mdefault\x1b[0m\x1b[0;33m 0:19709c5a4e75\x1b[0m (inactive) (esc)
435
435
436 default closed branch color:
436 default closed branch color:
437
437
438 $ hg branches --color=always --closed
438 $ hg branches --color=always --closed
439 \x1b[0;32mb\x1b[0m\x1b[0;33m 13:e23b5505d1ad\x1b[0m (esc)
439 \x1b[0;32mb\x1b[0m\x1b[0;33m 13:e23b5505d1ad\x1b[0m (esc)
440 \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
440 \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
441 \x1b[0;30;1mc\x1b[0m\x1b[0;33m 14:f894c25619d3\x1b[0m (closed) (esc)
441 \x1b[0;30;1mc\x1b[0m\x1b[0;33m 14:f894c25619d3\x1b[0m (closed) (esc)
442 \x1b[0;0ma\x1b[0m\x1b[0;33m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
442 \x1b[0;0ma\x1b[0m\x1b[0;33m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
443 \x1b[0;0mdefault\x1b[0m\x1b[0;33m 0:19709c5a4e75\x1b[0m (inactive) (esc)
443 \x1b[0;0mdefault\x1b[0m\x1b[0;33m 0:19709c5a4e75\x1b[0m (inactive) (esc)
444
444
445 $ cat <<EOF >> $HGRCPATH
445 $ cat <<EOF >> $HGRCPATH
446 > [extensions]
446 > [extensions]
447 > color =
447 > color =
448 > [color]
448 > [color]
449 > branches.active = green
449 > branches.active = green
450 > branches.closed = blue
450 > branches.closed = blue
451 > branches.current = red
451 > branches.current = red
452 > branches.inactive = magenta
452 > branches.inactive = magenta
453 > log.changeset = cyan
453 > log.changeset = cyan
454 > EOF
454 > EOF
455
455
456 custom branch colors:
456 custom branch colors:
457
457
458 $ hg branches --color=always
458 $ hg branches --color=always
459 \x1b[0;31mb\x1b[0m\x1b[0;36m 13:e23b5505d1ad\x1b[0m (esc)
459 \x1b[0;31mb\x1b[0m\x1b[0;36m 13:e23b5505d1ad\x1b[0m (esc)
460 \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
460 \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
461 \x1b[0;35ma\x1b[0m\x1b[0;36m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
461 \x1b[0;35ma\x1b[0m\x1b[0;36m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
462 \x1b[0;35mdefault\x1b[0m\x1b[0;36m 0:19709c5a4e75\x1b[0m (inactive) (esc)
462 \x1b[0;35mdefault\x1b[0m\x1b[0;36m 0:19709c5a4e75\x1b[0m (inactive) (esc)
463
463
464 custom closed branch color:
464 custom closed branch color:
465
465
466 $ hg branches --color=always --closed
466 $ hg branches --color=always --closed
467 \x1b[0;31mb\x1b[0m\x1b[0;36m 13:e23b5505d1ad\x1b[0m (esc)
467 \x1b[0;31mb\x1b[0m\x1b[0;36m 13:e23b5505d1ad\x1b[0m (esc)
468 \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
468 \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
469 \x1b[0;34mc\x1b[0m\x1b[0;36m 14:f894c25619d3\x1b[0m (closed) (esc)
469 \x1b[0;34mc\x1b[0m\x1b[0;36m 14:f894c25619d3\x1b[0m (closed) (esc)
470 \x1b[0;35ma\x1b[0m\x1b[0;36m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
470 \x1b[0;35ma\x1b[0m\x1b[0;36m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
471 \x1b[0;35mdefault\x1b[0m\x1b[0;36m 0:19709c5a4e75\x1b[0m (inactive) (esc)
471 \x1b[0;35mdefault\x1b[0m\x1b[0;36m 0:19709c5a4e75\x1b[0m (inactive) (esc)
472
472
473 template output:
473 template output:
474
474
475 $ hg branches -Tjson --closed
475 $ hg branches -Tjson --closed
476 [
476 [
477 {
477 {
478 "active": true,
478 "active": true,
479 "branch": "b",
479 "branch": "b",
480 "closed": false,
480 "closed": false,
481 "current": true,
481 "current": true,
482 "node": "e23b5505d1ad24aab6f84fd8c7cb8cd8e5e93be0",
482 "node": "e23b5505d1ad24aab6f84fd8c7cb8cd8e5e93be0",
483 "rev": 13
483 "rev": 13
484 },
484 },
485 {
485 {
486 "active": true,
486 "active": true,
487 "branch": "a branch name much longer than the default justification used by branches",
487 "branch": "a branch name much longer than the default justification used by branches",
488 "closed": false,
488 "closed": false,
489 "current": false,
489 "current": false,
490 "node": "10ff5895aa5793bd378da574af8cec8ea408d831",
490 "node": "10ff5895aa5793bd378da574af8cec8ea408d831",
491 "rev": 7
491 "rev": 7
492 },
492 },
493 {
493 {
494 "active": false,
494 "active": false,
495 "branch": "c",
495 "branch": "c",
496 "closed": true,
496 "closed": true,
497 "current": false,
497 "current": false,
498 "node": "f894c25619d3f1484639d81be950e0a07bc6f1f6",
498 "node": "f894c25619d3f1484639d81be950e0a07bc6f1f6",
499 "rev": 14
499 "rev": 14
500 },
500 },
501 {
501 {
502 "active": false,
502 "active": false,
503 "branch": "a",
503 "branch": "a",
504 "closed": false,
504 "closed": false,
505 "current": false,
505 "current": false,
506 "node": "d8cbc61dbaa6dc817175d1e301eecb863f280832",
506 "node": "d8cbc61dbaa6dc817175d1e301eecb863f280832",
507 "rev": 5
507 "rev": 5
508 },
508 },
509 {
509 {
510 "active": false,
510 "active": false,
511 "branch": "default",
511 "branch": "default",
512 "closed": false,
512 "closed": false,
513 "current": false,
513 "current": false,
514 "node": "19709c5a4e75bf938f8e349aff97438539bb729e",
514 "node": "19709c5a4e75bf938f8e349aff97438539bb729e",
515 "rev": 0
515 "rev": 0
516 }
516 }
517 ]
517 ]
518
518
519 $ hg branches --closed -T '{if(closed, "{branch}\n")}'
520 c
519
521
520 Tests of revision branch name caching
522 Tests of revision branch name caching
521
523
522 We rev branch cache is updated automatically. In these tests we use a trick to
524 We rev branch cache is updated automatically. In these tests we use a trick to
523 trigger rebuilds. We remove the branch head cache and run 'hg head' to cause a
525 trigger rebuilds. We remove the branch head cache and run 'hg head' to cause a
524 rebuild that also will populate the rev branch cache.
526 rebuild that also will populate the rev branch cache.
525
527
526 revision branch cache is created when building the branch head cache
528 revision branch cache is created when building the branch head cache
527 $ rm -rf .hg/cache; hg head a -T '{rev}\n'
529 $ rm -rf .hg/cache; hg head a -T '{rev}\n'
528 5
530 5
529 $ f --hexdump --size .hg/cache/rbc-*
531 $ f --hexdump --size .hg/cache/rbc-*
530 .hg/cache/rbc-names-v1: size=87
532 .hg/cache/rbc-names-v1: size=87
531 0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
533 0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
532 0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
534 0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
533 0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
535 0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
534 0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
536 0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
535 0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
537 0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
536 0050: 72 61 6e 63 68 65 73 |ranches|
538 0050: 72 61 6e 63 68 65 73 |ranches|
537 .hg/cache/rbc-revs-v1: size=120
539 .hg/cache/rbc-revs-v1: size=120
538 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
540 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
539 0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
541 0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
540 0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
542 0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
541 0030: 58 97 36 a2 00 00 00 03 10 ff 58 95 00 00 00 04 |X.6.......X.....|
543 0030: 58 97 36 a2 00 00 00 03 10 ff 58 95 00 00 00 04 |X.6.......X.....|
542 0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
544 0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
543 0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
545 0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
544 0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
546 0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
545 0070: f8 94 c2 56 80 00 00 03 |...V....|
547 0070: f8 94 c2 56 80 00 00 03 |...V....|
546
548
547 no errors when revbranchcache is not writable
549 no errors when revbranchcache is not writable
548
550
549 $ echo >> .hg/cache/rbc-revs-v1
551 $ echo >> .hg/cache/rbc-revs-v1
550 $ mv .hg/cache/rbc-revs-v1 .hg/cache/rbc-revs-v1_
552 $ mv .hg/cache/rbc-revs-v1 .hg/cache/rbc-revs-v1_
551 $ mkdir .hg/cache/rbc-revs-v1
553 $ mkdir .hg/cache/rbc-revs-v1
552 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n'
554 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n'
553 5
555 5
554 $ rmdir .hg/cache/rbc-revs-v1
556 $ rmdir .hg/cache/rbc-revs-v1
555 $ mv .hg/cache/rbc-revs-v1_ .hg/cache/rbc-revs-v1
557 $ mv .hg/cache/rbc-revs-v1_ .hg/cache/rbc-revs-v1
556
558
557 no errors when wlock cannot be acquired
559 no errors when wlock cannot be acquired
558
560
559 #if unix-permissions
561 #if unix-permissions
560 $ mv .hg/cache/rbc-revs-v1 .hg/cache/rbc-revs-v1_
562 $ mv .hg/cache/rbc-revs-v1 .hg/cache/rbc-revs-v1_
561 $ rm -f .hg/cache/branch*
563 $ rm -f .hg/cache/branch*
562 $ chmod 555 .hg
564 $ chmod 555 .hg
563 $ hg head a -T '{rev}\n'
565 $ hg head a -T '{rev}\n'
564 5
566 5
565 $ chmod 755 .hg
567 $ chmod 755 .hg
566 $ mv .hg/cache/rbc-revs-v1_ .hg/cache/rbc-revs-v1
568 $ mv .hg/cache/rbc-revs-v1_ .hg/cache/rbc-revs-v1
567 #endif
569 #endif
568
570
569 recovery from invalid cache revs file with trailing data
571 recovery from invalid cache revs file with trailing data
570 $ echo >> .hg/cache/rbc-revs-v1
572 $ echo >> .hg/cache/rbc-revs-v1
571 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
573 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
572 5
574 5
573 truncating cache/rbc-revs-v1 to 120
575 truncating cache/rbc-revs-v1 to 120
574 $ f --size .hg/cache/rbc-revs*
576 $ f --size .hg/cache/rbc-revs*
575 .hg/cache/rbc-revs-v1: size=120
577 .hg/cache/rbc-revs-v1: size=120
576 recovery from invalid cache file with partial last record
578 recovery from invalid cache file with partial last record
577 $ mv .hg/cache/rbc-revs-v1 .
579 $ mv .hg/cache/rbc-revs-v1 .
578 $ f -qDB 119 rbc-revs-v1 > .hg/cache/rbc-revs-v1
580 $ f -qDB 119 rbc-revs-v1 > .hg/cache/rbc-revs-v1
579 $ f --size .hg/cache/rbc-revs*
581 $ f --size .hg/cache/rbc-revs*
580 .hg/cache/rbc-revs-v1: size=119
582 .hg/cache/rbc-revs-v1: size=119
581 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
583 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
582 5
584 5
583 truncating cache/rbc-revs-v1 to 112
585 truncating cache/rbc-revs-v1 to 112
584 $ f --size .hg/cache/rbc-revs*
586 $ f --size .hg/cache/rbc-revs*
585 .hg/cache/rbc-revs-v1: size=120
587 .hg/cache/rbc-revs-v1: size=120
586 recovery from invalid cache file with missing record - no truncation
588 recovery from invalid cache file with missing record - no truncation
587 $ mv .hg/cache/rbc-revs-v1 .
589 $ mv .hg/cache/rbc-revs-v1 .
588 $ f -qDB 112 rbc-revs-v1 > .hg/cache/rbc-revs-v1
590 $ f -qDB 112 rbc-revs-v1 > .hg/cache/rbc-revs-v1
589 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
591 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
590 5
592 5
591 $ f --size .hg/cache/rbc-revs*
593 $ f --size .hg/cache/rbc-revs*
592 .hg/cache/rbc-revs-v1: size=120
594 .hg/cache/rbc-revs-v1: size=120
593 recovery from invalid cache file with some bad records
595 recovery from invalid cache file with some bad records
594 $ mv .hg/cache/rbc-revs-v1 .
596 $ mv .hg/cache/rbc-revs-v1 .
595 $ f -qDB 8 rbc-revs-v1 > .hg/cache/rbc-revs-v1
597 $ f -qDB 8 rbc-revs-v1 > .hg/cache/rbc-revs-v1
596 $ f --size .hg/cache/rbc-revs*
598 $ f --size .hg/cache/rbc-revs*
597 .hg/cache/rbc-revs-v1: size=8
599 .hg/cache/rbc-revs-v1: size=8
598 $ f -qDB 112 rbc-revs-v1 >> .hg/cache/rbc-revs-v1
600 $ f -qDB 112 rbc-revs-v1 >> .hg/cache/rbc-revs-v1
599 $ f --size .hg/cache/rbc-revs*
601 $ f --size .hg/cache/rbc-revs*
600 .hg/cache/rbc-revs-v1: size=120
602 .hg/cache/rbc-revs-v1: size=120
601 $ hg log -r 'branch(.)' -T '{rev} ' --debug
603 $ hg log -r 'branch(.)' -T '{rev} ' --debug
602 history modification detected - truncating revision branch cache to revision 13
604 history modification detected - truncating revision branch cache to revision 13
603 history modification detected - truncating revision branch cache to revision 1
605 history modification detected - truncating revision branch cache to revision 1
604 3 4 8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 8
606 3 4 8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 8
605 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
607 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
606 5
608 5
607 truncating cache/rbc-revs-v1 to 104
609 truncating cache/rbc-revs-v1 to 104
608 $ f --size --hexdump --bytes=16 .hg/cache/rbc-revs*
610 $ f --size --hexdump --bytes=16 .hg/cache/rbc-revs*
609 .hg/cache/rbc-revs-v1: size=120
611 .hg/cache/rbc-revs-v1: size=120
610 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
612 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
611 cache is updated when committing
613 cache is updated when committing
612 $ hg branch i-will-regret-this
614 $ hg branch i-will-regret-this
613 marked working directory as branch i-will-regret-this
615 marked working directory as branch i-will-regret-this
614 $ hg ci -m regrets
616 $ hg ci -m regrets
615 $ f --size .hg/cache/rbc-*
617 $ f --size .hg/cache/rbc-*
616 .hg/cache/rbc-names-v1: size=106
618 .hg/cache/rbc-names-v1: size=106
617 .hg/cache/rbc-revs-v1: size=128
619 .hg/cache/rbc-revs-v1: size=128
618 update after rollback - the cache will be correct but rbc-names will will still
620 update after rollback - the cache will be correct but rbc-names will will still
619 contain the branch name even though it no longer is used
621 contain the branch name even though it no longer is used
620 $ hg up -qr '.^'
622 $ hg up -qr '.^'
621 $ hg rollback -qf
623 $ hg rollback -qf
622 $ f --size --hexdump .hg/cache/rbc-*
624 $ f --size --hexdump .hg/cache/rbc-*
623 .hg/cache/rbc-names-v1: size=106
625 .hg/cache/rbc-names-v1: size=106
624 0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
626 0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
625 0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
627 0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
626 0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
628 0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
627 0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
629 0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
628 0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
630 0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
629 0050: 72 61 6e 63 68 65 73 00 69 2d 77 69 6c 6c 2d 72 |ranches.i-will-r|
631 0050: 72 61 6e 63 68 65 73 00 69 2d 77 69 6c 6c 2d 72 |ranches.i-will-r|
630 0060: 65 67 72 65 74 2d 74 68 69 73 |egret-this|
632 0060: 65 67 72 65 74 2d 74 68 69 73 |egret-this|
631 .hg/cache/rbc-revs-v1: size=120
633 .hg/cache/rbc-revs-v1: size=120
632 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
634 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
633 0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
635 0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
634 0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
636 0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
635 0030: 58 97 36 a2 00 00 00 03 10 ff 58 95 00 00 00 04 |X.6.......X.....|
637 0030: 58 97 36 a2 00 00 00 03 10 ff 58 95 00 00 00 04 |X.6.......X.....|
636 0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
638 0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
637 0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
639 0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
638 0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
640 0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
639 0070: f8 94 c2 56 80 00 00 03 |...V....|
641 0070: f8 94 c2 56 80 00 00 03 |...V....|
640 cache is updated/truncated when stripping - it is thus very hard to get in a
642 cache is updated/truncated when stripping - it is thus very hard to get in a
641 situation where the cache is out of sync and the hash check detects it
643 situation where the cache is out of sync and the hash check detects it
642 $ hg --config extensions.strip= strip -r tip --nob
644 $ hg --config extensions.strip= strip -r tip --nob
643 $ f --size .hg/cache/rbc-revs*
645 $ f --size .hg/cache/rbc-revs*
644 .hg/cache/rbc-revs-v1: size=112
646 .hg/cache/rbc-revs-v1: size=112
645
647
646 cache is rebuilt when corruption is detected
648 cache is rebuilt when corruption is detected
647 $ echo > .hg/cache/rbc-names-v1
649 $ echo > .hg/cache/rbc-names-v1
648 $ hg log -r '5:&branch(.)' -T '{rev} ' --debug
650 $ hg log -r '5:&branch(.)' -T '{rev} ' --debug
649 referenced branch names not found - rebuilding revision branch cache from scratch
651 referenced branch names not found - rebuilding revision branch cache from scratch
650 8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 40
652 8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 40
651 $ f --size --hexdump .hg/cache/rbc-*
653 $ f --size --hexdump .hg/cache/rbc-*
652 .hg/cache/rbc-names-v1: size=79
654 .hg/cache/rbc-names-v1: size=79
653 0000: 62 00 61 00 63 00 61 20 62 72 61 6e 63 68 20 6e |b.a.c.a branch n|
655 0000: 62 00 61 00 63 00 61 20 62 72 61 6e 63 68 20 6e |b.a.c.a branch n|
654 0010: 61 6d 65 20 6d 75 63 68 20 6c 6f 6e 67 65 72 20 |ame much longer |
656 0010: 61 6d 65 20 6d 75 63 68 20 6c 6f 6e 67 65 72 20 |ame much longer |
655 0020: 74 68 61 6e 20 74 68 65 20 64 65 66 61 75 6c 74 |than the default|
657 0020: 74 68 61 6e 20 74 68 65 20 64 65 66 61 75 6c 74 |than the default|
656 0030: 20 6a 75 73 74 69 66 69 63 61 74 69 6f 6e 20 75 | justification u|
658 0030: 20 6a 75 73 74 69 66 69 63 61 74 69 6f 6e 20 75 | justification u|
657 0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73 |sed by branches|
659 0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73 |sed by branches|
658 .hg/cache/rbc-revs-v1: size=112
660 .hg/cache/rbc-revs-v1: size=112
659 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
661 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
660 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
662 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
661 0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 01 |................|
663 0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 01 |................|
662 0030: 58 97 36 a2 00 00 00 02 10 ff 58 95 00 00 00 03 |X.6.......X.....|
664 0030: 58 97 36 a2 00 00 00 02 10 ff 58 95 00 00 00 03 |X.6.......X.....|
663 0040: ee bb 94 44 00 00 00 00 5f 40 61 bb 00 00 00 00 |...D...._@a.....|
665 0040: ee bb 94 44 00 00 00 00 5f 40 61 bb 00 00 00 00 |...D...._@a.....|
664 0050: bf be 84 1b 00 00 00 00 d3 f1 63 45 80 00 00 00 |..........cE....|
666 0050: bf be 84 1b 00 00 00 00 d3 f1 63 45 80 00 00 00 |..........cE....|
665 0060: e3 d4 9c 05 80 00 00 00 e2 3b 55 05 00 00 00 00 |.........;U.....|
667 0060: e3 d4 9c 05 80 00 00 00 e2 3b 55 05 00 00 00 00 |.........;U.....|
666
668
667 Test that cache files are created and grows correctly:
669 Test that cache files are created and grows correctly:
668
670
669 $ rm .hg/cache/rbc*
671 $ rm .hg/cache/rbc*
670 $ hg log -r "5 & branch(5)" -T "{rev}\n"
672 $ hg log -r "5 & branch(5)" -T "{rev}\n"
671 5
673 5
672 $ f --size --hexdump .hg/cache/rbc-*
674 $ f --size --hexdump .hg/cache/rbc-*
673 .hg/cache/rbc-names-v1: size=1
675 .hg/cache/rbc-names-v1: size=1
674 0000: 61 |a|
676 0000: 61 |a|
675 .hg/cache/rbc-revs-v1: size=112
677 .hg/cache/rbc-revs-v1: size=112
676 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
678 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
677 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
679 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
678 0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 00 |................|
680 0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 00 |................|
679 0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
681 0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
680 0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
682 0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
681 0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
683 0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
682 0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
684 0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
683
685
684 $ cd ..
686 $ cd ..
685
687
686 Test for multiple incorrect branch cache entries:
688 Test for multiple incorrect branch cache entries:
687
689
688 $ hg init b
690 $ hg init b
689 $ cd b
691 $ cd b
690 $ touch f
692 $ touch f
691 $ hg ci -Aqmf
693 $ hg ci -Aqmf
692 $ echo >> f
694 $ echo >> f
693 $ hg ci -Amf
695 $ hg ci -Amf
694 $ hg branch -q branch
696 $ hg branch -q branch
695 $ hg ci -Amf
697 $ hg ci -Amf
696
698
697 $ f --size --hexdump .hg/cache/rbc-*
699 $ f --size --hexdump .hg/cache/rbc-*
698 .hg/cache/rbc-names-v1: size=14
700 .hg/cache/rbc-names-v1: size=14
699 0000: 64 65 66 61 75 6c 74 00 62 72 61 6e 63 68 |default.branch|
701 0000: 64 65 66 61 75 6c 74 00 62 72 61 6e 63 68 |default.branch|
700 .hg/cache/rbc-revs-v1: size=24
702 .hg/cache/rbc-revs-v1: size=24
701 0000: 66 e5 f5 aa 00 00 00 00 fa 4c 04 e5 00 00 00 00 |f........L......|
703 0000: 66 e5 f5 aa 00 00 00 00 fa 4c 04 e5 00 00 00 00 |f........L......|
702 0010: 56 46 78 69 00 00 00 01 |VFxi....|
704 0010: 56 46 78 69 00 00 00 01 |VFxi....|
703 $ : > .hg/cache/rbc-revs-v1
705 $ : > .hg/cache/rbc-revs-v1
704
706
705 No superfluous rebuilding of cache:
707 No superfluous rebuilding of cache:
706 $ hg log -r "branch(null)&branch(branch)" --debug
708 $ hg log -r "branch(null)&branch(branch)" --debug
707 $ f --size --hexdump .hg/cache/rbc-*
709 $ f --size --hexdump .hg/cache/rbc-*
708 .hg/cache/rbc-names-v1: size=14
710 .hg/cache/rbc-names-v1: size=14
709 0000: 64 65 66 61 75 6c 74 00 62 72 61 6e 63 68 |default.branch|
711 0000: 64 65 66 61 75 6c 74 00 62 72 61 6e 63 68 |default.branch|
710 .hg/cache/rbc-revs-v1: size=24
712 .hg/cache/rbc-revs-v1: size=24
711 0000: 66 e5 f5 aa 00 00 00 00 fa 4c 04 e5 00 00 00 00 |f........L......|
713 0000: 66 e5 f5 aa 00 00 00 00 fa 4c 04 e5 00 00 00 00 |f........L......|
712 0010: 56 46 78 69 00 00 00 01 |VFxi....|
714 0010: 56 46 78 69 00 00 00 01 |VFxi....|
713
715
714 $ cd ..
716 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now