##// END OF EJS Templates
configitems: register the 'web.guessmime' config
Boris Feld -
r34609:f12de15c default
parent child Browse files
Show More
@@ -1,839 +1,842
1 # configitems.py - centralized declaration of configuration option
1 # configitems.py - centralized declaration of configuration option
2 #
2 #
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import functools
10 import functools
11
11
12 from . import (
12 from . import (
13 encoding,
13 encoding,
14 error,
14 error,
15 )
15 )
16
16
17 def loadconfigtable(ui, extname, configtable):
17 def loadconfigtable(ui, extname, configtable):
18 """update config item known to the ui with the extension ones"""
18 """update config item known to the ui with the extension ones"""
19 for section, items in configtable.items():
19 for section, items in configtable.items():
20 knownitems = ui._knownconfig.setdefault(section, {})
20 knownitems = ui._knownconfig.setdefault(section, {})
21 knownkeys = set(knownitems)
21 knownkeys = set(knownitems)
22 newkeys = set(items)
22 newkeys = set(items)
23 for key in sorted(knownkeys & newkeys):
23 for key in sorted(knownkeys & newkeys):
24 msg = "extension '%s' overwrite config item '%s.%s'"
24 msg = "extension '%s' overwrite config item '%s.%s'"
25 msg %= (extname, section, key)
25 msg %= (extname, section, key)
26 ui.develwarn(msg, config='warn-config')
26 ui.develwarn(msg, config='warn-config')
27
27
28 knownitems.update(items)
28 knownitems.update(items)
29
29
30 class configitem(object):
30 class configitem(object):
31 """represent a known config item
31 """represent a known config item
32
32
33 :section: the official config section where to find this item,
33 :section: the official config section where to find this item,
34 :name: the official name within the section,
34 :name: the official name within the section,
35 :default: default value for this item,
35 :default: default value for this item,
36 :alias: optional list of tuples as alternatives.
36 :alias: optional list of tuples as alternatives.
37 """
37 """
38
38
39 def __init__(self, section, name, default=None, alias=()):
39 def __init__(self, section, name, default=None, alias=()):
40 self.section = section
40 self.section = section
41 self.name = name
41 self.name = name
42 self.default = default
42 self.default = default
43 self.alias = list(alias)
43 self.alias = list(alias)
44
44
45 coreitems = {}
45 coreitems = {}
46
46
47 def _register(configtable, *args, **kwargs):
47 def _register(configtable, *args, **kwargs):
48 item = configitem(*args, **kwargs)
48 item = configitem(*args, **kwargs)
49 section = configtable.setdefault(item.section, {})
49 section = configtable.setdefault(item.section, {})
50 if item.name in section:
50 if item.name in section:
51 msg = "duplicated config item registration for '%s.%s'"
51 msg = "duplicated config item registration for '%s.%s'"
52 raise error.ProgrammingError(msg % (item.section, item.name))
52 raise error.ProgrammingError(msg % (item.section, item.name))
53 section[item.name] = item
53 section[item.name] = item
54
54
55 # special value for case where the default is derived from other values
55 # special value for case where the default is derived from other values
56 dynamicdefault = object()
56 dynamicdefault = object()
57
57
58 # Registering actual config items
58 # Registering actual config items
59
59
60 def getitemregister(configtable):
60 def getitemregister(configtable):
61 return functools.partial(_register, configtable)
61 return functools.partial(_register, configtable)
62
62
63 coreconfigitem = getitemregister(coreitems)
63 coreconfigitem = getitemregister(coreitems)
64
64
65 coreconfigitem('auth', 'cookiefile',
65 coreconfigitem('auth', 'cookiefile',
66 default=None,
66 default=None,
67 )
67 )
68 # bookmarks.pushing: internal hack for discovery
68 # bookmarks.pushing: internal hack for discovery
69 coreconfigitem('bookmarks', 'pushing',
69 coreconfigitem('bookmarks', 'pushing',
70 default=list,
70 default=list,
71 )
71 )
72 # bundle.mainreporoot: internal hack for bundlerepo
72 # bundle.mainreporoot: internal hack for bundlerepo
73 coreconfigitem('bundle', 'mainreporoot',
73 coreconfigitem('bundle', 'mainreporoot',
74 default='',
74 default='',
75 )
75 )
76 # bundle.reorder: experimental config
76 # bundle.reorder: experimental config
77 coreconfigitem('bundle', 'reorder',
77 coreconfigitem('bundle', 'reorder',
78 default='auto',
78 default='auto',
79 )
79 )
80 coreconfigitem('censor', 'policy',
80 coreconfigitem('censor', 'policy',
81 default='abort',
81 default='abort',
82 )
82 )
83 coreconfigitem('chgserver', 'idletimeout',
83 coreconfigitem('chgserver', 'idletimeout',
84 default=3600,
84 default=3600,
85 )
85 )
86 coreconfigitem('chgserver', 'skiphash',
86 coreconfigitem('chgserver', 'skiphash',
87 default=False,
87 default=False,
88 )
88 )
89 coreconfigitem('cmdserver', 'log',
89 coreconfigitem('cmdserver', 'log',
90 default=None,
90 default=None,
91 )
91 )
92 coreconfigitem('color', 'mode',
92 coreconfigitem('color', 'mode',
93 default='auto',
93 default='auto',
94 )
94 )
95 coreconfigitem('color', 'pagermode',
95 coreconfigitem('color', 'pagermode',
96 default=dynamicdefault,
96 default=dynamicdefault,
97 )
97 )
98 coreconfigitem('commands', 'status.relative',
98 coreconfigitem('commands', 'status.relative',
99 default=False,
99 default=False,
100 )
100 )
101 coreconfigitem('commands', 'status.skipstates',
101 coreconfigitem('commands', 'status.skipstates',
102 default=[],
102 default=[],
103 )
103 )
104 coreconfigitem('commands', 'status.verbose',
104 coreconfigitem('commands', 'status.verbose',
105 default=False,
105 default=False,
106 )
106 )
107 coreconfigitem('commands', 'update.requiredest',
107 coreconfigitem('commands', 'update.requiredest',
108 default=False,
108 default=False,
109 )
109 )
110 coreconfigitem('debug', 'dirstate.delaywrite',
110 coreconfigitem('debug', 'dirstate.delaywrite',
111 default=0,
111 default=0,
112 )
112 )
113 coreconfigitem('devel', 'all-warnings',
113 coreconfigitem('devel', 'all-warnings',
114 default=False,
114 default=False,
115 )
115 )
116 coreconfigitem('devel', 'bundle2.debug',
116 coreconfigitem('devel', 'bundle2.debug',
117 default=False,
117 default=False,
118 )
118 )
119 coreconfigitem('devel', 'cache-vfs',
119 coreconfigitem('devel', 'cache-vfs',
120 default=None,
120 default=None,
121 )
121 )
122 coreconfigitem('devel', 'check-locks',
122 coreconfigitem('devel', 'check-locks',
123 default=False,
123 default=False,
124 )
124 )
125 coreconfigitem('devel', 'check-relroot',
125 coreconfigitem('devel', 'check-relroot',
126 default=False,
126 default=False,
127 )
127 )
128 coreconfigitem('devel', 'default-date',
128 coreconfigitem('devel', 'default-date',
129 default=None,
129 default=None,
130 )
130 )
131 coreconfigitem('devel', 'deprec-warn',
131 coreconfigitem('devel', 'deprec-warn',
132 default=False,
132 default=False,
133 )
133 )
134 coreconfigitem('devel', 'disableloaddefaultcerts',
134 coreconfigitem('devel', 'disableloaddefaultcerts',
135 default=False,
135 default=False,
136 )
136 )
137 coreconfigitem('devel', 'empty-changegroup',
137 coreconfigitem('devel', 'empty-changegroup',
138 default=False,
138 default=False,
139 )
139 )
140 coreconfigitem('devel', 'legacy.exchange',
140 coreconfigitem('devel', 'legacy.exchange',
141 default=list,
141 default=list,
142 )
142 )
143 coreconfigitem('devel', 'servercafile',
143 coreconfigitem('devel', 'servercafile',
144 default='',
144 default='',
145 )
145 )
146 coreconfigitem('devel', 'serverexactprotocol',
146 coreconfigitem('devel', 'serverexactprotocol',
147 default='',
147 default='',
148 )
148 )
149 coreconfigitem('devel', 'serverrequirecert',
149 coreconfigitem('devel', 'serverrequirecert',
150 default=False,
150 default=False,
151 )
151 )
152 coreconfigitem('devel', 'strip-obsmarkers',
152 coreconfigitem('devel', 'strip-obsmarkers',
153 default=True,
153 default=True,
154 )
154 )
155 coreconfigitem('devel', 'warn-config',
155 coreconfigitem('devel', 'warn-config',
156 default=None,
156 default=None,
157 )
157 )
158 coreconfigitem('devel', 'warn-config-default',
158 coreconfigitem('devel', 'warn-config-default',
159 default=None,
159 default=None,
160 )
160 )
161 coreconfigitem('devel', 'user.obsmarker',
161 coreconfigitem('devel', 'user.obsmarker',
162 default=None,
162 default=None,
163 )
163 )
164 coreconfigitem('diff', 'nodates',
164 coreconfigitem('diff', 'nodates',
165 default=None,
165 default=None,
166 )
166 )
167 coreconfigitem('diff', 'showfunc',
167 coreconfigitem('diff', 'showfunc',
168 default=None,
168 default=None,
169 )
169 )
170 coreconfigitem('diff', 'unified',
170 coreconfigitem('diff', 'unified',
171 default=None,
171 default=None,
172 )
172 )
173 coreconfigitem('diff', 'git',
173 coreconfigitem('diff', 'git',
174 default=None,
174 default=None,
175 )
175 )
176 coreconfigitem('diff', 'ignorews',
176 coreconfigitem('diff', 'ignorews',
177 default=None,
177 default=None,
178 )
178 )
179 coreconfigitem('diff', 'ignorewsamount',
179 coreconfigitem('diff', 'ignorewsamount',
180 default=None,
180 default=None,
181 )
181 )
182 coreconfigitem('diff', 'ignoreblanklines',
182 coreconfigitem('diff', 'ignoreblanklines',
183 default=None,
183 default=None,
184 )
184 )
185 coreconfigitem('diff', 'ignorewseol',
185 coreconfigitem('diff', 'ignorewseol',
186 default=None,
186 default=None,
187 )
187 )
188 coreconfigitem('diff', 'nobinary',
188 coreconfigitem('diff', 'nobinary',
189 default=None,
189 default=None,
190 )
190 )
191 coreconfigitem('diff', 'noprefix',
191 coreconfigitem('diff', 'noprefix',
192 default=None,
192 default=None,
193 )
193 )
194 coreconfigitem('email', 'bcc',
194 coreconfigitem('email', 'bcc',
195 default=None,
195 default=None,
196 )
196 )
197 coreconfigitem('email', 'cc',
197 coreconfigitem('email', 'cc',
198 default=None,
198 default=None,
199 )
199 )
200 coreconfigitem('email', 'charsets',
200 coreconfigitem('email', 'charsets',
201 default=list,
201 default=list,
202 )
202 )
203 coreconfigitem('email', 'from',
203 coreconfigitem('email', 'from',
204 default=None,
204 default=None,
205 )
205 )
206 coreconfigitem('email', 'method',
206 coreconfigitem('email', 'method',
207 default='smtp',
207 default='smtp',
208 )
208 )
209 coreconfigitem('email', 'reply-to',
209 coreconfigitem('email', 'reply-to',
210 default=None,
210 default=None,
211 )
211 )
212 coreconfigitem('experimental', 'allowdivergence',
212 coreconfigitem('experimental', 'allowdivergence',
213 default=False,
213 default=False,
214 )
214 )
215 coreconfigitem('experimental', 'bundle-phases',
215 coreconfigitem('experimental', 'bundle-phases',
216 default=False,
216 default=False,
217 )
217 )
218 coreconfigitem('experimental', 'bundle2-advertise',
218 coreconfigitem('experimental', 'bundle2-advertise',
219 default=True,
219 default=True,
220 )
220 )
221 coreconfigitem('experimental', 'bundle2-output-capture',
221 coreconfigitem('experimental', 'bundle2-output-capture',
222 default=False,
222 default=False,
223 )
223 )
224 coreconfigitem('experimental', 'bundle2.pushback',
224 coreconfigitem('experimental', 'bundle2.pushback',
225 default=False,
225 default=False,
226 )
226 )
227 coreconfigitem('experimental', 'bundle2lazylocking',
227 coreconfigitem('experimental', 'bundle2lazylocking',
228 default=False,
228 default=False,
229 )
229 )
230 coreconfigitem('experimental', 'bundlecomplevel',
230 coreconfigitem('experimental', 'bundlecomplevel',
231 default=None,
231 default=None,
232 )
232 )
233 coreconfigitem('experimental', 'changegroup3',
233 coreconfigitem('experimental', 'changegroup3',
234 default=False,
234 default=False,
235 )
235 )
236 coreconfigitem('experimental', 'clientcompressionengines',
236 coreconfigitem('experimental', 'clientcompressionengines',
237 default=list,
237 default=list,
238 )
238 )
239 coreconfigitem('experimental', 'copytrace',
239 coreconfigitem('experimental', 'copytrace',
240 default='on',
240 default='on',
241 )
241 )
242 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
242 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
243 default=100,
243 default=100,
244 )
244 )
245 coreconfigitem('experimental', 'crecordtest',
245 coreconfigitem('experimental', 'crecordtest',
246 default=None,
246 default=None,
247 )
247 )
248 coreconfigitem('experimental', 'editortmpinhg',
248 coreconfigitem('experimental', 'editortmpinhg',
249 default=False,
249 default=False,
250 )
250 )
251 coreconfigitem('experimental', 'maxdeltachainspan',
251 coreconfigitem('experimental', 'maxdeltachainspan',
252 default=-1,
252 default=-1,
253 )
253 )
254 coreconfigitem('experimental', 'mmapindexthreshold',
254 coreconfigitem('experimental', 'mmapindexthreshold',
255 default=None,
255 default=None,
256 )
256 )
257 coreconfigitem('experimental', 'nonnormalparanoidcheck',
257 coreconfigitem('experimental', 'nonnormalparanoidcheck',
258 default=False,
258 default=False,
259 )
259 )
260 coreconfigitem('experimental', 'stabilization',
260 coreconfigitem('experimental', 'stabilization',
261 default=list,
261 default=list,
262 alias=[('experimental', 'evolution')],
262 alias=[('experimental', 'evolution')],
263 )
263 )
264 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
264 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
265 default=False,
265 default=False,
266 alias=[('experimental', 'evolution.bundle-obsmarker')],
266 alias=[('experimental', 'evolution.bundle-obsmarker')],
267 )
267 )
268 coreconfigitem('experimental', 'stabilization.track-operation',
268 coreconfigitem('experimental', 'stabilization.track-operation',
269 default=True,
269 default=True,
270 alias=[('experimental', 'evolution.track-operation')]
270 alias=[('experimental', 'evolution.track-operation')]
271 )
271 )
272 coreconfigitem('experimental', 'exportableenviron',
272 coreconfigitem('experimental', 'exportableenviron',
273 default=list,
273 default=list,
274 )
274 )
275 coreconfigitem('experimental', 'extendedheader.index',
275 coreconfigitem('experimental', 'extendedheader.index',
276 default=None,
276 default=None,
277 )
277 )
278 coreconfigitem('experimental', 'extendedheader.similarity',
278 coreconfigitem('experimental', 'extendedheader.similarity',
279 default=False,
279 default=False,
280 )
280 )
281 coreconfigitem('experimental', 'format.compression',
281 coreconfigitem('experimental', 'format.compression',
282 default='zlib',
282 default='zlib',
283 )
283 )
284 coreconfigitem('experimental', 'graphshorten',
284 coreconfigitem('experimental', 'graphshorten',
285 default=False,
285 default=False,
286 )
286 )
287 coreconfigitem('experimental', 'graphstyle.parent',
287 coreconfigitem('experimental', 'graphstyle.parent',
288 default=dynamicdefault,
288 default=dynamicdefault,
289 )
289 )
290 coreconfigitem('experimental', 'graphstyle.missing',
290 coreconfigitem('experimental', 'graphstyle.missing',
291 default=dynamicdefault,
291 default=dynamicdefault,
292 )
292 )
293 coreconfigitem('experimental', 'graphstyle.grandparent',
293 coreconfigitem('experimental', 'graphstyle.grandparent',
294 default=dynamicdefault,
294 default=dynamicdefault,
295 )
295 )
296 coreconfigitem('experimental', 'hook-track-tags',
296 coreconfigitem('experimental', 'hook-track-tags',
297 default=False,
297 default=False,
298 )
298 )
299 coreconfigitem('experimental', 'httppostargs',
299 coreconfigitem('experimental', 'httppostargs',
300 default=False,
300 default=False,
301 )
301 )
302 coreconfigitem('experimental', 'manifestv2',
302 coreconfigitem('experimental', 'manifestv2',
303 default=False,
303 default=False,
304 )
304 )
305 coreconfigitem('experimental', 'mergedriver',
305 coreconfigitem('experimental', 'mergedriver',
306 default=None,
306 default=None,
307 )
307 )
308 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
308 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
309 default=False,
309 default=False,
310 )
310 )
311 coreconfigitem('experimental', 'rebase.multidest',
311 coreconfigitem('experimental', 'rebase.multidest',
312 default=False,
312 default=False,
313 )
313 )
314 coreconfigitem('experimental', 'revertalternateinteractivemode',
314 coreconfigitem('experimental', 'revertalternateinteractivemode',
315 default=True,
315 default=True,
316 )
316 )
317 coreconfigitem('experimental', 'revlogv2',
317 coreconfigitem('experimental', 'revlogv2',
318 default=None,
318 default=None,
319 )
319 )
320 coreconfigitem('experimental', 'spacemovesdown',
320 coreconfigitem('experimental', 'spacemovesdown',
321 default=False,
321 default=False,
322 )
322 )
323 coreconfigitem('experimental', 'treemanifest',
323 coreconfigitem('experimental', 'treemanifest',
324 default=False,
324 default=False,
325 )
325 )
326 coreconfigitem('experimental', 'updatecheck',
326 coreconfigitem('experimental', 'updatecheck',
327 default=None,
327 default=None,
328 )
328 )
329 coreconfigitem('format', 'aggressivemergedeltas',
329 coreconfigitem('format', 'aggressivemergedeltas',
330 default=False,
330 default=False,
331 )
331 )
332 coreconfigitem('format', 'chunkcachesize',
332 coreconfigitem('format', 'chunkcachesize',
333 default=None,
333 default=None,
334 )
334 )
335 coreconfigitem('format', 'dotencode',
335 coreconfigitem('format', 'dotencode',
336 default=True,
336 default=True,
337 )
337 )
338 coreconfigitem('format', 'generaldelta',
338 coreconfigitem('format', 'generaldelta',
339 default=False,
339 default=False,
340 )
340 )
341 coreconfigitem('format', 'manifestcachesize',
341 coreconfigitem('format', 'manifestcachesize',
342 default=None,
342 default=None,
343 )
343 )
344 coreconfigitem('format', 'maxchainlen',
344 coreconfigitem('format', 'maxchainlen',
345 default=None,
345 default=None,
346 )
346 )
347 coreconfigitem('format', 'obsstore-version',
347 coreconfigitem('format', 'obsstore-version',
348 default=None,
348 default=None,
349 )
349 )
350 coreconfigitem('format', 'usefncache',
350 coreconfigitem('format', 'usefncache',
351 default=True,
351 default=True,
352 )
352 )
353 coreconfigitem('format', 'usegeneraldelta',
353 coreconfigitem('format', 'usegeneraldelta',
354 default=True,
354 default=True,
355 )
355 )
356 coreconfigitem('format', 'usestore',
356 coreconfigitem('format', 'usestore',
357 default=True,
357 default=True,
358 )
358 )
359 coreconfigitem('hostsecurity', 'ciphers',
359 coreconfigitem('hostsecurity', 'ciphers',
360 default=None,
360 default=None,
361 )
361 )
362 coreconfigitem('hostsecurity', 'disabletls10warning',
362 coreconfigitem('hostsecurity', 'disabletls10warning',
363 default=False,
363 default=False,
364 )
364 )
365 coreconfigitem('http_proxy', 'always',
365 coreconfigitem('http_proxy', 'always',
366 default=False,
366 default=False,
367 )
367 )
368 coreconfigitem('http_proxy', 'host',
368 coreconfigitem('http_proxy', 'host',
369 default=None,
369 default=None,
370 )
370 )
371 coreconfigitem('http_proxy', 'no',
371 coreconfigitem('http_proxy', 'no',
372 default=list,
372 default=list,
373 )
373 )
374 coreconfigitem('http_proxy', 'passwd',
374 coreconfigitem('http_proxy', 'passwd',
375 default=None,
375 default=None,
376 )
376 )
377 coreconfigitem('http_proxy', 'user',
377 coreconfigitem('http_proxy', 'user',
378 default=None,
378 default=None,
379 )
379 )
380 coreconfigitem('logtoprocess', 'commandexception',
380 coreconfigitem('logtoprocess', 'commandexception',
381 default=None,
381 default=None,
382 )
382 )
383 coreconfigitem('logtoprocess', 'commandfinish',
383 coreconfigitem('logtoprocess', 'commandfinish',
384 default=None,
384 default=None,
385 )
385 )
386 coreconfigitem('logtoprocess', 'command',
386 coreconfigitem('logtoprocess', 'command',
387 default=None,
387 default=None,
388 )
388 )
389 coreconfigitem('logtoprocess', 'develwarn',
389 coreconfigitem('logtoprocess', 'develwarn',
390 default=None,
390 default=None,
391 )
391 )
392 coreconfigitem('logtoprocess', 'uiblocked',
392 coreconfigitem('logtoprocess', 'uiblocked',
393 default=None,
393 default=None,
394 )
394 )
395 coreconfigitem('merge', 'checkunknown',
395 coreconfigitem('merge', 'checkunknown',
396 default='abort',
396 default='abort',
397 )
397 )
398 coreconfigitem('merge', 'checkignored',
398 coreconfigitem('merge', 'checkignored',
399 default='abort',
399 default='abort',
400 )
400 )
401 coreconfigitem('merge', 'followcopies',
401 coreconfigitem('merge', 'followcopies',
402 default=True,
402 default=True,
403 )
403 )
404 coreconfigitem('merge', 'preferancestor',
404 coreconfigitem('merge', 'preferancestor',
405 default=lambda: ['*'],
405 default=lambda: ['*'],
406 )
406 )
407 coreconfigitem('pager', 'ignore',
407 coreconfigitem('pager', 'ignore',
408 default=list,
408 default=list,
409 )
409 )
410 coreconfigitem('pager', 'pager',
410 coreconfigitem('pager', 'pager',
411 default=dynamicdefault,
411 default=dynamicdefault,
412 )
412 )
413 coreconfigitem('patch', 'eol',
413 coreconfigitem('patch', 'eol',
414 default='strict',
414 default='strict',
415 )
415 )
416 coreconfigitem('patch', 'fuzz',
416 coreconfigitem('patch', 'fuzz',
417 default=2,
417 default=2,
418 )
418 )
419 coreconfigitem('paths', 'default',
419 coreconfigitem('paths', 'default',
420 default=None,
420 default=None,
421 )
421 )
422 coreconfigitem('paths', 'default-push',
422 coreconfigitem('paths', 'default-push',
423 default=None,
423 default=None,
424 )
424 )
425 coreconfigitem('phases', 'checksubrepos',
425 coreconfigitem('phases', 'checksubrepos',
426 default='follow',
426 default='follow',
427 )
427 )
428 coreconfigitem('phases', 'new-commit',
428 coreconfigitem('phases', 'new-commit',
429 default='draft',
429 default='draft',
430 )
430 )
431 coreconfigitem('phases', 'publish',
431 coreconfigitem('phases', 'publish',
432 default=True,
432 default=True,
433 )
433 )
434 coreconfigitem('profiling', 'enabled',
434 coreconfigitem('profiling', 'enabled',
435 default=False,
435 default=False,
436 )
436 )
437 coreconfigitem('profiling', 'format',
437 coreconfigitem('profiling', 'format',
438 default='text',
438 default='text',
439 )
439 )
440 coreconfigitem('profiling', 'freq',
440 coreconfigitem('profiling', 'freq',
441 default=1000,
441 default=1000,
442 )
442 )
443 coreconfigitem('profiling', 'limit',
443 coreconfigitem('profiling', 'limit',
444 default=30,
444 default=30,
445 )
445 )
446 coreconfigitem('profiling', 'nested',
446 coreconfigitem('profiling', 'nested',
447 default=0,
447 default=0,
448 )
448 )
449 coreconfigitem('profiling', 'output',
449 coreconfigitem('profiling', 'output',
450 default=None,
450 default=None,
451 )
451 )
452 coreconfigitem('profiling', 'showmax',
452 coreconfigitem('profiling', 'showmax',
453 default=0.999,
453 default=0.999,
454 )
454 )
455 coreconfigitem('profiling', 'showmin',
455 coreconfigitem('profiling', 'showmin',
456 default=dynamicdefault,
456 default=dynamicdefault,
457 )
457 )
458 coreconfigitem('profiling', 'sort',
458 coreconfigitem('profiling', 'sort',
459 default='inlinetime',
459 default='inlinetime',
460 )
460 )
461 coreconfigitem('profiling', 'statformat',
461 coreconfigitem('profiling', 'statformat',
462 default='hotpath',
462 default='hotpath',
463 )
463 )
464 coreconfigitem('profiling', 'type',
464 coreconfigitem('profiling', 'type',
465 default='stat',
465 default='stat',
466 )
466 )
467 coreconfigitem('progress', 'assume-tty',
467 coreconfigitem('progress', 'assume-tty',
468 default=False,
468 default=False,
469 )
469 )
470 coreconfigitem('progress', 'changedelay',
470 coreconfigitem('progress', 'changedelay',
471 default=1,
471 default=1,
472 )
472 )
473 coreconfigitem('progress', 'clear-complete',
473 coreconfigitem('progress', 'clear-complete',
474 default=True,
474 default=True,
475 )
475 )
476 coreconfigitem('progress', 'debug',
476 coreconfigitem('progress', 'debug',
477 default=False,
477 default=False,
478 )
478 )
479 coreconfigitem('progress', 'delay',
479 coreconfigitem('progress', 'delay',
480 default=3,
480 default=3,
481 )
481 )
482 coreconfigitem('progress', 'disable',
482 coreconfigitem('progress', 'disable',
483 default=False,
483 default=False,
484 )
484 )
485 coreconfigitem('progress', 'estimateinterval',
485 coreconfigitem('progress', 'estimateinterval',
486 default=60.0,
486 default=60.0,
487 )
487 )
488 coreconfigitem('progress', 'refresh',
488 coreconfigitem('progress', 'refresh',
489 default=0.1,
489 default=0.1,
490 )
490 )
491 coreconfigitem('progress', 'width',
491 coreconfigitem('progress', 'width',
492 default=dynamicdefault,
492 default=dynamicdefault,
493 )
493 )
494 coreconfigitem('push', 'pushvars.server',
494 coreconfigitem('push', 'pushvars.server',
495 default=False,
495 default=False,
496 )
496 )
497 coreconfigitem('server', 'bundle1',
497 coreconfigitem('server', 'bundle1',
498 default=True,
498 default=True,
499 )
499 )
500 coreconfigitem('server', 'bundle1gd',
500 coreconfigitem('server', 'bundle1gd',
501 default=None,
501 default=None,
502 )
502 )
503 coreconfigitem('server', 'compressionengines',
503 coreconfigitem('server', 'compressionengines',
504 default=list,
504 default=list,
505 )
505 )
506 coreconfigitem('server', 'concurrent-push-mode',
506 coreconfigitem('server', 'concurrent-push-mode',
507 default='strict',
507 default='strict',
508 )
508 )
509 coreconfigitem('server', 'disablefullbundle',
509 coreconfigitem('server', 'disablefullbundle',
510 default=False,
510 default=False,
511 )
511 )
512 coreconfigitem('server', 'maxhttpheaderlen',
512 coreconfigitem('server', 'maxhttpheaderlen',
513 default=1024,
513 default=1024,
514 )
514 )
515 coreconfigitem('server', 'preferuncompressed',
515 coreconfigitem('server', 'preferuncompressed',
516 default=False,
516 default=False,
517 )
517 )
518 coreconfigitem('server', 'uncompressed',
518 coreconfigitem('server', 'uncompressed',
519 default=True,
519 default=True,
520 )
520 )
521 coreconfigitem('server', 'uncompressedallowsecret',
521 coreconfigitem('server', 'uncompressedallowsecret',
522 default=False,
522 default=False,
523 )
523 )
524 coreconfigitem('server', 'validate',
524 coreconfigitem('server', 'validate',
525 default=False,
525 default=False,
526 )
526 )
527 coreconfigitem('server', 'zliblevel',
527 coreconfigitem('server', 'zliblevel',
528 default=-1,
528 default=-1,
529 )
529 )
530 coreconfigitem('smtp', 'host',
530 coreconfigitem('smtp', 'host',
531 default=None,
531 default=None,
532 )
532 )
533 coreconfigitem('smtp', 'local_hostname',
533 coreconfigitem('smtp', 'local_hostname',
534 default=None,
534 default=None,
535 )
535 )
536 coreconfigitem('smtp', 'password',
536 coreconfigitem('smtp', 'password',
537 default=None,
537 default=None,
538 )
538 )
539 coreconfigitem('smtp', 'port',
539 coreconfigitem('smtp', 'port',
540 default=dynamicdefault,
540 default=dynamicdefault,
541 )
541 )
542 coreconfigitem('smtp', 'tls',
542 coreconfigitem('smtp', 'tls',
543 default='none',
543 default='none',
544 )
544 )
545 coreconfigitem('smtp', 'username',
545 coreconfigitem('smtp', 'username',
546 default=None,
546 default=None,
547 )
547 )
548 coreconfigitem('sparse', 'missingwarning',
548 coreconfigitem('sparse', 'missingwarning',
549 default=True,
549 default=True,
550 )
550 )
551 coreconfigitem('trusted', 'groups',
551 coreconfigitem('trusted', 'groups',
552 default=list,
552 default=list,
553 )
553 )
554 coreconfigitem('trusted', 'users',
554 coreconfigitem('trusted', 'users',
555 default=list,
555 default=list,
556 )
556 )
557 coreconfigitem('ui', '_usedassubrepo',
557 coreconfigitem('ui', '_usedassubrepo',
558 default=False,
558 default=False,
559 )
559 )
560 coreconfigitem('ui', 'allowemptycommit',
560 coreconfigitem('ui', 'allowemptycommit',
561 default=False,
561 default=False,
562 )
562 )
563 coreconfigitem('ui', 'archivemeta',
563 coreconfigitem('ui', 'archivemeta',
564 default=True,
564 default=True,
565 )
565 )
566 coreconfigitem('ui', 'askusername',
566 coreconfigitem('ui', 'askusername',
567 default=False,
567 default=False,
568 )
568 )
569 coreconfigitem('ui', 'clonebundlefallback',
569 coreconfigitem('ui', 'clonebundlefallback',
570 default=False,
570 default=False,
571 )
571 )
572 coreconfigitem('ui', 'clonebundleprefers',
572 coreconfigitem('ui', 'clonebundleprefers',
573 default=list,
573 default=list,
574 )
574 )
575 coreconfigitem('ui', 'clonebundles',
575 coreconfigitem('ui', 'clonebundles',
576 default=True,
576 default=True,
577 )
577 )
578 coreconfigitem('ui', 'color',
578 coreconfigitem('ui', 'color',
579 default='auto',
579 default='auto',
580 )
580 )
581 coreconfigitem('ui', 'commitsubrepos',
581 coreconfigitem('ui', 'commitsubrepos',
582 default=False,
582 default=False,
583 )
583 )
584 coreconfigitem('ui', 'debug',
584 coreconfigitem('ui', 'debug',
585 default=False,
585 default=False,
586 )
586 )
587 coreconfigitem('ui', 'debugger',
587 coreconfigitem('ui', 'debugger',
588 default=None,
588 default=None,
589 )
589 )
590 coreconfigitem('ui', 'fallbackencoding',
590 coreconfigitem('ui', 'fallbackencoding',
591 default=None,
591 default=None,
592 )
592 )
593 coreconfigitem('ui', 'forcecwd',
593 coreconfigitem('ui', 'forcecwd',
594 default=None,
594 default=None,
595 )
595 )
596 coreconfigitem('ui', 'forcemerge',
596 coreconfigitem('ui', 'forcemerge',
597 default=None,
597 default=None,
598 )
598 )
599 coreconfigitem('ui', 'formatdebug',
599 coreconfigitem('ui', 'formatdebug',
600 default=False,
600 default=False,
601 )
601 )
602 coreconfigitem('ui', 'formatjson',
602 coreconfigitem('ui', 'formatjson',
603 default=False,
603 default=False,
604 )
604 )
605 coreconfigitem('ui', 'formatted',
605 coreconfigitem('ui', 'formatted',
606 default=None,
606 default=None,
607 )
607 )
608 coreconfigitem('ui', 'graphnodetemplate',
608 coreconfigitem('ui', 'graphnodetemplate',
609 default=None,
609 default=None,
610 )
610 )
611 coreconfigitem('ui', 'http2debuglevel',
611 coreconfigitem('ui', 'http2debuglevel',
612 default=None,
612 default=None,
613 )
613 )
614 coreconfigitem('ui', 'interactive',
614 coreconfigitem('ui', 'interactive',
615 default=None,
615 default=None,
616 )
616 )
617 coreconfigitem('ui', 'interface',
617 coreconfigitem('ui', 'interface',
618 default=None,
618 default=None,
619 )
619 )
620 coreconfigitem('ui', 'logblockedtimes',
620 coreconfigitem('ui', 'logblockedtimes',
621 default=False,
621 default=False,
622 )
622 )
623 coreconfigitem('ui', 'logtemplate',
623 coreconfigitem('ui', 'logtemplate',
624 default=None,
624 default=None,
625 )
625 )
626 coreconfigitem('ui', 'merge',
626 coreconfigitem('ui', 'merge',
627 default=None,
627 default=None,
628 )
628 )
629 coreconfigitem('ui', 'mergemarkers',
629 coreconfigitem('ui', 'mergemarkers',
630 default='basic',
630 default='basic',
631 )
631 )
632 coreconfigitem('ui', 'mergemarkertemplate',
632 coreconfigitem('ui', 'mergemarkertemplate',
633 default=('{node|short} '
633 default=('{node|short} '
634 '{ifeq(tags, "tip", "", '
634 '{ifeq(tags, "tip", "", '
635 'ifeq(tags, "", "", "{tags} "))}'
635 'ifeq(tags, "", "", "{tags} "))}'
636 '{if(bookmarks, "{bookmarks} ")}'
636 '{if(bookmarks, "{bookmarks} ")}'
637 '{ifeq(branch, "default", "", "{branch} ")}'
637 '{ifeq(branch, "default", "", "{branch} ")}'
638 '- {author|user}: {desc|firstline}')
638 '- {author|user}: {desc|firstline}')
639 )
639 )
640 coreconfigitem('ui', 'nontty',
640 coreconfigitem('ui', 'nontty',
641 default=False,
641 default=False,
642 )
642 )
643 coreconfigitem('ui', 'origbackuppath',
643 coreconfigitem('ui', 'origbackuppath',
644 default=None,
644 default=None,
645 )
645 )
646 coreconfigitem('ui', 'paginate',
646 coreconfigitem('ui', 'paginate',
647 default=True,
647 default=True,
648 )
648 )
649 coreconfigitem('ui', 'patch',
649 coreconfigitem('ui', 'patch',
650 default=None,
650 default=None,
651 )
651 )
652 coreconfigitem('ui', 'portablefilenames',
652 coreconfigitem('ui', 'portablefilenames',
653 default='warn',
653 default='warn',
654 )
654 )
655 coreconfigitem('ui', 'promptecho',
655 coreconfigitem('ui', 'promptecho',
656 default=False,
656 default=False,
657 )
657 )
658 coreconfigitem('ui', 'quiet',
658 coreconfigitem('ui', 'quiet',
659 default=False,
659 default=False,
660 )
660 )
661 coreconfigitem('ui', 'quietbookmarkmove',
661 coreconfigitem('ui', 'quietbookmarkmove',
662 default=False,
662 default=False,
663 )
663 )
664 coreconfigitem('ui', 'remotecmd',
664 coreconfigitem('ui', 'remotecmd',
665 default='hg',
665 default='hg',
666 )
666 )
667 coreconfigitem('ui', 'report_untrusted',
667 coreconfigitem('ui', 'report_untrusted',
668 default=True,
668 default=True,
669 )
669 )
670 coreconfigitem('ui', 'rollback',
670 coreconfigitem('ui', 'rollback',
671 default=True,
671 default=True,
672 )
672 )
673 coreconfigitem('ui', 'slash',
673 coreconfigitem('ui', 'slash',
674 default=False,
674 default=False,
675 )
675 )
676 coreconfigitem('ui', 'ssh',
676 coreconfigitem('ui', 'ssh',
677 default='ssh',
677 default='ssh',
678 )
678 )
679 coreconfigitem('ui', 'statuscopies',
679 coreconfigitem('ui', 'statuscopies',
680 default=False,
680 default=False,
681 )
681 )
682 coreconfigitem('ui', 'strict',
682 coreconfigitem('ui', 'strict',
683 default=False,
683 default=False,
684 )
684 )
685 coreconfigitem('ui', 'style',
685 coreconfigitem('ui', 'style',
686 default='',
686 default='',
687 )
687 )
688 coreconfigitem('ui', 'supportcontact',
688 coreconfigitem('ui', 'supportcontact',
689 default=None,
689 default=None,
690 )
690 )
691 coreconfigitem('ui', 'textwidth',
691 coreconfigitem('ui', 'textwidth',
692 default=78,
692 default=78,
693 )
693 )
694 coreconfigitem('ui', 'timeout',
694 coreconfigitem('ui', 'timeout',
695 default='600',
695 default='600',
696 )
696 )
697 coreconfigitem('ui', 'traceback',
697 coreconfigitem('ui', 'traceback',
698 default=False,
698 default=False,
699 )
699 )
700 coreconfigitem('ui', 'tweakdefaults',
700 coreconfigitem('ui', 'tweakdefaults',
701 default=False,
701 default=False,
702 )
702 )
703 coreconfigitem('ui', 'usehttp2',
703 coreconfigitem('ui', 'usehttp2',
704 default=False,
704 default=False,
705 )
705 )
706 coreconfigitem('ui', 'username',
706 coreconfigitem('ui', 'username',
707 alias=[('ui', 'user')]
707 alias=[('ui', 'user')]
708 )
708 )
709 coreconfigitem('ui', 'verbose',
709 coreconfigitem('ui', 'verbose',
710 default=False,
710 default=False,
711 )
711 )
712 coreconfigitem('verify', 'skipflags',
712 coreconfigitem('verify', 'skipflags',
713 default=None,
713 default=None,
714 )
714 )
715 coreconfigitem('web', 'allowbz2',
715 coreconfigitem('web', 'allowbz2',
716 default=None,
716 default=None,
717 )
717 )
718 coreconfigitem('web', 'allowgz',
718 coreconfigitem('web', 'allowgz',
719 default=None,
719 default=None,
720 )
720 )
721 coreconfigitem('web', 'allowpull',
721 coreconfigitem('web', 'allowpull',
722 default=True,
722 default=True,
723 )
723 )
724 coreconfigitem('web', 'allow_push',
724 coreconfigitem('web', 'allow_push',
725 default=list,
725 default=list,
726 )
726 )
727 coreconfigitem('web', 'allowzip',
727 coreconfigitem('web', 'allowzip',
728 default=None,
728 default=None,
729 )
729 )
730 coreconfigitem('web', 'cache',
730 coreconfigitem('web', 'cache',
731 default=True,
731 default=True,
732 )
732 )
733 coreconfigitem('web', 'contact',
733 coreconfigitem('web', 'contact',
734 default=None,
734 default=None,
735 )
735 )
736 coreconfigitem('web', 'deny_push',
736 coreconfigitem('web', 'deny_push',
737 default=list,
737 default=list,
738 )
738 )
739 coreconfigitem('web', 'guessmime',
740 default=False,
741 )
739 coreconfigitem('web', 'accesslog',
742 coreconfigitem('web', 'accesslog',
740 default='-',
743 default='-',
741 )
744 )
742 coreconfigitem('web', 'address',
745 coreconfigitem('web', 'address',
743 default='',
746 default='',
744 )
747 )
745 coreconfigitem('web', 'allow_archive',
748 coreconfigitem('web', 'allow_archive',
746 default=list,
749 default=list,
747 )
750 )
748 coreconfigitem('web', 'allow_read',
751 coreconfigitem('web', 'allow_read',
749 default=list,
752 default=list,
750 )
753 )
751 coreconfigitem('web', 'baseurl',
754 coreconfigitem('web', 'baseurl',
752 default=None,
755 default=None,
753 )
756 )
754 coreconfigitem('web', 'cacerts',
757 coreconfigitem('web', 'cacerts',
755 default=None,
758 default=None,
756 )
759 )
757 coreconfigitem('web', 'certificate',
760 coreconfigitem('web', 'certificate',
758 default=None,
761 default=None,
759 )
762 )
760 coreconfigitem('web', 'collapse',
763 coreconfigitem('web', 'collapse',
761 default=False,
764 default=False,
762 )
765 )
763 coreconfigitem('web', 'csp',
766 coreconfigitem('web', 'csp',
764 default=None,
767 default=None,
765 )
768 )
766 coreconfigitem('web', 'deny_read',
769 coreconfigitem('web', 'deny_read',
767 default=list,
770 default=list,
768 )
771 )
769 coreconfigitem('web', 'descend',
772 coreconfigitem('web', 'descend',
770 default=True,
773 default=True,
771 )
774 )
772 coreconfigitem('web', 'description',
775 coreconfigitem('web', 'description',
773 default="",
776 default="",
774 )
777 )
775 coreconfigitem('web', 'encoding',
778 coreconfigitem('web', 'encoding',
776 default=lambda: encoding.encoding,
779 default=lambda: encoding.encoding,
777 )
780 )
778 coreconfigitem('web', 'errorlog',
781 coreconfigitem('web', 'errorlog',
779 default='-',
782 default='-',
780 )
783 )
781 coreconfigitem('web', 'ipv6',
784 coreconfigitem('web', 'ipv6',
782 default=False,
785 default=False,
783 )
786 )
784 coreconfigitem('web', 'maxchanges',
787 coreconfigitem('web', 'maxchanges',
785 default=10,
788 default=10,
786 )
789 )
787 coreconfigitem('web', 'maxfiles',
790 coreconfigitem('web', 'maxfiles',
788 default=10,
791 default=10,
789 )
792 )
790 coreconfigitem('web', 'maxshortchanges',
793 coreconfigitem('web', 'maxshortchanges',
791 default=60,
794 default=60,
792 )
795 )
793 coreconfigitem('web', 'motd',
796 coreconfigitem('web', 'motd',
794 default='',
797 default='',
795 )
798 )
796 coreconfigitem('web', 'name',
799 coreconfigitem('web', 'name',
797 default=dynamicdefault,
800 default=dynamicdefault,
798 )
801 )
799 coreconfigitem('web', 'port',
802 coreconfigitem('web', 'port',
800 default=8000,
803 default=8000,
801 )
804 )
802 coreconfigitem('web', 'prefix',
805 coreconfigitem('web', 'prefix',
803 default='',
806 default='',
804 )
807 )
805 coreconfigitem('web', 'push_ssl',
808 coreconfigitem('web', 'push_ssl',
806 default=True,
809 default=True,
807 )
810 )
808 coreconfigitem('web', 'refreshinterval',
811 coreconfigitem('web', 'refreshinterval',
809 default=20,
812 default=20,
810 )
813 )
811 coreconfigitem('web', 'stripes',
814 coreconfigitem('web', 'stripes',
812 default=1,
815 default=1,
813 )
816 )
814 coreconfigitem('web', 'style',
817 coreconfigitem('web', 'style',
815 default='paper',
818 default='paper',
816 )
819 )
817 coreconfigitem('web', 'templates',
820 coreconfigitem('web', 'templates',
818 default=None,
821 default=None,
819 )
822 )
820 coreconfigitem('web', 'view',
823 coreconfigitem('web', 'view',
821 default='served',
824 default='served',
822 )
825 )
823 coreconfigitem('worker', 'backgroundclose',
826 coreconfigitem('worker', 'backgroundclose',
824 default=dynamicdefault,
827 default=dynamicdefault,
825 )
828 )
826 # Windows defaults to a limit of 512 open files. A buffer of 128
829 # Windows defaults to a limit of 512 open files. A buffer of 128
827 # should give us enough headway.
830 # should give us enough headway.
828 coreconfigitem('worker', 'backgroundclosemaxqueue',
831 coreconfigitem('worker', 'backgroundclosemaxqueue',
829 default=384,
832 default=384,
830 )
833 )
831 coreconfigitem('worker', 'backgroundcloseminfilecount',
834 coreconfigitem('worker', 'backgroundcloseminfilecount',
832 default=2048,
835 default=2048,
833 )
836 )
834 coreconfigitem('worker', 'backgroundclosethreadcount',
837 coreconfigitem('worker', 'backgroundclosethreadcount',
835 default=4,
838 default=4,
836 )
839 )
837 coreconfigitem('worker', 'numcpus',
840 coreconfigitem('worker', 'numcpus',
838 default=None,
841 default=None,
839 )
842 )
@@ -1,1400 +1,1400
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import cgi
10 import cgi
11 import copy
11 import copy
12 import mimetypes
12 import mimetypes
13 import os
13 import os
14 import re
14 import re
15
15
16 from ..i18n import _
16 from ..i18n import _
17 from ..node import hex, short
17 from ..node import hex, short
18
18
19 from .common import (
19 from .common import (
20 ErrorResponse,
20 ErrorResponse,
21 HTTP_FORBIDDEN,
21 HTTP_FORBIDDEN,
22 HTTP_NOT_FOUND,
22 HTTP_NOT_FOUND,
23 HTTP_OK,
23 HTTP_OK,
24 get_contact,
24 get_contact,
25 paritygen,
25 paritygen,
26 staticfile,
26 staticfile,
27 )
27 )
28
28
29 from .. import (
29 from .. import (
30 archival,
30 archival,
31 dagop,
31 dagop,
32 encoding,
32 encoding,
33 error,
33 error,
34 graphmod,
34 graphmod,
35 revset,
35 revset,
36 revsetlang,
36 revsetlang,
37 scmutil,
37 scmutil,
38 smartset,
38 smartset,
39 templatefilters,
39 templatefilters,
40 templater,
40 templater,
41 util,
41 util,
42 )
42 )
43
43
44 from . import (
44 from . import (
45 webutil,
45 webutil,
46 )
46 )
47
47
48 __all__ = []
48 __all__ = []
49 commands = {}
49 commands = {}
50
50
51 class webcommand(object):
51 class webcommand(object):
52 """Decorator used to register a web command handler.
52 """Decorator used to register a web command handler.
53
53
54 The decorator takes as its positional arguments the name/path the
54 The decorator takes as its positional arguments the name/path the
55 command should be accessible under.
55 command should be accessible under.
56
56
57 Usage:
57 Usage:
58
58
59 @webcommand('mycommand')
59 @webcommand('mycommand')
60 def mycommand(web, req, tmpl):
60 def mycommand(web, req, tmpl):
61 pass
61 pass
62 """
62 """
63
63
64 def __init__(self, name):
64 def __init__(self, name):
65 self.name = name
65 self.name = name
66
66
67 def __call__(self, func):
67 def __call__(self, func):
68 __all__.append(self.name)
68 __all__.append(self.name)
69 commands[self.name] = func
69 commands[self.name] = func
70 return func
70 return func
71
71
72 @webcommand('log')
72 @webcommand('log')
73 def log(web, req, tmpl):
73 def log(web, req, tmpl):
74 """
74 """
75 /log[/{revision}[/{path}]]
75 /log[/{revision}[/{path}]]
76 --------------------------
76 --------------------------
77
77
78 Show repository or file history.
78 Show repository or file history.
79
79
80 For URLs of the form ``/log/{revision}``, a list of changesets starting at
80 For URLs of the form ``/log/{revision}``, a list of changesets starting at
81 the specified changeset identifier is shown. If ``{revision}`` is not
81 the specified changeset identifier is shown. If ``{revision}`` is not
82 defined, the default is ``tip``. This form is equivalent to the
82 defined, the default is ``tip``. This form is equivalent to the
83 ``changelog`` handler.
83 ``changelog`` handler.
84
84
85 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
85 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
86 file will be shown. This form is equivalent to the ``filelog`` handler.
86 file will be shown. This form is equivalent to the ``filelog`` handler.
87 """
87 """
88
88
89 if 'file' in req.form and req.form['file'][0]:
89 if 'file' in req.form and req.form['file'][0]:
90 return filelog(web, req, tmpl)
90 return filelog(web, req, tmpl)
91 else:
91 else:
92 return changelog(web, req, tmpl)
92 return changelog(web, req, tmpl)
93
93
94 @webcommand('rawfile')
94 @webcommand('rawfile')
95 def rawfile(web, req, tmpl):
95 def rawfile(web, req, tmpl):
96 guessmime = web.configbool('web', 'guessmime', False)
96 guessmime = web.configbool('web', 'guessmime')
97
97
98 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
98 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
99 if not path:
99 if not path:
100 content = manifest(web, req, tmpl)
100 content = manifest(web, req, tmpl)
101 req.respond(HTTP_OK, web.ctype)
101 req.respond(HTTP_OK, web.ctype)
102 return content
102 return content
103
103
104 try:
104 try:
105 fctx = webutil.filectx(web.repo, req)
105 fctx = webutil.filectx(web.repo, req)
106 except error.LookupError as inst:
106 except error.LookupError as inst:
107 try:
107 try:
108 content = manifest(web, req, tmpl)
108 content = manifest(web, req, tmpl)
109 req.respond(HTTP_OK, web.ctype)
109 req.respond(HTTP_OK, web.ctype)
110 return content
110 return content
111 except ErrorResponse:
111 except ErrorResponse:
112 raise inst
112 raise inst
113
113
114 path = fctx.path()
114 path = fctx.path()
115 text = fctx.data()
115 text = fctx.data()
116 mt = 'application/binary'
116 mt = 'application/binary'
117 if guessmime:
117 if guessmime:
118 mt = mimetypes.guess_type(path)[0]
118 mt = mimetypes.guess_type(path)[0]
119 if mt is None:
119 if mt is None:
120 if util.binary(text):
120 if util.binary(text):
121 mt = 'application/binary'
121 mt = 'application/binary'
122 else:
122 else:
123 mt = 'text/plain'
123 mt = 'text/plain'
124 if mt.startswith('text/'):
124 if mt.startswith('text/'):
125 mt += '; charset="%s"' % encoding.encoding
125 mt += '; charset="%s"' % encoding.encoding
126
126
127 req.respond(HTTP_OK, mt, path, body=text)
127 req.respond(HTTP_OK, mt, path, body=text)
128 return []
128 return []
129
129
130 def _filerevision(web, req, tmpl, fctx):
130 def _filerevision(web, req, tmpl, fctx):
131 f = fctx.path()
131 f = fctx.path()
132 text = fctx.data()
132 text = fctx.data()
133 parity = paritygen(web.stripecount)
133 parity = paritygen(web.stripecount)
134 ishead = fctx.filerev() in fctx.filelog().headrevs()
134 ishead = fctx.filerev() in fctx.filelog().headrevs()
135
135
136 if util.binary(text):
136 if util.binary(text):
137 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
137 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
138 text = '(binary:%s)' % mt
138 text = '(binary:%s)' % mt
139
139
140 def lines():
140 def lines():
141 for lineno, t in enumerate(text.splitlines(True)):
141 for lineno, t in enumerate(text.splitlines(True)):
142 yield {"line": t,
142 yield {"line": t,
143 "lineid": "l%d" % (lineno + 1),
143 "lineid": "l%d" % (lineno + 1),
144 "linenumber": "% 6d" % (lineno + 1),
144 "linenumber": "% 6d" % (lineno + 1),
145 "parity": next(parity)}
145 "parity": next(parity)}
146
146
147 return tmpl("filerevision",
147 return tmpl("filerevision",
148 file=f,
148 file=f,
149 path=webutil.up(f),
149 path=webutil.up(f),
150 text=lines(),
150 text=lines(),
151 symrev=webutil.symrevorshortnode(req, fctx),
151 symrev=webutil.symrevorshortnode(req, fctx),
152 rename=webutil.renamelink(fctx),
152 rename=webutil.renamelink(fctx),
153 permissions=fctx.manifest().flags(f),
153 permissions=fctx.manifest().flags(f),
154 ishead=int(ishead),
154 ishead=int(ishead),
155 **webutil.commonentry(web.repo, fctx))
155 **webutil.commonentry(web.repo, fctx))
156
156
157 @webcommand('file')
157 @webcommand('file')
158 def file(web, req, tmpl):
158 def file(web, req, tmpl):
159 """
159 """
160 /file/{revision}[/{path}]
160 /file/{revision}[/{path}]
161 -------------------------
161 -------------------------
162
162
163 Show information about a directory or file in the repository.
163 Show information about a directory or file in the repository.
164
164
165 Info about the ``path`` given as a URL parameter will be rendered.
165 Info about the ``path`` given as a URL parameter will be rendered.
166
166
167 If ``path`` is a directory, information about the entries in that
167 If ``path`` is a directory, information about the entries in that
168 directory will be rendered. This form is equivalent to the ``manifest``
168 directory will be rendered. This form is equivalent to the ``manifest``
169 handler.
169 handler.
170
170
171 If ``path`` is a file, information about that file will be shown via
171 If ``path`` is a file, information about that file will be shown via
172 the ``filerevision`` template.
172 the ``filerevision`` template.
173
173
174 If ``path`` is not defined, information about the root directory will
174 If ``path`` is not defined, information about the root directory will
175 be rendered.
175 be rendered.
176 """
176 """
177 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
177 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
178 if not path:
178 if not path:
179 return manifest(web, req, tmpl)
179 return manifest(web, req, tmpl)
180 try:
180 try:
181 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
181 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
182 except error.LookupError as inst:
182 except error.LookupError as inst:
183 try:
183 try:
184 return manifest(web, req, tmpl)
184 return manifest(web, req, tmpl)
185 except ErrorResponse:
185 except ErrorResponse:
186 raise inst
186 raise inst
187
187
188 def _search(web, req, tmpl):
188 def _search(web, req, tmpl):
189 MODE_REVISION = 'rev'
189 MODE_REVISION = 'rev'
190 MODE_KEYWORD = 'keyword'
190 MODE_KEYWORD = 'keyword'
191 MODE_REVSET = 'revset'
191 MODE_REVSET = 'revset'
192
192
193 def revsearch(ctx):
193 def revsearch(ctx):
194 yield ctx
194 yield ctx
195
195
196 def keywordsearch(query):
196 def keywordsearch(query):
197 lower = encoding.lower
197 lower = encoding.lower
198 qw = lower(query).split()
198 qw = lower(query).split()
199
199
200 def revgen():
200 def revgen():
201 cl = web.repo.changelog
201 cl = web.repo.changelog
202 for i in xrange(len(web.repo) - 1, 0, -100):
202 for i in xrange(len(web.repo) - 1, 0, -100):
203 l = []
203 l = []
204 for j in cl.revs(max(0, i - 99), i):
204 for j in cl.revs(max(0, i - 99), i):
205 ctx = web.repo[j]
205 ctx = web.repo[j]
206 l.append(ctx)
206 l.append(ctx)
207 l.reverse()
207 l.reverse()
208 for e in l:
208 for e in l:
209 yield e
209 yield e
210
210
211 for ctx in revgen():
211 for ctx in revgen():
212 miss = 0
212 miss = 0
213 for q in qw:
213 for q in qw:
214 if not (q in lower(ctx.user()) or
214 if not (q in lower(ctx.user()) or
215 q in lower(ctx.description()) or
215 q in lower(ctx.description()) or
216 q in lower(" ".join(ctx.files()))):
216 q in lower(" ".join(ctx.files()))):
217 miss = 1
217 miss = 1
218 break
218 break
219 if miss:
219 if miss:
220 continue
220 continue
221
221
222 yield ctx
222 yield ctx
223
223
224 def revsetsearch(revs):
224 def revsetsearch(revs):
225 for r in revs:
225 for r in revs:
226 yield web.repo[r]
226 yield web.repo[r]
227
227
228 searchfuncs = {
228 searchfuncs = {
229 MODE_REVISION: (revsearch, 'exact revision search'),
229 MODE_REVISION: (revsearch, 'exact revision search'),
230 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
230 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
231 MODE_REVSET: (revsetsearch, 'revset expression search'),
231 MODE_REVSET: (revsetsearch, 'revset expression search'),
232 }
232 }
233
233
234 def getsearchmode(query):
234 def getsearchmode(query):
235 try:
235 try:
236 ctx = web.repo[query]
236 ctx = web.repo[query]
237 except (error.RepoError, error.LookupError):
237 except (error.RepoError, error.LookupError):
238 # query is not an exact revision pointer, need to
238 # query is not an exact revision pointer, need to
239 # decide if it's a revset expression or keywords
239 # decide if it's a revset expression or keywords
240 pass
240 pass
241 else:
241 else:
242 return MODE_REVISION, ctx
242 return MODE_REVISION, ctx
243
243
244 revdef = 'reverse(%s)' % query
244 revdef = 'reverse(%s)' % query
245 try:
245 try:
246 tree = revsetlang.parse(revdef)
246 tree = revsetlang.parse(revdef)
247 except error.ParseError:
247 except error.ParseError:
248 # can't parse to a revset tree
248 # can't parse to a revset tree
249 return MODE_KEYWORD, query
249 return MODE_KEYWORD, query
250
250
251 if revsetlang.depth(tree) <= 2:
251 if revsetlang.depth(tree) <= 2:
252 # no revset syntax used
252 # no revset syntax used
253 return MODE_KEYWORD, query
253 return MODE_KEYWORD, query
254
254
255 if any((token, (value or '')[:3]) == ('string', 're:')
255 if any((token, (value or '')[:3]) == ('string', 're:')
256 for token, value, pos in revsetlang.tokenize(revdef)):
256 for token, value, pos in revsetlang.tokenize(revdef)):
257 return MODE_KEYWORD, query
257 return MODE_KEYWORD, query
258
258
259 funcsused = revsetlang.funcsused(tree)
259 funcsused = revsetlang.funcsused(tree)
260 if not funcsused.issubset(revset.safesymbols):
260 if not funcsused.issubset(revset.safesymbols):
261 return MODE_KEYWORD, query
261 return MODE_KEYWORD, query
262
262
263 mfunc = revset.match(web.repo.ui, revdef, repo=web.repo)
263 mfunc = revset.match(web.repo.ui, revdef, repo=web.repo)
264 try:
264 try:
265 revs = mfunc(web.repo)
265 revs = mfunc(web.repo)
266 return MODE_REVSET, revs
266 return MODE_REVSET, revs
267 # ParseError: wrongly placed tokens, wrongs arguments, etc
267 # ParseError: wrongly placed tokens, wrongs arguments, etc
268 # RepoLookupError: no such revision, e.g. in 'revision:'
268 # RepoLookupError: no such revision, e.g. in 'revision:'
269 # Abort: bookmark/tag not exists
269 # Abort: bookmark/tag not exists
270 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
270 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
271 except (error.ParseError, error.RepoLookupError, error.Abort,
271 except (error.ParseError, error.RepoLookupError, error.Abort,
272 LookupError):
272 LookupError):
273 return MODE_KEYWORD, query
273 return MODE_KEYWORD, query
274
274
275 def changelist(**map):
275 def changelist(**map):
276 count = 0
276 count = 0
277
277
278 for ctx in searchfunc[0](funcarg):
278 for ctx in searchfunc[0](funcarg):
279 count += 1
279 count += 1
280 n = ctx.node()
280 n = ctx.node()
281 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
281 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
282 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
282 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
283
283
284 yield tmpl('searchentry',
284 yield tmpl('searchentry',
285 parity=next(parity),
285 parity=next(parity),
286 changelogtag=showtags,
286 changelogtag=showtags,
287 files=files,
287 files=files,
288 **webutil.commonentry(web.repo, ctx))
288 **webutil.commonentry(web.repo, ctx))
289
289
290 if count >= revcount:
290 if count >= revcount:
291 break
291 break
292
292
293 query = req.form['rev'][0]
293 query = req.form['rev'][0]
294 revcount = web.maxchanges
294 revcount = web.maxchanges
295 if 'revcount' in req.form:
295 if 'revcount' in req.form:
296 try:
296 try:
297 revcount = int(req.form.get('revcount', [revcount])[0])
297 revcount = int(req.form.get('revcount', [revcount])[0])
298 revcount = max(revcount, 1)
298 revcount = max(revcount, 1)
299 tmpl.defaults['sessionvars']['revcount'] = revcount
299 tmpl.defaults['sessionvars']['revcount'] = revcount
300 except ValueError:
300 except ValueError:
301 pass
301 pass
302
302
303 lessvars = copy.copy(tmpl.defaults['sessionvars'])
303 lessvars = copy.copy(tmpl.defaults['sessionvars'])
304 lessvars['revcount'] = max(revcount / 2, 1)
304 lessvars['revcount'] = max(revcount / 2, 1)
305 lessvars['rev'] = query
305 lessvars['rev'] = query
306 morevars = copy.copy(tmpl.defaults['sessionvars'])
306 morevars = copy.copy(tmpl.defaults['sessionvars'])
307 morevars['revcount'] = revcount * 2
307 morevars['revcount'] = revcount * 2
308 morevars['rev'] = query
308 morevars['rev'] = query
309
309
310 mode, funcarg = getsearchmode(query)
310 mode, funcarg = getsearchmode(query)
311
311
312 if 'forcekw' in req.form:
312 if 'forcekw' in req.form:
313 showforcekw = ''
313 showforcekw = ''
314 showunforcekw = searchfuncs[mode][1]
314 showunforcekw = searchfuncs[mode][1]
315 mode = MODE_KEYWORD
315 mode = MODE_KEYWORD
316 funcarg = query
316 funcarg = query
317 else:
317 else:
318 if mode != MODE_KEYWORD:
318 if mode != MODE_KEYWORD:
319 showforcekw = searchfuncs[MODE_KEYWORD][1]
319 showforcekw = searchfuncs[MODE_KEYWORD][1]
320 else:
320 else:
321 showforcekw = ''
321 showforcekw = ''
322 showunforcekw = ''
322 showunforcekw = ''
323
323
324 searchfunc = searchfuncs[mode]
324 searchfunc = searchfuncs[mode]
325
325
326 tip = web.repo['tip']
326 tip = web.repo['tip']
327 parity = paritygen(web.stripecount)
327 parity = paritygen(web.stripecount)
328
328
329 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
329 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
330 entries=changelist, archives=web.archivelist("tip"),
330 entries=changelist, archives=web.archivelist("tip"),
331 morevars=morevars, lessvars=lessvars,
331 morevars=morevars, lessvars=lessvars,
332 modedesc=searchfunc[1],
332 modedesc=searchfunc[1],
333 showforcekw=showforcekw, showunforcekw=showunforcekw)
333 showforcekw=showforcekw, showunforcekw=showunforcekw)
334
334
335 @webcommand('changelog')
335 @webcommand('changelog')
336 def changelog(web, req, tmpl, shortlog=False):
336 def changelog(web, req, tmpl, shortlog=False):
337 """
337 """
338 /changelog[/{revision}]
338 /changelog[/{revision}]
339 -----------------------
339 -----------------------
340
340
341 Show information about multiple changesets.
341 Show information about multiple changesets.
342
342
343 If the optional ``revision`` URL argument is absent, information about
343 If the optional ``revision`` URL argument is absent, information about
344 all changesets starting at ``tip`` will be rendered. If the ``revision``
344 all changesets starting at ``tip`` will be rendered. If the ``revision``
345 argument is present, changesets will be shown starting from the specified
345 argument is present, changesets will be shown starting from the specified
346 revision.
346 revision.
347
347
348 If ``revision`` is absent, the ``rev`` query string argument may be
348 If ``revision`` is absent, the ``rev`` query string argument may be
349 defined. This will perform a search for changesets.
349 defined. This will perform a search for changesets.
350
350
351 The argument for ``rev`` can be a single revision, a revision set,
351 The argument for ``rev`` can be a single revision, a revision set,
352 or a literal keyword to search for in changeset data (equivalent to
352 or a literal keyword to search for in changeset data (equivalent to
353 :hg:`log -k`).
353 :hg:`log -k`).
354
354
355 The ``revcount`` query string argument defines the maximum numbers of
355 The ``revcount`` query string argument defines the maximum numbers of
356 changesets to render.
356 changesets to render.
357
357
358 For non-searches, the ``changelog`` template will be rendered.
358 For non-searches, the ``changelog`` template will be rendered.
359 """
359 """
360
360
361 query = ''
361 query = ''
362 if 'node' in req.form:
362 if 'node' in req.form:
363 ctx = webutil.changectx(web.repo, req)
363 ctx = webutil.changectx(web.repo, req)
364 symrev = webutil.symrevorshortnode(req, ctx)
364 symrev = webutil.symrevorshortnode(req, ctx)
365 elif 'rev' in req.form:
365 elif 'rev' in req.form:
366 return _search(web, req, tmpl)
366 return _search(web, req, tmpl)
367 else:
367 else:
368 ctx = web.repo['tip']
368 ctx = web.repo['tip']
369 symrev = 'tip'
369 symrev = 'tip'
370
370
371 def changelist():
371 def changelist():
372 revs = []
372 revs = []
373 if pos != -1:
373 if pos != -1:
374 revs = web.repo.changelog.revs(pos, 0)
374 revs = web.repo.changelog.revs(pos, 0)
375 curcount = 0
375 curcount = 0
376 for rev in revs:
376 for rev in revs:
377 curcount += 1
377 curcount += 1
378 if curcount > revcount + 1:
378 if curcount > revcount + 1:
379 break
379 break
380
380
381 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
381 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
382 entry['parity'] = next(parity)
382 entry['parity'] = next(parity)
383 yield entry
383 yield entry
384
384
385 if shortlog:
385 if shortlog:
386 revcount = web.maxshortchanges
386 revcount = web.maxshortchanges
387 else:
387 else:
388 revcount = web.maxchanges
388 revcount = web.maxchanges
389
389
390 if 'revcount' in req.form:
390 if 'revcount' in req.form:
391 try:
391 try:
392 revcount = int(req.form.get('revcount', [revcount])[0])
392 revcount = int(req.form.get('revcount', [revcount])[0])
393 revcount = max(revcount, 1)
393 revcount = max(revcount, 1)
394 tmpl.defaults['sessionvars']['revcount'] = revcount
394 tmpl.defaults['sessionvars']['revcount'] = revcount
395 except ValueError:
395 except ValueError:
396 pass
396 pass
397
397
398 lessvars = copy.copy(tmpl.defaults['sessionvars'])
398 lessvars = copy.copy(tmpl.defaults['sessionvars'])
399 lessvars['revcount'] = max(revcount / 2, 1)
399 lessvars['revcount'] = max(revcount / 2, 1)
400 morevars = copy.copy(tmpl.defaults['sessionvars'])
400 morevars = copy.copy(tmpl.defaults['sessionvars'])
401 morevars['revcount'] = revcount * 2
401 morevars['revcount'] = revcount * 2
402
402
403 count = len(web.repo)
403 count = len(web.repo)
404 pos = ctx.rev()
404 pos = ctx.rev()
405 parity = paritygen(web.stripecount)
405 parity = paritygen(web.stripecount)
406
406
407 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
407 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
408
408
409 entries = list(changelist())
409 entries = list(changelist())
410 latestentry = entries[:1]
410 latestentry = entries[:1]
411 if len(entries) > revcount:
411 if len(entries) > revcount:
412 nextentry = entries[-1:]
412 nextentry = entries[-1:]
413 entries = entries[:-1]
413 entries = entries[:-1]
414 else:
414 else:
415 nextentry = []
415 nextentry = []
416
416
417 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
417 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
418 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
418 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
419 entries=entries,
419 entries=entries,
420 latestentry=latestentry, nextentry=nextentry,
420 latestentry=latestentry, nextentry=nextentry,
421 archives=web.archivelist("tip"), revcount=revcount,
421 archives=web.archivelist("tip"), revcount=revcount,
422 morevars=morevars, lessvars=lessvars, query=query)
422 morevars=morevars, lessvars=lessvars, query=query)
423
423
424 @webcommand('shortlog')
424 @webcommand('shortlog')
425 def shortlog(web, req, tmpl):
425 def shortlog(web, req, tmpl):
426 """
426 """
427 /shortlog
427 /shortlog
428 ---------
428 ---------
429
429
430 Show basic information about a set of changesets.
430 Show basic information about a set of changesets.
431
431
432 This accepts the same parameters as the ``changelog`` handler. The only
432 This accepts the same parameters as the ``changelog`` handler. The only
433 difference is the ``shortlog`` template will be rendered instead of the
433 difference is the ``shortlog`` template will be rendered instead of the
434 ``changelog`` template.
434 ``changelog`` template.
435 """
435 """
436 return changelog(web, req, tmpl, shortlog=True)
436 return changelog(web, req, tmpl, shortlog=True)
437
437
438 @webcommand('changeset')
438 @webcommand('changeset')
439 def changeset(web, req, tmpl):
439 def changeset(web, req, tmpl):
440 """
440 """
441 /changeset[/{revision}]
441 /changeset[/{revision}]
442 -----------------------
442 -----------------------
443
443
444 Show information about a single changeset.
444 Show information about a single changeset.
445
445
446 A URL path argument is the changeset identifier to show. See ``hg help
446 A URL path argument is the changeset identifier to show. See ``hg help
447 revisions`` for possible values. If not defined, the ``tip`` changeset
447 revisions`` for possible values. If not defined, the ``tip`` changeset
448 will be shown.
448 will be shown.
449
449
450 The ``changeset`` template is rendered. Contents of the ``changesettag``,
450 The ``changeset`` template is rendered. Contents of the ``changesettag``,
451 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
451 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
452 templates related to diffs may all be used to produce the output.
452 templates related to diffs may all be used to produce the output.
453 """
453 """
454 ctx = webutil.changectx(web.repo, req)
454 ctx = webutil.changectx(web.repo, req)
455
455
456 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
456 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
457
457
458 rev = webcommand('rev')(changeset)
458 rev = webcommand('rev')(changeset)
459
459
460 def decodepath(path):
460 def decodepath(path):
461 """Hook for mapping a path in the repository to a path in the
461 """Hook for mapping a path in the repository to a path in the
462 working copy.
462 working copy.
463
463
464 Extensions (e.g., largefiles) can override this to remap files in
464 Extensions (e.g., largefiles) can override this to remap files in
465 the virtual file system presented by the manifest command below."""
465 the virtual file system presented by the manifest command below."""
466 return path
466 return path
467
467
468 @webcommand('manifest')
468 @webcommand('manifest')
469 def manifest(web, req, tmpl):
469 def manifest(web, req, tmpl):
470 """
470 """
471 /manifest[/{revision}[/{path}]]
471 /manifest[/{revision}[/{path}]]
472 -------------------------------
472 -------------------------------
473
473
474 Show information about a directory.
474 Show information about a directory.
475
475
476 If the URL path arguments are omitted, information about the root
476 If the URL path arguments are omitted, information about the root
477 directory for the ``tip`` changeset will be shown.
477 directory for the ``tip`` changeset will be shown.
478
478
479 Because this handler can only show information for directories, it
479 Because this handler can only show information for directories, it
480 is recommended to use the ``file`` handler instead, as it can handle both
480 is recommended to use the ``file`` handler instead, as it can handle both
481 directories and files.
481 directories and files.
482
482
483 The ``manifest`` template will be rendered for this handler.
483 The ``manifest`` template will be rendered for this handler.
484 """
484 """
485 if 'node' in req.form:
485 if 'node' in req.form:
486 ctx = webutil.changectx(web.repo, req)
486 ctx = webutil.changectx(web.repo, req)
487 symrev = webutil.symrevorshortnode(req, ctx)
487 symrev = webutil.symrevorshortnode(req, ctx)
488 else:
488 else:
489 ctx = web.repo['tip']
489 ctx = web.repo['tip']
490 symrev = 'tip'
490 symrev = 'tip'
491 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
491 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
492 mf = ctx.manifest()
492 mf = ctx.manifest()
493 node = ctx.node()
493 node = ctx.node()
494
494
495 files = {}
495 files = {}
496 dirs = {}
496 dirs = {}
497 parity = paritygen(web.stripecount)
497 parity = paritygen(web.stripecount)
498
498
499 if path and path[-1] != "/":
499 if path and path[-1] != "/":
500 path += "/"
500 path += "/"
501 l = len(path)
501 l = len(path)
502 abspath = "/" + path
502 abspath = "/" + path
503
503
504 for full, n in mf.iteritems():
504 for full, n in mf.iteritems():
505 # the virtual path (working copy path) used for the full
505 # the virtual path (working copy path) used for the full
506 # (repository) path
506 # (repository) path
507 f = decodepath(full)
507 f = decodepath(full)
508
508
509 if f[:l] != path:
509 if f[:l] != path:
510 continue
510 continue
511 remain = f[l:]
511 remain = f[l:]
512 elements = remain.split('/')
512 elements = remain.split('/')
513 if len(elements) == 1:
513 if len(elements) == 1:
514 files[remain] = full
514 files[remain] = full
515 else:
515 else:
516 h = dirs # need to retain ref to dirs (root)
516 h = dirs # need to retain ref to dirs (root)
517 for elem in elements[0:-1]:
517 for elem in elements[0:-1]:
518 if elem not in h:
518 if elem not in h:
519 h[elem] = {}
519 h[elem] = {}
520 h = h[elem]
520 h = h[elem]
521 if len(h) > 1:
521 if len(h) > 1:
522 break
522 break
523 h[None] = None # denotes files present
523 h[None] = None # denotes files present
524
524
525 if mf and not files and not dirs:
525 if mf and not files and not dirs:
526 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
526 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
527
527
528 def filelist(**map):
528 def filelist(**map):
529 for f in sorted(files):
529 for f in sorted(files):
530 full = files[f]
530 full = files[f]
531
531
532 fctx = ctx.filectx(full)
532 fctx = ctx.filectx(full)
533 yield {"file": full,
533 yield {"file": full,
534 "parity": next(parity),
534 "parity": next(parity),
535 "basename": f,
535 "basename": f,
536 "date": fctx.date(),
536 "date": fctx.date(),
537 "size": fctx.size(),
537 "size": fctx.size(),
538 "permissions": mf.flags(full)}
538 "permissions": mf.flags(full)}
539
539
540 def dirlist(**map):
540 def dirlist(**map):
541 for d in sorted(dirs):
541 for d in sorted(dirs):
542
542
543 emptydirs = []
543 emptydirs = []
544 h = dirs[d]
544 h = dirs[d]
545 while isinstance(h, dict) and len(h) == 1:
545 while isinstance(h, dict) and len(h) == 1:
546 k, v = h.items()[0]
546 k, v = h.items()[0]
547 if v:
547 if v:
548 emptydirs.append(k)
548 emptydirs.append(k)
549 h = v
549 h = v
550
550
551 path = "%s%s" % (abspath, d)
551 path = "%s%s" % (abspath, d)
552 yield {"parity": next(parity),
552 yield {"parity": next(parity),
553 "path": path,
553 "path": path,
554 "emptydirs": "/".join(emptydirs),
554 "emptydirs": "/".join(emptydirs),
555 "basename": d}
555 "basename": d}
556
556
557 return tmpl("manifest",
557 return tmpl("manifest",
558 symrev=symrev,
558 symrev=symrev,
559 path=abspath,
559 path=abspath,
560 up=webutil.up(abspath),
560 up=webutil.up(abspath),
561 upparity=next(parity),
561 upparity=next(parity),
562 fentries=filelist,
562 fentries=filelist,
563 dentries=dirlist,
563 dentries=dirlist,
564 archives=web.archivelist(hex(node)),
564 archives=web.archivelist(hex(node)),
565 **webutil.commonentry(web.repo, ctx))
565 **webutil.commonentry(web.repo, ctx))
566
566
567 @webcommand('tags')
567 @webcommand('tags')
568 def tags(web, req, tmpl):
568 def tags(web, req, tmpl):
569 """
569 """
570 /tags
570 /tags
571 -----
571 -----
572
572
573 Show information about tags.
573 Show information about tags.
574
574
575 No arguments are accepted.
575 No arguments are accepted.
576
576
577 The ``tags`` template is rendered.
577 The ``tags`` template is rendered.
578 """
578 """
579 i = list(reversed(web.repo.tagslist()))
579 i = list(reversed(web.repo.tagslist()))
580 parity = paritygen(web.stripecount)
580 parity = paritygen(web.stripecount)
581
581
582 def entries(notip, latestonly, **map):
582 def entries(notip, latestonly, **map):
583 t = i
583 t = i
584 if notip:
584 if notip:
585 t = [(k, n) for k, n in i if k != "tip"]
585 t = [(k, n) for k, n in i if k != "tip"]
586 if latestonly:
586 if latestonly:
587 t = t[:1]
587 t = t[:1]
588 for k, n in t:
588 for k, n in t:
589 yield {"parity": next(parity),
589 yield {"parity": next(parity),
590 "tag": k,
590 "tag": k,
591 "date": web.repo[n].date(),
591 "date": web.repo[n].date(),
592 "node": hex(n)}
592 "node": hex(n)}
593
593
594 return tmpl("tags",
594 return tmpl("tags",
595 node=hex(web.repo.changelog.tip()),
595 node=hex(web.repo.changelog.tip()),
596 entries=lambda **x: entries(False, False, **x),
596 entries=lambda **x: entries(False, False, **x),
597 entriesnotip=lambda **x: entries(True, False, **x),
597 entriesnotip=lambda **x: entries(True, False, **x),
598 latestentry=lambda **x: entries(True, True, **x))
598 latestentry=lambda **x: entries(True, True, **x))
599
599
600 @webcommand('bookmarks')
600 @webcommand('bookmarks')
601 def bookmarks(web, req, tmpl):
601 def bookmarks(web, req, tmpl):
602 """
602 """
603 /bookmarks
603 /bookmarks
604 ----------
604 ----------
605
605
606 Show information about bookmarks.
606 Show information about bookmarks.
607
607
608 No arguments are accepted.
608 No arguments are accepted.
609
609
610 The ``bookmarks`` template is rendered.
610 The ``bookmarks`` template is rendered.
611 """
611 """
612 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
612 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
613 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
613 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
614 i = sorted(i, key=sortkey, reverse=True)
614 i = sorted(i, key=sortkey, reverse=True)
615 parity = paritygen(web.stripecount)
615 parity = paritygen(web.stripecount)
616
616
617 def entries(latestonly, **map):
617 def entries(latestonly, **map):
618 t = i
618 t = i
619 if latestonly:
619 if latestonly:
620 t = i[:1]
620 t = i[:1]
621 for k, n in t:
621 for k, n in t:
622 yield {"parity": next(parity),
622 yield {"parity": next(parity),
623 "bookmark": k,
623 "bookmark": k,
624 "date": web.repo[n].date(),
624 "date": web.repo[n].date(),
625 "node": hex(n)}
625 "node": hex(n)}
626
626
627 if i:
627 if i:
628 latestrev = i[0][1]
628 latestrev = i[0][1]
629 else:
629 else:
630 latestrev = -1
630 latestrev = -1
631
631
632 return tmpl("bookmarks",
632 return tmpl("bookmarks",
633 node=hex(web.repo.changelog.tip()),
633 node=hex(web.repo.changelog.tip()),
634 lastchange=[{"date": web.repo[latestrev].date()}],
634 lastchange=[{"date": web.repo[latestrev].date()}],
635 entries=lambda **x: entries(latestonly=False, **x),
635 entries=lambda **x: entries(latestonly=False, **x),
636 latestentry=lambda **x: entries(latestonly=True, **x))
636 latestentry=lambda **x: entries(latestonly=True, **x))
637
637
638 @webcommand('branches')
638 @webcommand('branches')
639 def branches(web, req, tmpl):
639 def branches(web, req, tmpl):
640 """
640 """
641 /branches
641 /branches
642 ---------
642 ---------
643
643
644 Show information about branches.
644 Show information about branches.
645
645
646 All known branches are contained in the output, even closed branches.
646 All known branches are contained in the output, even closed branches.
647
647
648 No arguments are accepted.
648 No arguments are accepted.
649
649
650 The ``branches`` template is rendered.
650 The ``branches`` template is rendered.
651 """
651 """
652 entries = webutil.branchentries(web.repo, web.stripecount)
652 entries = webutil.branchentries(web.repo, web.stripecount)
653 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
653 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
654 return tmpl('branches', node=hex(web.repo.changelog.tip()),
654 return tmpl('branches', node=hex(web.repo.changelog.tip()),
655 entries=entries, latestentry=latestentry)
655 entries=entries, latestentry=latestentry)
656
656
657 @webcommand('summary')
657 @webcommand('summary')
658 def summary(web, req, tmpl):
658 def summary(web, req, tmpl):
659 """
659 """
660 /summary
660 /summary
661 --------
661 --------
662
662
663 Show a summary of repository state.
663 Show a summary of repository state.
664
664
665 Information about the latest changesets, bookmarks, tags, and branches
665 Information about the latest changesets, bookmarks, tags, and branches
666 is captured by this handler.
666 is captured by this handler.
667
667
668 The ``summary`` template is rendered.
668 The ``summary`` template is rendered.
669 """
669 """
670 i = reversed(web.repo.tagslist())
670 i = reversed(web.repo.tagslist())
671
671
672 def tagentries(**map):
672 def tagentries(**map):
673 parity = paritygen(web.stripecount)
673 parity = paritygen(web.stripecount)
674 count = 0
674 count = 0
675 for k, n in i:
675 for k, n in i:
676 if k == "tip": # skip tip
676 if k == "tip": # skip tip
677 continue
677 continue
678
678
679 count += 1
679 count += 1
680 if count > 10: # limit to 10 tags
680 if count > 10: # limit to 10 tags
681 break
681 break
682
682
683 yield tmpl("tagentry",
683 yield tmpl("tagentry",
684 parity=next(parity),
684 parity=next(parity),
685 tag=k,
685 tag=k,
686 node=hex(n),
686 node=hex(n),
687 date=web.repo[n].date())
687 date=web.repo[n].date())
688
688
689 def bookmarks(**map):
689 def bookmarks(**map):
690 parity = paritygen(web.stripecount)
690 parity = paritygen(web.stripecount)
691 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
691 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
692 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
692 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
693 marks = sorted(marks, key=sortkey, reverse=True)
693 marks = sorted(marks, key=sortkey, reverse=True)
694 for k, n in marks[:10]: # limit to 10 bookmarks
694 for k, n in marks[:10]: # limit to 10 bookmarks
695 yield {'parity': next(parity),
695 yield {'parity': next(parity),
696 'bookmark': k,
696 'bookmark': k,
697 'date': web.repo[n].date(),
697 'date': web.repo[n].date(),
698 'node': hex(n)}
698 'node': hex(n)}
699
699
700 def changelist(**map):
700 def changelist(**map):
701 parity = paritygen(web.stripecount, offset=start - end)
701 parity = paritygen(web.stripecount, offset=start - end)
702 l = [] # build a list in forward order for efficiency
702 l = [] # build a list in forward order for efficiency
703 revs = []
703 revs = []
704 if start < end:
704 if start < end:
705 revs = web.repo.changelog.revs(start, end - 1)
705 revs = web.repo.changelog.revs(start, end - 1)
706 for i in revs:
706 for i in revs:
707 ctx = web.repo[i]
707 ctx = web.repo[i]
708
708
709 l.append(tmpl(
709 l.append(tmpl(
710 'shortlogentry',
710 'shortlogentry',
711 parity=next(parity),
711 parity=next(parity),
712 **webutil.commonentry(web.repo, ctx)))
712 **webutil.commonentry(web.repo, ctx)))
713
713
714 for entry in reversed(l):
714 for entry in reversed(l):
715 yield entry
715 yield entry
716
716
717 tip = web.repo['tip']
717 tip = web.repo['tip']
718 count = len(web.repo)
718 count = len(web.repo)
719 start = max(0, count - web.maxchanges)
719 start = max(0, count - web.maxchanges)
720 end = min(count, start + web.maxchanges)
720 end = min(count, start + web.maxchanges)
721
721
722 desc = web.config("web", "description")
722 desc = web.config("web", "description")
723 if not desc:
723 if not desc:
724 desc = 'unknown'
724 desc = 'unknown'
725 return tmpl("summary",
725 return tmpl("summary",
726 desc=desc,
726 desc=desc,
727 owner=get_contact(web.config) or "unknown",
727 owner=get_contact(web.config) or "unknown",
728 lastchange=tip.date(),
728 lastchange=tip.date(),
729 tags=tagentries,
729 tags=tagentries,
730 bookmarks=bookmarks,
730 bookmarks=bookmarks,
731 branches=webutil.branchentries(web.repo, web.stripecount, 10),
731 branches=webutil.branchentries(web.repo, web.stripecount, 10),
732 shortlog=changelist,
732 shortlog=changelist,
733 node=tip.hex(),
733 node=tip.hex(),
734 symrev='tip',
734 symrev='tip',
735 archives=web.archivelist("tip"),
735 archives=web.archivelist("tip"),
736 labels=web.configlist('web', 'labels'))
736 labels=web.configlist('web', 'labels'))
737
737
738 @webcommand('filediff')
738 @webcommand('filediff')
739 def filediff(web, req, tmpl):
739 def filediff(web, req, tmpl):
740 """
740 """
741 /diff/{revision}/{path}
741 /diff/{revision}/{path}
742 -----------------------
742 -----------------------
743
743
744 Show how a file changed in a particular commit.
744 Show how a file changed in a particular commit.
745
745
746 The ``filediff`` template is rendered.
746 The ``filediff`` template is rendered.
747
747
748 This handler is registered under both the ``/diff`` and ``/filediff``
748 This handler is registered under both the ``/diff`` and ``/filediff``
749 paths. ``/diff`` is used in modern code.
749 paths. ``/diff`` is used in modern code.
750 """
750 """
751 fctx, ctx = None, None
751 fctx, ctx = None, None
752 try:
752 try:
753 fctx = webutil.filectx(web.repo, req)
753 fctx = webutil.filectx(web.repo, req)
754 except LookupError:
754 except LookupError:
755 ctx = webutil.changectx(web.repo, req)
755 ctx = webutil.changectx(web.repo, req)
756 path = webutil.cleanpath(web.repo, req.form['file'][0])
756 path = webutil.cleanpath(web.repo, req.form['file'][0])
757 if path not in ctx.files():
757 if path not in ctx.files():
758 raise
758 raise
759
759
760 if fctx is not None:
760 if fctx is not None:
761 path = fctx.path()
761 path = fctx.path()
762 ctx = fctx.changectx()
762 ctx = fctx.changectx()
763 basectx = ctx.p1()
763 basectx = ctx.p1()
764
764
765 style = web.config('web', 'style')
765 style = web.config('web', 'style')
766 if 'style' in req.form:
766 if 'style' in req.form:
767 style = req.form['style'][0]
767 style = req.form['style'][0]
768
768
769 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
769 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
770 if fctx is not None:
770 if fctx is not None:
771 rename = webutil.renamelink(fctx)
771 rename = webutil.renamelink(fctx)
772 ctx = fctx
772 ctx = fctx
773 else:
773 else:
774 rename = []
774 rename = []
775 ctx = ctx
775 ctx = ctx
776 return tmpl("filediff",
776 return tmpl("filediff",
777 file=path,
777 file=path,
778 symrev=webutil.symrevorshortnode(req, ctx),
778 symrev=webutil.symrevorshortnode(req, ctx),
779 rename=rename,
779 rename=rename,
780 diff=diffs,
780 diff=diffs,
781 **webutil.commonentry(web.repo, ctx))
781 **webutil.commonentry(web.repo, ctx))
782
782
783 diff = webcommand('diff')(filediff)
783 diff = webcommand('diff')(filediff)
784
784
785 @webcommand('comparison')
785 @webcommand('comparison')
786 def comparison(web, req, tmpl):
786 def comparison(web, req, tmpl):
787 """
787 """
788 /comparison/{revision}/{path}
788 /comparison/{revision}/{path}
789 -----------------------------
789 -----------------------------
790
790
791 Show a comparison between the old and new versions of a file from changes
791 Show a comparison between the old and new versions of a file from changes
792 made on a particular revision.
792 made on a particular revision.
793
793
794 This is similar to the ``diff`` handler. However, this form features
794 This is similar to the ``diff`` handler. However, this form features
795 a split or side-by-side diff rather than a unified diff.
795 a split or side-by-side diff rather than a unified diff.
796
796
797 The ``context`` query string argument can be used to control the lines of
797 The ``context`` query string argument can be used to control the lines of
798 context in the diff.
798 context in the diff.
799
799
800 The ``filecomparison`` template is rendered.
800 The ``filecomparison`` template is rendered.
801 """
801 """
802 ctx = webutil.changectx(web.repo, req)
802 ctx = webutil.changectx(web.repo, req)
803 if 'file' not in req.form:
803 if 'file' not in req.form:
804 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
804 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
805 path = webutil.cleanpath(web.repo, req.form['file'][0])
805 path = webutil.cleanpath(web.repo, req.form['file'][0])
806
806
807 parsecontext = lambda v: v == 'full' and -1 or int(v)
807 parsecontext = lambda v: v == 'full' and -1 or int(v)
808 if 'context' in req.form:
808 if 'context' in req.form:
809 context = parsecontext(req.form['context'][0])
809 context = parsecontext(req.form['context'][0])
810 else:
810 else:
811 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
811 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
812
812
813 def filelines(f):
813 def filelines(f):
814 if f.isbinary():
814 if f.isbinary():
815 mt = mimetypes.guess_type(f.path())[0]
815 mt = mimetypes.guess_type(f.path())[0]
816 if not mt:
816 if not mt:
817 mt = 'application/octet-stream'
817 mt = 'application/octet-stream'
818 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
818 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
819 return f.data().splitlines()
819 return f.data().splitlines()
820
820
821 fctx = None
821 fctx = None
822 parent = ctx.p1()
822 parent = ctx.p1()
823 leftrev = parent.rev()
823 leftrev = parent.rev()
824 leftnode = parent.node()
824 leftnode = parent.node()
825 rightrev = ctx.rev()
825 rightrev = ctx.rev()
826 rightnode = ctx.node()
826 rightnode = ctx.node()
827 if path in ctx:
827 if path in ctx:
828 fctx = ctx[path]
828 fctx = ctx[path]
829 rightlines = filelines(fctx)
829 rightlines = filelines(fctx)
830 if path not in parent:
830 if path not in parent:
831 leftlines = ()
831 leftlines = ()
832 else:
832 else:
833 pfctx = parent[path]
833 pfctx = parent[path]
834 leftlines = filelines(pfctx)
834 leftlines = filelines(pfctx)
835 else:
835 else:
836 rightlines = ()
836 rightlines = ()
837 pfctx = ctx.parents()[0][path]
837 pfctx = ctx.parents()[0][path]
838 leftlines = filelines(pfctx)
838 leftlines = filelines(pfctx)
839
839
840 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
840 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
841 if fctx is not None:
841 if fctx is not None:
842 rename = webutil.renamelink(fctx)
842 rename = webutil.renamelink(fctx)
843 ctx = fctx
843 ctx = fctx
844 else:
844 else:
845 rename = []
845 rename = []
846 ctx = ctx
846 ctx = ctx
847 return tmpl('filecomparison',
847 return tmpl('filecomparison',
848 file=path,
848 file=path,
849 symrev=webutil.symrevorshortnode(req, ctx),
849 symrev=webutil.symrevorshortnode(req, ctx),
850 rename=rename,
850 rename=rename,
851 leftrev=leftrev,
851 leftrev=leftrev,
852 leftnode=hex(leftnode),
852 leftnode=hex(leftnode),
853 rightrev=rightrev,
853 rightrev=rightrev,
854 rightnode=hex(rightnode),
854 rightnode=hex(rightnode),
855 comparison=comparison,
855 comparison=comparison,
856 **webutil.commonentry(web.repo, ctx))
856 **webutil.commonentry(web.repo, ctx))
857
857
858 @webcommand('annotate')
858 @webcommand('annotate')
859 def annotate(web, req, tmpl):
859 def annotate(web, req, tmpl):
860 """
860 """
861 /annotate/{revision}/{path}
861 /annotate/{revision}/{path}
862 ---------------------------
862 ---------------------------
863
863
864 Show changeset information for each line in a file.
864 Show changeset information for each line in a file.
865
865
866 The ``ignorews``, ``ignorewsamount``, ``ignorewseol``, and
866 The ``ignorews``, ``ignorewsamount``, ``ignorewseol``, and
867 ``ignoreblanklines`` query string arguments have the same meaning as
867 ``ignoreblanklines`` query string arguments have the same meaning as
868 their ``[annotate]`` config equivalents. It uses the hgrc boolean
868 their ``[annotate]`` config equivalents. It uses the hgrc boolean
869 parsing logic to interpret the value. e.g. ``0`` and ``false`` are
869 parsing logic to interpret the value. e.g. ``0`` and ``false`` are
870 false and ``1`` and ``true`` are true. If not defined, the server
870 false and ``1`` and ``true`` are true. If not defined, the server
871 default settings are used.
871 default settings are used.
872
872
873 The ``fileannotate`` template is rendered.
873 The ``fileannotate`` template is rendered.
874 """
874 """
875 fctx = webutil.filectx(web.repo, req)
875 fctx = webutil.filectx(web.repo, req)
876 f = fctx.path()
876 f = fctx.path()
877 parity = paritygen(web.stripecount)
877 parity = paritygen(web.stripecount)
878 ishead = fctx.filerev() in fctx.filelog().headrevs()
878 ishead = fctx.filerev() in fctx.filelog().headrevs()
879
879
880 # parents() is called once per line and several lines likely belong to
880 # parents() is called once per line and several lines likely belong to
881 # same revision. So it is worth caching.
881 # same revision. So it is worth caching.
882 # TODO there are still redundant operations within basefilectx.parents()
882 # TODO there are still redundant operations within basefilectx.parents()
883 # and from the fctx.annotate() call itself that could be cached.
883 # and from the fctx.annotate() call itself that could be cached.
884 parentscache = {}
884 parentscache = {}
885 def parents(f):
885 def parents(f):
886 rev = f.rev()
886 rev = f.rev()
887 if rev not in parentscache:
887 if rev not in parentscache:
888 parentscache[rev] = []
888 parentscache[rev] = []
889 for p in f.parents():
889 for p in f.parents():
890 entry = {
890 entry = {
891 'node': p.hex(),
891 'node': p.hex(),
892 'rev': p.rev(),
892 'rev': p.rev(),
893 }
893 }
894 parentscache[rev].append(entry)
894 parentscache[rev].append(entry)
895
895
896 for p in parentscache[rev]:
896 for p in parentscache[rev]:
897 yield p
897 yield p
898
898
899 def annotate(**map):
899 def annotate(**map):
900 if fctx.isbinary():
900 if fctx.isbinary():
901 mt = (mimetypes.guess_type(fctx.path())[0]
901 mt = (mimetypes.guess_type(fctx.path())[0]
902 or 'application/octet-stream')
902 or 'application/octet-stream')
903 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
903 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
904 else:
904 else:
905 lines = webutil.annotate(req, fctx, web.repo.ui)
905 lines = webutil.annotate(req, fctx, web.repo.ui)
906
906
907 previousrev = None
907 previousrev = None
908 blockparitygen = paritygen(1)
908 blockparitygen = paritygen(1)
909 for lineno, (aline, l) in enumerate(lines):
909 for lineno, (aline, l) in enumerate(lines):
910 f = aline.fctx
910 f = aline.fctx
911 rev = f.rev()
911 rev = f.rev()
912 if rev != previousrev:
912 if rev != previousrev:
913 blockhead = True
913 blockhead = True
914 blockparity = next(blockparitygen)
914 blockparity = next(blockparitygen)
915 else:
915 else:
916 blockhead = None
916 blockhead = None
917 previousrev = rev
917 previousrev = rev
918 yield {"parity": next(parity),
918 yield {"parity": next(parity),
919 "node": f.hex(),
919 "node": f.hex(),
920 "rev": rev,
920 "rev": rev,
921 "author": f.user(),
921 "author": f.user(),
922 "parents": parents(f),
922 "parents": parents(f),
923 "desc": f.description(),
923 "desc": f.description(),
924 "extra": f.extra(),
924 "extra": f.extra(),
925 "file": f.path(),
925 "file": f.path(),
926 "blockhead": blockhead,
926 "blockhead": blockhead,
927 "blockparity": blockparity,
927 "blockparity": blockparity,
928 "targetline": aline.lineno,
928 "targetline": aline.lineno,
929 "line": l,
929 "line": l,
930 "lineno": lineno + 1,
930 "lineno": lineno + 1,
931 "lineid": "l%d" % (lineno + 1),
931 "lineid": "l%d" % (lineno + 1),
932 "linenumber": "% 6d" % (lineno + 1),
932 "linenumber": "% 6d" % (lineno + 1),
933 "revdate": f.date()}
933 "revdate": f.date()}
934
934
935 diffopts = webutil.difffeatureopts(req, web.repo.ui, 'annotate')
935 diffopts = webutil.difffeatureopts(req, web.repo.ui, 'annotate')
936 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults}
936 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults}
937
937
938 return tmpl("fileannotate",
938 return tmpl("fileannotate",
939 file=f,
939 file=f,
940 annotate=annotate,
940 annotate=annotate,
941 path=webutil.up(f),
941 path=webutil.up(f),
942 symrev=webutil.symrevorshortnode(req, fctx),
942 symrev=webutil.symrevorshortnode(req, fctx),
943 rename=webutil.renamelink(fctx),
943 rename=webutil.renamelink(fctx),
944 permissions=fctx.manifest().flags(f),
944 permissions=fctx.manifest().flags(f),
945 ishead=int(ishead),
945 ishead=int(ishead),
946 diffopts=diffopts,
946 diffopts=diffopts,
947 **webutil.commonentry(web.repo, fctx))
947 **webutil.commonentry(web.repo, fctx))
948
948
949 @webcommand('filelog')
949 @webcommand('filelog')
950 def filelog(web, req, tmpl):
950 def filelog(web, req, tmpl):
951 """
951 """
952 /filelog/{revision}/{path}
952 /filelog/{revision}/{path}
953 --------------------------
953 --------------------------
954
954
955 Show information about the history of a file in the repository.
955 Show information about the history of a file in the repository.
956
956
957 The ``revcount`` query string argument can be defined to control the
957 The ``revcount`` query string argument can be defined to control the
958 maximum number of entries to show.
958 maximum number of entries to show.
959
959
960 The ``filelog`` template will be rendered.
960 The ``filelog`` template will be rendered.
961 """
961 """
962
962
963 try:
963 try:
964 fctx = webutil.filectx(web.repo, req)
964 fctx = webutil.filectx(web.repo, req)
965 f = fctx.path()
965 f = fctx.path()
966 fl = fctx.filelog()
966 fl = fctx.filelog()
967 except error.LookupError:
967 except error.LookupError:
968 f = webutil.cleanpath(web.repo, req.form['file'][0])
968 f = webutil.cleanpath(web.repo, req.form['file'][0])
969 fl = web.repo.file(f)
969 fl = web.repo.file(f)
970 numrevs = len(fl)
970 numrevs = len(fl)
971 if not numrevs: # file doesn't exist at all
971 if not numrevs: # file doesn't exist at all
972 raise
972 raise
973 rev = webutil.changectx(web.repo, req).rev()
973 rev = webutil.changectx(web.repo, req).rev()
974 first = fl.linkrev(0)
974 first = fl.linkrev(0)
975 if rev < first: # current rev is from before file existed
975 if rev < first: # current rev is from before file existed
976 raise
976 raise
977 frev = numrevs - 1
977 frev = numrevs - 1
978 while fl.linkrev(frev) > rev:
978 while fl.linkrev(frev) > rev:
979 frev -= 1
979 frev -= 1
980 fctx = web.repo.filectx(f, fl.linkrev(frev))
980 fctx = web.repo.filectx(f, fl.linkrev(frev))
981
981
982 revcount = web.maxshortchanges
982 revcount = web.maxshortchanges
983 if 'revcount' in req.form:
983 if 'revcount' in req.form:
984 try:
984 try:
985 revcount = int(req.form.get('revcount', [revcount])[0])
985 revcount = int(req.form.get('revcount', [revcount])[0])
986 revcount = max(revcount, 1)
986 revcount = max(revcount, 1)
987 tmpl.defaults['sessionvars']['revcount'] = revcount
987 tmpl.defaults['sessionvars']['revcount'] = revcount
988 except ValueError:
988 except ValueError:
989 pass
989 pass
990
990
991 lrange = webutil.linerange(req)
991 lrange = webutil.linerange(req)
992
992
993 lessvars = copy.copy(tmpl.defaults['sessionvars'])
993 lessvars = copy.copy(tmpl.defaults['sessionvars'])
994 lessvars['revcount'] = max(revcount / 2, 1)
994 lessvars['revcount'] = max(revcount / 2, 1)
995 morevars = copy.copy(tmpl.defaults['sessionvars'])
995 morevars = copy.copy(tmpl.defaults['sessionvars'])
996 morevars['revcount'] = revcount * 2
996 morevars['revcount'] = revcount * 2
997
997
998 patch = 'patch' in req.form
998 patch = 'patch' in req.form
999 if patch:
999 if patch:
1000 lessvars['patch'] = morevars['patch'] = req.form['patch'][0]
1000 lessvars['patch'] = morevars['patch'] = req.form['patch'][0]
1001 descend = 'descend' in req.form
1001 descend = 'descend' in req.form
1002 if descend:
1002 if descend:
1003 lessvars['descend'] = morevars['descend'] = req.form['descend'][0]
1003 lessvars['descend'] = morevars['descend'] = req.form['descend'][0]
1004
1004
1005 count = fctx.filerev() + 1
1005 count = fctx.filerev() + 1
1006 start = max(0, count - revcount) # first rev on this page
1006 start = max(0, count - revcount) # first rev on this page
1007 end = min(count, start + revcount) # last rev on this page
1007 end = min(count, start + revcount) # last rev on this page
1008 parity = paritygen(web.stripecount, offset=start - end)
1008 parity = paritygen(web.stripecount, offset=start - end)
1009
1009
1010 repo = web.repo
1010 repo = web.repo
1011 revs = fctx.filelog().revs(start, end - 1)
1011 revs = fctx.filelog().revs(start, end - 1)
1012 entries = []
1012 entries = []
1013
1013
1014 diffstyle = web.config('web', 'style')
1014 diffstyle = web.config('web', 'style')
1015 if 'style' in req.form:
1015 if 'style' in req.form:
1016 diffstyle = req.form['style'][0]
1016 diffstyle = req.form['style'][0]
1017
1017
1018 def diff(fctx, linerange=None):
1018 def diff(fctx, linerange=None):
1019 ctx = fctx.changectx()
1019 ctx = fctx.changectx()
1020 basectx = ctx.p1()
1020 basectx = ctx.p1()
1021 path = fctx.path()
1021 path = fctx.path()
1022 return webutil.diffs(web, tmpl, ctx, basectx, [path], diffstyle,
1022 return webutil.diffs(web, tmpl, ctx, basectx, [path], diffstyle,
1023 linerange=linerange,
1023 linerange=linerange,
1024 lineidprefix='%s-' % ctx.hex()[:12])
1024 lineidprefix='%s-' % ctx.hex()[:12])
1025
1025
1026 linerange = None
1026 linerange = None
1027 if lrange is not None:
1027 if lrange is not None:
1028 linerange = webutil.formatlinerange(*lrange)
1028 linerange = webutil.formatlinerange(*lrange)
1029 # deactivate numeric nav links when linerange is specified as this
1029 # deactivate numeric nav links when linerange is specified as this
1030 # would required a dedicated "revnav" class
1030 # would required a dedicated "revnav" class
1031 nav = None
1031 nav = None
1032 if descend:
1032 if descend:
1033 it = dagop.blockdescendants(fctx, *lrange)
1033 it = dagop.blockdescendants(fctx, *lrange)
1034 else:
1034 else:
1035 it = dagop.blockancestors(fctx, *lrange)
1035 it = dagop.blockancestors(fctx, *lrange)
1036 for i, (c, lr) in enumerate(it, 1):
1036 for i, (c, lr) in enumerate(it, 1):
1037 diffs = None
1037 diffs = None
1038 if patch:
1038 if patch:
1039 diffs = diff(c, linerange=lr)
1039 diffs = diff(c, linerange=lr)
1040 # follow renames accross filtered (not in range) revisions
1040 # follow renames accross filtered (not in range) revisions
1041 path = c.path()
1041 path = c.path()
1042 entries.append(dict(
1042 entries.append(dict(
1043 parity=next(parity),
1043 parity=next(parity),
1044 filerev=c.rev(),
1044 filerev=c.rev(),
1045 file=path,
1045 file=path,
1046 diff=diffs,
1046 diff=diffs,
1047 linerange=webutil.formatlinerange(*lr),
1047 linerange=webutil.formatlinerange(*lr),
1048 **webutil.commonentry(repo, c)))
1048 **webutil.commonentry(repo, c)))
1049 if i == revcount:
1049 if i == revcount:
1050 break
1050 break
1051 lessvars['linerange'] = webutil.formatlinerange(*lrange)
1051 lessvars['linerange'] = webutil.formatlinerange(*lrange)
1052 morevars['linerange'] = lessvars['linerange']
1052 morevars['linerange'] = lessvars['linerange']
1053 else:
1053 else:
1054 for i in revs:
1054 for i in revs:
1055 iterfctx = fctx.filectx(i)
1055 iterfctx = fctx.filectx(i)
1056 diffs = None
1056 diffs = None
1057 if patch:
1057 if patch:
1058 diffs = diff(iterfctx)
1058 diffs = diff(iterfctx)
1059 entries.append(dict(
1059 entries.append(dict(
1060 parity=next(parity),
1060 parity=next(parity),
1061 filerev=i,
1061 filerev=i,
1062 file=f,
1062 file=f,
1063 diff=diffs,
1063 diff=diffs,
1064 rename=webutil.renamelink(iterfctx),
1064 rename=webutil.renamelink(iterfctx),
1065 **webutil.commonentry(repo, iterfctx)))
1065 **webutil.commonentry(repo, iterfctx)))
1066 entries.reverse()
1066 entries.reverse()
1067 revnav = webutil.filerevnav(web.repo, fctx.path())
1067 revnav = webutil.filerevnav(web.repo, fctx.path())
1068 nav = revnav.gen(end - 1, revcount, count)
1068 nav = revnav.gen(end - 1, revcount, count)
1069
1069
1070 latestentry = entries[:1]
1070 latestentry = entries[:1]
1071
1071
1072 return tmpl("filelog",
1072 return tmpl("filelog",
1073 file=f,
1073 file=f,
1074 nav=nav,
1074 nav=nav,
1075 symrev=webutil.symrevorshortnode(req, fctx),
1075 symrev=webutil.symrevorshortnode(req, fctx),
1076 entries=entries,
1076 entries=entries,
1077 descend=descend,
1077 descend=descend,
1078 patch=patch,
1078 patch=patch,
1079 latestentry=latestentry,
1079 latestentry=latestentry,
1080 linerange=linerange,
1080 linerange=linerange,
1081 revcount=revcount,
1081 revcount=revcount,
1082 morevars=morevars,
1082 morevars=morevars,
1083 lessvars=lessvars,
1083 lessvars=lessvars,
1084 **webutil.commonentry(web.repo, fctx))
1084 **webutil.commonentry(web.repo, fctx))
1085
1085
1086 @webcommand('archive')
1086 @webcommand('archive')
1087 def archive(web, req, tmpl):
1087 def archive(web, req, tmpl):
1088 """
1088 """
1089 /archive/{revision}.{format}[/{path}]
1089 /archive/{revision}.{format}[/{path}]
1090 -------------------------------------
1090 -------------------------------------
1091
1091
1092 Obtain an archive of repository content.
1092 Obtain an archive of repository content.
1093
1093
1094 The content and type of the archive is defined by a URL path parameter.
1094 The content and type of the archive is defined by a URL path parameter.
1095 ``format`` is the file extension of the archive type to be generated. e.g.
1095 ``format`` is the file extension of the archive type to be generated. e.g.
1096 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1096 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1097 server configuration.
1097 server configuration.
1098
1098
1099 The optional ``path`` URL parameter controls content to include in the
1099 The optional ``path`` URL parameter controls content to include in the
1100 archive. If omitted, every file in the specified revision is present in the
1100 archive. If omitted, every file in the specified revision is present in the
1101 archive. If included, only the specified file or contents of the specified
1101 archive. If included, only the specified file or contents of the specified
1102 directory will be included in the archive.
1102 directory will be included in the archive.
1103
1103
1104 No template is used for this handler. Raw, binary content is generated.
1104 No template is used for this handler. Raw, binary content is generated.
1105 """
1105 """
1106
1106
1107 type_ = req.form.get('type', [None])[0]
1107 type_ = req.form.get('type', [None])[0]
1108 allowed = web.configlist("web", "allow_archive")
1108 allowed = web.configlist("web", "allow_archive")
1109 key = req.form['node'][0]
1109 key = req.form['node'][0]
1110
1110
1111 if type_ not in web.archivespecs:
1111 if type_ not in web.archivespecs:
1112 msg = 'Unsupported archive type: %s' % type_
1112 msg = 'Unsupported archive type: %s' % type_
1113 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1113 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1114
1114
1115 if not ((type_ in allowed or
1115 if not ((type_ in allowed or
1116 web.configbool("web", "allow" + type_, False))):
1116 web.configbool("web", "allow" + type_, False))):
1117 msg = 'Archive type not allowed: %s' % type_
1117 msg = 'Archive type not allowed: %s' % type_
1118 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1118 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1119
1119
1120 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1120 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1121 cnode = web.repo.lookup(key)
1121 cnode = web.repo.lookup(key)
1122 arch_version = key
1122 arch_version = key
1123 if cnode == key or key == 'tip':
1123 if cnode == key or key == 'tip':
1124 arch_version = short(cnode)
1124 arch_version = short(cnode)
1125 name = "%s-%s" % (reponame, arch_version)
1125 name = "%s-%s" % (reponame, arch_version)
1126
1126
1127 ctx = webutil.changectx(web.repo, req)
1127 ctx = webutil.changectx(web.repo, req)
1128 pats = []
1128 pats = []
1129 match = scmutil.match(ctx, [])
1129 match = scmutil.match(ctx, [])
1130 file = req.form.get('file', None)
1130 file = req.form.get('file', None)
1131 if file:
1131 if file:
1132 pats = ['path:' + file[0]]
1132 pats = ['path:' + file[0]]
1133 match = scmutil.match(ctx, pats, default='path')
1133 match = scmutil.match(ctx, pats, default='path')
1134 if pats:
1134 if pats:
1135 files = [f for f in ctx.manifest().keys() if match(f)]
1135 files = [f for f in ctx.manifest().keys() if match(f)]
1136 if not files:
1136 if not files:
1137 raise ErrorResponse(HTTP_NOT_FOUND,
1137 raise ErrorResponse(HTTP_NOT_FOUND,
1138 'file(s) not found: %s' % file[0])
1138 'file(s) not found: %s' % file[0])
1139
1139
1140 mimetype, artype, extension, encoding = web.archivespecs[type_]
1140 mimetype, artype, extension, encoding = web.archivespecs[type_]
1141 headers = [
1141 headers = [
1142 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1142 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1143 ]
1143 ]
1144 if encoding:
1144 if encoding:
1145 headers.append(('Content-Encoding', encoding))
1145 headers.append(('Content-Encoding', encoding))
1146 req.headers.extend(headers)
1146 req.headers.extend(headers)
1147 req.respond(HTTP_OK, mimetype)
1147 req.respond(HTTP_OK, mimetype)
1148
1148
1149 archival.archive(web.repo, req, cnode, artype, prefix=name,
1149 archival.archive(web.repo, req, cnode, artype, prefix=name,
1150 matchfn=match,
1150 matchfn=match,
1151 subrepos=web.configbool("web", "archivesubrepos"))
1151 subrepos=web.configbool("web", "archivesubrepos"))
1152 return []
1152 return []
1153
1153
1154
1154
1155 @webcommand('static')
1155 @webcommand('static')
1156 def static(web, req, tmpl):
1156 def static(web, req, tmpl):
1157 fname = req.form['file'][0]
1157 fname = req.form['file'][0]
1158 # a repo owner may set web.static in .hg/hgrc to get any file
1158 # a repo owner may set web.static in .hg/hgrc to get any file
1159 # readable by the user running the CGI script
1159 # readable by the user running the CGI script
1160 static = web.config("web", "static", None, untrusted=False)
1160 static = web.config("web", "static", None, untrusted=False)
1161 if not static:
1161 if not static:
1162 tp = web.templatepath or templater.templatepaths()
1162 tp = web.templatepath or templater.templatepaths()
1163 if isinstance(tp, str):
1163 if isinstance(tp, str):
1164 tp = [tp]
1164 tp = [tp]
1165 static = [os.path.join(p, 'static') for p in tp]
1165 static = [os.path.join(p, 'static') for p in tp]
1166 staticfile(static, fname, req)
1166 staticfile(static, fname, req)
1167 return []
1167 return []
1168
1168
1169 @webcommand('graph')
1169 @webcommand('graph')
1170 def graph(web, req, tmpl):
1170 def graph(web, req, tmpl):
1171 """
1171 """
1172 /graph[/{revision}]
1172 /graph[/{revision}]
1173 -------------------
1173 -------------------
1174
1174
1175 Show information about the graphical topology of the repository.
1175 Show information about the graphical topology of the repository.
1176
1176
1177 Information rendered by this handler can be used to create visual
1177 Information rendered by this handler can be used to create visual
1178 representations of repository topology.
1178 representations of repository topology.
1179
1179
1180 The ``revision`` URL parameter controls the starting changeset.
1180 The ``revision`` URL parameter controls the starting changeset.
1181
1181
1182 The ``revcount`` query string argument can define the number of changesets
1182 The ``revcount`` query string argument can define the number of changesets
1183 to show information for.
1183 to show information for.
1184
1184
1185 This handler will render the ``graph`` template.
1185 This handler will render the ``graph`` template.
1186 """
1186 """
1187
1187
1188 if 'node' in req.form:
1188 if 'node' in req.form:
1189 ctx = webutil.changectx(web.repo, req)
1189 ctx = webutil.changectx(web.repo, req)
1190 symrev = webutil.symrevorshortnode(req, ctx)
1190 symrev = webutil.symrevorshortnode(req, ctx)
1191 else:
1191 else:
1192 ctx = web.repo['tip']
1192 ctx = web.repo['tip']
1193 symrev = 'tip'
1193 symrev = 'tip'
1194 rev = ctx.rev()
1194 rev = ctx.rev()
1195
1195
1196 bg_height = 39
1196 bg_height = 39
1197 revcount = web.maxshortchanges
1197 revcount = web.maxshortchanges
1198 if 'revcount' in req.form:
1198 if 'revcount' in req.form:
1199 try:
1199 try:
1200 revcount = int(req.form.get('revcount', [revcount])[0])
1200 revcount = int(req.form.get('revcount', [revcount])[0])
1201 revcount = max(revcount, 1)
1201 revcount = max(revcount, 1)
1202 tmpl.defaults['sessionvars']['revcount'] = revcount
1202 tmpl.defaults['sessionvars']['revcount'] = revcount
1203 except ValueError:
1203 except ValueError:
1204 pass
1204 pass
1205
1205
1206 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1206 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1207 lessvars['revcount'] = max(revcount / 2, 1)
1207 lessvars['revcount'] = max(revcount / 2, 1)
1208 morevars = copy.copy(tmpl.defaults['sessionvars'])
1208 morevars = copy.copy(tmpl.defaults['sessionvars'])
1209 morevars['revcount'] = revcount * 2
1209 morevars['revcount'] = revcount * 2
1210
1210
1211 count = len(web.repo)
1211 count = len(web.repo)
1212 pos = rev
1212 pos = rev
1213
1213
1214 uprev = min(max(0, count - 1), rev + revcount)
1214 uprev = min(max(0, count - 1), rev + revcount)
1215 downrev = max(0, rev - revcount)
1215 downrev = max(0, rev - revcount)
1216 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1216 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1217
1217
1218 tree = []
1218 tree = []
1219 if pos != -1:
1219 if pos != -1:
1220 allrevs = web.repo.changelog.revs(pos, 0)
1220 allrevs = web.repo.changelog.revs(pos, 0)
1221 revs = []
1221 revs = []
1222 for i in allrevs:
1222 for i in allrevs:
1223 revs.append(i)
1223 revs.append(i)
1224 if len(revs) >= revcount:
1224 if len(revs) >= revcount:
1225 break
1225 break
1226
1226
1227 # We have to feed a baseset to dagwalker as it is expecting smartset
1227 # We have to feed a baseset to dagwalker as it is expecting smartset
1228 # object. This does not have a big impact on hgweb performance itself
1228 # object. This does not have a big impact on hgweb performance itself
1229 # since hgweb graphing code is not itself lazy yet.
1229 # since hgweb graphing code is not itself lazy yet.
1230 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1230 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1231 # As we said one line above... not lazy.
1231 # As we said one line above... not lazy.
1232 tree = list(graphmod.colored(dag, web.repo))
1232 tree = list(graphmod.colored(dag, web.repo))
1233
1233
1234 def getcolumns(tree):
1234 def getcolumns(tree):
1235 cols = 0
1235 cols = 0
1236 for (id, type, ctx, vtx, edges) in tree:
1236 for (id, type, ctx, vtx, edges) in tree:
1237 if type != graphmod.CHANGESET:
1237 if type != graphmod.CHANGESET:
1238 continue
1238 continue
1239 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1239 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1240 max([edge[1] for edge in edges] or [0]))
1240 max([edge[1] for edge in edges] or [0]))
1241 return cols
1241 return cols
1242
1242
1243 def graphdata(usetuples, encodestr):
1243 def graphdata(usetuples, encodestr):
1244 data = []
1244 data = []
1245
1245
1246 row = 0
1246 row = 0
1247 for (id, type, ctx, vtx, edges) in tree:
1247 for (id, type, ctx, vtx, edges) in tree:
1248 if type != graphmod.CHANGESET:
1248 if type != graphmod.CHANGESET:
1249 continue
1249 continue
1250 node = str(ctx)
1250 node = str(ctx)
1251 age = encodestr(templatefilters.age(ctx.date()))
1251 age = encodestr(templatefilters.age(ctx.date()))
1252 desc = templatefilters.firstline(encodestr(ctx.description()))
1252 desc = templatefilters.firstline(encodestr(ctx.description()))
1253 desc = cgi.escape(templatefilters.nonempty(desc))
1253 desc = cgi.escape(templatefilters.nonempty(desc))
1254 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1254 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1255 branch = cgi.escape(encodestr(ctx.branch()))
1255 branch = cgi.escape(encodestr(ctx.branch()))
1256 try:
1256 try:
1257 branchnode = web.repo.branchtip(branch)
1257 branchnode = web.repo.branchtip(branch)
1258 except error.RepoLookupError:
1258 except error.RepoLookupError:
1259 branchnode = None
1259 branchnode = None
1260 branch = branch, branchnode == ctx.node()
1260 branch = branch, branchnode == ctx.node()
1261
1261
1262 if usetuples:
1262 if usetuples:
1263 data.append((node, vtx, edges, desc, user, age, branch,
1263 data.append((node, vtx, edges, desc, user, age, branch,
1264 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1264 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1265 [cgi.escape(encodestr(x))
1265 [cgi.escape(encodestr(x))
1266 for x in ctx.bookmarks()]))
1266 for x in ctx.bookmarks()]))
1267 else:
1267 else:
1268 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1268 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1269 'color': (edge[2] - 1) % 6 + 1,
1269 'color': (edge[2] - 1) % 6 + 1,
1270 'width': edge[3], 'bcolor': edge[4]}
1270 'width': edge[3], 'bcolor': edge[4]}
1271 for edge in edges]
1271 for edge in edges]
1272
1272
1273 data.append(
1273 data.append(
1274 {'node': node,
1274 {'node': node,
1275 'col': vtx[0],
1275 'col': vtx[0],
1276 'color': (vtx[1] - 1) % 6 + 1,
1276 'color': (vtx[1] - 1) % 6 + 1,
1277 'edges': edgedata,
1277 'edges': edgedata,
1278 'row': row,
1278 'row': row,
1279 'nextrow': row + 1,
1279 'nextrow': row + 1,
1280 'desc': desc,
1280 'desc': desc,
1281 'user': user,
1281 'user': user,
1282 'age': age,
1282 'age': age,
1283 'bookmarks': webutil.nodebookmarksdict(
1283 'bookmarks': webutil.nodebookmarksdict(
1284 web.repo, ctx.node()),
1284 web.repo, ctx.node()),
1285 'branches': webutil.nodebranchdict(web.repo, ctx),
1285 'branches': webutil.nodebranchdict(web.repo, ctx),
1286 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1286 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1287 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1287 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1288
1288
1289 row += 1
1289 row += 1
1290
1290
1291 return data
1291 return data
1292
1292
1293 cols = getcolumns(tree)
1293 cols = getcolumns(tree)
1294 rows = len(tree)
1294 rows = len(tree)
1295 canvasheight = (rows + 1) * bg_height - 27
1295 canvasheight = (rows + 1) * bg_height - 27
1296
1296
1297 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1297 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1298 uprev=uprev,
1298 uprev=uprev,
1299 lessvars=lessvars, morevars=morevars, downrev=downrev,
1299 lessvars=lessvars, morevars=morevars, downrev=downrev,
1300 cols=cols, rows=rows,
1300 cols=cols, rows=rows,
1301 canvaswidth=(cols + 1) * bg_height,
1301 canvaswidth=(cols + 1) * bg_height,
1302 truecanvasheight=rows * bg_height,
1302 truecanvasheight=rows * bg_height,
1303 canvasheight=canvasheight, bg_height=bg_height,
1303 canvasheight=canvasheight, bg_height=bg_height,
1304 # {jsdata} will be passed to |json, so it must be in utf-8
1304 # {jsdata} will be passed to |json, so it must be in utf-8
1305 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1305 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1306 nodes=lambda **x: graphdata(False, str),
1306 nodes=lambda **x: graphdata(False, str),
1307 node=ctx.hex(), changenav=changenav)
1307 node=ctx.hex(), changenav=changenav)
1308
1308
1309 def _getdoc(e):
1309 def _getdoc(e):
1310 doc = e[0].__doc__
1310 doc = e[0].__doc__
1311 if doc:
1311 if doc:
1312 doc = _(doc).partition('\n')[0]
1312 doc = _(doc).partition('\n')[0]
1313 else:
1313 else:
1314 doc = _('(no help text available)')
1314 doc = _('(no help text available)')
1315 return doc
1315 return doc
1316
1316
1317 @webcommand('help')
1317 @webcommand('help')
1318 def help(web, req, tmpl):
1318 def help(web, req, tmpl):
1319 """
1319 """
1320 /help[/{topic}]
1320 /help[/{topic}]
1321 ---------------
1321 ---------------
1322
1322
1323 Render help documentation.
1323 Render help documentation.
1324
1324
1325 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1325 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1326 is defined, that help topic will be rendered. If not, an index of
1326 is defined, that help topic will be rendered. If not, an index of
1327 available help topics will be rendered.
1327 available help topics will be rendered.
1328
1328
1329 The ``help`` template will be rendered when requesting help for a topic.
1329 The ``help`` template will be rendered when requesting help for a topic.
1330 ``helptopics`` will be rendered for the index of help topics.
1330 ``helptopics`` will be rendered for the index of help topics.
1331 """
1331 """
1332 from .. import commands, help as helpmod # avoid cycle
1332 from .. import commands, help as helpmod # avoid cycle
1333
1333
1334 topicname = req.form.get('node', [None])[0]
1334 topicname = req.form.get('node', [None])[0]
1335 if not topicname:
1335 if not topicname:
1336 def topics(**map):
1336 def topics(**map):
1337 for entries, summary, _doc in helpmod.helptable:
1337 for entries, summary, _doc in helpmod.helptable:
1338 yield {'topic': entries[0], 'summary': summary}
1338 yield {'topic': entries[0], 'summary': summary}
1339
1339
1340 early, other = [], []
1340 early, other = [], []
1341 primary = lambda s: s.partition('|')[0]
1341 primary = lambda s: s.partition('|')[0]
1342 for c, e in commands.table.iteritems():
1342 for c, e in commands.table.iteritems():
1343 doc = _getdoc(e)
1343 doc = _getdoc(e)
1344 if 'DEPRECATED' in doc or c.startswith('debug'):
1344 if 'DEPRECATED' in doc or c.startswith('debug'):
1345 continue
1345 continue
1346 cmd = primary(c)
1346 cmd = primary(c)
1347 if cmd.startswith('^'):
1347 if cmd.startswith('^'):
1348 early.append((cmd[1:], doc))
1348 early.append((cmd[1:], doc))
1349 else:
1349 else:
1350 other.append((cmd, doc))
1350 other.append((cmd, doc))
1351
1351
1352 early.sort()
1352 early.sort()
1353 other.sort()
1353 other.sort()
1354
1354
1355 def earlycommands(**map):
1355 def earlycommands(**map):
1356 for c, doc in early:
1356 for c, doc in early:
1357 yield {'topic': c, 'summary': doc}
1357 yield {'topic': c, 'summary': doc}
1358
1358
1359 def othercommands(**map):
1359 def othercommands(**map):
1360 for c, doc in other:
1360 for c, doc in other:
1361 yield {'topic': c, 'summary': doc}
1361 yield {'topic': c, 'summary': doc}
1362
1362
1363 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1363 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1364 othercommands=othercommands, title='Index')
1364 othercommands=othercommands, title='Index')
1365
1365
1366 # Render an index of sub-topics.
1366 # Render an index of sub-topics.
1367 if topicname in helpmod.subtopics:
1367 if topicname in helpmod.subtopics:
1368 topics = []
1368 topics = []
1369 for entries, summary, _doc in helpmod.subtopics[topicname]:
1369 for entries, summary, _doc in helpmod.subtopics[topicname]:
1370 topics.append({
1370 topics.append({
1371 'topic': '%s.%s' % (topicname, entries[0]),
1371 'topic': '%s.%s' % (topicname, entries[0]),
1372 'basename': entries[0],
1372 'basename': entries[0],
1373 'summary': summary,
1373 'summary': summary,
1374 })
1374 })
1375
1375
1376 return tmpl('helptopics', topics=topics, title=topicname,
1376 return tmpl('helptopics', topics=topics, title=topicname,
1377 subindex=True)
1377 subindex=True)
1378
1378
1379 u = webutil.wsgiui.load()
1379 u = webutil.wsgiui.load()
1380 u.verbose = True
1380 u.verbose = True
1381
1381
1382 # Render a page from a sub-topic.
1382 # Render a page from a sub-topic.
1383 if '.' in topicname:
1383 if '.' in topicname:
1384 # TODO implement support for rendering sections, like
1384 # TODO implement support for rendering sections, like
1385 # `hg help` works.
1385 # `hg help` works.
1386 topic, subtopic = topicname.split('.', 1)
1386 topic, subtopic = topicname.split('.', 1)
1387 if topic not in helpmod.subtopics:
1387 if topic not in helpmod.subtopics:
1388 raise ErrorResponse(HTTP_NOT_FOUND)
1388 raise ErrorResponse(HTTP_NOT_FOUND)
1389 else:
1389 else:
1390 topic = topicname
1390 topic = topicname
1391 subtopic = None
1391 subtopic = None
1392
1392
1393 try:
1393 try:
1394 doc = helpmod.help_(u, commands, topic, subtopic=subtopic)
1394 doc = helpmod.help_(u, commands, topic, subtopic=subtopic)
1395 except error.UnknownCommand:
1395 except error.UnknownCommand:
1396 raise ErrorResponse(HTTP_NOT_FOUND)
1396 raise ErrorResponse(HTTP_NOT_FOUND)
1397 return tmpl('help', topic=topicname, doc=doc)
1397 return tmpl('help', topic=topicname, doc=doc)
1398
1398
1399 # tell hggettext to extract docstrings from these functions:
1399 # tell hggettext to extract docstrings from these functions:
1400 i18nfunctions = commands.values()
1400 i18nfunctions = commands.values()
General Comments 0
You need to be logged in to leave comments. Login now