##// END OF EJS Templates
templatekw: add {reporoot} keyword...
Yuya Nishihara -
r36261:48a6b1a2 default
parent child Browse files
Show More
@@ -1,929 +1,934 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 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 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 hex,
12 hex,
13 nullid,
13 nullid,
14 )
14 )
15
15
16 from . import (
16 from . import (
17 encoding,
17 encoding,
18 error,
18 error,
19 hbisect,
19 hbisect,
20 i18n,
20 i18n,
21 obsutil,
21 obsutil,
22 patch,
22 patch,
23 pycompat,
23 pycompat,
24 registrar,
24 registrar,
25 scmutil,
25 scmutil,
26 util,
26 util,
27 )
27 )
28
28
29 class _hybrid(object):
29 class _hybrid(object):
30 """Wrapper for list or dict to support legacy template
30 """Wrapper for list or dict to support legacy template
31
31
32 This class allows us to handle both:
32 This class allows us to handle both:
33 - "{files}" (legacy command-line-specific list hack) and
33 - "{files}" (legacy command-line-specific list hack) and
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
35 and to access raw values:
35 and to access raw values:
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
37 - "{get(extras, key)}"
37 - "{get(extras, key)}"
38 - "{files|json}"
38 - "{files|json}"
39 """
39 """
40
40
41 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
41 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
42 if gen is not None:
42 if gen is not None:
43 self.gen = gen # generator or function returning generator
43 self.gen = gen # generator or function returning generator
44 self._values = values
44 self._values = values
45 self._makemap = makemap
45 self._makemap = makemap
46 self.joinfmt = joinfmt
46 self.joinfmt = joinfmt
47 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
47 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
48 def gen(self):
48 def gen(self):
49 """Default generator to stringify this as {join(self, ' ')}"""
49 """Default generator to stringify this as {join(self, ' ')}"""
50 for i, x in enumerate(self._values):
50 for i, x in enumerate(self._values):
51 if i > 0:
51 if i > 0:
52 yield ' '
52 yield ' '
53 yield self.joinfmt(x)
53 yield self.joinfmt(x)
54 def itermaps(self):
54 def itermaps(self):
55 makemap = self._makemap
55 makemap = self._makemap
56 for x in self._values:
56 for x in self._values:
57 yield makemap(x)
57 yield makemap(x)
58 def __contains__(self, x):
58 def __contains__(self, x):
59 return x in self._values
59 return x in self._values
60 def __getitem__(self, key):
60 def __getitem__(self, key):
61 return self._values[key]
61 return self._values[key]
62 def __len__(self):
62 def __len__(self):
63 return len(self._values)
63 return len(self._values)
64 def __iter__(self):
64 def __iter__(self):
65 return iter(self._values)
65 return iter(self._values)
66 def __getattr__(self, name):
66 def __getattr__(self, name):
67 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
67 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
68 'keys', 'values'):
68 'keys', 'values'):
69 raise AttributeError(name)
69 raise AttributeError(name)
70 return getattr(self._values, name)
70 return getattr(self._values, name)
71
71
72 class _mappable(object):
72 class _mappable(object):
73 """Wrapper for non-list/dict object to support map operation
73 """Wrapper for non-list/dict object to support map operation
74
74
75 This class allows us to handle both:
75 This class allows us to handle both:
76 - "{manifest}"
76 - "{manifest}"
77 - "{manifest % '{rev}:{node}'}"
77 - "{manifest % '{rev}:{node}'}"
78 - "{manifest.rev}"
78 - "{manifest.rev}"
79
79
80 Unlike a _hybrid, this does not simulate the behavior of the underling
80 Unlike a _hybrid, this does not simulate the behavior of the underling
81 value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
81 value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
82 """
82 """
83
83
84 def __init__(self, gen, key, value, makemap):
84 def __init__(self, gen, key, value, makemap):
85 if gen is not None:
85 if gen is not None:
86 self.gen = gen # generator or function returning generator
86 self.gen = gen # generator or function returning generator
87 self._key = key
87 self._key = key
88 self._value = value # may be generator of strings
88 self._value = value # may be generator of strings
89 self._makemap = makemap
89 self._makemap = makemap
90
90
91 def gen(self):
91 def gen(self):
92 yield pycompat.bytestr(self._value)
92 yield pycompat.bytestr(self._value)
93
93
94 def tomap(self):
94 def tomap(self):
95 return self._makemap(self._key)
95 return self._makemap(self._key)
96
96
97 def itermaps(self):
97 def itermaps(self):
98 yield self.tomap()
98 yield self.tomap()
99
99
100 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
100 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
101 """Wrap data to support both dict-like and string-like operations"""
101 """Wrap data to support both dict-like and string-like operations"""
102 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
102 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
103 lambda k: fmt % (k, data[k]))
103 lambda k: fmt % (k, data[k]))
104
104
105 def hybridlist(data, name, fmt='%s', gen=None):
105 def hybridlist(data, name, fmt='%s', gen=None):
106 """Wrap data to support both list-like and string-like operations"""
106 """Wrap data to support both list-like and string-like operations"""
107 return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % x)
107 return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % x)
108
108
109 def unwraphybrid(thing):
109 def unwraphybrid(thing):
110 """Return an object which can be stringified possibly by using a legacy
110 """Return an object which can be stringified possibly by using a legacy
111 template"""
111 template"""
112 gen = getattr(thing, 'gen', None)
112 gen = getattr(thing, 'gen', None)
113 if gen is None:
113 if gen is None:
114 return thing
114 return thing
115 if callable(gen):
115 if callable(gen):
116 return gen()
116 return gen()
117 return gen
117 return gen
118
118
119 def unwrapvalue(thing):
119 def unwrapvalue(thing):
120 """Move the inner value object out of the wrapper"""
120 """Move the inner value object out of the wrapper"""
121 if not util.safehasattr(thing, '_value'):
121 if not util.safehasattr(thing, '_value'):
122 return thing
122 return thing
123 return thing._value
123 return thing._value
124
124
125 def wraphybridvalue(container, key, value):
125 def wraphybridvalue(container, key, value):
126 """Wrap an element of hybrid container to be mappable
126 """Wrap an element of hybrid container to be mappable
127
127
128 The key is passed to the makemap function of the given container, which
128 The key is passed to the makemap function of the given container, which
129 should be an item generated by iter(container).
129 should be an item generated by iter(container).
130 """
130 """
131 makemap = getattr(container, '_makemap', None)
131 makemap = getattr(container, '_makemap', None)
132 if makemap is None:
132 if makemap is None:
133 return value
133 return value
134 if util.safehasattr(value, '_makemap'):
134 if util.safehasattr(value, '_makemap'):
135 # a nested hybrid list/dict, which has its own way of map operation
135 # a nested hybrid list/dict, which has its own way of map operation
136 return value
136 return value
137 return _mappable(None, key, value, makemap)
137 return _mappable(None, key, value, makemap)
138
138
139 def showdict(name, data, mapping, plural=None, key='key', value='value',
139 def showdict(name, data, mapping, plural=None, key='key', value='value',
140 fmt='%s=%s', separator=' '):
140 fmt='%s=%s', separator=' '):
141 c = [{key: k, value: v} for k, v in data.iteritems()]
141 c = [{key: k, value: v} for k, v in data.iteritems()]
142 f = _showlist(name, c, mapping, plural, separator)
142 f = _showlist(name, c, mapping, plural, separator)
143 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
143 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
144
144
145 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
145 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
146 if not element:
146 if not element:
147 element = name
147 element = name
148 f = _showlist(name, values, mapping, plural, separator)
148 f = _showlist(name, values, mapping, plural, separator)
149 return hybridlist(values, name=element, gen=f)
149 return hybridlist(values, name=element, gen=f)
150
150
151 def _showlist(name, values, mapping, plural=None, separator=' '):
151 def _showlist(name, values, mapping, plural=None, separator=' '):
152 '''expand set of values.
152 '''expand set of values.
153 name is name of key in template map.
153 name is name of key in template map.
154 values is list of strings or dicts.
154 values is list of strings or dicts.
155 plural is plural of name, if not simply name + 's'.
155 plural is plural of name, if not simply name + 's'.
156 separator is used to join values as a string
156 separator is used to join values as a string
157
157
158 expansion works like this, given name 'foo'.
158 expansion works like this, given name 'foo'.
159
159
160 if values is empty, expand 'no_foos'.
160 if values is empty, expand 'no_foos'.
161
161
162 if 'foo' not in template map, return values as a string,
162 if 'foo' not in template map, return values as a string,
163 joined by 'separator'.
163 joined by 'separator'.
164
164
165 expand 'start_foos'.
165 expand 'start_foos'.
166
166
167 for each value, expand 'foo'. if 'last_foo' in template
167 for each value, expand 'foo'. if 'last_foo' in template
168 map, expand it instead of 'foo' for last key.
168 map, expand it instead of 'foo' for last key.
169
169
170 expand 'end_foos'.
170 expand 'end_foos'.
171 '''
171 '''
172 templ = mapping['templ']
172 templ = mapping['templ']
173 strmapping = pycompat.strkwargs(mapping)
173 strmapping = pycompat.strkwargs(mapping)
174 if not plural:
174 if not plural:
175 plural = name + 's'
175 plural = name + 's'
176 if not values:
176 if not values:
177 noname = 'no_' + plural
177 noname = 'no_' + plural
178 if noname in templ:
178 if noname in templ:
179 yield templ(noname, **strmapping)
179 yield templ(noname, **strmapping)
180 return
180 return
181 if name not in templ:
181 if name not in templ:
182 if isinstance(values[0], bytes):
182 if isinstance(values[0], bytes):
183 yield separator.join(values)
183 yield separator.join(values)
184 else:
184 else:
185 for v in values:
185 for v in values:
186 yield dict(v, **strmapping)
186 yield dict(v, **strmapping)
187 return
187 return
188 startname = 'start_' + plural
188 startname = 'start_' + plural
189 if startname in templ:
189 if startname in templ:
190 yield templ(startname, **strmapping)
190 yield templ(startname, **strmapping)
191 vmapping = mapping.copy()
191 vmapping = mapping.copy()
192 def one(v, tag=name):
192 def one(v, tag=name):
193 try:
193 try:
194 vmapping.update(v)
194 vmapping.update(v)
195 # Python 2 raises ValueError if the type of v is wrong. Python
195 # Python 2 raises ValueError if the type of v is wrong. Python
196 # 3 raises TypeError.
196 # 3 raises TypeError.
197 except (AttributeError, TypeError, ValueError):
197 except (AttributeError, TypeError, ValueError):
198 try:
198 try:
199 # Python 2 raises ValueError trying to destructure an e.g.
199 # Python 2 raises ValueError trying to destructure an e.g.
200 # bytes. Python 3 raises TypeError.
200 # bytes. Python 3 raises TypeError.
201 for a, b in v:
201 for a, b in v:
202 vmapping[a] = b
202 vmapping[a] = b
203 except (TypeError, ValueError):
203 except (TypeError, ValueError):
204 vmapping[name] = v
204 vmapping[name] = v
205 return templ(tag, **pycompat.strkwargs(vmapping))
205 return templ(tag, **pycompat.strkwargs(vmapping))
206 lastname = 'last_' + name
206 lastname = 'last_' + name
207 if lastname in templ:
207 if lastname in templ:
208 last = values.pop()
208 last = values.pop()
209 else:
209 else:
210 last = None
210 last = None
211 for v in values:
211 for v in values:
212 yield one(v)
212 yield one(v)
213 if last is not None:
213 if last is not None:
214 yield one(last, tag=lastname)
214 yield one(last, tag=lastname)
215 endname = 'end_' + plural
215 endname = 'end_' + plural
216 if endname in templ:
216 if endname in templ:
217 yield templ(endname, **strmapping)
217 yield templ(endname, **strmapping)
218
218
219 def getfiles(repo, ctx, revcache):
219 def getfiles(repo, ctx, revcache):
220 if 'files' not in revcache:
220 if 'files' not in revcache:
221 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
221 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
222 return revcache['files']
222 return revcache['files']
223
223
224 def getlatesttags(repo, ctx, cache, pattern=None):
224 def getlatesttags(repo, ctx, cache, pattern=None):
225 '''return date, distance and name for the latest tag of rev'''
225 '''return date, distance and name for the latest tag of rev'''
226
226
227 cachename = 'latesttags'
227 cachename = 'latesttags'
228 if pattern is not None:
228 if pattern is not None:
229 cachename += '-' + pattern
229 cachename += '-' + pattern
230 match = util.stringmatcher(pattern)[2]
230 match = util.stringmatcher(pattern)[2]
231 else:
231 else:
232 match = util.always
232 match = util.always
233
233
234 if cachename not in cache:
234 if cachename not in cache:
235 # Cache mapping from rev to a tuple with tag date, tag
235 # Cache mapping from rev to a tuple with tag date, tag
236 # distance and tag name
236 # distance and tag name
237 cache[cachename] = {-1: (0, 0, ['null'])}
237 cache[cachename] = {-1: (0, 0, ['null'])}
238 latesttags = cache[cachename]
238 latesttags = cache[cachename]
239
239
240 rev = ctx.rev()
240 rev = ctx.rev()
241 todo = [rev]
241 todo = [rev]
242 while todo:
242 while todo:
243 rev = todo.pop()
243 rev = todo.pop()
244 if rev in latesttags:
244 if rev in latesttags:
245 continue
245 continue
246 ctx = repo[rev]
246 ctx = repo[rev]
247 tags = [t for t in ctx.tags()
247 tags = [t for t in ctx.tags()
248 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
248 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
249 and match(t))]
249 and match(t))]
250 if tags:
250 if tags:
251 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
251 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
252 continue
252 continue
253 try:
253 try:
254 ptags = [latesttags[p.rev()] for p in ctx.parents()]
254 ptags = [latesttags[p.rev()] for p in ctx.parents()]
255 if len(ptags) > 1:
255 if len(ptags) > 1:
256 if ptags[0][2] == ptags[1][2]:
256 if ptags[0][2] == ptags[1][2]:
257 # The tuples are laid out so the right one can be found by
257 # The tuples are laid out so the right one can be found by
258 # comparison in this case.
258 # comparison in this case.
259 pdate, pdist, ptag = max(ptags)
259 pdate, pdist, ptag = max(ptags)
260 else:
260 else:
261 def key(x):
261 def key(x):
262 changessincetag = len(repo.revs('only(%d, %s)',
262 changessincetag = len(repo.revs('only(%d, %s)',
263 ctx.rev(), x[2][0]))
263 ctx.rev(), x[2][0]))
264 # Smallest number of changes since tag wins. Date is
264 # Smallest number of changes since tag wins. Date is
265 # used as tiebreaker.
265 # used as tiebreaker.
266 return [-changessincetag, x[0]]
266 return [-changessincetag, x[0]]
267 pdate, pdist, ptag = max(ptags, key=key)
267 pdate, pdist, ptag = max(ptags, key=key)
268 else:
268 else:
269 pdate, pdist, ptag = ptags[0]
269 pdate, pdist, ptag = ptags[0]
270 except KeyError:
270 except KeyError:
271 # Cache miss - recurse
271 # Cache miss - recurse
272 todo.append(rev)
272 todo.append(rev)
273 todo.extend(p.rev() for p in ctx.parents())
273 todo.extend(p.rev() for p in ctx.parents())
274 continue
274 continue
275 latesttags[rev] = pdate, pdist + 1, ptag
275 latesttags[rev] = pdate, pdist + 1, ptag
276 return latesttags[rev]
276 return latesttags[rev]
277
277
278 def getrenamedfn(repo, endrev=None):
278 def getrenamedfn(repo, endrev=None):
279 rcache = {}
279 rcache = {}
280 if endrev is None:
280 if endrev is None:
281 endrev = len(repo)
281 endrev = len(repo)
282
282
283 def getrenamed(fn, rev):
283 def getrenamed(fn, rev):
284 '''looks up all renames for a file (up to endrev) the first
284 '''looks up all renames for a file (up to endrev) the first
285 time the file is given. It indexes on the changerev and only
285 time the file is given. It indexes on the changerev and only
286 parses the manifest if linkrev != changerev.
286 parses the manifest if linkrev != changerev.
287 Returns rename info for fn at changerev rev.'''
287 Returns rename info for fn at changerev rev.'''
288 if fn not in rcache:
288 if fn not in rcache:
289 rcache[fn] = {}
289 rcache[fn] = {}
290 fl = repo.file(fn)
290 fl = repo.file(fn)
291 for i in fl:
291 for i in fl:
292 lr = fl.linkrev(i)
292 lr = fl.linkrev(i)
293 renamed = fl.renamed(fl.node(i))
293 renamed = fl.renamed(fl.node(i))
294 rcache[fn][lr] = renamed
294 rcache[fn][lr] = renamed
295 if lr >= endrev:
295 if lr >= endrev:
296 break
296 break
297 if rev in rcache[fn]:
297 if rev in rcache[fn]:
298 return rcache[fn][rev]
298 return rcache[fn][rev]
299
299
300 # If linkrev != rev (i.e. rev not found in rcache) fallback to
300 # If linkrev != rev (i.e. rev not found in rcache) fallback to
301 # filectx logic.
301 # filectx logic.
302 try:
302 try:
303 return repo[rev][fn].renamed()
303 return repo[rev][fn].renamed()
304 except error.LookupError:
304 except error.LookupError:
305 return None
305 return None
306
306
307 return getrenamed
307 return getrenamed
308
308
309 def getlogcolumns():
309 def getlogcolumns():
310 """Return a dict of log column labels"""
310 """Return a dict of log column labels"""
311 _ = pycompat.identity # temporarily disable gettext
311 _ = pycompat.identity # temporarily disable gettext
312 # i18n: column positioning for "hg log"
312 # i18n: column positioning for "hg log"
313 columns = _('bookmark: %s\n'
313 columns = _('bookmark: %s\n'
314 'branch: %s\n'
314 'branch: %s\n'
315 'changeset: %s\n'
315 'changeset: %s\n'
316 'copies: %s\n'
316 'copies: %s\n'
317 'date: %s\n'
317 'date: %s\n'
318 'extra: %s=%s\n'
318 'extra: %s=%s\n'
319 'files+: %s\n'
319 'files+: %s\n'
320 'files-: %s\n'
320 'files-: %s\n'
321 'files: %s\n'
321 'files: %s\n'
322 'instability: %s\n'
322 'instability: %s\n'
323 'manifest: %s\n'
323 'manifest: %s\n'
324 'obsolete: %s\n'
324 'obsolete: %s\n'
325 'parent: %s\n'
325 'parent: %s\n'
326 'phase: %s\n'
326 'phase: %s\n'
327 'summary: %s\n'
327 'summary: %s\n'
328 'tag: %s\n'
328 'tag: %s\n'
329 'user: %s\n')
329 'user: %s\n')
330 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
330 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
331 i18n._(columns).splitlines(True)))
331 i18n._(columns).splitlines(True)))
332
332
333 # default templates internally used for rendering of lists
333 # default templates internally used for rendering of lists
334 defaulttempl = {
334 defaulttempl = {
335 'parent': '{rev}:{node|formatnode} ',
335 'parent': '{rev}:{node|formatnode} ',
336 'manifest': '{rev}:{node|formatnode}',
336 'manifest': '{rev}:{node|formatnode}',
337 'file_copy': '{name} ({source})',
337 'file_copy': '{name} ({source})',
338 'envvar': '{key}={value}',
338 'envvar': '{key}={value}',
339 'extra': '{key}={value|stringescape}'
339 'extra': '{key}={value|stringescape}'
340 }
340 }
341 # filecopy is preserved for compatibility reasons
341 # filecopy is preserved for compatibility reasons
342 defaulttempl['filecopy'] = defaulttempl['file_copy']
342 defaulttempl['filecopy'] = defaulttempl['file_copy']
343
343
344 # keywords are callables like:
344 # keywords are callables like:
345 # fn(repo, ctx, templ, cache, revcache, **args)
345 # fn(repo, ctx, templ, cache, revcache, **args)
346 # with:
346 # with:
347 # repo - current repository instance
347 # repo - current repository instance
348 # ctx - the changectx being displayed
348 # ctx - the changectx being displayed
349 # templ - the templater instance
349 # templ - the templater instance
350 # cache - a cache dictionary for the whole templater run
350 # cache - a cache dictionary for the whole templater run
351 # revcache - a cache dictionary for the current revision
351 # revcache - a cache dictionary for the current revision
352 keywords = {}
352 keywords = {}
353
353
354 templatekeyword = registrar.templatekeyword(keywords)
354 templatekeyword = registrar.templatekeyword(keywords)
355
355
356 @templatekeyword('author')
356 @templatekeyword('author')
357 def showauthor(repo, ctx, templ, **args):
357 def showauthor(repo, ctx, templ, **args):
358 """String. The unmodified author of the changeset."""
358 """String. The unmodified author of the changeset."""
359 return ctx.user()
359 return ctx.user()
360
360
361 @templatekeyword('bisect')
361 @templatekeyword('bisect')
362 def showbisect(repo, ctx, templ, **args):
362 def showbisect(repo, ctx, templ, **args):
363 """String. The changeset bisection status."""
363 """String. The changeset bisection status."""
364 return hbisect.label(repo, ctx.node())
364 return hbisect.label(repo, ctx.node())
365
365
366 @templatekeyword('branch')
366 @templatekeyword('branch')
367 def showbranch(**args):
367 def showbranch(**args):
368 """String. The name of the branch on which the changeset was
368 """String. The name of the branch on which the changeset was
369 committed.
369 committed.
370 """
370 """
371 return args[r'ctx'].branch()
371 return args[r'ctx'].branch()
372
372
373 @templatekeyword('branches')
373 @templatekeyword('branches')
374 def showbranches(**args):
374 def showbranches(**args):
375 """List of strings. The name of the branch on which the
375 """List of strings. The name of the branch on which the
376 changeset was committed. Will be empty if the branch name was
376 changeset was committed. Will be empty if the branch name was
377 default. (DEPRECATED)
377 default. (DEPRECATED)
378 """
378 """
379 args = pycompat.byteskwargs(args)
379 args = pycompat.byteskwargs(args)
380 branch = args['ctx'].branch()
380 branch = args['ctx'].branch()
381 if branch != 'default':
381 if branch != 'default':
382 return showlist('branch', [branch], args, plural='branches')
382 return showlist('branch', [branch], args, plural='branches')
383 return showlist('branch', [], args, plural='branches')
383 return showlist('branch', [], args, plural='branches')
384
384
385 @templatekeyword('bookmarks')
385 @templatekeyword('bookmarks')
386 def showbookmarks(**args):
386 def showbookmarks(**args):
387 """List of strings. Any bookmarks associated with the
387 """List of strings. Any bookmarks associated with the
388 changeset. Also sets 'active', the name of the active bookmark.
388 changeset. Also sets 'active', the name of the active bookmark.
389 """
389 """
390 args = pycompat.byteskwargs(args)
390 args = pycompat.byteskwargs(args)
391 repo = args['ctx']._repo
391 repo = args['ctx']._repo
392 bookmarks = args['ctx'].bookmarks()
392 bookmarks = args['ctx'].bookmarks()
393 active = repo._activebookmark
393 active = repo._activebookmark
394 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
394 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
395 f = _showlist('bookmark', bookmarks, args)
395 f = _showlist('bookmark', bookmarks, args)
396 return _hybrid(f, bookmarks, makemap, pycompat.identity)
396 return _hybrid(f, bookmarks, makemap, pycompat.identity)
397
397
398 @templatekeyword('children')
398 @templatekeyword('children')
399 def showchildren(**args):
399 def showchildren(**args):
400 """List of strings. The children of the changeset."""
400 """List of strings. The children of the changeset."""
401 args = pycompat.byteskwargs(args)
401 args = pycompat.byteskwargs(args)
402 ctx = args['ctx']
402 ctx = args['ctx']
403 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
403 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
404 return showlist('children', childrevs, args, element='child')
404 return showlist('children', childrevs, args, element='child')
405
405
406 # Deprecated, but kept alive for help generation a purpose.
406 # Deprecated, but kept alive for help generation a purpose.
407 @templatekeyword('currentbookmark')
407 @templatekeyword('currentbookmark')
408 def showcurrentbookmark(**args):
408 def showcurrentbookmark(**args):
409 """String. The active bookmark, if it is associated with the changeset.
409 """String. The active bookmark, if it is associated with the changeset.
410 (DEPRECATED)"""
410 (DEPRECATED)"""
411 return showactivebookmark(**args)
411 return showactivebookmark(**args)
412
412
413 @templatekeyword('activebookmark')
413 @templatekeyword('activebookmark')
414 def showactivebookmark(**args):
414 def showactivebookmark(**args):
415 """String. The active bookmark, if it is associated with the changeset."""
415 """String. The active bookmark, if it is associated with the changeset."""
416 active = args[r'repo']._activebookmark
416 active = args[r'repo']._activebookmark
417 if active and active in args[r'ctx'].bookmarks():
417 if active and active in args[r'ctx'].bookmarks():
418 return active
418 return active
419 return ''
419 return ''
420
420
421 @templatekeyword('date')
421 @templatekeyword('date')
422 def showdate(repo, ctx, templ, **args):
422 def showdate(repo, ctx, templ, **args):
423 """Date information. The date when the changeset was committed."""
423 """Date information. The date when the changeset was committed."""
424 return ctx.date()
424 return ctx.date()
425
425
426 @templatekeyword('desc')
426 @templatekeyword('desc')
427 def showdescription(repo, ctx, templ, **args):
427 def showdescription(repo, ctx, templ, **args):
428 """String. The text of the changeset description."""
428 """String. The text of the changeset description."""
429 s = ctx.description()
429 s = ctx.description()
430 if isinstance(s, encoding.localstr):
430 if isinstance(s, encoding.localstr):
431 # try hard to preserve utf-8 bytes
431 # try hard to preserve utf-8 bytes
432 return encoding.tolocal(encoding.fromlocal(s).strip())
432 return encoding.tolocal(encoding.fromlocal(s).strip())
433 else:
433 else:
434 return s.strip()
434 return s.strip()
435
435
436 @templatekeyword('diffstat')
436 @templatekeyword('diffstat')
437 def showdiffstat(repo, ctx, templ, **args):
437 def showdiffstat(repo, ctx, templ, **args):
438 """String. Statistics of changes with the following format:
438 """String. Statistics of changes with the following format:
439 "modified files: +added/-removed lines"
439 "modified files: +added/-removed lines"
440 """
440 """
441 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
441 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
442 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
442 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
443 return '%s: +%s/-%s' % (len(stats), adds, removes)
443 return '%s: +%s/-%s' % (len(stats), adds, removes)
444
444
445 @templatekeyword('envvars')
445 @templatekeyword('envvars')
446 def showenvvars(repo, **args):
446 def showenvvars(repo, **args):
447 """A dictionary of environment variables. (EXPERIMENTAL)"""
447 """A dictionary of environment variables. (EXPERIMENTAL)"""
448 args = pycompat.byteskwargs(args)
448 args = pycompat.byteskwargs(args)
449 env = repo.ui.exportableenviron()
449 env = repo.ui.exportableenviron()
450 env = util.sortdict((k, env[k]) for k in sorted(env))
450 env = util.sortdict((k, env[k]) for k in sorted(env))
451 return showdict('envvar', env, args, plural='envvars')
451 return showdict('envvar', env, args, plural='envvars')
452
452
453 @templatekeyword('extras')
453 @templatekeyword('extras')
454 def showextras(**args):
454 def showextras(**args):
455 """List of dicts with key, value entries of the 'extras'
455 """List of dicts with key, value entries of the 'extras'
456 field of this changeset."""
456 field of this changeset."""
457 args = pycompat.byteskwargs(args)
457 args = pycompat.byteskwargs(args)
458 extras = args['ctx'].extra()
458 extras = args['ctx'].extra()
459 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
459 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
460 makemap = lambda k: {'key': k, 'value': extras[k]}
460 makemap = lambda k: {'key': k, 'value': extras[k]}
461 c = [makemap(k) for k in extras]
461 c = [makemap(k) for k in extras]
462 f = _showlist('extra', c, args, plural='extras')
462 f = _showlist('extra', c, args, plural='extras')
463 return _hybrid(f, extras, makemap,
463 return _hybrid(f, extras, makemap,
464 lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
464 lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
465
465
466 @templatekeyword('file_adds')
466 @templatekeyword('file_adds')
467 def showfileadds(**args):
467 def showfileadds(**args):
468 """List of strings. Files added by this changeset."""
468 """List of strings. Files added by this changeset."""
469 args = pycompat.byteskwargs(args)
469 args = pycompat.byteskwargs(args)
470 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
470 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
471 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
471 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
472 element='file')
472 element='file')
473
473
474 @templatekeyword('file_copies')
474 @templatekeyword('file_copies')
475 def showfilecopies(**args):
475 def showfilecopies(**args):
476 """List of strings. Files copied in this changeset with
476 """List of strings. Files copied in this changeset with
477 their sources.
477 their sources.
478 """
478 """
479 args = pycompat.byteskwargs(args)
479 args = pycompat.byteskwargs(args)
480 cache, ctx = args['cache'], args['ctx']
480 cache, ctx = args['cache'], args['ctx']
481 copies = args['revcache'].get('copies')
481 copies = args['revcache'].get('copies')
482 if copies is None:
482 if copies is None:
483 if 'getrenamed' not in cache:
483 if 'getrenamed' not in cache:
484 cache['getrenamed'] = getrenamedfn(args['repo'])
484 cache['getrenamed'] = getrenamedfn(args['repo'])
485 copies = []
485 copies = []
486 getrenamed = cache['getrenamed']
486 getrenamed = cache['getrenamed']
487 for fn in ctx.files():
487 for fn in ctx.files():
488 rename = getrenamed(fn, ctx.rev())
488 rename = getrenamed(fn, ctx.rev())
489 if rename:
489 if rename:
490 copies.append((fn, rename[0]))
490 copies.append((fn, rename[0]))
491
491
492 copies = util.sortdict(copies)
492 copies = util.sortdict(copies)
493 return showdict('file_copy', copies, args, plural='file_copies',
493 return showdict('file_copy', copies, args, plural='file_copies',
494 key='name', value='source', fmt='%s (%s)')
494 key='name', value='source', fmt='%s (%s)')
495
495
496 # showfilecopiesswitch() displays file copies only if copy records are
496 # showfilecopiesswitch() displays file copies only if copy records are
497 # provided before calling the templater, usually with a --copies
497 # provided before calling the templater, usually with a --copies
498 # command line switch.
498 # command line switch.
499 @templatekeyword('file_copies_switch')
499 @templatekeyword('file_copies_switch')
500 def showfilecopiesswitch(**args):
500 def showfilecopiesswitch(**args):
501 """List of strings. Like "file_copies" but displayed
501 """List of strings. Like "file_copies" but displayed
502 only if the --copied switch is set.
502 only if the --copied switch is set.
503 """
503 """
504 args = pycompat.byteskwargs(args)
504 args = pycompat.byteskwargs(args)
505 copies = args['revcache'].get('copies') or []
505 copies = args['revcache'].get('copies') or []
506 copies = util.sortdict(copies)
506 copies = util.sortdict(copies)
507 return showdict('file_copy', copies, args, plural='file_copies',
507 return showdict('file_copy', copies, args, plural='file_copies',
508 key='name', value='source', fmt='%s (%s)')
508 key='name', value='source', fmt='%s (%s)')
509
509
510 @templatekeyword('file_dels')
510 @templatekeyword('file_dels')
511 def showfiledels(**args):
511 def showfiledels(**args):
512 """List of strings. Files removed by this changeset."""
512 """List of strings. Files removed by this changeset."""
513 args = pycompat.byteskwargs(args)
513 args = pycompat.byteskwargs(args)
514 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
514 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
515 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
515 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
516 element='file')
516 element='file')
517
517
518 @templatekeyword('file_mods')
518 @templatekeyword('file_mods')
519 def showfilemods(**args):
519 def showfilemods(**args):
520 """List of strings. Files modified by this changeset."""
520 """List of strings. Files modified by this changeset."""
521 args = pycompat.byteskwargs(args)
521 args = pycompat.byteskwargs(args)
522 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
522 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
523 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
523 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
524 element='file')
524 element='file')
525
525
526 @templatekeyword('files')
526 @templatekeyword('files')
527 def showfiles(**args):
527 def showfiles(**args):
528 """List of strings. All files modified, added, or removed by this
528 """List of strings. All files modified, added, or removed by this
529 changeset.
529 changeset.
530 """
530 """
531 args = pycompat.byteskwargs(args)
531 args = pycompat.byteskwargs(args)
532 return showlist('file', args['ctx'].files(), args)
532 return showlist('file', args['ctx'].files(), args)
533
533
534 @templatekeyword('graphnode')
534 @templatekeyword('graphnode')
535 def showgraphnode(repo, ctx, **args):
535 def showgraphnode(repo, ctx, **args):
536 """String. The character representing the changeset node in an ASCII
536 """String. The character representing the changeset node in an ASCII
537 revision graph."""
537 revision graph."""
538 wpnodes = repo.dirstate.parents()
538 wpnodes = repo.dirstate.parents()
539 if wpnodes[1] == nullid:
539 if wpnodes[1] == nullid:
540 wpnodes = wpnodes[:1]
540 wpnodes = wpnodes[:1]
541 if ctx.node() in wpnodes:
541 if ctx.node() in wpnodes:
542 return '@'
542 return '@'
543 elif ctx.obsolete():
543 elif ctx.obsolete():
544 return 'x'
544 return 'x'
545 elif ctx.isunstable():
545 elif ctx.isunstable():
546 return '*'
546 return '*'
547 elif ctx.closesbranch():
547 elif ctx.closesbranch():
548 return '_'
548 return '_'
549 else:
549 else:
550 return 'o'
550 return 'o'
551
551
552 @templatekeyword('graphwidth')
552 @templatekeyword('graphwidth')
553 def showgraphwidth(repo, ctx, templ, **args):
553 def showgraphwidth(repo, ctx, templ, **args):
554 """Integer. The width of the graph drawn by 'log --graph' or zero."""
554 """Integer. The width of the graph drawn by 'log --graph' or zero."""
555 # The value args['graphwidth'] will be this function, so we use an internal
555 # The value args['graphwidth'] will be this function, so we use an internal
556 # name to pass the value through props into this function.
556 # name to pass the value through props into this function.
557 return args.get('_graphwidth', 0)
557 return args.get('_graphwidth', 0)
558
558
559 @templatekeyword('index')
559 @templatekeyword('index')
560 def showindex(**args):
560 def showindex(**args):
561 """Integer. The current iteration of the loop. (0 indexed)"""
561 """Integer. The current iteration of the loop. (0 indexed)"""
562 # just hosts documentation; should be overridden by template mapping
562 # just hosts documentation; should be overridden by template mapping
563 raise error.Abort(_("can't use index in this context"))
563 raise error.Abort(_("can't use index in this context"))
564
564
565 @templatekeyword('latesttag')
565 @templatekeyword('latesttag')
566 def showlatesttag(**args):
566 def showlatesttag(**args):
567 """List of strings. The global tags on the most recent globally
567 """List of strings. The global tags on the most recent globally
568 tagged ancestor of this changeset. If no such tags exist, the list
568 tagged ancestor of this changeset. If no such tags exist, the list
569 consists of the single string "null".
569 consists of the single string "null".
570 """
570 """
571 return showlatesttags(None, **args)
571 return showlatesttags(None, **args)
572
572
573 def showlatesttags(pattern, **args):
573 def showlatesttags(pattern, **args):
574 """helper method for the latesttag keyword and function"""
574 """helper method for the latesttag keyword and function"""
575 args = pycompat.byteskwargs(args)
575 args = pycompat.byteskwargs(args)
576 repo, ctx = args['repo'], args['ctx']
576 repo, ctx = args['repo'], args['ctx']
577 cache = args['cache']
577 cache = args['cache']
578 latesttags = getlatesttags(repo, ctx, cache, pattern)
578 latesttags = getlatesttags(repo, ctx, cache, pattern)
579
579
580 # latesttag[0] is an implementation detail for sorting csets on different
580 # latesttag[0] is an implementation detail for sorting csets on different
581 # branches in a stable manner- it is the date the tagged cset was created,
581 # branches in a stable manner- it is the date the tagged cset was created,
582 # not the date the tag was created. Therefore it isn't made visible here.
582 # not the date the tag was created. Therefore it isn't made visible here.
583 makemap = lambda v: {
583 makemap = lambda v: {
584 'changes': _showchangessincetag,
584 'changes': _showchangessincetag,
585 'distance': latesttags[1],
585 'distance': latesttags[1],
586 'latesttag': v, # BC with {latesttag % '{latesttag}'}
586 'latesttag': v, # BC with {latesttag % '{latesttag}'}
587 'tag': v
587 'tag': v
588 }
588 }
589
589
590 tags = latesttags[2]
590 tags = latesttags[2]
591 f = _showlist('latesttag', tags, args, separator=':')
591 f = _showlist('latesttag', tags, args, separator=':')
592 return _hybrid(f, tags, makemap, pycompat.identity)
592 return _hybrid(f, tags, makemap, pycompat.identity)
593
593
594 @templatekeyword('latesttagdistance')
594 @templatekeyword('latesttagdistance')
595 def showlatesttagdistance(repo, ctx, templ, cache, **args):
595 def showlatesttagdistance(repo, ctx, templ, cache, **args):
596 """Integer. Longest path to the latest tag."""
596 """Integer. Longest path to the latest tag."""
597 return getlatesttags(repo, ctx, cache)[1]
597 return getlatesttags(repo, ctx, cache)[1]
598
598
599 @templatekeyword('changessincelatesttag')
599 @templatekeyword('changessincelatesttag')
600 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
600 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
601 """Integer. All ancestors not in the latest tag."""
601 """Integer. All ancestors not in the latest tag."""
602 latesttag = getlatesttags(repo, ctx, cache)[2][0]
602 latesttag = getlatesttags(repo, ctx, cache)[2][0]
603
603
604 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
604 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
605
605
606 def _showchangessincetag(repo, ctx, **args):
606 def _showchangessincetag(repo, ctx, **args):
607 offset = 0
607 offset = 0
608 revs = [ctx.rev()]
608 revs = [ctx.rev()]
609 tag = args[r'tag']
609 tag = args[r'tag']
610
610
611 # The only() revset doesn't currently support wdir()
611 # The only() revset doesn't currently support wdir()
612 if ctx.rev() is None:
612 if ctx.rev() is None:
613 offset = 1
613 offset = 1
614 revs = [p.rev() for p in ctx.parents()]
614 revs = [p.rev() for p in ctx.parents()]
615
615
616 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
616 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
617
617
618 @templatekeyword('manifest')
618 @templatekeyword('manifest')
619 def showmanifest(**args):
619 def showmanifest(**args):
620 repo, ctx, templ = args[r'repo'], args[r'ctx'], args[r'templ']
620 repo, ctx, templ = args[r'repo'], args[r'ctx'], args[r'templ']
621 mnode = ctx.manifestnode()
621 mnode = ctx.manifestnode()
622 if mnode is None:
622 if mnode is None:
623 # just avoid crash, we might want to use the 'ff...' hash in future
623 # just avoid crash, we might want to use the 'ff...' hash in future
624 return
624 return
625 mrev = repo.manifestlog._revlog.rev(mnode)
625 mrev = repo.manifestlog._revlog.rev(mnode)
626 mhex = hex(mnode)
626 mhex = hex(mnode)
627 args = args.copy()
627 args = args.copy()
628 args.update({r'rev': mrev, r'node': mhex})
628 args.update({r'rev': mrev, r'node': mhex})
629 f = templ('manifest', **args)
629 f = templ('manifest', **args)
630 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
630 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
631 # rev and node are completely different from changeset's.
631 # rev and node are completely different from changeset's.
632 return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
632 return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
633
633
634 @templatekeyword('obsfate')
634 @templatekeyword('obsfate')
635 def showobsfate(**args):
635 def showobsfate(**args):
636 # this function returns a list containing pre-formatted obsfate strings.
636 # this function returns a list containing pre-formatted obsfate strings.
637 #
637 #
638 # This function will be replaced by templates fragments when we will have
638 # This function will be replaced by templates fragments when we will have
639 # the verbosity templatekw available.
639 # the verbosity templatekw available.
640 succsandmarkers = showsuccsandmarkers(**args)
640 succsandmarkers = showsuccsandmarkers(**args)
641
641
642 args = pycompat.byteskwargs(args)
642 args = pycompat.byteskwargs(args)
643 ui = args['ui']
643 ui = args['ui']
644
644
645 values = []
645 values = []
646
646
647 for x in succsandmarkers:
647 for x in succsandmarkers:
648 values.append(obsutil.obsfateprinter(x['successors'], x['markers'], ui))
648 values.append(obsutil.obsfateprinter(x['successors'], x['markers'], ui))
649
649
650 return showlist("fate", values, args)
650 return showlist("fate", values, args)
651
651
652 def shownames(namespace, **args):
652 def shownames(namespace, **args):
653 """helper method to generate a template keyword for a namespace"""
653 """helper method to generate a template keyword for a namespace"""
654 args = pycompat.byteskwargs(args)
654 args = pycompat.byteskwargs(args)
655 ctx = args['ctx']
655 ctx = args['ctx']
656 repo = ctx.repo()
656 repo = ctx.repo()
657 ns = repo.names[namespace]
657 ns = repo.names[namespace]
658 names = ns.names(repo, ctx.node())
658 names = ns.names(repo, ctx.node())
659 return showlist(ns.templatename, names, args, plural=namespace)
659 return showlist(ns.templatename, names, args, plural=namespace)
660
660
661 @templatekeyword('namespaces')
661 @templatekeyword('namespaces')
662 def shownamespaces(**args):
662 def shownamespaces(**args):
663 """Dict of lists. Names attached to this changeset per
663 """Dict of lists. Names attached to this changeset per
664 namespace."""
664 namespace."""
665 args = pycompat.byteskwargs(args)
665 args = pycompat.byteskwargs(args)
666 ctx = args['ctx']
666 ctx = args['ctx']
667 repo = ctx.repo()
667 repo = ctx.repo()
668
668
669 namespaces = util.sortdict()
669 namespaces = util.sortdict()
670 def makensmapfn(ns):
670 def makensmapfn(ns):
671 # 'name' for iterating over namespaces, templatename for local reference
671 # 'name' for iterating over namespaces, templatename for local reference
672 return lambda v: {'name': v, ns.templatename: v}
672 return lambda v: {'name': v, ns.templatename: v}
673
673
674 for k, ns in repo.names.iteritems():
674 for k, ns in repo.names.iteritems():
675 names = ns.names(repo, ctx.node())
675 names = ns.names(repo, ctx.node())
676 f = _showlist('name', names, args)
676 f = _showlist('name', names, args)
677 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
677 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
678
678
679 f = _showlist('namespace', list(namespaces), args)
679 f = _showlist('namespace', list(namespaces), args)
680
680
681 def makemap(ns):
681 def makemap(ns):
682 return {
682 return {
683 'namespace': ns,
683 'namespace': ns,
684 'names': namespaces[ns],
684 'names': namespaces[ns],
685 'builtin': repo.names[ns].builtin,
685 'builtin': repo.names[ns].builtin,
686 'colorname': repo.names[ns].colorname,
686 'colorname': repo.names[ns].colorname,
687 }
687 }
688
688
689 return _hybrid(f, namespaces, makemap, pycompat.identity)
689 return _hybrid(f, namespaces, makemap, pycompat.identity)
690
690
691 @templatekeyword('node')
691 @templatekeyword('node')
692 def shownode(repo, ctx, templ, **args):
692 def shownode(repo, ctx, templ, **args):
693 """String. The changeset identification hash, as a 40 hexadecimal
693 """String. The changeset identification hash, as a 40 hexadecimal
694 digit string.
694 digit string.
695 """
695 """
696 return ctx.hex()
696 return ctx.hex()
697
697
698 @templatekeyword('obsolete')
698 @templatekeyword('obsolete')
699 def showobsolete(repo, ctx, templ, **args):
699 def showobsolete(repo, ctx, templ, **args):
700 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
700 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
701 if ctx.obsolete():
701 if ctx.obsolete():
702 return 'obsolete'
702 return 'obsolete'
703 return ''
703 return ''
704
704
705 @templatekeyword('peerurls')
705 @templatekeyword('peerurls')
706 def showpeerurls(repo, **args):
706 def showpeerurls(repo, **args):
707 """A dictionary of repository locations defined in the [paths] section
707 """A dictionary of repository locations defined in the [paths] section
708 of your configuration file."""
708 of your configuration file."""
709 # see commands.paths() for naming of dictionary keys
709 # see commands.paths() for naming of dictionary keys
710 paths = repo.ui.paths
710 paths = repo.ui.paths
711 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
711 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
712 def makemap(k):
712 def makemap(k):
713 p = paths[k]
713 p = paths[k]
714 d = {'name': k, 'url': p.rawloc}
714 d = {'name': k, 'url': p.rawloc}
715 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
715 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
716 return d
716 return d
717 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
717 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
718
718
719 @templatekeyword("predecessors")
719 @templatekeyword("predecessors")
720 def showpredecessors(repo, ctx, **args):
720 def showpredecessors(repo, ctx, **args):
721 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
721 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
722 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
722 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
723 predecessors = map(hex, predecessors)
723 predecessors = map(hex, predecessors)
724
724
725 return _hybrid(None, predecessors,
725 return _hybrid(None, predecessors,
726 lambda x: {'ctx': repo[x], 'revcache': {}},
726 lambda x: {'ctx': repo[x], 'revcache': {}},
727 lambda x: scmutil.formatchangeid(repo[x]))
727 lambda x: scmutil.formatchangeid(repo[x]))
728
728
729 @templatekeyword('reporoot')
730 def showreporoot(repo, **args):
731 """String. The root directory of the current repository."""
732 return repo.root
733
729 @templatekeyword("successorssets")
734 @templatekeyword("successorssets")
730 def showsuccessorssets(repo, ctx, **args):
735 def showsuccessorssets(repo, ctx, **args):
731 """Returns a string of sets of successors for a changectx. Format used
736 """Returns a string of sets of successors for a changectx. Format used
732 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
737 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
733 while also diverged into ctx3. (EXPERIMENTAL)"""
738 while also diverged into ctx3. (EXPERIMENTAL)"""
734 if not ctx.obsolete():
739 if not ctx.obsolete():
735 return ''
740 return ''
736 args = pycompat.byteskwargs(args)
741 args = pycompat.byteskwargs(args)
737
742
738 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
743 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
739 ssets = [[hex(n) for n in ss] for ss in ssets]
744 ssets = [[hex(n) for n in ss] for ss in ssets]
740
745
741 data = []
746 data = []
742 for ss in ssets:
747 for ss in ssets:
743 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
748 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
744 lambda x: scmutil.formatchangeid(repo[x]))
749 lambda x: scmutil.formatchangeid(repo[x]))
745 data.append(h)
750 data.append(h)
746
751
747 # Format the successorssets
752 # Format the successorssets
748 def render(d):
753 def render(d):
749 t = []
754 t = []
750 for i in d.gen():
755 for i in d.gen():
751 t.append(i)
756 t.append(i)
752 return "".join(t)
757 return "".join(t)
753
758
754 def gen(data):
759 def gen(data):
755 yield "; ".join(render(d) for d in data)
760 yield "; ".join(render(d) for d in data)
756
761
757 return _hybrid(gen(data), data, lambda x: {'successorset': x},
762 return _hybrid(gen(data), data, lambda x: {'successorset': x},
758 pycompat.identity)
763 pycompat.identity)
759
764
760 @templatekeyword("succsandmarkers")
765 @templatekeyword("succsandmarkers")
761 def showsuccsandmarkers(repo, ctx, **args):
766 def showsuccsandmarkers(repo, ctx, **args):
762 """Returns a list of dict for each final successor of ctx. The dict
767 """Returns a list of dict for each final successor of ctx. The dict
763 contains successors node id in "successors" keys and the list of
768 contains successors node id in "successors" keys and the list of
764 obs-markers from ctx to the set of successors in "markers".
769 obs-markers from ctx to the set of successors in "markers".
765 (EXPERIMENTAL)
770 (EXPERIMENTAL)
766 """
771 """
767
772
768 values = obsutil.successorsandmarkers(repo, ctx)
773 values = obsutil.successorsandmarkers(repo, ctx)
769
774
770 if values is None:
775 if values is None:
771 values = []
776 values = []
772
777
773 # Format successors and markers to avoid exposing binary to templates
778 # Format successors and markers to avoid exposing binary to templates
774 data = []
779 data = []
775 for i in values:
780 for i in values:
776 # Format successors
781 # Format successors
777 successors = i['successors']
782 successors = i['successors']
778
783
779 successors = [hex(n) for n in successors]
784 successors = [hex(n) for n in successors]
780 successors = _hybrid(None, successors,
785 successors = _hybrid(None, successors,
781 lambda x: {'ctx': repo[x], 'revcache': {}},
786 lambda x: {'ctx': repo[x], 'revcache': {}},
782 lambda x: scmutil.formatchangeid(repo[x]))
787 lambda x: scmutil.formatchangeid(repo[x]))
783
788
784 # Format markers
789 # Format markers
785 finalmarkers = []
790 finalmarkers = []
786 for m in i['markers']:
791 for m in i['markers']:
787 hexprec = hex(m[0])
792 hexprec = hex(m[0])
788 hexsucs = tuple(hex(n) for n in m[1])
793 hexsucs = tuple(hex(n) for n in m[1])
789 hexparents = None
794 hexparents = None
790 if m[5] is not None:
795 if m[5] is not None:
791 hexparents = tuple(hex(n) for n in m[5])
796 hexparents = tuple(hex(n) for n in m[5])
792 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
797 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
793 finalmarkers.append(newmarker)
798 finalmarkers.append(newmarker)
794
799
795 data.append({'successors': successors, 'markers': finalmarkers})
800 data.append({'successors': successors, 'markers': finalmarkers})
796
801
797 f = _showlist('succsandmarkers', data, args)
802 f = _showlist('succsandmarkers', data, args)
798 return _hybrid(f, data, lambda x: x, pycompat.identity)
803 return _hybrid(f, data, lambda x: x, pycompat.identity)
799
804
800 @templatekeyword('p1rev')
805 @templatekeyword('p1rev')
801 def showp1rev(repo, ctx, templ, **args):
806 def showp1rev(repo, ctx, templ, **args):
802 """Integer. The repository-local revision number of the changeset's
807 """Integer. The repository-local revision number of the changeset's
803 first parent, or -1 if the changeset has no parents."""
808 first parent, or -1 if the changeset has no parents."""
804 return ctx.p1().rev()
809 return ctx.p1().rev()
805
810
806 @templatekeyword('p2rev')
811 @templatekeyword('p2rev')
807 def showp2rev(repo, ctx, templ, **args):
812 def showp2rev(repo, ctx, templ, **args):
808 """Integer. The repository-local revision number of the changeset's
813 """Integer. The repository-local revision number of the changeset's
809 second parent, or -1 if the changeset has no second parent."""
814 second parent, or -1 if the changeset has no second parent."""
810 return ctx.p2().rev()
815 return ctx.p2().rev()
811
816
812 @templatekeyword('p1node')
817 @templatekeyword('p1node')
813 def showp1node(repo, ctx, templ, **args):
818 def showp1node(repo, ctx, templ, **args):
814 """String. The identification hash of the changeset's first parent,
819 """String. The identification hash of the changeset's first parent,
815 as a 40 digit hexadecimal string. If the changeset has no parents, all
820 as a 40 digit hexadecimal string. If the changeset has no parents, all
816 digits are 0."""
821 digits are 0."""
817 return ctx.p1().hex()
822 return ctx.p1().hex()
818
823
819 @templatekeyword('p2node')
824 @templatekeyword('p2node')
820 def showp2node(repo, ctx, templ, **args):
825 def showp2node(repo, ctx, templ, **args):
821 """String. The identification hash of the changeset's second
826 """String. The identification hash of the changeset's second
822 parent, as a 40 digit hexadecimal string. If the changeset has no second
827 parent, as a 40 digit hexadecimal string. If the changeset has no second
823 parent, all digits are 0."""
828 parent, all digits are 0."""
824 return ctx.p2().hex()
829 return ctx.p2().hex()
825
830
826 @templatekeyword('parents')
831 @templatekeyword('parents')
827 def showparents(**args):
832 def showparents(**args):
828 """List of strings. The parents of the changeset in "rev:node"
833 """List of strings. The parents of the changeset in "rev:node"
829 format. If the changeset has only one "natural" parent (the predecessor
834 format. If the changeset has only one "natural" parent (the predecessor
830 revision) nothing is shown."""
835 revision) nothing is shown."""
831 args = pycompat.byteskwargs(args)
836 args = pycompat.byteskwargs(args)
832 repo = args['repo']
837 repo = args['repo']
833 ctx = args['ctx']
838 ctx = args['ctx']
834 pctxs = scmutil.meaningfulparents(repo, ctx)
839 pctxs = scmutil.meaningfulparents(repo, ctx)
835 prevs = [p.rev() for p in pctxs]
840 prevs = [p.rev() for p in pctxs]
836 parents = [[('rev', p.rev()),
841 parents = [[('rev', p.rev()),
837 ('node', p.hex()),
842 ('node', p.hex()),
838 ('phase', p.phasestr())]
843 ('phase', p.phasestr())]
839 for p in pctxs]
844 for p in pctxs]
840 f = _showlist('parent', parents, args)
845 f = _showlist('parent', parents, args)
841 return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
846 return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
842 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
847 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
843
848
844 @templatekeyword('phase')
849 @templatekeyword('phase')
845 def showphase(repo, ctx, templ, **args):
850 def showphase(repo, ctx, templ, **args):
846 """String. The changeset phase name."""
851 """String. The changeset phase name."""
847 return ctx.phasestr()
852 return ctx.phasestr()
848
853
849 @templatekeyword('phaseidx')
854 @templatekeyword('phaseidx')
850 def showphaseidx(repo, ctx, templ, **args):
855 def showphaseidx(repo, ctx, templ, **args):
851 """Integer. The changeset phase index. (ADVANCED)"""
856 """Integer. The changeset phase index. (ADVANCED)"""
852 return ctx.phase()
857 return ctx.phase()
853
858
854 @templatekeyword('rev')
859 @templatekeyword('rev')
855 def showrev(repo, ctx, templ, **args):
860 def showrev(repo, ctx, templ, **args):
856 """Integer. The repository-local changeset revision number."""
861 """Integer. The repository-local changeset revision number."""
857 return scmutil.intrev(ctx)
862 return scmutil.intrev(ctx)
858
863
859 def showrevslist(name, revs, **args):
864 def showrevslist(name, revs, **args):
860 """helper to generate a list of revisions in which a mapped template will
865 """helper to generate a list of revisions in which a mapped template will
861 be evaluated"""
866 be evaluated"""
862 args = pycompat.byteskwargs(args)
867 args = pycompat.byteskwargs(args)
863 repo = args['ctx'].repo()
868 repo = args['ctx'].repo()
864 f = _showlist(name, ['%d' % r for r in revs], args)
869 f = _showlist(name, ['%d' % r for r in revs], args)
865 return _hybrid(f, revs,
870 return _hybrid(f, revs,
866 lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
871 lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
867 pycompat.identity, keytype=int)
872 pycompat.identity, keytype=int)
868
873
869 @templatekeyword('subrepos')
874 @templatekeyword('subrepos')
870 def showsubrepos(**args):
875 def showsubrepos(**args):
871 """List of strings. Updated subrepositories in the changeset."""
876 """List of strings. Updated subrepositories in the changeset."""
872 args = pycompat.byteskwargs(args)
877 args = pycompat.byteskwargs(args)
873 ctx = args['ctx']
878 ctx = args['ctx']
874 substate = ctx.substate
879 substate = ctx.substate
875 if not substate:
880 if not substate:
876 return showlist('subrepo', [], args)
881 return showlist('subrepo', [], args)
877 psubstate = ctx.parents()[0].substate or {}
882 psubstate = ctx.parents()[0].substate or {}
878 subrepos = []
883 subrepos = []
879 for sub in substate:
884 for sub in substate:
880 if sub not in psubstate or substate[sub] != psubstate[sub]:
885 if sub not in psubstate or substate[sub] != psubstate[sub]:
881 subrepos.append(sub) # modified or newly added in ctx
886 subrepos.append(sub) # modified or newly added in ctx
882 for sub in psubstate:
887 for sub in psubstate:
883 if sub not in substate:
888 if sub not in substate:
884 subrepos.append(sub) # removed in ctx
889 subrepos.append(sub) # removed in ctx
885 return showlist('subrepo', sorted(subrepos), args)
890 return showlist('subrepo', sorted(subrepos), args)
886
891
887 # don't remove "showtags" definition, even though namespaces will put
892 # don't remove "showtags" definition, even though namespaces will put
888 # a helper function for "tags" keyword into "keywords" map automatically,
893 # a helper function for "tags" keyword into "keywords" map automatically,
889 # because online help text is built without namespaces initialization
894 # because online help text is built without namespaces initialization
890 @templatekeyword('tags')
895 @templatekeyword('tags')
891 def showtags(**args):
896 def showtags(**args):
892 """List of strings. Any tags associated with the changeset."""
897 """List of strings. Any tags associated with the changeset."""
893 return shownames('tags', **args)
898 return shownames('tags', **args)
894
899
895 @templatekeyword('termwidth')
900 @templatekeyword('termwidth')
896 def showtermwidth(repo, ctx, templ, **args):
901 def showtermwidth(repo, ctx, templ, **args):
897 """Integer. The width of the current terminal."""
902 """Integer. The width of the current terminal."""
898 return repo.ui.termwidth()
903 return repo.ui.termwidth()
899
904
900 @templatekeyword('instabilities')
905 @templatekeyword('instabilities')
901 def showinstabilities(**args):
906 def showinstabilities(**args):
902 """List of strings. Evolution instabilities affecting the changeset.
907 """List of strings. Evolution instabilities affecting the changeset.
903 (EXPERIMENTAL)
908 (EXPERIMENTAL)
904 """
909 """
905 args = pycompat.byteskwargs(args)
910 args = pycompat.byteskwargs(args)
906 return showlist('instability', args['ctx'].instabilities(), args,
911 return showlist('instability', args['ctx'].instabilities(), args,
907 plural='instabilities')
912 plural='instabilities')
908
913
909 @templatekeyword('verbosity')
914 @templatekeyword('verbosity')
910 def showverbosity(ui, **args):
915 def showverbosity(ui, **args):
911 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
916 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
912 or ''."""
917 or ''."""
913 # see logcmdutil.changesettemplater for priority of these flags
918 # see logcmdutil.changesettemplater for priority of these flags
914 if ui.debugflag:
919 if ui.debugflag:
915 return 'debug'
920 return 'debug'
916 elif ui.quiet:
921 elif ui.quiet:
917 return 'quiet'
922 return 'quiet'
918 elif ui.verbose:
923 elif ui.verbose:
919 return 'verbose'
924 return 'verbose'
920 return ''
925 return ''
921
926
922 def loadkeyword(ui, extname, registrarobj):
927 def loadkeyword(ui, extname, registrarobj):
923 """Load template keyword from specified registrarobj
928 """Load template keyword from specified registrarobj
924 """
929 """
925 for name, func in registrarobj._table.iteritems():
930 for name, func in registrarobj._table.iteritems():
926 keywords[name] = func
931 keywords[name] = func
927
932
928 # tell hggettext to extract docstrings from these functions:
933 # tell hggettext to extract docstrings from these functions:
929 i18nfunctions = keywords.values()
934 i18nfunctions = keywords.values()
@@ -1,92 +1,99 b''
1 Create a repository:
1 Create a repository:
2
2
3 $ hg config
3 $ hg config
4 devel.all-warnings=true
4 devel.all-warnings=true
5 devel.default-date=0 0
5 devel.default-date=0 0
6 extensions.fsmonitor= (fsmonitor !)
6 extensions.fsmonitor= (fsmonitor !)
7 largefiles.usercache=$TESTTMP/.cache/largefiles
7 largefiles.usercache=$TESTTMP/.cache/largefiles
8 lfs.usercache=$TESTTMP/.cache/lfs
8 lfs.usercache=$TESTTMP/.cache/lfs
9 ui.slash=True
9 ui.slash=True
10 ui.interactive=False
10 ui.interactive=False
11 ui.mergemarkers=detailed
11 ui.mergemarkers=detailed
12 ui.promptecho=True
12 ui.promptecho=True
13 web.address=localhost
13 web.address=localhost
14 web\.ipv6=(?:True|False) (re)
14 web\.ipv6=(?:True|False) (re)
15 $ hg init t
15 $ hg init t
16 $ cd t
16 $ cd t
17
17
18 Prepare a changeset:
18 Prepare a changeset:
19
19
20 $ echo a > a
20 $ echo a > a
21 $ hg add a
21 $ hg add a
22
22
23 $ hg status
23 $ hg status
24 A a
24 A a
25
25
26 Writes to stdio succeed and fail appropriately
26 Writes to stdio succeed and fail appropriately
27
27
28 #if devfull
28 #if devfull
29 $ hg status 2>/dev/full
29 $ hg status 2>/dev/full
30 A a
30 A a
31
31
32 $ hg status >/dev/full
32 $ hg status >/dev/full
33 abort: No space left on device
33 abort: No space left on device
34 [255]
34 [255]
35 #endif
35 #endif
36
36
37 #if devfull
37 #if devfull
38 $ hg status >/dev/full 2>&1
38 $ hg status >/dev/full 2>&1
39 [255]
39 [255]
40
40
41 $ hg status ENOENT 2>/dev/full
41 $ hg status ENOENT 2>/dev/full
42 [255]
42 [255]
43 #endif
43 #endif
44
44
45 $ hg commit -m test
45 $ hg commit -m test
46
46
47 This command is ancient:
47 This command is ancient:
48
48
49 $ hg history
49 $ hg history
50 changeset: 0:acb14030fe0a
50 changeset: 0:acb14030fe0a
51 tag: tip
51 tag: tip
52 user: test
52 user: test
53 date: Thu Jan 01 00:00:00 1970 +0000
53 date: Thu Jan 01 00:00:00 1970 +0000
54 summary: test
54 summary: test
55
55
56
56
57 Verify that updating to revision 0 via commands.update() works properly
57 Verify that updating to revision 0 via commands.update() works properly
58
58
59 $ cat <<EOF > update_to_rev0.py
59 $ cat <<EOF > update_to_rev0.py
60 > from mercurial import ui, hg, commands
60 > from mercurial import ui, hg, commands
61 > myui = ui.ui.load()
61 > myui = ui.ui.load()
62 > repo = hg.repository(myui, path='.')
62 > repo = hg.repository(myui, path='.')
63 > commands.update(myui, repo, rev=0)
63 > commands.update(myui, repo, rev=0)
64 > EOF
64 > EOF
65 $ hg up null
65 $ hg up null
66 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
66 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
67 $ $PYTHON ./update_to_rev0.py
67 $ $PYTHON ./update_to_rev0.py
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 $ hg identify -n
69 $ hg identify -n
70 0
70 0
71
71
72
72
73 Poke around at hashes:
73 Poke around at hashes:
74
74
75 $ hg manifest --debug
75 $ hg manifest --debug
76 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
76 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
77
77
78 $ hg cat a
78 $ hg cat a
79 a
79 a
80
80
81 Verify should succeed:
81 Verify should succeed:
82
82
83 $ hg verify
83 $ hg verify
84 checking changesets
84 checking changesets
85 checking manifests
85 checking manifests
86 crosschecking files in changesets and manifests
86 crosschecking files in changesets and manifests
87 checking files
87 checking files
88 1 files, 1 changesets, 1 total revisions
88 1 files, 1 changesets, 1 total revisions
89
89
90 Repository root:
91
92 $ hg root
93 $TESTTMP/t
94 $ hg log -l1 -T '{reporoot}\n'
95 $TESTTMP/t
96
90 At the end...
97 At the end...
91
98
92 $ cd ..
99 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now