##// END OF EJS Templates
template: add a `paths` field to all entry in peerurls...
marmoute -
r47955:9c4d30b0 default
parent child Browse files
Show More
@@ -1,1007 +1,1012 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2009 Olivia Mackall <olivia@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 wdirrev,
13 wdirrev,
14 )
14 )
15
15
16 from . import (
16 from . import (
17 diffutil,
17 diffutil,
18 encoding,
18 encoding,
19 error,
19 error,
20 hbisect,
20 hbisect,
21 i18n,
21 i18n,
22 obsutil,
22 obsutil,
23 patch,
23 patch,
24 pycompat,
24 pycompat,
25 registrar,
25 registrar,
26 scmutil,
26 scmutil,
27 templateutil,
27 templateutil,
28 util,
28 util,
29 )
29 )
30 from .utils import (
30 from .utils import (
31 stringutil,
31 stringutil,
32 urlutil,
32 urlutil,
33 )
33 )
34
34
35 _hybrid = templateutil.hybrid
35 _hybrid = templateutil.hybrid
36 hybriddict = templateutil.hybriddict
36 hybriddict = templateutil.hybriddict
37 hybridlist = templateutil.hybridlist
37 hybridlist = templateutil.hybridlist
38 compatdict = templateutil.compatdict
38 compatdict = templateutil.compatdict
39 compatlist = templateutil.compatlist
39 compatlist = templateutil.compatlist
40 _showcompatlist = templateutil._showcompatlist
40 _showcompatlist = templateutil._showcompatlist
41
41
42
42
43 def getlatesttags(context, mapping, pattern=None):
43 def getlatesttags(context, mapping, pattern=None):
44 '''return date, distance and name for the latest tag of rev'''
44 '''return date, distance and name for the latest tag of rev'''
45 repo = context.resource(mapping, b'repo')
45 repo = context.resource(mapping, b'repo')
46 ctx = context.resource(mapping, b'ctx')
46 ctx = context.resource(mapping, b'ctx')
47 cache = context.resource(mapping, b'cache')
47 cache = context.resource(mapping, b'cache')
48
48
49 cachename = b'latesttags'
49 cachename = b'latesttags'
50 if pattern is not None:
50 if pattern is not None:
51 cachename += b'-' + pattern
51 cachename += b'-' + pattern
52 match = stringutil.stringmatcher(pattern)[2]
52 match = stringutil.stringmatcher(pattern)[2]
53 else:
53 else:
54 match = util.always
54 match = util.always
55
55
56 if cachename not in cache:
56 if cachename not in cache:
57 # Cache mapping from rev to a tuple with tag date, tag
57 # Cache mapping from rev to a tuple with tag date, tag
58 # distance and tag name
58 # distance and tag name
59 cache[cachename] = {-1: (0, 0, [b'null'])}
59 cache[cachename] = {-1: (0, 0, [b'null'])}
60 latesttags = cache[cachename]
60 latesttags = cache[cachename]
61
61
62 rev = ctx.rev()
62 rev = ctx.rev()
63 todo = [rev]
63 todo = [rev]
64 while todo:
64 while todo:
65 rev = todo.pop()
65 rev = todo.pop()
66 if rev in latesttags:
66 if rev in latesttags:
67 continue
67 continue
68 ctx = repo[rev]
68 ctx = repo[rev]
69 tags = [
69 tags = [
70 t
70 t
71 for t in ctx.tags()
71 for t in ctx.tags()
72 if (repo.tagtype(t) and repo.tagtype(t) != b'local' and match(t))
72 if (repo.tagtype(t) and repo.tagtype(t) != b'local' and match(t))
73 ]
73 ]
74 if tags:
74 if tags:
75 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
75 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
76 continue
76 continue
77 try:
77 try:
78 ptags = [latesttags[p.rev()] for p in ctx.parents()]
78 ptags = [latesttags[p.rev()] for p in ctx.parents()]
79 if len(ptags) > 1:
79 if len(ptags) > 1:
80 if ptags[0][2] == ptags[1][2]:
80 if ptags[0][2] == ptags[1][2]:
81 # The tuples are laid out so the right one can be found by
81 # The tuples are laid out so the right one can be found by
82 # comparison in this case.
82 # comparison in this case.
83 pdate, pdist, ptag = max(ptags)
83 pdate, pdist, ptag = max(ptags)
84 else:
84 else:
85
85
86 def key(x):
86 def key(x):
87 tag = x[2][0]
87 tag = x[2][0]
88 if ctx.rev() is None:
88 if ctx.rev() is None:
89 # only() doesn't support wdir
89 # only() doesn't support wdir
90 prevs = [c.rev() for c in ctx.parents()]
90 prevs = [c.rev() for c in ctx.parents()]
91 changes = repo.revs(b'only(%ld, %s)', prevs, tag)
91 changes = repo.revs(b'only(%ld, %s)', prevs, tag)
92 changessincetag = len(changes) + 1
92 changessincetag = len(changes) + 1
93 else:
93 else:
94 changes = repo.revs(b'only(%d, %s)', ctx.rev(), tag)
94 changes = repo.revs(b'only(%d, %s)', ctx.rev(), tag)
95 changessincetag = len(changes)
95 changessincetag = len(changes)
96 # Smallest number of changes since tag wins. Date is
96 # Smallest number of changes since tag wins. Date is
97 # used as tiebreaker.
97 # used as tiebreaker.
98 return [-changessincetag, x[0]]
98 return [-changessincetag, x[0]]
99
99
100 pdate, pdist, ptag = max(ptags, key=key)
100 pdate, pdist, ptag = max(ptags, key=key)
101 else:
101 else:
102 pdate, pdist, ptag = ptags[0]
102 pdate, pdist, ptag = ptags[0]
103 except KeyError:
103 except KeyError:
104 # Cache miss - recurse
104 # Cache miss - recurse
105 todo.append(rev)
105 todo.append(rev)
106 todo.extend(p.rev() for p in ctx.parents())
106 todo.extend(p.rev() for p in ctx.parents())
107 continue
107 continue
108 latesttags[rev] = pdate, pdist + 1, ptag
108 latesttags[rev] = pdate, pdist + 1, ptag
109 return latesttags[rev]
109 return latesttags[rev]
110
110
111
111
112 def getlogcolumns():
112 def getlogcolumns():
113 """Return a dict of log column labels"""
113 """Return a dict of log column labels"""
114 _ = pycompat.identity # temporarily disable gettext
114 _ = pycompat.identity # temporarily disable gettext
115 # i18n: column positioning for "hg log"
115 # i18n: column positioning for "hg log"
116 columns = _(
116 columns = _(
117 b'bookmark: %s\n'
117 b'bookmark: %s\n'
118 b'branch: %s\n'
118 b'branch: %s\n'
119 b'changeset: %s\n'
119 b'changeset: %s\n'
120 b'copies: %s\n'
120 b'copies: %s\n'
121 b'date: %s\n'
121 b'date: %s\n'
122 b'extra: %s=%s\n'
122 b'extra: %s=%s\n'
123 b'files+: %s\n'
123 b'files+: %s\n'
124 b'files-: %s\n'
124 b'files-: %s\n'
125 b'files: %s\n'
125 b'files: %s\n'
126 b'instability: %s\n'
126 b'instability: %s\n'
127 b'manifest: %s\n'
127 b'manifest: %s\n'
128 b'obsolete: %s\n'
128 b'obsolete: %s\n'
129 b'parent: %s\n'
129 b'parent: %s\n'
130 b'phase: %s\n'
130 b'phase: %s\n'
131 b'summary: %s\n'
131 b'summary: %s\n'
132 b'tag: %s\n'
132 b'tag: %s\n'
133 b'user: %s\n'
133 b'user: %s\n'
134 )
134 )
135 return dict(
135 return dict(
136 zip(
136 zip(
137 [s.split(b':', 1)[0] for s in columns.splitlines()],
137 [s.split(b':', 1)[0] for s in columns.splitlines()],
138 i18n._(columns).splitlines(True),
138 i18n._(columns).splitlines(True),
139 )
139 )
140 )
140 )
141
141
142
142
143 # basic internal templates
143 # basic internal templates
144 _changeidtmpl = b'{rev}:{node|formatnode}'
144 _changeidtmpl = b'{rev}:{node|formatnode}'
145
145
146 # default templates internally used for rendering of lists
146 # default templates internally used for rendering of lists
147 defaulttempl = {
147 defaulttempl = {
148 b'parent': _changeidtmpl + b' ',
148 b'parent': _changeidtmpl + b' ',
149 b'manifest': _changeidtmpl,
149 b'manifest': _changeidtmpl,
150 b'file_copy': b'{name} ({source})',
150 b'file_copy': b'{name} ({source})',
151 b'envvar': b'{key}={value}',
151 b'envvar': b'{key}={value}',
152 b'extra': b'{key}={value|stringescape}',
152 b'extra': b'{key}={value|stringescape}',
153 }
153 }
154 # filecopy is preserved for compatibility reasons
154 # filecopy is preserved for compatibility reasons
155 defaulttempl[b'filecopy'] = defaulttempl[b'file_copy']
155 defaulttempl[b'filecopy'] = defaulttempl[b'file_copy']
156
156
157 # keywords are callables (see registrar.templatekeyword for details)
157 # keywords are callables (see registrar.templatekeyword for details)
158 keywords = {}
158 keywords = {}
159 templatekeyword = registrar.templatekeyword(keywords)
159 templatekeyword = registrar.templatekeyword(keywords)
160
160
161
161
162 @templatekeyword(b'author', requires={b'ctx'})
162 @templatekeyword(b'author', requires={b'ctx'})
163 def showauthor(context, mapping):
163 def showauthor(context, mapping):
164 """Alias for ``{user}``"""
164 """Alias for ``{user}``"""
165 return showuser(context, mapping)
165 return showuser(context, mapping)
166
166
167
167
168 @templatekeyword(b'bisect', requires={b'repo', b'ctx'})
168 @templatekeyword(b'bisect', requires={b'repo', b'ctx'})
169 def showbisect(context, mapping):
169 def showbisect(context, mapping):
170 """String. The changeset bisection status."""
170 """String. The changeset bisection status."""
171 repo = context.resource(mapping, b'repo')
171 repo = context.resource(mapping, b'repo')
172 ctx = context.resource(mapping, b'ctx')
172 ctx = context.resource(mapping, b'ctx')
173 return hbisect.label(repo, ctx.node())
173 return hbisect.label(repo, ctx.node())
174
174
175
175
176 @templatekeyword(b'branch', requires={b'ctx'})
176 @templatekeyword(b'branch', requires={b'ctx'})
177 def showbranch(context, mapping):
177 def showbranch(context, mapping):
178 """String. The name of the branch on which the changeset was
178 """String. The name of the branch on which the changeset was
179 committed.
179 committed.
180 """
180 """
181 ctx = context.resource(mapping, b'ctx')
181 ctx = context.resource(mapping, b'ctx')
182 return ctx.branch()
182 return ctx.branch()
183
183
184
184
185 @templatekeyword(b'branches', requires={b'ctx'})
185 @templatekeyword(b'branches', requires={b'ctx'})
186 def showbranches(context, mapping):
186 def showbranches(context, mapping):
187 """List of strings. The name of the branch on which the
187 """List of strings. The name of the branch on which the
188 changeset was committed. Will be empty if the branch name was
188 changeset was committed. Will be empty if the branch name was
189 default. (DEPRECATED)
189 default. (DEPRECATED)
190 """
190 """
191 ctx = context.resource(mapping, b'ctx')
191 ctx = context.resource(mapping, b'ctx')
192 branch = ctx.branch()
192 branch = ctx.branch()
193 if branch != b'default':
193 if branch != b'default':
194 return compatlist(
194 return compatlist(
195 context, mapping, b'branch', [branch], plural=b'branches'
195 context, mapping, b'branch', [branch], plural=b'branches'
196 )
196 )
197 return compatlist(context, mapping, b'branch', [], plural=b'branches')
197 return compatlist(context, mapping, b'branch', [], plural=b'branches')
198
198
199
199
200 @templatekeyword(b'bookmarks', requires={b'repo', b'ctx'})
200 @templatekeyword(b'bookmarks', requires={b'repo', b'ctx'})
201 def showbookmarks(context, mapping):
201 def showbookmarks(context, mapping):
202 """List of strings. Any bookmarks associated with the
202 """List of strings. Any bookmarks associated with the
203 changeset. Also sets 'active', the name of the active bookmark.
203 changeset. Also sets 'active', the name of the active bookmark.
204 """
204 """
205 repo = context.resource(mapping, b'repo')
205 repo = context.resource(mapping, b'repo')
206 ctx = context.resource(mapping, b'ctx')
206 ctx = context.resource(mapping, b'ctx')
207 bookmarks = ctx.bookmarks()
207 bookmarks = ctx.bookmarks()
208 active = repo._activebookmark
208 active = repo._activebookmark
209 makemap = lambda v: {b'bookmark': v, b'active': active, b'current': active}
209 makemap = lambda v: {b'bookmark': v, b'active': active, b'current': active}
210 f = _showcompatlist(context, mapping, b'bookmark', bookmarks)
210 f = _showcompatlist(context, mapping, b'bookmark', bookmarks)
211 return _hybrid(f, bookmarks, makemap, pycompat.identity)
211 return _hybrid(f, bookmarks, makemap, pycompat.identity)
212
212
213
213
214 @templatekeyword(b'children', requires={b'ctx'})
214 @templatekeyword(b'children', requires={b'ctx'})
215 def showchildren(context, mapping):
215 def showchildren(context, mapping):
216 """List of strings. The children of the changeset."""
216 """List of strings. The children of the changeset."""
217 ctx = context.resource(mapping, b'ctx')
217 ctx = context.resource(mapping, b'ctx')
218 childrevs = [b'%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
218 childrevs = [b'%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
219 return compatlist(
219 return compatlist(
220 context, mapping, b'children', childrevs, element=b'child'
220 context, mapping, b'children', childrevs, element=b'child'
221 )
221 )
222
222
223
223
224 # Deprecated, but kept alive for help generation a purpose.
224 # Deprecated, but kept alive for help generation a purpose.
225 @templatekeyword(b'currentbookmark', requires={b'repo', b'ctx'})
225 @templatekeyword(b'currentbookmark', requires={b'repo', b'ctx'})
226 def showcurrentbookmark(context, mapping):
226 def showcurrentbookmark(context, mapping):
227 """String. The active bookmark, if it is associated with the changeset.
227 """String. The active bookmark, if it is associated with the changeset.
228 (DEPRECATED)"""
228 (DEPRECATED)"""
229 return showactivebookmark(context, mapping)
229 return showactivebookmark(context, mapping)
230
230
231
231
232 @templatekeyword(b'activebookmark', requires={b'repo', b'ctx'})
232 @templatekeyword(b'activebookmark', requires={b'repo', b'ctx'})
233 def showactivebookmark(context, mapping):
233 def showactivebookmark(context, mapping):
234 """String. The active bookmark, if it is associated with the changeset."""
234 """String. The active bookmark, if it is associated with the changeset."""
235 repo = context.resource(mapping, b'repo')
235 repo = context.resource(mapping, b'repo')
236 ctx = context.resource(mapping, b'ctx')
236 ctx = context.resource(mapping, b'ctx')
237 active = repo._activebookmark
237 active = repo._activebookmark
238 if active and active in ctx.bookmarks():
238 if active and active in ctx.bookmarks():
239 return active
239 return active
240 return b''
240 return b''
241
241
242
242
243 @templatekeyword(b'date', requires={b'ctx'})
243 @templatekeyword(b'date', requires={b'ctx'})
244 def showdate(context, mapping):
244 def showdate(context, mapping):
245 """Date information. The date when the changeset was committed."""
245 """Date information. The date when the changeset was committed."""
246 ctx = context.resource(mapping, b'ctx')
246 ctx = context.resource(mapping, b'ctx')
247 # the default string format is '<float(unixtime)><tzoffset>' because
247 # the default string format is '<float(unixtime)><tzoffset>' because
248 # python-hglib splits date at decimal separator.
248 # python-hglib splits date at decimal separator.
249 return templateutil.date(ctx.date(), showfmt=b'%d.0%d')
249 return templateutil.date(ctx.date(), showfmt=b'%d.0%d')
250
250
251
251
252 @templatekeyword(b'desc', requires={b'ctx'})
252 @templatekeyword(b'desc', requires={b'ctx'})
253 def showdescription(context, mapping):
253 def showdescription(context, mapping):
254 """String. The text of the changeset description."""
254 """String. The text of the changeset description."""
255 ctx = context.resource(mapping, b'ctx')
255 ctx = context.resource(mapping, b'ctx')
256 s = ctx.description()
256 s = ctx.description()
257 if isinstance(s, encoding.localstr):
257 if isinstance(s, encoding.localstr):
258 # try hard to preserve utf-8 bytes
258 # try hard to preserve utf-8 bytes
259 return encoding.tolocal(encoding.fromlocal(s).strip())
259 return encoding.tolocal(encoding.fromlocal(s).strip())
260 elif isinstance(s, encoding.safelocalstr):
260 elif isinstance(s, encoding.safelocalstr):
261 return encoding.safelocalstr(s.strip())
261 return encoding.safelocalstr(s.strip())
262 else:
262 else:
263 return s.strip()
263 return s.strip()
264
264
265
265
266 @templatekeyword(b'diffstat', requires={b'ui', b'ctx'})
266 @templatekeyword(b'diffstat', requires={b'ui', b'ctx'})
267 def showdiffstat(context, mapping):
267 def showdiffstat(context, mapping):
268 """String. Statistics of changes with the following format:
268 """String. Statistics of changes with the following format:
269 "modified files: +added/-removed lines"
269 "modified files: +added/-removed lines"
270 """
270 """
271 ui = context.resource(mapping, b'ui')
271 ui = context.resource(mapping, b'ui')
272 ctx = context.resource(mapping, b'ctx')
272 ctx = context.resource(mapping, b'ctx')
273 diffopts = diffutil.diffallopts(ui, {b'noprefix': False})
273 diffopts = diffutil.diffallopts(ui, {b'noprefix': False})
274 diff = ctx.diff(opts=diffopts)
274 diff = ctx.diff(opts=diffopts)
275 stats = patch.diffstatdata(util.iterlines(diff))
275 stats = patch.diffstatdata(util.iterlines(diff))
276 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
276 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
277 return b'%d: +%d/-%d' % (len(stats), adds, removes)
277 return b'%d: +%d/-%d' % (len(stats), adds, removes)
278
278
279
279
280 @templatekeyword(b'envvars', requires={b'ui'})
280 @templatekeyword(b'envvars', requires={b'ui'})
281 def showenvvars(context, mapping):
281 def showenvvars(context, mapping):
282 """A dictionary of environment variables. (EXPERIMENTAL)"""
282 """A dictionary of environment variables. (EXPERIMENTAL)"""
283 ui = context.resource(mapping, b'ui')
283 ui = context.resource(mapping, b'ui')
284 env = ui.exportableenviron()
284 env = ui.exportableenviron()
285 env = util.sortdict((k, env[k]) for k in sorted(env))
285 env = util.sortdict((k, env[k]) for k in sorted(env))
286 return compatdict(context, mapping, b'envvar', env, plural=b'envvars')
286 return compatdict(context, mapping, b'envvar', env, plural=b'envvars')
287
287
288
288
289 @templatekeyword(b'extras', requires={b'ctx'})
289 @templatekeyword(b'extras', requires={b'ctx'})
290 def showextras(context, mapping):
290 def showextras(context, mapping):
291 """List of dicts with key, value entries of the 'extras'
291 """List of dicts with key, value entries of the 'extras'
292 field of this changeset."""
292 field of this changeset."""
293 ctx = context.resource(mapping, b'ctx')
293 ctx = context.resource(mapping, b'ctx')
294 extras = ctx.extra()
294 extras = ctx.extra()
295 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
295 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
296 makemap = lambda k: {b'key': k, b'value': extras[k]}
296 makemap = lambda k: {b'key': k, b'value': extras[k]}
297 c = [makemap(k) for k in extras]
297 c = [makemap(k) for k in extras]
298 f = _showcompatlist(context, mapping, b'extra', c, plural=b'extras')
298 f = _showcompatlist(context, mapping, b'extra', c, plural=b'extras')
299 return _hybrid(
299 return _hybrid(
300 f,
300 f,
301 extras,
301 extras,
302 makemap,
302 makemap,
303 lambda k: b'%s=%s' % (k, stringutil.escapestr(extras[k])),
303 lambda k: b'%s=%s' % (k, stringutil.escapestr(extras[k])),
304 )
304 )
305
305
306
306
307 def _getfilestatus(context, mapping, listall=False):
307 def _getfilestatus(context, mapping, listall=False):
308 ctx = context.resource(mapping, b'ctx')
308 ctx = context.resource(mapping, b'ctx')
309 revcache = context.resource(mapping, b'revcache')
309 revcache = context.resource(mapping, b'revcache')
310 if b'filestatus' not in revcache or revcache[b'filestatusall'] < listall:
310 if b'filestatus' not in revcache or revcache[b'filestatusall'] < listall:
311 stat = ctx.p1().status(
311 stat = ctx.p1().status(
312 ctx, listignored=listall, listclean=listall, listunknown=listall
312 ctx, listignored=listall, listclean=listall, listunknown=listall
313 )
313 )
314 revcache[b'filestatus'] = stat
314 revcache[b'filestatus'] = stat
315 revcache[b'filestatusall'] = listall
315 revcache[b'filestatusall'] = listall
316 return revcache[b'filestatus']
316 return revcache[b'filestatus']
317
317
318
318
319 def _getfilestatusmap(context, mapping, listall=False):
319 def _getfilestatusmap(context, mapping, listall=False):
320 revcache = context.resource(mapping, b'revcache')
320 revcache = context.resource(mapping, b'revcache')
321 if b'filestatusmap' not in revcache or revcache[b'filestatusall'] < listall:
321 if b'filestatusmap' not in revcache or revcache[b'filestatusall'] < listall:
322 stat = _getfilestatus(context, mapping, listall=listall)
322 stat = _getfilestatus(context, mapping, listall=listall)
323 revcache[b'filestatusmap'] = statmap = {}
323 revcache[b'filestatusmap'] = statmap = {}
324 for char, files in zip(pycompat.iterbytestr(b'MAR!?IC'), stat):
324 for char, files in zip(pycompat.iterbytestr(b'MAR!?IC'), stat):
325 statmap.update((f, char) for f in files)
325 statmap.update((f, char) for f in files)
326 return revcache[b'filestatusmap'] # {path: statchar}
326 return revcache[b'filestatusmap'] # {path: statchar}
327
327
328
328
329 @templatekeyword(
329 @templatekeyword(
330 b'file_copies', requires={b'repo', b'ctx', b'cache', b'revcache'}
330 b'file_copies', requires={b'repo', b'ctx', b'cache', b'revcache'}
331 )
331 )
332 def showfilecopies(context, mapping):
332 def showfilecopies(context, mapping):
333 """List of strings. Files copied in this changeset with
333 """List of strings. Files copied in this changeset with
334 their sources.
334 their sources.
335 """
335 """
336 repo = context.resource(mapping, b'repo')
336 repo = context.resource(mapping, b'repo')
337 ctx = context.resource(mapping, b'ctx')
337 ctx = context.resource(mapping, b'ctx')
338 cache = context.resource(mapping, b'cache')
338 cache = context.resource(mapping, b'cache')
339 copies = context.resource(mapping, b'revcache').get(b'copies')
339 copies = context.resource(mapping, b'revcache').get(b'copies')
340 if copies is None:
340 if copies is None:
341 if b'getcopies' not in cache:
341 if b'getcopies' not in cache:
342 cache[b'getcopies'] = scmutil.getcopiesfn(repo)
342 cache[b'getcopies'] = scmutil.getcopiesfn(repo)
343 getcopies = cache[b'getcopies']
343 getcopies = cache[b'getcopies']
344 copies = getcopies(ctx)
344 copies = getcopies(ctx)
345 return templateutil.compatfilecopiesdict(
345 return templateutil.compatfilecopiesdict(
346 context, mapping, b'file_copy', copies
346 context, mapping, b'file_copy', copies
347 )
347 )
348
348
349
349
350 # showfilecopiesswitch() displays file copies only if copy records are
350 # showfilecopiesswitch() displays file copies only if copy records are
351 # provided before calling the templater, usually with a --copies
351 # provided before calling the templater, usually with a --copies
352 # command line switch.
352 # command line switch.
353 @templatekeyword(b'file_copies_switch', requires={b'revcache'})
353 @templatekeyword(b'file_copies_switch', requires={b'revcache'})
354 def showfilecopiesswitch(context, mapping):
354 def showfilecopiesswitch(context, mapping):
355 """List of strings. Like "file_copies" but displayed
355 """List of strings. Like "file_copies" but displayed
356 only if the --copied switch is set.
356 only if the --copied switch is set.
357 """
357 """
358 copies = context.resource(mapping, b'revcache').get(b'copies') or []
358 copies = context.resource(mapping, b'revcache').get(b'copies') or []
359 return templateutil.compatfilecopiesdict(
359 return templateutil.compatfilecopiesdict(
360 context, mapping, b'file_copy', copies
360 context, mapping, b'file_copy', copies
361 )
361 )
362
362
363
363
364 @templatekeyword(b'file_adds', requires={b'ctx', b'revcache'})
364 @templatekeyword(b'file_adds', requires={b'ctx', b'revcache'})
365 def showfileadds(context, mapping):
365 def showfileadds(context, mapping):
366 """List of strings. Files added by this changeset."""
366 """List of strings. Files added by this changeset."""
367 ctx = context.resource(mapping, b'ctx')
367 ctx = context.resource(mapping, b'ctx')
368 return templateutil.compatfileslist(
368 return templateutil.compatfileslist(
369 context, mapping, b'file_add', ctx.filesadded()
369 context, mapping, b'file_add', ctx.filesadded()
370 )
370 )
371
371
372
372
373 @templatekeyword(b'file_dels', requires={b'ctx', b'revcache'})
373 @templatekeyword(b'file_dels', requires={b'ctx', b'revcache'})
374 def showfiledels(context, mapping):
374 def showfiledels(context, mapping):
375 """List of strings. Files removed by this changeset."""
375 """List of strings. Files removed by this changeset."""
376 ctx = context.resource(mapping, b'ctx')
376 ctx = context.resource(mapping, b'ctx')
377 return templateutil.compatfileslist(
377 return templateutil.compatfileslist(
378 context, mapping, b'file_del', ctx.filesremoved()
378 context, mapping, b'file_del', ctx.filesremoved()
379 )
379 )
380
380
381
381
382 @templatekeyword(b'file_mods', requires={b'ctx', b'revcache'})
382 @templatekeyword(b'file_mods', requires={b'ctx', b'revcache'})
383 def showfilemods(context, mapping):
383 def showfilemods(context, mapping):
384 """List of strings. Files modified by this changeset."""
384 """List of strings. Files modified by this changeset."""
385 ctx = context.resource(mapping, b'ctx')
385 ctx = context.resource(mapping, b'ctx')
386 return templateutil.compatfileslist(
386 return templateutil.compatfileslist(
387 context, mapping, b'file_mod', ctx.filesmodified()
387 context, mapping, b'file_mod', ctx.filesmodified()
388 )
388 )
389
389
390
390
391 @templatekeyword(b'files', requires={b'ctx'})
391 @templatekeyword(b'files', requires={b'ctx'})
392 def showfiles(context, mapping):
392 def showfiles(context, mapping):
393 """List of strings. All files modified, added, or removed by this
393 """List of strings. All files modified, added, or removed by this
394 changeset.
394 changeset.
395 """
395 """
396 ctx = context.resource(mapping, b'ctx')
396 ctx = context.resource(mapping, b'ctx')
397 return templateutil.compatfileslist(context, mapping, b'file', ctx.files())
397 return templateutil.compatfileslist(context, mapping, b'file', ctx.files())
398
398
399
399
400 @templatekeyword(b'graphnode', requires={b'repo', b'ctx', b'cache'})
400 @templatekeyword(b'graphnode', requires={b'repo', b'ctx', b'cache'})
401 def showgraphnode(context, mapping):
401 def showgraphnode(context, mapping):
402 """String. The character representing the changeset node in an ASCII
402 """String. The character representing the changeset node in an ASCII
403 revision graph."""
403 revision graph."""
404 repo = context.resource(mapping, b'repo')
404 repo = context.resource(mapping, b'repo')
405 ctx = context.resource(mapping, b'ctx')
405 ctx = context.resource(mapping, b'ctx')
406 cache = context.resource(mapping, b'cache')
406 cache = context.resource(mapping, b'cache')
407 return getgraphnode(repo, ctx, cache)
407 return getgraphnode(repo, ctx, cache)
408
408
409
409
410 def getgraphnode(repo, ctx, cache):
410 def getgraphnode(repo, ctx, cache):
411 return getgraphnodecurrent(repo, ctx, cache) or getgraphnodesymbol(ctx)
411 return getgraphnodecurrent(repo, ctx, cache) or getgraphnodesymbol(ctx)
412
412
413
413
414 def getgraphnodecurrent(repo, ctx, cache):
414 def getgraphnodecurrent(repo, ctx, cache):
415 wpnodes = repo.dirstate.parents()
415 wpnodes = repo.dirstate.parents()
416 if wpnodes[1] == repo.nullid:
416 if wpnodes[1] == repo.nullid:
417 wpnodes = wpnodes[:1]
417 wpnodes = wpnodes[:1]
418 if ctx.node() in wpnodes:
418 if ctx.node() in wpnodes:
419 return b'@'
419 return b'@'
420 else:
420 else:
421 merge_nodes = cache.get(b'merge_nodes')
421 merge_nodes = cache.get(b'merge_nodes')
422 if merge_nodes is None:
422 if merge_nodes is None:
423 from . import mergestate as mergestatemod
423 from . import mergestate as mergestatemod
424
424
425 mergestate = mergestatemod.mergestate.read(repo)
425 mergestate = mergestatemod.mergestate.read(repo)
426 if mergestate.unresolvedcount():
426 if mergestate.unresolvedcount():
427 merge_nodes = (mergestate.local, mergestate.other)
427 merge_nodes = (mergestate.local, mergestate.other)
428 else:
428 else:
429 merge_nodes = ()
429 merge_nodes = ()
430 cache[b'merge_nodes'] = merge_nodes
430 cache[b'merge_nodes'] = merge_nodes
431
431
432 if ctx.node() in merge_nodes:
432 if ctx.node() in merge_nodes:
433 return b'%'
433 return b'%'
434 return b''
434 return b''
435
435
436
436
437 def getgraphnodesymbol(ctx):
437 def getgraphnodesymbol(ctx):
438 if ctx.obsolete():
438 if ctx.obsolete():
439 return b'x'
439 return b'x'
440 elif ctx.isunstable():
440 elif ctx.isunstable():
441 return b'*'
441 return b'*'
442 elif ctx.closesbranch():
442 elif ctx.closesbranch():
443 return b'_'
443 return b'_'
444 else:
444 else:
445 return b'o'
445 return b'o'
446
446
447
447
448 @templatekeyword(b'graphwidth', requires=())
448 @templatekeyword(b'graphwidth', requires=())
449 def showgraphwidth(context, mapping):
449 def showgraphwidth(context, mapping):
450 """Integer. The width of the graph drawn by 'log --graph' or zero."""
450 """Integer. The width of the graph drawn by 'log --graph' or zero."""
451 # just hosts documentation; should be overridden by template mapping
451 # just hosts documentation; should be overridden by template mapping
452 return 0
452 return 0
453
453
454
454
455 @templatekeyword(b'index', requires=())
455 @templatekeyword(b'index', requires=())
456 def showindex(context, mapping):
456 def showindex(context, mapping):
457 """Integer. The current iteration of the loop. (0 indexed)"""
457 """Integer. The current iteration of the loop. (0 indexed)"""
458 # just hosts documentation; should be overridden by template mapping
458 # just hosts documentation; should be overridden by template mapping
459 raise error.Abort(_(b"can't use index in this context"))
459 raise error.Abort(_(b"can't use index in this context"))
460
460
461
461
462 @templatekeyword(b'latesttag', requires={b'repo', b'ctx', b'cache'})
462 @templatekeyword(b'latesttag', requires={b'repo', b'ctx', b'cache'})
463 def showlatesttag(context, mapping):
463 def showlatesttag(context, mapping):
464 """List of strings. The global tags on the most recent globally
464 """List of strings. The global tags on the most recent globally
465 tagged ancestor of this changeset. If no such tags exist, the list
465 tagged ancestor of this changeset. If no such tags exist, the list
466 consists of the single string "null".
466 consists of the single string "null".
467 """
467 """
468 return showlatesttags(context, mapping, None)
468 return showlatesttags(context, mapping, None)
469
469
470
470
471 def showlatesttags(context, mapping, pattern):
471 def showlatesttags(context, mapping, pattern):
472 """helper method for the latesttag keyword and function"""
472 """helper method for the latesttag keyword and function"""
473 latesttags = getlatesttags(context, mapping, pattern)
473 latesttags = getlatesttags(context, mapping, pattern)
474
474
475 # latesttag[0] is an implementation detail for sorting csets on different
475 # latesttag[0] is an implementation detail for sorting csets on different
476 # branches in a stable manner- it is the date the tagged cset was created,
476 # branches in a stable manner- it is the date the tagged cset was created,
477 # not the date the tag was created. Therefore it isn't made visible here.
477 # not the date the tag was created. Therefore it isn't made visible here.
478 makemap = lambda v: {
478 makemap = lambda v: {
479 b'changes': _showchangessincetag,
479 b'changes': _showchangessincetag,
480 b'distance': latesttags[1],
480 b'distance': latesttags[1],
481 b'latesttag': v, # BC with {latesttag % '{latesttag}'}
481 b'latesttag': v, # BC with {latesttag % '{latesttag}'}
482 b'tag': v,
482 b'tag': v,
483 }
483 }
484
484
485 tags = latesttags[2]
485 tags = latesttags[2]
486 f = _showcompatlist(context, mapping, b'latesttag', tags, separator=b':')
486 f = _showcompatlist(context, mapping, b'latesttag', tags, separator=b':')
487 return _hybrid(f, tags, makemap, pycompat.identity)
487 return _hybrid(f, tags, makemap, pycompat.identity)
488
488
489
489
490 @templatekeyword(b'latesttagdistance', requires={b'repo', b'ctx', b'cache'})
490 @templatekeyword(b'latesttagdistance', requires={b'repo', b'ctx', b'cache'})
491 def showlatesttagdistance(context, mapping):
491 def showlatesttagdistance(context, mapping):
492 """Integer. Longest path to the latest tag."""
492 """Integer. Longest path to the latest tag."""
493 return getlatesttags(context, mapping)[1]
493 return getlatesttags(context, mapping)[1]
494
494
495
495
496 @templatekeyword(b'changessincelatesttag', requires={b'repo', b'ctx', b'cache'})
496 @templatekeyword(b'changessincelatesttag', requires={b'repo', b'ctx', b'cache'})
497 def showchangessincelatesttag(context, mapping):
497 def showchangessincelatesttag(context, mapping):
498 """Integer. All ancestors not in the latest tag."""
498 """Integer. All ancestors not in the latest tag."""
499 tag = getlatesttags(context, mapping)[2][0]
499 tag = getlatesttags(context, mapping)[2][0]
500 mapping = context.overlaymap(mapping, {b'tag': tag})
500 mapping = context.overlaymap(mapping, {b'tag': tag})
501 return _showchangessincetag(context, mapping)
501 return _showchangessincetag(context, mapping)
502
502
503
503
504 def _showchangessincetag(context, mapping):
504 def _showchangessincetag(context, mapping):
505 repo = context.resource(mapping, b'repo')
505 repo = context.resource(mapping, b'repo')
506 ctx = context.resource(mapping, b'ctx')
506 ctx = context.resource(mapping, b'ctx')
507 offset = 0
507 offset = 0
508 revs = [ctx.rev()]
508 revs = [ctx.rev()]
509 tag = context.symbol(mapping, b'tag')
509 tag = context.symbol(mapping, b'tag')
510
510
511 # The only() revset doesn't currently support wdir()
511 # The only() revset doesn't currently support wdir()
512 if ctx.rev() is None:
512 if ctx.rev() is None:
513 offset = 1
513 offset = 1
514 revs = [p.rev() for p in ctx.parents()]
514 revs = [p.rev() for p in ctx.parents()]
515
515
516 return len(repo.revs(b'only(%ld, %s)', revs, tag)) + offset
516 return len(repo.revs(b'only(%ld, %s)', revs, tag)) + offset
517
517
518
518
519 # teach templater latesttags.changes is switched to (context, mapping) API
519 # teach templater latesttags.changes is switched to (context, mapping) API
520 _showchangessincetag._requires = {b'repo', b'ctx'}
520 _showchangessincetag._requires = {b'repo', b'ctx'}
521
521
522
522
523 @templatekeyword(b'manifest', requires={b'repo', b'ctx'})
523 @templatekeyword(b'manifest', requires={b'repo', b'ctx'})
524 def showmanifest(context, mapping):
524 def showmanifest(context, mapping):
525 repo = context.resource(mapping, b'repo')
525 repo = context.resource(mapping, b'repo')
526 ctx = context.resource(mapping, b'ctx')
526 ctx = context.resource(mapping, b'ctx')
527 mnode = ctx.manifestnode()
527 mnode = ctx.manifestnode()
528 if mnode is None:
528 if mnode is None:
529 mnode = repo.nodeconstants.wdirid
529 mnode = repo.nodeconstants.wdirid
530 mrev = wdirrev
530 mrev = wdirrev
531 mhex = repo.nodeconstants.wdirhex
531 mhex = repo.nodeconstants.wdirhex
532 else:
532 else:
533 mrev = repo.manifestlog.rev(mnode)
533 mrev = repo.manifestlog.rev(mnode)
534 mhex = hex(mnode)
534 mhex = hex(mnode)
535 mapping = context.overlaymap(mapping, {b'rev': mrev, b'node': mhex})
535 mapping = context.overlaymap(mapping, {b'rev': mrev, b'node': mhex})
536 f = context.process(b'manifest', mapping)
536 f = context.process(b'manifest', mapping)
537 return templateutil.hybriditem(
537 return templateutil.hybriditem(
538 f, None, f, lambda x: {b'rev': mrev, b'node': mhex}
538 f, None, f, lambda x: {b'rev': mrev, b'node': mhex}
539 )
539 )
540
540
541
541
542 @templatekeyword(b'obsfate', requires={b'ui', b'repo', b'ctx'})
542 @templatekeyword(b'obsfate', requires={b'ui', b'repo', b'ctx'})
543 def showobsfate(context, mapping):
543 def showobsfate(context, mapping):
544 # this function returns a list containing pre-formatted obsfate strings.
544 # this function returns a list containing pre-formatted obsfate strings.
545 #
545 #
546 # This function will be replaced by templates fragments when we will have
546 # This function will be replaced by templates fragments when we will have
547 # the verbosity templatekw available.
547 # the verbosity templatekw available.
548 succsandmarkers = showsuccsandmarkers(context, mapping)
548 succsandmarkers = showsuccsandmarkers(context, mapping)
549
549
550 ui = context.resource(mapping, b'ui')
550 ui = context.resource(mapping, b'ui')
551 repo = context.resource(mapping, b'repo')
551 repo = context.resource(mapping, b'repo')
552 values = []
552 values = []
553
553
554 for x in succsandmarkers.tovalue(context, mapping):
554 for x in succsandmarkers.tovalue(context, mapping):
555 v = obsutil.obsfateprinter(
555 v = obsutil.obsfateprinter(
556 ui, repo, x[b'successors'], x[b'markers'], scmutil.formatchangeid
556 ui, repo, x[b'successors'], x[b'markers'], scmutil.formatchangeid
557 )
557 )
558 values.append(v)
558 values.append(v)
559
559
560 return compatlist(context, mapping, b"fate", values)
560 return compatlist(context, mapping, b"fate", values)
561
561
562
562
563 def shownames(context, mapping, namespace):
563 def shownames(context, mapping, namespace):
564 """helper method to generate a template keyword for a namespace"""
564 """helper method to generate a template keyword for a namespace"""
565 repo = context.resource(mapping, b'repo')
565 repo = context.resource(mapping, b'repo')
566 ctx = context.resource(mapping, b'ctx')
566 ctx = context.resource(mapping, b'ctx')
567 ns = repo.names.get(namespace)
567 ns = repo.names.get(namespace)
568 if ns is None:
568 if ns is None:
569 # namespaces.addnamespace() registers new template keyword, but
569 # namespaces.addnamespace() registers new template keyword, but
570 # the registered namespace might not exist in the current repo.
570 # the registered namespace might not exist in the current repo.
571 return
571 return
572 names = ns.names(repo, ctx.node())
572 names = ns.names(repo, ctx.node())
573 return compatlist(
573 return compatlist(
574 context, mapping, ns.templatename, names, plural=namespace
574 context, mapping, ns.templatename, names, plural=namespace
575 )
575 )
576
576
577
577
578 @templatekeyword(b'namespaces', requires={b'repo', b'ctx'})
578 @templatekeyword(b'namespaces', requires={b'repo', b'ctx'})
579 def shownamespaces(context, mapping):
579 def shownamespaces(context, mapping):
580 """Dict of lists. Names attached to this changeset per
580 """Dict of lists. Names attached to this changeset per
581 namespace."""
581 namespace."""
582 repo = context.resource(mapping, b'repo')
582 repo = context.resource(mapping, b'repo')
583 ctx = context.resource(mapping, b'ctx')
583 ctx = context.resource(mapping, b'ctx')
584
584
585 namespaces = util.sortdict()
585 namespaces = util.sortdict()
586
586
587 def makensmapfn(ns):
587 def makensmapfn(ns):
588 # 'name' for iterating over namespaces, templatename for local reference
588 # 'name' for iterating over namespaces, templatename for local reference
589 return lambda v: {b'name': v, ns.templatename: v}
589 return lambda v: {b'name': v, ns.templatename: v}
590
590
591 for k, ns in pycompat.iteritems(repo.names):
591 for k, ns in pycompat.iteritems(repo.names):
592 names = ns.names(repo, ctx.node())
592 names = ns.names(repo, ctx.node())
593 f = _showcompatlist(context, mapping, b'name', names)
593 f = _showcompatlist(context, mapping, b'name', names)
594 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
594 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
595
595
596 f = _showcompatlist(context, mapping, b'namespace', list(namespaces))
596 f = _showcompatlist(context, mapping, b'namespace', list(namespaces))
597
597
598 def makemap(ns):
598 def makemap(ns):
599 return {
599 return {
600 b'namespace': ns,
600 b'namespace': ns,
601 b'names': namespaces[ns],
601 b'names': namespaces[ns],
602 b'builtin': repo.names[ns].builtin,
602 b'builtin': repo.names[ns].builtin,
603 b'colorname': repo.names[ns].colorname,
603 b'colorname': repo.names[ns].colorname,
604 }
604 }
605
605
606 return _hybrid(f, namespaces, makemap, pycompat.identity)
606 return _hybrid(f, namespaces, makemap, pycompat.identity)
607
607
608
608
609 @templatekeyword(b'negrev', requires={b'repo', b'ctx'})
609 @templatekeyword(b'negrev', requires={b'repo', b'ctx'})
610 def shownegrev(context, mapping):
610 def shownegrev(context, mapping):
611 """Integer. The repository-local changeset negative revision number,
611 """Integer. The repository-local changeset negative revision number,
612 which counts in the opposite direction."""
612 which counts in the opposite direction."""
613 ctx = context.resource(mapping, b'ctx')
613 ctx = context.resource(mapping, b'ctx')
614 rev = ctx.rev()
614 rev = ctx.rev()
615 if rev is None or rev < 0: # wdir() or nullrev?
615 if rev is None or rev < 0: # wdir() or nullrev?
616 return None
616 return None
617 repo = context.resource(mapping, b'repo')
617 repo = context.resource(mapping, b'repo')
618 return rev - len(repo)
618 return rev - len(repo)
619
619
620
620
621 @templatekeyword(b'node', requires={b'ctx'})
621 @templatekeyword(b'node', requires={b'ctx'})
622 def shownode(context, mapping):
622 def shownode(context, mapping):
623 """String. The changeset identification hash, as a 40 hexadecimal
623 """String. The changeset identification hash, as a 40 hexadecimal
624 digit string.
624 digit string.
625 """
625 """
626 ctx = context.resource(mapping, b'ctx')
626 ctx = context.resource(mapping, b'ctx')
627 return ctx.hex()
627 return ctx.hex()
628
628
629
629
630 @templatekeyword(b'obsolete', requires={b'ctx'})
630 @templatekeyword(b'obsolete', requires={b'ctx'})
631 def showobsolete(context, mapping):
631 def showobsolete(context, mapping):
632 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
632 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
633 ctx = context.resource(mapping, b'ctx')
633 ctx = context.resource(mapping, b'ctx')
634 if ctx.obsolete():
634 if ctx.obsolete():
635 return b'obsolete'
635 return b'obsolete'
636 return b''
636 return b''
637
637
638
638
639 @templatekeyword(b'onelinesummary', requires={b'ui', b'ctx'})
639 @templatekeyword(b'onelinesummary', requires={b'ui', b'ctx'})
640 def showonelinesummary(context, mapping):
640 def showonelinesummary(context, mapping):
641 """String. A one-line summary for the ctx (not including trailing newline).
641 """String. A one-line summary for the ctx (not including trailing newline).
642 The default template be overridden in command-templates.oneline-summary."""
642 The default template be overridden in command-templates.oneline-summary."""
643 # Avoid cycle:
643 # Avoid cycle:
644 # mercurial.cmdutil -> mercurial.templatekw -> mercurial.cmdutil
644 # mercurial.cmdutil -> mercurial.templatekw -> mercurial.cmdutil
645 from . import cmdutil
645 from . import cmdutil
646
646
647 ui = context.resource(mapping, b'ui')
647 ui = context.resource(mapping, b'ui')
648 ctx = context.resource(mapping, b'ctx')
648 ctx = context.resource(mapping, b'ctx')
649 return cmdutil.format_changeset_summary(ui, ctx)
649 return cmdutil.format_changeset_summary(ui, ctx)
650
650
651
651
652 @templatekeyword(b'path', requires={b'fctx'})
652 @templatekeyword(b'path', requires={b'fctx'})
653 def showpath(context, mapping):
653 def showpath(context, mapping):
654 """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
654 """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
655 fctx = context.resource(mapping, b'fctx')
655 fctx = context.resource(mapping, b'fctx')
656 return fctx.path()
656 return fctx.path()
657
657
658
658
659 @templatekeyword(b'peerurls', requires={b'repo'})
659 @templatekeyword(b'peerurls', requires={b'repo'})
660 def showpeerurls(context, mapping):
660 def showpeerurls(context, mapping):
661 """A dictionary of repository locations defined in the [paths] section
661 """A dictionary of repository locations defined in the [paths] section
662 of your configuration file."""
662 of your configuration file."""
663 repo = context.resource(mapping, b'repo')
663 repo = context.resource(mapping, b'repo')
664 # see commands.paths() for naming of dictionary keys
664 # see commands.paths() for naming of dictionary keys
665 paths = repo.ui.paths
665 paths = repo.ui.paths
666 all_paths = urlutil.list_paths(repo.ui)
666 all_paths = urlutil.list_paths(repo.ui)
667 urls = util.sortdict((k, p.rawloc) for k, p in all_paths)
667 urls = util.sortdict((k, p.rawloc) for k, p in all_paths)
668
668
669 def makemap(k):
669 def makemap(k):
670 p = paths[k]
670 p = paths[k]
671 d = {b'name': k, b'url': p.rawloc}
671 d = {b'name': k, b'url': p.rawloc}
672 d.update((o, v) for o, v in sorted(pycompat.iteritems(p.suboptions)))
672 sub_opts = util.sortdict(sorted(pycompat.iteritems(p.suboptions)))
673 d.update(sub_opts)
674 path_dict = util.sortdict()
675 path_dict[b'url'] = p.rawloc
676 path_dict.update(sub_opts)
677 d[b'urls'] = [path_dict]
673 return d
678 return d
674
679
675 def format_one(k):
680 def format_one(k):
676 return b'%s=%s' % (k, urls[k])
681 return b'%s=%s' % (k, urls[k])
677
682
678 return _hybrid(None, urls, makemap, format_one)
683 return _hybrid(None, urls, makemap, format_one)
679
684
680
685
681 @templatekeyword(b"predecessors", requires={b'repo', b'ctx'})
686 @templatekeyword(b"predecessors", requires={b'repo', b'ctx'})
682 def showpredecessors(context, mapping):
687 def showpredecessors(context, mapping):
683 """Returns the list of the closest visible predecessors. (EXPERIMENTAL)"""
688 """Returns the list of the closest visible predecessors. (EXPERIMENTAL)"""
684 repo = context.resource(mapping, b'repo')
689 repo = context.resource(mapping, b'repo')
685 ctx = context.resource(mapping, b'ctx')
690 ctx = context.resource(mapping, b'ctx')
686 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
691 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
687 predecessors = pycompat.maplist(hex, predecessors)
692 predecessors = pycompat.maplist(hex, predecessors)
688
693
689 return _hybrid(
694 return _hybrid(
690 None,
695 None,
691 predecessors,
696 predecessors,
692 lambda x: {b'ctx': repo[x]},
697 lambda x: {b'ctx': repo[x]},
693 lambda x: scmutil.formatchangeid(repo[x]),
698 lambda x: scmutil.formatchangeid(repo[x]),
694 )
699 )
695
700
696
701
697 @templatekeyword(b'reporoot', requires={b'repo'})
702 @templatekeyword(b'reporoot', requires={b'repo'})
698 def showreporoot(context, mapping):
703 def showreporoot(context, mapping):
699 """String. The root directory of the current repository."""
704 """String. The root directory of the current repository."""
700 repo = context.resource(mapping, b'repo')
705 repo = context.resource(mapping, b'repo')
701 return repo.root
706 return repo.root
702
707
703
708
704 @templatekeyword(b'size', requires={b'fctx'})
709 @templatekeyword(b'size', requires={b'fctx'})
705 def showsize(context, mapping):
710 def showsize(context, mapping):
706 """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
711 """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
707 fctx = context.resource(mapping, b'fctx')
712 fctx = context.resource(mapping, b'fctx')
708 return fctx.size()
713 return fctx.size()
709
714
710
715
711 # requires 'fctx' to denote {status} depends on (ctx, path) pair
716 # requires 'fctx' to denote {status} depends on (ctx, path) pair
712 @templatekeyword(b'status', requires={b'ctx', b'fctx', b'revcache'})
717 @templatekeyword(b'status', requires={b'ctx', b'fctx', b'revcache'})
713 def showstatus(context, mapping):
718 def showstatus(context, mapping):
714 """String. Status code of the current file. (EXPERIMENTAL)"""
719 """String. Status code of the current file. (EXPERIMENTAL)"""
715 path = templateutil.runsymbol(context, mapping, b'path')
720 path = templateutil.runsymbol(context, mapping, b'path')
716 path = templateutil.stringify(context, mapping, path)
721 path = templateutil.stringify(context, mapping, path)
717 if not path:
722 if not path:
718 return
723 return
719 statmap = _getfilestatusmap(context, mapping)
724 statmap = _getfilestatusmap(context, mapping)
720 if path not in statmap:
725 if path not in statmap:
721 statmap = _getfilestatusmap(context, mapping, listall=True)
726 statmap = _getfilestatusmap(context, mapping, listall=True)
722 return statmap.get(path)
727 return statmap.get(path)
723
728
724
729
725 @templatekeyword(b"successorssets", requires={b'repo', b'ctx'})
730 @templatekeyword(b"successorssets", requires={b'repo', b'ctx'})
726 def showsuccessorssets(context, mapping):
731 def showsuccessorssets(context, mapping):
727 """Returns a string of sets of successors for a changectx. Format used
732 """Returns a string of sets of successors for a changectx. Format used
728 is: [ctx1, ctx2], [ctx3] if ctx has been split into ctx1 and ctx2
733 is: [ctx1, ctx2], [ctx3] if ctx has been split into ctx1 and ctx2
729 while also diverged into ctx3. (EXPERIMENTAL)"""
734 while also diverged into ctx3. (EXPERIMENTAL)"""
730 repo = context.resource(mapping, b'repo')
735 repo = context.resource(mapping, b'repo')
731 ctx = context.resource(mapping, b'ctx')
736 ctx = context.resource(mapping, b'ctx')
732 data = []
737 data = []
733
738
734 if ctx.obsolete():
739 if ctx.obsolete():
735 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
740 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
736 ssets = [[hex(n) for n in ss] for ss in ssets]
741 ssets = [[hex(n) for n in ss] for ss in ssets]
737
742
738 for ss in ssets:
743 for ss in ssets:
739 h = _hybrid(
744 h = _hybrid(
740 None,
745 None,
741 ss,
746 ss,
742 lambda x: {b'ctx': repo[x]},
747 lambda x: {b'ctx': repo[x]},
743 lambda x: scmutil.formatchangeid(repo[x]),
748 lambda x: scmutil.formatchangeid(repo[x]),
744 )
749 )
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 return templateutil.stringify(context, mapping, d)
754 return templateutil.stringify(context, mapping, d)
750
755
751 def gen(data):
756 def gen(data):
752 yield b"; ".join(render(d) for d in data)
757 yield b"; ".join(render(d) for d in data)
753
758
754 return _hybrid(
759 return _hybrid(
755 gen(data), data, lambda x: {b'successorset': x}, pycompat.identity
760 gen(data), data, lambda x: {b'successorset': x}, pycompat.identity
756 )
761 )
757
762
758
763
759 @templatekeyword(b"succsandmarkers", requires={b'repo', b'ctx'})
764 @templatekeyword(b"succsandmarkers", requires={b'repo', b'ctx'})
760 def showsuccsandmarkers(context, mapping):
765 def showsuccsandmarkers(context, mapping):
761 """Returns a list of dict for each final successor of ctx. The dict
766 """Returns a list of dict for each final successor of ctx. The dict
762 contains successors node id in "successors" keys and the list of
767 contains successors node id in "successors" keys and the list of
763 obs-markers from ctx to the set of successors in "markers".
768 obs-markers from ctx to the set of successors in "markers".
764 (EXPERIMENTAL)
769 (EXPERIMENTAL)
765 """
770 """
766 repo = context.resource(mapping, b'repo')
771 repo = context.resource(mapping, b'repo')
767 ctx = context.resource(mapping, b'ctx')
772 ctx = context.resource(mapping, b'ctx')
768
773
769 values = obsutil.successorsandmarkers(repo, ctx)
774 values = obsutil.successorsandmarkers(repo, ctx)
770
775
771 if values is None:
776 if values is None:
772 values = []
777 values = []
773
778
774 # Format successors and markers to avoid exposing binary to templates
779 # Format successors and markers to avoid exposing binary to templates
775 data = []
780 data = []
776 for i in values:
781 for i in values:
777 # Format successors
782 # Format successors
778 successors = i[b'successors']
783 successors = i[b'successors']
779
784
780 successors = [hex(n) for n in successors]
785 successors = [hex(n) for n in successors]
781 successors = _hybrid(
786 successors = _hybrid(
782 None,
787 None,
783 successors,
788 successors,
784 lambda x: {b'ctx': repo[x]},
789 lambda x: {b'ctx': repo[x]},
785 lambda x: scmutil.formatchangeid(repo[x]),
790 lambda x: scmutil.formatchangeid(repo[x]),
786 )
791 )
787
792
788 # Format markers
793 # Format markers
789 finalmarkers = []
794 finalmarkers = []
790 for m in i[b'markers']:
795 for m in i[b'markers']:
791 hexprec = hex(m[0])
796 hexprec = hex(m[0])
792 hexsucs = tuple(hex(n) for n in m[1])
797 hexsucs = tuple(hex(n) for n in m[1])
793 hexparents = None
798 hexparents = None
794 if m[5] is not None:
799 if m[5] is not None:
795 hexparents = tuple(hex(n) for n in m[5])
800 hexparents = tuple(hex(n) for n in m[5])
796 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
801 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
797 finalmarkers.append(newmarker)
802 finalmarkers.append(newmarker)
798
803
799 data.append({b'successors': successors, b'markers': finalmarkers})
804 data.append({b'successors': successors, b'markers': finalmarkers})
800
805
801 return templateutil.mappinglist(data)
806 return templateutil.mappinglist(data)
802
807
803
808
804 @templatekeyword(b'p1', requires={b'ctx'})
809 @templatekeyword(b'p1', requires={b'ctx'})
805 def showp1(context, mapping):
810 def showp1(context, mapping):
806 """Changeset. The changeset's first parent. ``{p1.rev}`` for the revision
811 """Changeset. The changeset's first parent. ``{p1.rev}`` for the revision
807 number, and ``{p1.node}`` for the identification hash."""
812 number, and ``{p1.node}`` for the identification hash."""
808 ctx = context.resource(mapping, b'ctx')
813 ctx = context.resource(mapping, b'ctx')
809 return templateutil.mappingdict({b'ctx': ctx.p1()}, tmpl=_changeidtmpl)
814 return templateutil.mappingdict({b'ctx': ctx.p1()}, tmpl=_changeidtmpl)
810
815
811
816
812 @templatekeyword(b'p2', requires={b'ctx'})
817 @templatekeyword(b'p2', requires={b'ctx'})
813 def showp2(context, mapping):
818 def showp2(context, mapping):
814 """Changeset. The changeset's second parent. ``{p2.rev}`` for the revision
819 """Changeset. The changeset's second parent. ``{p2.rev}`` for the revision
815 number, and ``{p2.node}`` for the identification hash."""
820 number, and ``{p2.node}`` for the identification hash."""
816 ctx = context.resource(mapping, b'ctx')
821 ctx = context.resource(mapping, b'ctx')
817 return templateutil.mappingdict({b'ctx': ctx.p2()}, tmpl=_changeidtmpl)
822 return templateutil.mappingdict({b'ctx': ctx.p2()}, tmpl=_changeidtmpl)
818
823
819
824
820 @templatekeyword(b'p1rev', requires={b'ctx'})
825 @templatekeyword(b'p1rev', requires={b'ctx'})
821 def showp1rev(context, mapping):
826 def showp1rev(context, mapping):
822 """Integer. The repository-local revision number of the changeset's
827 """Integer. The repository-local revision number of the changeset's
823 first parent, or -1 if the changeset has no parents. (DEPRECATED)"""
828 first parent, or -1 if the changeset has no parents. (DEPRECATED)"""
824 ctx = context.resource(mapping, b'ctx')
829 ctx = context.resource(mapping, b'ctx')
825 return ctx.p1().rev()
830 return ctx.p1().rev()
826
831
827
832
828 @templatekeyword(b'p2rev', requires={b'ctx'})
833 @templatekeyword(b'p2rev', requires={b'ctx'})
829 def showp2rev(context, mapping):
834 def showp2rev(context, mapping):
830 """Integer. The repository-local revision number of the changeset's
835 """Integer. The repository-local revision number of the changeset's
831 second parent, or -1 if the changeset has no second parent. (DEPRECATED)"""
836 second parent, or -1 if the changeset has no second parent. (DEPRECATED)"""
832 ctx = context.resource(mapping, b'ctx')
837 ctx = context.resource(mapping, b'ctx')
833 return ctx.p2().rev()
838 return ctx.p2().rev()
834
839
835
840
836 @templatekeyword(b'p1node', requires={b'ctx'})
841 @templatekeyword(b'p1node', requires={b'ctx'})
837 def showp1node(context, mapping):
842 def showp1node(context, mapping):
838 """String. The identification hash of the changeset's first parent,
843 """String. The identification hash of the changeset's first parent,
839 as a 40 digit hexadecimal string. If the changeset has no parents, all
844 as a 40 digit hexadecimal string. If the changeset has no parents, all
840 digits are 0. (DEPRECATED)"""
845 digits are 0. (DEPRECATED)"""
841 ctx = context.resource(mapping, b'ctx')
846 ctx = context.resource(mapping, b'ctx')
842 return ctx.p1().hex()
847 return ctx.p1().hex()
843
848
844
849
845 @templatekeyword(b'p2node', requires={b'ctx'})
850 @templatekeyword(b'p2node', requires={b'ctx'})
846 def showp2node(context, mapping):
851 def showp2node(context, mapping):
847 """String. The identification hash of the changeset's second
852 """String. The identification hash of the changeset's second
848 parent, as a 40 digit hexadecimal string. If the changeset has no second
853 parent, as a 40 digit hexadecimal string. If the changeset has no second
849 parent, all digits are 0. (DEPRECATED)"""
854 parent, all digits are 0. (DEPRECATED)"""
850 ctx = context.resource(mapping, b'ctx')
855 ctx = context.resource(mapping, b'ctx')
851 return ctx.p2().hex()
856 return ctx.p2().hex()
852
857
853
858
854 @templatekeyword(b'parents', requires={b'repo', b'ctx'})
859 @templatekeyword(b'parents', requires={b'repo', b'ctx'})
855 def showparents(context, mapping):
860 def showparents(context, mapping):
856 """List of strings. The parents of the changeset in "rev:node"
861 """List of strings. The parents of the changeset in "rev:node"
857 format. If the changeset has only one "natural" parent (the predecessor
862 format. If the changeset has only one "natural" parent (the predecessor
858 revision) nothing is shown."""
863 revision) nothing is shown."""
859 repo = context.resource(mapping, b'repo')
864 repo = context.resource(mapping, b'repo')
860 ctx = context.resource(mapping, b'ctx')
865 ctx = context.resource(mapping, b'ctx')
861 pctxs = scmutil.meaningfulparents(repo, ctx)
866 pctxs = scmutil.meaningfulparents(repo, ctx)
862 prevs = [p.rev() for p in pctxs]
867 prevs = [p.rev() for p in pctxs]
863 parents = [
868 parents = [
864 [(b'rev', p.rev()), (b'node', p.hex()), (b'phase', p.phasestr())]
869 [(b'rev', p.rev()), (b'node', p.hex()), (b'phase', p.phasestr())]
865 for p in pctxs
870 for p in pctxs
866 ]
871 ]
867 f = _showcompatlist(context, mapping, b'parent', parents)
872 f = _showcompatlist(context, mapping, b'parent', parents)
868 return _hybrid(
873 return _hybrid(
869 f,
874 f,
870 prevs,
875 prevs,
871 lambda x: {b'ctx': repo[x]},
876 lambda x: {b'ctx': repo[x]},
872 lambda x: scmutil.formatchangeid(repo[x]),
877 lambda x: scmutil.formatchangeid(repo[x]),
873 keytype=int,
878 keytype=int,
874 )
879 )
875
880
876
881
877 @templatekeyword(b'phase', requires={b'ctx'})
882 @templatekeyword(b'phase', requires={b'ctx'})
878 def showphase(context, mapping):
883 def showphase(context, mapping):
879 """String. The changeset phase name."""
884 """String. The changeset phase name."""
880 ctx = context.resource(mapping, b'ctx')
885 ctx = context.resource(mapping, b'ctx')
881 return ctx.phasestr()
886 return ctx.phasestr()
882
887
883
888
884 @templatekeyword(b'phaseidx', requires={b'ctx'})
889 @templatekeyword(b'phaseidx', requires={b'ctx'})
885 def showphaseidx(context, mapping):
890 def showphaseidx(context, mapping):
886 """Integer. The changeset phase index. (ADVANCED)"""
891 """Integer. The changeset phase index. (ADVANCED)"""
887 ctx = context.resource(mapping, b'ctx')
892 ctx = context.resource(mapping, b'ctx')
888 return ctx.phase()
893 return ctx.phase()
889
894
890
895
891 @templatekeyword(b'rev', requires={b'ctx'})
896 @templatekeyword(b'rev', requires={b'ctx'})
892 def showrev(context, mapping):
897 def showrev(context, mapping):
893 """Integer. The repository-local changeset revision number."""
898 """Integer. The repository-local changeset revision number."""
894 ctx = context.resource(mapping, b'ctx')
899 ctx = context.resource(mapping, b'ctx')
895 return scmutil.intrev(ctx)
900 return scmutil.intrev(ctx)
896
901
897
902
898 @templatekeyword(b'subrepos', requires={b'ctx'})
903 @templatekeyword(b'subrepos', requires={b'ctx'})
899 def showsubrepos(context, mapping):
904 def showsubrepos(context, mapping):
900 """List of strings. Updated subrepositories in the changeset."""
905 """List of strings. Updated subrepositories in the changeset."""
901 ctx = context.resource(mapping, b'ctx')
906 ctx = context.resource(mapping, b'ctx')
902 substate = ctx.substate
907 substate = ctx.substate
903 if not substate:
908 if not substate:
904 return compatlist(context, mapping, b'subrepo', [])
909 return compatlist(context, mapping, b'subrepo', [])
905 psubstate = ctx.p1().substate or {}
910 psubstate = ctx.p1().substate or {}
906 subrepos = []
911 subrepos = []
907 for sub in substate:
912 for sub in substate:
908 if sub not in psubstate or substate[sub] != psubstate[sub]:
913 if sub not in psubstate or substate[sub] != psubstate[sub]:
909 subrepos.append(sub) # modified or newly added in ctx
914 subrepos.append(sub) # modified or newly added in ctx
910 for sub in psubstate:
915 for sub in psubstate:
911 if sub not in substate:
916 if sub not in substate:
912 subrepos.append(sub) # removed in ctx
917 subrepos.append(sub) # removed in ctx
913 return compatlist(context, mapping, b'subrepo', sorted(subrepos))
918 return compatlist(context, mapping, b'subrepo', sorted(subrepos))
914
919
915
920
916 # don't remove "showtags" definition, even though namespaces will put
921 # don't remove "showtags" definition, even though namespaces will put
917 # a helper function for "tags" keyword into "keywords" map automatically,
922 # a helper function for "tags" keyword into "keywords" map automatically,
918 # because online help text is built without namespaces initialization
923 # because online help text is built without namespaces initialization
919 @templatekeyword(b'tags', requires={b'repo', b'ctx'})
924 @templatekeyword(b'tags', requires={b'repo', b'ctx'})
920 def showtags(context, mapping):
925 def showtags(context, mapping):
921 """List of strings. Any tags associated with the changeset."""
926 """List of strings. Any tags associated with the changeset."""
922 return shownames(context, mapping, b'tags')
927 return shownames(context, mapping, b'tags')
923
928
924
929
925 @templatekeyword(b'termwidth', requires={b'ui'})
930 @templatekeyword(b'termwidth', requires={b'ui'})
926 def showtermwidth(context, mapping):
931 def showtermwidth(context, mapping):
927 """Integer. The width of the current terminal."""
932 """Integer. The width of the current terminal."""
928 ui = context.resource(mapping, b'ui')
933 ui = context.resource(mapping, b'ui')
929 return ui.termwidth()
934 return ui.termwidth()
930
935
931
936
932 @templatekeyword(b'user', requires={b'ctx'})
937 @templatekeyword(b'user', requires={b'ctx'})
933 def showuser(context, mapping):
938 def showuser(context, mapping):
934 """String. The unmodified author of the changeset."""
939 """String. The unmodified author of the changeset."""
935 ctx = context.resource(mapping, b'ctx')
940 ctx = context.resource(mapping, b'ctx')
936 return ctx.user()
941 return ctx.user()
937
942
938
943
939 @templatekeyword(b'instabilities', requires={b'ctx'})
944 @templatekeyword(b'instabilities', requires={b'ctx'})
940 def showinstabilities(context, mapping):
945 def showinstabilities(context, mapping):
941 """List of strings. Evolution instabilities affecting the changeset.
946 """List of strings. Evolution instabilities affecting the changeset.
942 (EXPERIMENTAL)
947 (EXPERIMENTAL)
943 """
948 """
944 ctx = context.resource(mapping, b'ctx')
949 ctx = context.resource(mapping, b'ctx')
945 return compatlist(
950 return compatlist(
946 context,
951 context,
947 mapping,
952 mapping,
948 b'instability',
953 b'instability',
949 ctx.instabilities(),
954 ctx.instabilities(),
950 plural=b'instabilities',
955 plural=b'instabilities',
951 )
956 )
952
957
953
958
954 @templatekeyword(b'verbosity', requires={b'ui'})
959 @templatekeyword(b'verbosity', requires={b'ui'})
955 def showverbosity(context, mapping):
960 def showverbosity(context, mapping):
956 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
961 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
957 or ''."""
962 or ''."""
958 ui = context.resource(mapping, b'ui')
963 ui = context.resource(mapping, b'ui')
959 # see logcmdutil.changesettemplater for priority of these flags
964 # see logcmdutil.changesettemplater for priority of these flags
960 if ui.debugflag:
965 if ui.debugflag:
961 return b'debug'
966 return b'debug'
962 elif ui.quiet:
967 elif ui.quiet:
963 return b'quiet'
968 return b'quiet'
964 elif ui.verbose:
969 elif ui.verbose:
965 return b'verbose'
970 return b'verbose'
966 return b''
971 return b''
967
972
968
973
969 @templatekeyword(b'whyunstable', requires={b'repo', b'ctx'})
974 @templatekeyword(b'whyunstable', requires={b'repo', b'ctx'})
970 def showwhyunstable(context, mapping):
975 def showwhyunstable(context, mapping):
971 """List of dicts explaining all instabilities of a changeset.
976 """List of dicts explaining all instabilities of a changeset.
972 (EXPERIMENTAL)
977 (EXPERIMENTAL)
973 """
978 """
974 repo = context.resource(mapping, b'repo')
979 repo = context.resource(mapping, b'repo')
975 ctx = context.resource(mapping, b'ctx')
980 ctx = context.resource(mapping, b'ctx')
976
981
977 def formatnode(ctx):
982 def formatnode(ctx):
978 return b'%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
983 return b'%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
979
984
980 entries = obsutil.whyunstable(repo, ctx)
985 entries = obsutil.whyunstable(repo, ctx)
981
986
982 for entry in entries:
987 for entry in entries:
983 if entry.get(b'divergentnodes'):
988 if entry.get(b'divergentnodes'):
984 dnodes = entry[b'divergentnodes']
989 dnodes = entry[b'divergentnodes']
985 dnhybrid = _hybrid(
990 dnhybrid = _hybrid(
986 None,
991 None,
987 [dnode.hex() for dnode in dnodes],
992 [dnode.hex() for dnode in dnodes],
988 lambda x: {b'ctx': repo[x]},
993 lambda x: {b'ctx': repo[x]},
989 lambda x: formatnode(repo[x]),
994 lambda x: formatnode(repo[x]),
990 )
995 )
991 entry[b'divergentnodes'] = dnhybrid
996 entry[b'divergentnodes'] = dnhybrid
992
997
993 tmpl = (
998 tmpl = (
994 b'{instability}:{if(divergentnodes, " ")}{divergentnodes} '
999 b'{instability}:{if(divergentnodes, " ")}{divergentnodes} '
995 b'{reason} {node|short}'
1000 b'{reason} {node|short}'
996 )
1001 )
997 return templateutil.mappinglist(entries, tmpl=tmpl, sep=b'\n')
1002 return templateutil.mappinglist(entries, tmpl=tmpl, sep=b'\n')
998
1003
999
1004
1000 def loadkeyword(ui, extname, registrarobj):
1005 def loadkeyword(ui, extname, registrarobj):
1001 """Load template keyword from specified registrarobj"""
1006 """Load template keyword from specified registrarobj"""
1002 for name, func in pycompat.iteritems(registrarobj._table):
1007 for name, func in pycompat.iteritems(registrarobj._table):
1003 keywords[name] = func
1008 keywords[name] = func
1004
1009
1005
1010
1006 # tell hggettext to extract docstrings from these functions:
1011 # tell hggettext to extract docstrings from these functions:
1007 i18nfunctions = keywords.values()
1012 i18nfunctions = keywords.values()
@@ -1,387 +1,390 b''
1 $ hg init a
1 $ hg init a
2 $ hg clone a b
2 $ hg clone a b
3 updating to branch default
3 updating to branch default
4 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
4 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 $ cd a
5 $ cd a
6
6
7 with no paths:
7 with no paths:
8
8
9 $ hg paths
9 $ hg paths
10 $ hg paths unknown
10 $ hg paths unknown
11 not found!
11 not found!
12 [1]
12 [1]
13 $ hg paths -Tjson
13 $ hg paths -Tjson
14 [
14 [
15 ]
15 ]
16
16
17 with paths:
17 with paths:
18
18
19 $ echo '[paths]' >> .hg/hgrc
19 $ echo '[paths]' >> .hg/hgrc
20 $ echo 'dupe = ../b#tip' >> .hg/hgrc
20 $ echo 'dupe = ../b#tip' >> .hg/hgrc
21 $ echo 'expand = $SOMETHING/bar' >> .hg/hgrc
21 $ echo 'expand = $SOMETHING/bar' >> .hg/hgrc
22 $ hg in dupe
22 $ hg in dupe
23 comparing with $TESTTMP/b
23 comparing with $TESTTMP/b
24 no changes found
24 no changes found
25 [1]
25 [1]
26 $ cd ..
26 $ cd ..
27 $ hg -R a in dupe
27 $ hg -R a in dupe
28 comparing with $TESTTMP/b
28 comparing with $TESTTMP/b
29 no changes found
29 no changes found
30 [1]
30 [1]
31 $ cd a
31 $ cd a
32 $ hg paths
32 $ hg paths
33 dupe = $TESTTMP/b#tip
33 dupe = $TESTTMP/b#tip
34 expand = $TESTTMP/a/$SOMETHING/bar
34 expand = $TESTTMP/a/$SOMETHING/bar
35 $ SOMETHING=foo hg paths
35 $ SOMETHING=foo hg paths
36 dupe = $TESTTMP/b#tip
36 dupe = $TESTTMP/b#tip
37 expand = $TESTTMP/a/foo/bar
37 expand = $TESTTMP/a/foo/bar
38 #if msys
38 #if msys
39 $ SOMETHING=//foo hg paths
39 $ SOMETHING=//foo hg paths
40 dupe = $TESTTMP/b#tip
40 dupe = $TESTTMP/b#tip
41 expand = /foo/bar
41 expand = /foo/bar
42 #else
42 #else
43 $ SOMETHING=/foo hg paths
43 $ SOMETHING=/foo hg paths
44 dupe = $TESTTMP/b#tip
44 dupe = $TESTTMP/b#tip
45 expand = /foo/bar
45 expand = /foo/bar
46 #endif
46 #endif
47 $ hg paths -q
47 $ hg paths -q
48 dupe
48 dupe
49 expand
49 expand
50 $ hg paths dupe
50 $ hg paths dupe
51 $TESTTMP/b#tip
51 $TESTTMP/b#tip
52 $ hg paths -q dupe
52 $ hg paths -q dupe
53 $ hg paths unknown
53 $ hg paths unknown
54 not found!
54 not found!
55 [1]
55 [1]
56 $ hg paths -q unknown
56 $ hg paths -q unknown
57 [1]
57 [1]
58
58
59 formatter output with paths:
59 formatter output with paths:
60
60
61 $ echo 'dupe:pushurl = https://example.com/dupe' >> .hg/hgrc
61 $ echo 'dupe:pushurl = https://example.com/dupe' >> .hg/hgrc
62 $ hg paths -Tjson | sed 's|\\\\|\\|g'
62 $ hg paths -Tjson | sed 's|\\\\|\\|g'
63 [
63 [
64 {
64 {
65 "name": "dupe",
65 "name": "dupe",
66 "pushurl": "https://example.com/dupe",
66 "pushurl": "https://example.com/dupe",
67 "url": "$TESTTMP/b#tip"
67 "url": "$TESTTMP/b#tip"
68 },
68 },
69 {
69 {
70 "name": "expand",
70 "name": "expand",
71 "url": "$TESTTMP/a/$SOMETHING/bar"
71 "url": "$TESTTMP/a/$SOMETHING/bar"
72 }
72 }
73 ]
73 ]
74 $ hg paths -Tjson dupe | sed 's|\\\\|\\|g'
74 $ hg paths -Tjson dupe | sed 's|\\\\|\\|g'
75 [
75 [
76 {
76 {
77 "name": "dupe",
77 "name": "dupe",
78 "pushurl": "https://example.com/dupe",
78 "pushurl": "https://example.com/dupe",
79 "url": "$TESTTMP/b#tip"
79 "url": "$TESTTMP/b#tip"
80 }
80 }
81 ]
81 ]
82 $ hg paths -Tjson -q unknown
82 $ hg paths -Tjson -q unknown
83 [
83 [
84 ]
84 ]
85 [1]
85 [1]
86
86
87 log template:
87 log template:
88
88
89 (behaves as a {name: path-string} dict by default)
89 (behaves as a {name: path-string} dict by default)
90
90
91 $ hg log -rnull -T '{peerurls}\n'
91 $ hg log -rnull -T '{peerurls}\n'
92 dupe=$TESTTMP/b#tip expand=$TESTTMP/a/$SOMETHING/bar
92 dupe=$TESTTMP/b#tip expand=$TESTTMP/a/$SOMETHING/bar
93 $ hg log -rnull -T '{join(peerurls, "\n")}\n'
93 $ hg log -rnull -T '{join(peerurls, "\n")}\n'
94 dupe=$TESTTMP/b#tip
94 dupe=$TESTTMP/b#tip
95 expand=$TESTTMP/a/$SOMETHING/bar
95 expand=$TESTTMP/a/$SOMETHING/bar
96 $ hg log -rnull -T '{peerurls % "{name}: {url}\n"}'
96 $ hg log -rnull -T '{peerurls % "{name}: {url}\n"}'
97 dupe: $TESTTMP/b#tip
97 dupe: $TESTTMP/b#tip
98 expand: $TESTTMP/a/$SOMETHING/bar
98 expand: $TESTTMP/a/$SOMETHING/bar
99 $ hg log -rnull -T '{get(peerurls, "dupe")}\n'
99 $ hg log -rnull -T '{get(peerurls, "dupe")}\n'
100 $TESTTMP/b#tip
100 $TESTTMP/b#tip
101 $ hg log -rnull -T '{peerurls % "{urls|json}\n"}'
102 [{"pushurl": "https://example.com/dupe", "url": "$TESTTMP/b#tip"}]
103 [{"url": "$TESTTMP/a/$SOMETHING/bar"}]
101
104
102 (sub options can be populated by map/dot operation)
105 (sub options can be populated by map/dot operation)
103
106
104 $ hg log -rnull \
107 $ hg log -rnull \
105 > -T '{get(peerurls, "dupe") % "url: {url}\npushurl: {pushurl}\n"}'
108 > -T '{get(peerurls, "dupe") % "url: {url}\npushurl: {pushurl}\n"}'
106 url: $TESTTMP/b#tip
109 url: $TESTTMP/b#tip
107 pushurl: https://example.com/dupe
110 pushurl: https://example.com/dupe
108 $ hg log -rnull -T '{peerurls.dupe.pushurl}\n'
111 $ hg log -rnull -T '{peerurls.dupe.pushurl}\n'
109 https://example.com/dupe
112 https://example.com/dupe
110
113
111 (in JSON, it's a dict of urls)
114 (in JSON, it's a dict of urls)
112
115
113 $ hg log -rnull -T '{peerurls|json}\n' | sed 's|\\\\|/|g'
116 $ hg log -rnull -T '{peerurls|json}\n' | sed 's|\\\\|/|g'
114 {"dupe": "$TESTTMP/b#tip", "expand": "$TESTTMP/a/$SOMETHING/bar"}
117 {"dupe": "$TESTTMP/b#tip", "expand": "$TESTTMP/a/$SOMETHING/bar"}
115
118
116 password should be masked in plain output, but not in machine-readable/template
119 password should be masked in plain output, but not in machine-readable/template
117 output:
120 output:
118
121
119 $ echo 'insecure = http://foo:insecure@example.com/' >> .hg/hgrc
122 $ echo 'insecure = http://foo:insecure@example.com/' >> .hg/hgrc
120 $ hg paths insecure
123 $ hg paths insecure
121 http://foo:***@example.com/
124 http://foo:***@example.com/
122 $ hg paths -Tjson insecure
125 $ hg paths -Tjson insecure
123 [
126 [
124 {
127 {
125 "name": "insecure",
128 "name": "insecure",
126 "url": "http://foo:insecure@example.com/"
129 "url": "http://foo:insecure@example.com/"
127 }
130 }
128 ]
131 ]
129 $ hg log -rnull -T '{get(peerurls, "insecure")}\n'
132 $ hg log -rnull -T '{get(peerurls, "insecure")}\n'
130 http://foo:insecure@example.com/
133 http://foo:insecure@example.com/
131
134
132 zeroconf wraps ui.configitems(), which shouldn't crash at least:
135 zeroconf wraps ui.configitems(), which shouldn't crash at least:
133
136
134 $ hg paths --config extensions.zeroconf=
137 $ hg paths --config extensions.zeroconf=
135 dupe = $TESTTMP/b#tip
138 dupe = $TESTTMP/b#tip
136 dupe:pushurl = https://example.com/dupe
139 dupe:pushurl = https://example.com/dupe
137 expand = $TESTTMP/a/$SOMETHING/bar
140 expand = $TESTTMP/a/$SOMETHING/bar
138 insecure = http://foo:***@example.com/
141 insecure = http://foo:***@example.com/
139
142
140 $ cd ..
143 $ cd ..
141
144
142 sub-options for an undeclared path are ignored
145 sub-options for an undeclared path are ignored
143
146
144 $ hg init suboptions
147 $ hg init suboptions
145 $ cd suboptions
148 $ cd suboptions
146
149
147 $ cat > .hg/hgrc << EOF
150 $ cat > .hg/hgrc << EOF
148 > [paths]
151 > [paths]
149 > path0 = https://example.com/path0
152 > path0 = https://example.com/path0
150 > path1:pushurl = https://example.com/path1
153 > path1:pushurl = https://example.com/path1
151 > EOF
154 > EOF
152 $ hg paths
155 $ hg paths
153 path0 = https://example.com/path0
156 path0 = https://example.com/path0
154
157
155 unknown sub-options aren't displayed
158 unknown sub-options aren't displayed
156
159
157 $ cat > .hg/hgrc << EOF
160 $ cat > .hg/hgrc << EOF
158 > [paths]
161 > [paths]
159 > path0 = https://example.com/path0
162 > path0 = https://example.com/path0
160 > path0:foo = https://example.com/path1
163 > path0:foo = https://example.com/path1
161 > EOF
164 > EOF
162
165
163 $ hg paths
166 $ hg paths
164 path0 = https://example.com/path0
167 path0 = https://example.com/path0
165
168
166 :pushurl must be a URL
169 :pushurl must be a URL
167
170
168 $ cat > .hg/hgrc << EOF
171 $ cat > .hg/hgrc << EOF
169 > [paths]
172 > [paths]
170 > default = /path/to/nothing
173 > default = /path/to/nothing
171 > default:pushurl = /not/a/url
174 > default:pushurl = /not/a/url
172 > EOF
175 > EOF
173
176
174 $ hg paths
177 $ hg paths
175 (paths.default:pushurl not a URL; ignoring)
178 (paths.default:pushurl not a URL; ignoring)
176 default = /path/to/nothing
179 default = /path/to/nothing
177
180
178 #fragment is not allowed in :pushurl
181 #fragment is not allowed in :pushurl
179
182
180 $ cat > .hg/hgrc << EOF
183 $ cat > .hg/hgrc << EOF
181 > [paths]
184 > [paths]
182 > default = https://example.com/repo
185 > default = https://example.com/repo
183 > invalid = https://example.com/repo
186 > invalid = https://example.com/repo
184 > invalid:pushurl = https://example.com/repo#branch
187 > invalid:pushurl = https://example.com/repo#branch
185 > EOF
188 > EOF
186
189
187 $ hg paths
190 $ hg paths
188 ("#fragment" in paths.invalid:pushurl not supported; ignoring)
191 ("#fragment" in paths.invalid:pushurl not supported; ignoring)
189 default = https://example.com/repo
192 default = https://example.com/repo
190 invalid = https://example.com/repo
193 invalid = https://example.com/repo
191 invalid:pushurl = https://example.com/repo
194 invalid:pushurl = https://example.com/repo
192
195
193 $ cd ..
196 $ cd ..
194
197
195 'file:' disables [paths] entries for clone destination
198 'file:' disables [paths] entries for clone destination
196
199
197 $ cat >> $HGRCPATH <<EOF
200 $ cat >> $HGRCPATH <<EOF
198 > [paths]
201 > [paths]
199 > gpath1 = http://hg.example.com
202 > gpath1 = http://hg.example.com
200 > EOF
203 > EOF
201
204
202 $ hg clone a gpath1
205 $ hg clone a gpath1
203 abort: cannot create new http repository
206 abort: cannot create new http repository
204 [255]
207 [255]
205
208
206 $ hg clone a file:gpath1
209 $ hg clone a file:gpath1
207 updating to branch default
210 updating to branch default
208 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
211 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
209 $ cd gpath1
212 $ cd gpath1
210 $ hg -q id
213 $ hg -q id
211 000000000000
214 000000000000
212
215
213 $ cd ..
216 $ cd ..
214
217
215 Testing path referencing other paths
218 Testing path referencing other paths
216 ====================================
219 ====================================
217
220
218 basic setup
221 basic setup
219 -----------
222 -----------
220
223
221 $ ls -1
224 $ ls -1
222 a
225 a
223 b
226 b
224 gpath1
227 gpath1
225 suboptions
228 suboptions
226 $ hg init chained_path
229 $ hg init chained_path
227 $ cd chained_path
230 $ cd chained_path
228 $ cat << EOF > .hg/hgrc
231 $ cat << EOF > .hg/hgrc
229 > [paths]
232 > [paths]
230 > default=../a
233 > default=../a
231 > other_default=path://default
234 > other_default=path://default
232 > path_with_branch=../branchy#foo
235 > path_with_branch=../branchy#foo
233 > other_branch=path://path_with_branch
236 > other_branch=path://path_with_branch
234 > other_branched=path://path_with_branch#default
237 > other_branched=path://path_with_branch#default
235 > pushdest=../push-dest
238 > pushdest=../push-dest
236 > pushdest:pushrev=default
239 > pushdest:pushrev=default
237 > pushdest2=path://pushdest
240 > pushdest2=path://pushdest
238 > pushdest-overwrite=path://pushdest
241 > pushdest-overwrite=path://pushdest
239 > pushdest-overwrite:pushrev=foo
242 > pushdest-overwrite:pushrev=foo
240 > EOF
243 > EOF
241
244
242 $ hg init ../branchy
245 $ hg init ../branchy
243 $ hg init ../push-dest
246 $ hg init ../push-dest
244 $ hg debugbuilddag -R ../branchy '.:base+3<base@foo+5'
247 $ hg debugbuilddag -R ../branchy '.:base+3<base@foo+5'
245 $ hg log -G -T '{branch}\n' -R ../branchy
248 $ hg log -G -T '{branch}\n' -R ../branchy
246 o foo
249 o foo
247 |
250 |
248 o foo
251 o foo
249 |
252 |
250 o foo
253 o foo
251 |
254 |
252 o foo
255 o foo
253 |
256 |
254 o foo
257 o foo
255 |
258 |
256 | o default
259 | o default
257 | |
260 | |
258 | o default
261 | o default
259 | |
262 | |
260 | o default
263 | o default
261 |/
264 |/
262 o default
265 o default
263
266
264
267
265 $ hg paths
268 $ hg paths
266 default = $TESTTMP/a
269 default = $TESTTMP/a
267 gpath1 = http://hg.example.com/
270 gpath1 = http://hg.example.com/
268 other_branch = $TESTTMP/branchy#foo
271 other_branch = $TESTTMP/branchy#foo
269 other_branched = $TESTTMP/branchy#default
272 other_branched = $TESTTMP/branchy#default
270 other_default = $TESTTMP/a
273 other_default = $TESTTMP/a
271 path_with_branch = $TESTTMP/branchy#foo
274 path_with_branch = $TESTTMP/branchy#foo
272 pushdest = $TESTTMP/push-dest
275 pushdest = $TESTTMP/push-dest
273 pushdest:pushrev = default
276 pushdest:pushrev = default
274 pushdest-overwrite = $TESTTMP/push-dest
277 pushdest-overwrite = $TESTTMP/push-dest
275 pushdest-overwrite:pushrev = foo
278 pushdest-overwrite:pushrev = foo
276 pushdest2 = $TESTTMP/push-dest
279 pushdest2 = $TESTTMP/push-dest
277 pushdest2:pushrev = default
280 pushdest2:pushrev = default
278
281
279 test basic chaining
282 test basic chaining
280 -------------------
283 -------------------
281
284
282 $ hg path other_default
285 $ hg path other_default
283 $TESTTMP/a
286 $TESTTMP/a
284 $ hg pull default
287 $ hg pull default
285 pulling from $TESTTMP/a
288 pulling from $TESTTMP/a
286 no changes found
289 no changes found
287 $ hg pull other_default
290 $ hg pull other_default
288 pulling from $TESTTMP/a
291 pulling from $TESTTMP/a
289 no changes found
292 no changes found
290
293
291 test inheritance of the #fragment part
294 test inheritance of the #fragment part
292 --------------------------------------
295 --------------------------------------
293
296
294 $ hg pull path_with_branch
297 $ hg pull path_with_branch
295 pulling from $TESTTMP/branchy
298 pulling from $TESTTMP/branchy
296 adding changesets
299 adding changesets
297 adding manifests
300 adding manifests
298 adding file changes
301 adding file changes
299 added 6 changesets with 0 changes to 0 files
302 added 6 changesets with 0 changes to 0 files
300 new changesets 1ea73414a91b:bcebb50b77de
303 new changesets 1ea73414a91b:bcebb50b77de
301 (run 'hg update' to get a working copy)
304 (run 'hg update' to get a working copy)
302 $ hg pull other_branch
305 $ hg pull other_branch
303 pulling from $TESTTMP/branchy
306 pulling from $TESTTMP/branchy
304 no changes found
307 no changes found
305 $ hg pull other_branched
308 $ hg pull other_branched
306 pulling from $TESTTMP/branchy
309 pulling from $TESTTMP/branchy
307 searching for changes
310 searching for changes
308 adding changesets
311 adding changesets
309 adding manifests
312 adding manifests
310 adding file changes
313 adding file changes
311 added 3 changesets with 0 changes to 0 files (+1 heads)
314 added 3 changesets with 0 changes to 0 files (+1 heads)
312 new changesets 66f7d451a68b:2dc09a01254d
315 new changesets 66f7d451a68b:2dc09a01254d
313 (run 'hg heads' to see heads)
316 (run 'hg heads' to see heads)
314
317
315 test inheritance of the suboptions
318 test inheritance of the suboptions
316 ----------------------------------
319 ----------------------------------
317
320
318 $ hg push pushdest
321 $ hg push pushdest
319 pushing to $TESTTMP/push-dest
322 pushing to $TESTTMP/push-dest
320 searching for changes
323 searching for changes
321 adding changesets
324 adding changesets
322 adding manifests
325 adding manifests
323 adding file changes
326 adding file changes
324 added 4 changesets with 0 changes to 0 files
327 added 4 changesets with 0 changes to 0 files
325 $ hg push pushdest2
328 $ hg push pushdest2
326 pushing to $TESTTMP/push-dest
329 pushing to $TESTTMP/push-dest
327 searching for changes
330 searching for changes
328 no changes found
331 no changes found
329 [1]
332 [1]
330 $ hg push pushdest-overwrite --new-branch
333 $ hg push pushdest-overwrite --new-branch
331 pushing to $TESTTMP/push-dest
334 pushing to $TESTTMP/push-dest
332 searching for changes
335 searching for changes
333 adding changesets
336 adding changesets
334 adding manifests
337 adding manifests
335 adding file changes
338 adding file changes
336 added 5 changesets with 0 changes to 0 files (+1 heads)
339 added 5 changesets with 0 changes to 0 files (+1 heads)
337
340
338 Test chaining path:// definition
341 Test chaining path:// definition
339 --------------------------------
342 --------------------------------
340
343
341 This is currently unsupported, but feel free to implement the necessary
344 This is currently unsupported, but feel free to implement the necessary
342 dependency detection.
345 dependency detection.
343
346
344 $ cat << EOF >> .hg/hgrc
347 $ cat << EOF >> .hg/hgrc
345 > chain_path=path://other_default
348 > chain_path=path://other_default
346 > EOF
349 > EOF
347
350
348 $ hg id
351 $ hg id
349 000000000000
352 000000000000
350 $ hg path
353 $ hg path
351 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
354 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
352 [255]
355 [255]
353 $ hg pull chain_path
356 $ hg pull chain_path
354 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
357 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
355 [255]
358 [255]
356
359
357 Doing an actual circle should always be an issue
360 Doing an actual circle should always be an issue
358
361
359 $ cat << EOF >> .hg/hgrc
362 $ cat << EOF >> .hg/hgrc
360 > rock=path://cissors
363 > rock=path://cissors
361 > cissors=path://paper
364 > cissors=path://paper
362 > paper=://rock
365 > paper=://rock
363 > EOF
366 > EOF
364
367
365 $ hg id
368 $ hg id
366 000000000000
369 000000000000
367 $ hg path
370 $ hg path
368 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
371 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
369 [255]
372 [255]
370 $ hg pull chain_path
373 $ hg pull chain_path
371 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
374 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
372 [255]
375 [255]
373
376
374 Test basic error cases
377 Test basic error cases
375 ----------------------
378 ----------------------
376
379
377 $ cat << EOF > .hg/hgrc
380 $ cat << EOF > .hg/hgrc
378 > [paths]
381 > [paths]
379 > error-missing=path://unknown
382 > error-missing=path://unknown
380 > EOF
383 > EOF
381 $ hg path
384 $ hg path
382 abort: cannot use `path://unknown`, "unknown" is not a known path
385 abort: cannot use `path://unknown`, "unknown" is not a known path
383 [255]
386 [255]
384 $ hg pull error-missing
387 $ hg pull error-missing
385 abort: cannot use `path://unknown`, "unknown" is not a known path
388 abort: cannot use `path://unknown`, "unknown" is not a known path
386 [255]
389 [255]
387
390
General Comments 0
You need to be logged in to leave comments. Login now