##// END OF EJS Templates
Backed out changeset b913d3aacddc (see issue971/msg5317)
Thomas Arendsen Hein -
r6141:90e5c82a default
parent child Browse files
Show More
@@ -1,52 +1,50 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # An example CGI script to export multiple hgweb repos, edit as necessary
4 4
5 5 # adjust python path if not a system-wide install:
6 6 #import sys
7 7 #sys.path.insert(0, "/path/to/python/lib")
8 8
9 9 # enable demandloading to reduce startup time
10 10 from mercurial import demandimport; demandimport.enable()
11 11
12 12 # send python tracebacks to the browser if an error occurs:
13 13 import cgitb
14 14 cgitb.enable()
15 15
16 16 # If you'd like to serve pages with UTF-8 instead of your default
17 17 # locale charset, you can do so by uncommenting the following lines.
18 18 # Note that this will cause your .hgrc files to be interpreted in
19 19 # UTF-8 and all your repo files to be displayed using UTF-8.
20 20 #
21 21 #import os
22 22 #os.environ["HGENCODING"] = "UTF-8"
23 23
24 24 from mercurial.hgweb.hgwebdir_mod import hgwebdir
25 25 from mercurial.hgweb.request import wsgiapplication
26 from mercurial import dispatch, ui
27 26 from flup.server.fcgi import WSGIServer
28 27
29 28 # The config file looks like this. You can have paths to individual
30 29 # repos, collections of repos in a directory tree, or both.
31 30 #
32 31 # [paths]
33 32 # virtual/path = /real/path
34 33 # virtual/path = /real/path
35 34 #
36 35 # [collections]
37 36 # /prefix/to/strip/off = /root/of/tree/full/of/repos
38 37 #
39 38 # collections example: say directory tree /foo contains repos /foo/bar,
40 39 # /foo/quux/baz. Give this config section:
41 40 # [collections]
42 41 # /foo = /foo
43 42 # Then repos will list as bar and quux/baz.
44 43 #
45 44 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
46 45 # or use a dictionary with entries like 'virtual/path': '/real/path'
47 46
48 def web_app(ui):
49 return lambda: hgwebdir("hgweb.config", ui)
47 def make_web_app():
48 return hgwebdir("hgweb.config")
50 49
51 u = ui.ui(report_untrusted=False, interactive=False)
52 dispatch.profiled(u, lambda: WSGIServer(wsgiapplication(web_app(u))).run())
50 WSGIServer(wsgiapplication(make_web_app)).run()
@@ -1,601 +1,590 b''
1 1 HGRC(5)
2 2 =======
3 3 Bryan O'Sullivan <bos@serpentine.com>
4 4
5 5 NAME
6 6 ----
7 7 hgrc - configuration files for Mercurial
8 8
9 9 SYNOPSIS
10 10 --------
11 11
12 12 The Mercurial system uses a set of configuration files to control
13 13 aspects of its behaviour.
14 14
15 15 FILES
16 16 -----
17 17
18 18 Mercurial reads configuration data from several files, if they exist.
19 19 The names of these files depend on the system on which Mercurial is
20 20 installed. Windows registry keys contain PATH-like strings, every
21 21 part must reference a Mercurial.ini file or be a directory where *.rc
22 22 files will be read.
23 23
24 24 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
25 25 (Unix) <install-root>/etc/mercurial/hgrc::
26 26 Per-installation configuration files, searched for in the
27 27 directory where Mercurial is installed. For example, if installed
28 28 in /shared/tools, Mercurial will look in
29 29 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
30 30 all Mercurial commands executed by any user in any directory.
31 31
32 32 (Unix) /etc/mercurial/hgrc.d/*.rc::
33 33 (Unix) /etc/mercurial/hgrc::
34 34 (Windows) HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial::
35 35 or::
36 36 (Windows) C:\Mercurial\Mercurial.ini::
37 37 Per-system configuration files, for the system on which Mercurial
38 38 is running. Options in these files apply to all Mercurial
39 39 commands executed by any user in any directory. Options in these
40 40 files override per-installation options.
41 41
42 42 (Unix) $HOME/.hgrc::
43 43 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
44 44 (Windows) $HOME\Mercurial.ini::
45 45 Per-user configuration file, for the user running Mercurial.
46 46 Options in this file apply to all Mercurial commands executed by
47 47 any user in any directory. Options in this file override
48 48 per-installation and per-system options.
49 49 On Windows system, one of these is chosen exclusively according
50 50 to definition of HOME environment variable.
51 51
52 52 (Unix, Windows) <repo>/.hg/hgrc::
53 53 Per-repository configuration options that only apply in a
54 54 particular repository. This file is not version-controlled, and
55 55 will not get transferred during a "clone" operation. Options in
56 56 this file override options in all other configuration files.
57 57 On Unix, most of this file will be ignored if it doesn't belong
58 58 to a trusted user or to a trusted group. See the documentation
59 59 for the trusted section below for more details.
60 60
61 61 SYNTAX
62 62 ------
63 63
64 64 A configuration file consists of sections, led by a "[section]" header
65 65 and followed by "name: value" entries; "name=value" is also accepted.
66 66
67 67 [spam]
68 68 eggs=ham
69 69 green=
70 70 eggs
71 71
72 72 Each line contains one entry. If the lines that follow are indented,
73 73 they are treated as continuations of that entry.
74 74
75 75 Leading whitespace is removed from values. Empty lines are skipped.
76 76
77 77 The optional values can contain format strings which refer to other
78 78 values in the same section, or values in a special DEFAULT section.
79 79
80 80 Lines beginning with "#" or ";" are ignored and may be used to provide
81 81 comments.
82 82
83 83 SECTIONS
84 84 --------
85 85
86 86 This section describes the different sections that may appear in a
87 87 Mercurial "hgrc" file, the purpose of each section, its possible
88 88 keys, and their possible values.
89 89
90 90 decode/encode::
91 91 Filters for transforming files on checkout/checkin. This would
92 92 typically be used for newline processing or other
93 93 localization/canonicalization of files.
94 94
95 95 Filters consist of a filter pattern followed by a filter command.
96 96 Filter patterns are globs by default, rooted at the repository
97 97 root. For example, to match any file ending in ".txt" in the root
98 98 directory only, use the pattern "*.txt". To match any file ending
99 99 in ".c" anywhere in the repository, use the pattern "**.c".
100 100
101 101 The filter command can start with a specifier, either "pipe:" or
102 102 "tempfile:". If no specifier is given, "pipe:" is used by default.
103 103
104 104 A "pipe:" command must accept data on stdin and return the
105 105 transformed data on stdout.
106 106
107 107 Pipe example:
108 108
109 109 [encode]
110 110 # uncompress gzip files on checkin to improve delta compression
111 111 # note: not necessarily a good idea, just an example
112 112 *.gz = pipe: gunzip
113 113
114 114 [decode]
115 115 # recompress gzip files when writing them to the working dir (we
116 116 # can safely omit "pipe:", because it's the default)
117 117 *.gz = gzip
118 118
119 119 A "tempfile:" command is a template. The string INFILE is replaced
120 120 with the name of a temporary file that contains the data to be
121 121 filtered by the command. The string OUTFILE is replaced with the
122 122 name of an empty temporary file, where the filtered data must be
123 123 written by the command.
124 124
125 125 NOTE: the tempfile mechanism is recommended for Windows systems,
126 126 where the standard shell I/O redirection operators often have
127 127 strange effects and may corrupt the contents of your files.
128 128
129 129 The most common usage is for LF <-> CRLF translation on Windows.
130 130 For this, use the "smart" convertors which check for binary files:
131 131
132 132 [extensions]
133 133 hgext.win32text =
134 134 [encode]
135 135 ** = cleverencode:
136 136 [decode]
137 137 ** = cleverdecode:
138 138
139 139 or if you only want to translate certain files:
140 140
141 141 [extensions]
142 142 hgext.win32text =
143 143 [encode]
144 144 **.txt = dumbencode:
145 145 [decode]
146 146 **.txt = dumbdecode:
147 147
148 148 defaults::
149 149 Use the [defaults] section to define command defaults, i.e. the
150 150 default options/arguments to pass to the specified commands.
151 151
152 152 The following example makes 'hg log' run in verbose mode, and
153 153 'hg status' show only the modified files, by default.
154 154
155 155 [defaults]
156 156 log = -v
157 157 status = -m
158 158
159 159 The actual commands, instead of their aliases, must be used when
160 160 defining command defaults. The command defaults will also be
161 161 applied to the aliases of the commands defined.
162 162
163 163 diff::
164 164 Settings used when displaying diffs. They are all boolean and
165 165 defaults to False.
166 166 git;;
167 167 Use git extended diff format.
168 168 nodates;;
169 169 Don't include dates in diff headers.
170 170 showfunc;;
171 171 Show which function each change is in.
172 172 ignorews;;
173 173 Ignore white space when comparing lines.
174 174 ignorewsamount;;
175 175 Ignore changes in the amount of white space.
176 176 ignoreblanklines;;
177 177 Ignore changes whose lines are all blank.
178 178
179 179 email::
180 180 Settings for extensions that send email messages.
181 181 from;;
182 182 Optional. Email address to use in "From" header and SMTP envelope
183 183 of outgoing messages.
184 184 to;;
185 185 Optional. Comma-separated list of recipients' email addresses.
186 186 cc;;
187 187 Optional. Comma-separated list of carbon copy recipients'
188 188 email addresses.
189 189 bcc;;
190 190 Optional. Comma-separated list of blind carbon copy
191 191 recipients' email addresses. Cannot be set interactively.
192 192 method;;
193 193 Optional. Method to use to send email messages. If value is
194 194 "smtp" (default), use SMTP (see section "[smtp]" for
195 195 configuration). Otherwise, use as name of program to run that
196 196 acts like sendmail (takes "-f" option for sender, list of
197 197 recipients on command line, message on stdin). Normally, setting
198 198 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
199 199 sendmail to send messages.
200 200
201 201 Email example:
202 202
203 203 [email]
204 204 from = Joseph User <joe.user@example.com>
205 205 method = /usr/sbin/sendmail
206 206
207 207 extensions::
208 208 Mercurial has an extension mechanism for adding new features. To
209 209 enable an extension, create an entry for it in this section.
210 210
211 211 If you know that the extension is already in Python's search path,
212 212 you can give the name of the module, followed by "=", with nothing
213 213 after the "=".
214 214
215 215 Otherwise, give a name that you choose, followed by "=", followed by
216 216 the path to the ".py" file (including the file name extension) that
217 217 defines the extension.
218 218
219 219 Example for ~/.hgrc:
220 220
221 221 [extensions]
222 222 # (the mq extension will get loaded from mercurial's path)
223 223 hgext.mq =
224 224 # (this extension will get loaded from the file specified)
225 225 myfeature = ~/.hgext/myfeature.py
226 226
227 227 format::
228 228
229 229 usestore;;
230 230 Enable or disable the "store" repository format which improves
231 231 compatibility with systems that fold case or otherwise mangle
232 232 filenames. Enabled by default. Disabling this option will allow
233 233 you to store longer filenames in some situations at the expense of
234 234 compatibility.
235 235
236 236 hooks::
237 237 Commands or Python functions that get automatically executed by
238 238 various actions such as starting or finishing a commit. Multiple
239 239 hooks can be run for the same action by appending a suffix to the
240 240 action. Overriding a site-wide hook can be done by changing its
241 241 value or setting it to an empty string.
242 242
243 243 Example .hg/hgrc:
244 244
245 245 [hooks]
246 246 # do not use the site-wide hook
247 247 incoming =
248 248 incoming.email = /my/email/hook
249 249 incoming.autobuild = /my/build/hook
250 250
251 251 Most hooks are run with environment variables set that give added
252 252 useful information. For each hook below, the environment variables
253 253 it is passed are listed with names of the form "$HG_foo".
254 254
255 255 changegroup;;
256 256 Run after a changegroup has been added via push, pull or
257 257 unbundle. ID of the first new changeset is in $HG_NODE. URL from
258 258 which changes came is in $HG_URL.
259 259 commit;;
260 260 Run after a changeset has been created in the local repository.
261 261 ID of the newly created changeset is in $HG_NODE. Parent
262 262 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
263 263 incoming;;
264 264 Run after a changeset has been pulled, pushed, or unbundled into
265 265 the local repository. The ID of the newly arrived changeset is in
266 266 $HG_NODE. URL that was source of changes came is in $HG_URL.
267 267 outgoing;;
268 268 Run after sending changes from local repository to another. ID of
269 269 first changeset sent is in $HG_NODE. Source of operation is in
270 270 $HG_SOURCE; see "preoutgoing" hook for description.
271 271 post-<command>;;
272 272 Run after successful invocations of the associated command. The
273 273 contents of the command line are passed as $HG_ARGS and the result
274 274 code in $HG_RESULT. Hook failure is ignored.
275 275 pre-<command>;;
276 276 Run before executing the associated command. The contents of the
277 277 command line are passed as $HG_ARGS. If the hook returns failure,
278 278 the command doesn't execute and Mercurial returns the failure code.
279 279 prechangegroup;;
280 280 Run before a changegroup is added via push, pull or unbundle.
281 281 Exit status 0 allows the changegroup to proceed. Non-zero status
282 282 will cause the push, pull or unbundle to fail. URL from which
283 283 changes will come is in $HG_URL.
284 284 precommit;;
285 285 Run before starting a local commit. Exit status 0 allows the
286 286 commit to proceed. Non-zero status will cause the commit to fail.
287 287 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
288 288 preoutgoing;;
289 289 Run before collecting changes to send from the local repository to
290 290 another. Non-zero status will cause failure. This lets you
291 291 prevent pull over http or ssh. Also prevents against local pull,
292 292 push (outbound) or bundle commands, but not effective, since you
293 293 can just copy files instead then. Source of operation is in
294 294 $HG_SOURCE. If "serve", operation is happening on behalf of
295 295 remote ssh or http repository. If "push", "pull" or "bundle",
296 296 operation is happening on behalf of repository on same system.
297 297 pretag;;
298 298 Run before creating a tag. Exit status 0 allows the tag to be
299 299 created. Non-zero status will cause the tag to fail. ID of
300 300 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
301 301 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
302 302 pretxnchangegroup;;
303 303 Run after a changegroup has been added via push, pull or unbundle,
304 304 but before the transaction has been committed. Changegroup is
305 305 visible to hook program. This lets you validate incoming changes
306 306 before accepting them. Passed the ID of the first new changeset
307 307 in $HG_NODE. Exit status 0 allows the transaction to commit.
308 308 Non-zero status will cause the transaction to be rolled back and
309 309 the push, pull or unbundle will fail. URL that was source of
310 310 changes is in $HG_URL.
311 311 pretxncommit;;
312 312 Run after a changeset has been created but the transaction not yet
313 313 committed. Changeset is visible to hook program. This lets you
314 314 validate commit message and changes. Exit status 0 allows the
315 315 commit to proceed. Non-zero status will cause the transaction to
316 316 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
317 317 IDs are in $HG_PARENT1 and $HG_PARENT2.
318 318 preupdate;;
319 319 Run before updating the working directory. Exit status 0 allows
320 320 the update to proceed. Non-zero status will prevent the update.
321 321 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
322 322 of second new parent is in $HG_PARENT2.
323 323 tag;;
324 324 Run after a tag is created. ID of tagged changeset is in
325 325 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
326 326 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
327 327 update;;
328 328 Run after updating the working directory. Changeset ID of first
329 329 new parent is in $HG_PARENT1. If merge, ID of second new parent
330 330 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
331 331 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
332 332
333 333 Note: it is generally better to use standard hooks rather than the
334 334 generic pre- and post- command hooks as they are guaranteed to be
335 335 called in the appropriate contexts for influencing transactions.
336 336 Also, hooks like "commit" will be called in all contexts that
337 337 generate a commit (eg. tag) and not just the commit command.
338 338
339 339 Note2: Environment variables with empty values may not be passed to
340 340 hooks on platforms like Windows. For instance, $HG_PARENT2 will
341 341 not be available under Windows for non-merge changesets while being
342 342 set to an empty value under Unix-like systems.
343 343
344 344 The syntax for Python hooks is as follows:
345 345
346 346 hookname = python:modulename.submodule.callable
347 347
348 348 Python hooks are run within the Mercurial process. Each hook is
349 349 called with at least three keyword arguments: a ui object (keyword
350 350 "ui"), a repository object (keyword "repo"), and a "hooktype"
351 351 keyword that tells what kind of hook is used. Arguments listed as
352 352 environment variables above are passed as keyword arguments, with no
353 353 "HG_" prefix, and names in lower case.
354 354
355 355 If a Python hook returns a "true" value or raises an exception, this
356 356 is treated as failure of the hook.
357 357
358 358 http_proxy::
359 359 Used to access web-based Mercurial repositories through a HTTP
360 360 proxy.
361 361 host;;
362 362 Host name and (optional) port of the proxy server, for example
363 363 "myproxy:8000".
364 364 no;;
365 365 Optional. Comma-separated list of host names that should bypass
366 366 the proxy.
367 367 passwd;;
368 368 Optional. Password to authenticate with at the proxy server.
369 369 user;;
370 370 Optional. User name to authenticate with at the proxy server.
371 371
372 372 smtp::
373 373 Configuration for extensions that need to send email messages.
374 374 host;;
375 375 Host name of mail server, e.g. "mail.example.com".
376 376 port;;
377 377 Optional. Port to connect to on mail server. Default: 25.
378 378 tls;;
379 379 Optional. Whether to connect to mail server using TLS. True or
380 380 False. Default: False.
381 381 username;;
382 382 Optional. User name to authenticate to SMTP server with.
383 383 If username is specified, password must also be specified.
384 384 Default: none.
385 385 password;;
386 386 Optional. Password to authenticate to SMTP server with.
387 387 If username is specified, password must also be specified.
388 388 Default: none.
389 389 local_hostname;;
390 390 Optional. It's the hostname that the sender can use to identify itself
391 391 to the MTA.
392 392
393 393 paths::
394 394 Assigns symbolic names to repositories. The left side is the
395 395 symbolic name, and the right gives the directory or URL that is the
396 396 location of the repository. Default paths can be declared by
397 397 setting the following entries.
398 398 default;;
399 399 Directory or URL to use when pulling if no source is specified.
400 400 Default is set to repository from which the current repository
401 401 was cloned.
402 402 default-push;;
403 403 Optional. Directory or URL to use when pushing if no destination
404 404 is specified.
405 405
406 profile::
407 Configuration of profiling options, for in-depth performance
408 analysis. Mostly useful to developers.
409 enable;;
410 Enable a particular profiling mode. Useful for profiling
411 server-side processes. "lsprof" enables modern profiling.
412 "hotshot" is deprecated, and produces less reliable results.
413 Default is no profiling.
414 output;;
415 The name of a file to write profiling data to. Default is stderr.
416
417 406 server::
418 407 Controls generic server settings.
419 408 uncompressed;;
420 409 Whether to allow clients to clone a repo using the uncompressed
421 410 streaming protocol. This transfers about 40% more data than a
422 411 regular clone, but uses less memory and CPU on both server and
423 412 client. Over a LAN (100Mbps or better) or a very fast WAN, an
424 413 uncompressed streaming clone is a lot faster (~10x) than a regular
425 414 clone. Over most WAN connections (anything slower than about
426 415 6Mbps), uncompressed streaming is slower, because of the extra
427 416 data transfer overhead. Default is False.
428 417
429 418 trusted::
430 419 For security reasons, Mercurial will not use the settings in
431 420 the .hg/hgrc file from a repository if it doesn't belong to a
432 421 trusted user or to a trusted group. The main exception is the
433 422 web interface, which automatically uses some safe settings, since
434 423 it's common to serve repositories from different users.
435 424
436 425 This section specifies what users and groups are trusted. The
437 426 current user is always trusted. To trust everybody, list a user
438 427 or a group with name "*".
439 428
440 429 users;;
441 430 Comma-separated list of trusted users.
442 431 groups;;
443 432 Comma-separated list of trusted groups.
444 433
445 434 ui::
446 435 User interface controls.
447 436 debug;;
448 437 Print debugging information. True or False. Default is False.
449 438 editor;;
450 439 The editor to use during a commit. Default is $EDITOR or "vi".
451 440 fallbackencoding;;
452 441 Encoding to try if it's not possible to decode the changelog using
453 442 UTF-8. Default is ISO-8859-1.
454 443 ignore;;
455 444 A file to read per-user ignore patterns from. This file should be in
456 445 the same format as a repository-wide .hgignore file. This option
457 446 supports hook syntax, so if you want to specify multiple ignore
458 447 files, you can do so by setting something like
459 448 "ignore.other = ~/.hgignore2". For details of the ignore file
460 449 format, see the hgignore(5) man page.
461 450 interactive;;
462 451 Allow to prompt the user. True or False. Default is True.
463 452 logtemplate;;
464 453 Template string for commands that print changesets.
465 454 merge;;
466 455 The conflict resolution program to use during a manual merge.
467 456 Default is "hgmerge".
468 457 patch;;
469 458 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
470 459 unset.
471 460 quiet;;
472 461 Reduce the amount of output printed. True or False. Default is False.
473 462 remotecmd;;
474 463 remote command to use for clone/push/pull operations. Default is 'hg'.
475 464 report_untrusted;;
476 465 Warn if a .hg/hgrc file is ignored due to not being owned by a
477 466 trusted user or group. True or False. Default is True.
478 467 slash;;
479 468 Display paths using a slash ("/") as the path separator. This only
480 469 makes a difference on systems where the default path separator is not
481 470 the slash character (e.g. Windows uses the backslash character ("\")).
482 471 Default is False.
483 472 ssh;;
484 473 command to use for SSH connections. Default is 'ssh'.
485 474 strict;;
486 475 Require exact command names, instead of allowing unambiguous
487 476 abbreviations. True or False. Default is False.
488 477 style;;
489 478 Name of style to use for command output.
490 479 timeout;;
491 480 The timeout used when a lock is held (in seconds), a negative value
492 481 means no timeout. Default is 600.
493 482 username;;
494 483 The committer of a changeset created when running "commit".
495 484 Typically a person's name and email address, e.g. "Fred Widget
496 485 <fred@example.com>". Default is $EMAIL or username@hostname.
497 486 If the username in hgrc is empty, it has to be specified manually or
498 487 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
499 488 in the system hgrc).
500 489 verbose;;
501 490 Increase the amount of output printed. True or False. Default is False.
502 491
503 492
504 493 web::
505 494 Web interface configuration.
506 495 accesslog;;
507 496 Where to output the access log. Default is stdout.
508 497 address;;
509 498 Interface address to bind to. Default is all.
510 499 allow_archive;;
511 500 List of archive format (bz2, gz, zip) allowed for downloading.
512 501 Default is empty.
513 502 allowbz2;;
514 503 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
515 504 Default is false.
516 505 allowgz;;
517 506 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
518 507 Default is false.
519 508 allowpull;;
520 509 Whether to allow pulling from the repository. Default is true.
521 510 allow_push;;
522 511 Whether to allow pushing to the repository. If empty or not set,
523 512 push is not allowed. If the special value "*", any remote user
524 513 can push, including unauthenticated users. Otherwise, the remote
525 514 user must have been authenticated, and the authenticated user name
526 515 must be present in this list (separated by whitespace or ",").
527 516 The contents of the allow_push list are examined after the
528 517 deny_push list.
529 518 allowzip;;
530 519 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
531 520 Default is false. This feature creates temporary files.
532 521 baseurl;;
533 522 Base URL to use when publishing URLs in other locations, so
534 523 third-party tools like email notification hooks can construct URLs.
535 524 Example: "http://hgserver/repos/"
536 525 contact;;
537 526 Name or email address of the person in charge of the repository.
538 527 Defaults to ui.username or $EMAIL or "unknown" if unset or empty.
539 528 deny_push;;
540 529 Whether to deny pushing to the repository. If empty or not set,
541 530 push is not denied. If the special value "*", all remote users
542 531 are denied push. Otherwise, unauthenticated users are all denied,
543 532 and any authenticated user name present in this list (separated by
544 533 whitespace or ",") is also denied. The contents of the deny_push
545 534 list are examined before the allow_push list.
546 535 description;;
547 536 Textual description of the repository's purpose or contents.
548 537 Default is "unknown".
549 538 encoding;;
550 539 Character encoding name.
551 540 Example: "UTF-8"
552 541 errorlog;;
553 542 Where to output the error log. Default is stderr.
554 543 hidden;;
555 544 Whether to hide the repository in the hgwebdir index. Default is false.
556 545 ipv6;;
557 546 Whether to use IPv6. Default is false.
558 547 name;;
559 548 Repository name to use in the web interface. Default is current
560 549 working directory.
561 550 maxchanges;;
562 551 Maximum number of changes to list on the changelog. Default is 10.
563 552 maxfiles;;
564 553 Maximum number of files to list per changeset. Default is 10.
565 554 port;;
566 555 Port to listen on. Default is 8000.
567 556 prefix;;
568 557 Prefix path to serve from. Default is '' (server root).
569 558 push_ssl;;
570 559 Whether to require that inbound pushes be transported over SSL to
571 560 prevent password sniffing. Default is true.
572 561 staticurl;;
573 562 Base URL to use for static files. If unset, static files (e.g.
574 563 the hgicon.png favicon) will be served by the CGI script itself.
575 564 Use this setting to serve them directly with the HTTP server.
576 565 Example: "http://hgserver/static/"
577 566 stripes;;
578 567 How many lines a "zebra stripe" should span in multiline output.
579 568 Default is 1; set to 0 to disable.
580 569 style;;
581 570 Which template map style to use.
582 571 templates;;
583 572 Where to find the HTML templates. Default is install path.
584 573
585 574
586 575 AUTHOR
587 576 ------
588 577 Bryan O'Sullivan <bos@serpentine.com>.
589 578
590 579 Mercurial was written by Matt Mackall <mpm@selenic.com>.
591 580
592 581 SEE ALSO
593 582 --------
594 583 hg(1), hgignore(5)
595 584
596 585 COPYING
597 586 -------
598 587 This manual page is copyright 2005 Bryan O'Sullivan.
599 588 Mercurial is copyright 2005-2007 Matt Mackall.
600 589 Free use of this software is granted under the terms of the GNU General
601 590 Public License (GPL).
@@ -1,30 +1,28 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # An example CGI script to use hgweb, edit as necessary
4 4
5 5 # adjust python path if not a system-wide install:
6 6 #import sys
7 7 #sys.path.insert(0, "/path/to/python/lib")
8 8
9 9 # enable importing on demand to reduce startup time
10 10 from mercurial import demandimport; demandimport.enable()
11 11
12 12 # send python tracebacks to the browser if an error occurs:
13 13 import cgitb
14 14 cgitb.enable()
15 15
16 16 # If you'd like to serve pages with UTF-8 instead of your default
17 17 # locale charset, you can do so by uncommenting the following lines.
18 18 # Note that this will cause your .hgrc files to be interpreted in
19 19 # UTF-8 and all your repo files to be displayed using UTF-8.
20 20 #
21 21 #import os
22 22 #os.environ["HGENCODING"] = "UTF-8"
23 23
24 24 from mercurial.hgweb.hgweb_mod import hgweb
25 from mercurial import dispatch, ui
26 25 import mercurial.hgweb.wsgicgi as wsgicgi
27 26
28 u = ui.ui(report_untrusted=False, interactive=False)
29 dispatch.profiled(u, lambda: wsgicgi.launch(hgweb("/path/to/repo",
30 "repository name", u)))
27 application = hgweb("/path/to/repo", "repository name")
28 wsgicgi.launch(application)
@@ -1,48 +1,47 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # An example CGI script to export multiple hgweb repos, edit as necessary
4 4
5 5 # adjust python path if not a system-wide install:
6 6 #import sys
7 7 #sys.path.insert(0, "/path/to/python/lib")
8 8
9 9 # enable importing on demand to reduce startup time
10 10 from mercurial import demandimport; demandimport.enable()
11 11
12 12 # send python tracebacks to the browser if an error occurs:
13 13 import cgitb
14 14 cgitb.enable()
15 15
16 16 # If you'd like to serve pages with UTF-8 instead of your default
17 17 # locale charset, you can do so by uncommenting the following lines.
18 18 # Note that this will cause your .hgrc files to be interpreted in
19 19 # UTF-8 and all your repo files to be displayed using UTF-8.
20 20 #
21 21 #import os
22 22 #os.environ["HGENCODING"] = "UTF-8"
23 23
24 24 from mercurial.hgweb.hgwebdir_mod import hgwebdir
25 from mercurial import dispatch, ui
26 25 import mercurial.hgweb.wsgicgi as wsgicgi
27 26
28 27 # The config file looks like this. You can have paths to individual
29 28 # repos, collections of repos in a directory tree, or both.
30 29 #
31 30 # [paths]
32 31 # virtual/path = /real/path
33 32 # virtual/path = /real/path
34 33 #
35 34 # [collections]
36 35 # /prefix/to/strip/off = /root/of/tree/full/of/repos
37 36 #
38 37 # collections example: say directory tree /foo contains repos /foo/bar,
39 38 # /foo/quux/baz. Give this config section:
40 39 # [collections]
41 40 # /foo = /foo
42 41 # Then repos will list as bar and quux/baz.
43 42 #
44 43 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
45 44 # or use a dictionary with entries like 'virtual/path': '/real/path'
46 45
47 u = ui.ui(report_untrusted=False, interactive=False)
48 dispatch.profiled(u, lambda: wsgicgi.launch(hgwebdir('hgweb.config', u)))
46 application = hgwebdir('hgweb.config')
47 wsgicgi.launch(application)
@@ -1,423 +1,413 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex, time
11 11 import util, commands, hg, lock, fancyopts, revlog, version, extensions, hook
12 12 import cmdutil
13 13 import ui as _ui
14 14
15 15 class ParseError(Exception):
16 16 """Exception raised on errors in parsing the command line."""
17 17
18 18 def run():
19 19 "run the command in sys.argv"
20 20 sys.exit(dispatch(sys.argv[1:]))
21 21
22 22 def dispatch(args):
23 23 "run the command specified in args"
24 24 try:
25 25 u = _ui.ui(traceback='--traceback' in args)
26 26 except util.Abort, inst:
27 27 sys.stderr.write(_("abort: %s\n") % inst)
28 28 return -1
29 29 return _runcatch(u, args)
30 30
31 31 def _runcatch(ui, args):
32 32 def catchterm(*args):
33 33 raise util.SignalInterrupt
34 34
35 35 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
36 36 num = getattr(signal, name, None)
37 37 if num: signal.signal(num, catchterm)
38 38
39 39 try:
40 40 try:
41 41 # enter the debugger before command execution
42 42 if '--debugger' in args:
43 43 pdb.set_trace()
44 44 try:
45 45 return _dispatch(ui, args)
46 46 finally:
47 47 ui.flush()
48 48 except:
49 49 # enter the debugger when we hit an exception
50 50 if '--debugger' in args:
51 51 pdb.post_mortem(sys.exc_info()[2])
52 52 ui.print_exc()
53 53 raise
54 54
55 55 except ParseError, inst:
56 56 if inst.args[0]:
57 57 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
58 58 commands.help_(ui, inst.args[0])
59 59 else:
60 60 ui.warn(_("hg: %s\n") % inst.args[1])
61 61 commands.help_(ui, 'shortlist')
62 62 except cmdutil.AmbiguousCommand, inst:
63 63 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
64 64 (inst.args[0], " ".join(inst.args[1])))
65 65 except cmdutil.UnknownCommand, inst:
66 66 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
67 67 commands.help_(ui, 'shortlist')
68 68 except hg.RepoError, inst:
69 69 ui.warn(_("abort: %s!\n") % inst)
70 70 except lock.LockHeld, inst:
71 71 if inst.errno == errno.ETIMEDOUT:
72 72 reason = _('timed out waiting for lock held by %s') % inst.locker
73 73 else:
74 74 reason = _('lock held by %s') % inst.locker
75 75 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
76 76 except lock.LockUnavailable, inst:
77 77 ui.warn(_("abort: could not lock %s: %s\n") %
78 78 (inst.desc or inst.filename, inst.strerror))
79 79 except revlog.RevlogError, inst:
80 80 ui.warn(_("abort: %s!\n") % inst)
81 81 except util.SignalInterrupt:
82 82 ui.warn(_("killed!\n"))
83 83 except KeyboardInterrupt:
84 84 try:
85 85 ui.warn(_("interrupted!\n"))
86 86 except IOError, inst:
87 87 if inst.errno == errno.EPIPE:
88 88 if ui.debugflag:
89 89 ui.warn(_("\nbroken pipe\n"))
90 90 else:
91 91 raise
92 92 except socket.error, inst:
93 93 ui.warn(_("abort: %s\n") % inst[1])
94 94 except IOError, inst:
95 95 if hasattr(inst, "code"):
96 96 ui.warn(_("abort: %s\n") % inst)
97 97 elif hasattr(inst, "reason"):
98 98 try: # usually it is in the form (errno, strerror)
99 99 reason = inst.reason.args[1]
100 100 except: # it might be anything, for example a string
101 101 reason = inst.reason
102 102 ui.warn(_("abort: error: %s\n") % reason)
103 103 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
104 104 if ui.debugflag:
105 105 ui.warn(_("broken pipe\n"))
106 106 elif getattr(inst, "strerror", None):
107 107 if getattr(inst, "filename", None):
108 108 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
109 109 else:
110 110 ui.warn(_("abort: %s\n") % inst.strerror)
111 111 else:
112 112 raise
113 113 except OSError, inst:
114 114 if getattr(inst, "filename", None):
115 115 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
116 116 else:
117 117 ui.warn(_("abort: %s\n") % inst.strerror)
118 118 except util.UnexpectedOutput, inst:
119 119 ui.warn(_("abort: %s") % inst[0])
120 120 if not isinstance(inst[1], basestring):
121 121 ui.warn(" %r\n" % (inst[1],))
122 122 elif not inst[1]:
123 123 ui.warn(_(" empty string\n"))
124 124 else:
125 125 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
126 126 except ImportError, inst:
127 127 m = str(inst).split()[-1]
128 128 ui.warn(_("abort: could not import module %s!\n") % m)
129 129 if m in "mpatch bdiff".split():
130 130 ui.warn(_("(did you forget to compile extensions?)\n"))
131 131 elif m in "zlib".split():
132 132 ui.warn(_("(is your Python install correct?)\n"))
133 133
134 134 except util.Abort, inst:
135 135 ui.warn(_("abort: %s\n") % inst)
136 136 except MemoryError:
137 137 ui.warn(_("abort: out of memory\n"))
138 138 except SystemExit, inst:
139 139 # Commands shouldn't sys.exit directly, but give a return code.
140 140 # Just in case catch this and and pass exit code to caller.
141 141 return inst.code
142 142 except:
143 143 ui.warn(_("** unknown exception encountered, details follow\n"))
144 144 ui.warn(_("** report bug details to "
145 145 "http://www.selenic.com/mercurial/bts\n"))
146 146 ui.warn(_("** or mercurial@selenic.com\n"))
147 147 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
148 148 % version.get_version())
149 149 raise
150 150
151 151 return -1
152 152
153 153 def _findrepo():
154 154 p = os.getcwd()
155 155 while not os.path.isdir(os.path.join(p, ".hg")):
156 156 oldp, p = p, os.path.dirname(p)
157 157 if p == oldp:
158 158 return None
159 159
160 160 return p
161 161
162 162 def _parse(ui, args):
163 163 options = {}
164 164 cmdoptions = {}
165 165
166 166 try:
167 167 args = fancyopts.fancyopts(args, commands.globalopts, options)
168 168 except fancyopts.getopt.GetoptError, inst:
169 169 raise ParseError(None, inst)
170 170
171 171 if args:
172 172 cmd, args = args[0], args[1:]
173 173 aliases, i = cmdutil.findcmd(ui, cmd, commands.table)
174 174 cmd = aliases[0]
175 175 defaults = ui.config("defaults", cmd)
176 176 if defaults:
177 177 args = shlex.split(defaults) + args
178 178 c = list(i[1])
179 179 else:
180 180 cmd = None
181 181 c = []
182 182
183 183 # combine global options into local
184 184 for o in commands.globalopts:
185 185 c.append((o[0], o[1], options[o[1]], o[3]))
186 186
187 187 try:
188 188 args = fancyopts.fancyopts(args, c, cmdoptions)
189 189 except fancyopts.getopt.GetoptError, inst:
190 190 raise ParseError(cmd, inst)
191 191
192 192 # separate global options back out
193 193 for o in commands.globalopts:
194 194 n = o[1]
195 195 options[n] = cmdoptions[n]
196 196 del cmdoptions[n]
197 197
198 198 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
199 199
200 200 def _parseconfig(config):
201 201 """parse the --config options from the command line"""
202 202 parsed = []
203 203 for cfg in config:
204 204 try:
205 205 name, value = cfg.split('=', 1)
206 206 section, name = name.split('.', 1)
207 207 if not section or not name:
208 208 raise IndexError
209 209 parsed.append((section, name, value))
210 210 except (IndexError, ValueError):
211 211 raise util.Abort(_('malformed --config option: %s') % cfg)
212 212 return parsed
213 213
214 214 def _earlygetopt(aliases, args):
215 215 """Return list of values for an option (or aliases).
216 216
217 217 The values are listed in the order they appear in args.
218 218 The options and values are removed from args.
219 219 """
220 220 try:
221 221 argcount = args.index("--")
222 222 except ValueError:
223 223 argcount = len(args)
224 224 shortopts = [opt for opt in aliases if len(opt) == 2]
225 225 values = []
226 226 pos = 0
227 227 while pos < argcount:
228 228 if args[pos] in aliases:
229 229 if pos + 1 >= argcount:
230 230 # ignore and let getopt report an error if there is no value
231 231 break
232 232 del args[pos]
233 233 values.append(args.pop(pos))
234 234 argcount -= 2
235 235 elif args[pos][:2] in shortopts:
236 236 # short option can have no following space, e.g. hg log -Rfoo
237 237 values.append(args.pop(pos)[2:])
238 238 argcount -= 1
239 239 else:
240 240 pos += 1
241 241 return values
242 242
243 243 _loaded = {}
244 244 def _dispatch(ui, args):
245 245 # read --config before doing anything else
246 246 # (e.g. to change trust settings for reading .hg/hgrc)
247 247 config = _earlygetopt(['--config'], args)
248 248 if config:
249 249 ui.updateopts(config=_parseconfig(config))
250 250
251 251 # check for cwd
252 252 cwd = _earlygetopt(['--cwd'], args)
253 253 if cwd:
254 254 os.chdir(cwd[-1])
255 255
256 256 # read the local repository .hgrc into a local ui object
257 257 path = _findrepo() or ""
258 258 if not path:
259 259 lui = ui
260 260 if path:
261 261 try:
262 262 lui = _ui.ui(parentui=ui)
263 263 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
264 264 except IOError:
265 265 pass
266 266
267 267 # now we can expand paths, even ones in .hg/hgrc
268 268 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
269 269 if rpath:
270 270 path = lui.expandpath(rpath[-1])
271 271 lui = _ui.ui(parentui=ui)
272 272 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
273 273
274 274 extensions.loadall(lui)
275 275 for name, module in extensions.extensions():
276 276 if name in _loaded:
277 277 continue
278 278
279 279 # setup extensions
280 280 # TODO this should be generalized to scheme, where extensions can
281 281 # redepend on other extensions. then we should toposort them, and
282 282 # do initialization in correct order
283 283 extsetup = getattr(module, 'extsetup', None)
284 284 if extsetup:
285 285 extsetup()
286 286
287 287 cmdtable = getattr(module, 'cmdtable', {})
288 288 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
289 289 if overrides:
290 290 ui.warn(_("extension '%s' overrides commands: %s\n")
291 291 % (name, " ".join(overrides)))
292 292 commands.table.update(cmdtable)
293 293 _loaded[name] = 1
294 294 # check for fallback encoding
295 295 fallback = lui.config('ui', 'fallbackencoding')
296 296 if fallback:
297 297 util._fallbackencoding = fallback
298 298
299 299 fullargs = args
300 300 cmd, func, args, options, cmdoptions = _parse(lui, args)
301 301
302 302 if options["config"]:
303 303 raise util.Abort(_("Option --config may not be abbreviated!"))
304 304 if options["cwd"]:
305 305 raise util.Abort(_("Option --cwd may not be abbreviated!"))
306 306 if options["repository"]:
307 307 raise util.Abort(_(
308 308 "Option -R has to be separated from other options (i.e. not -qR) "
309 309 "and --repository may only be abbreviated as --repo!"))
310 310
311 311 if options["encoding"]:
312 312 util._encoding = options["encoding"]
313 313 if options["encodingmode"]:
314 314 util._encodingmode = options["encodingmode"]
315 315 if options["time"]:
316 316 def get_times():
317 317 t = os.times()
318 318 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
319 319 t = (t[0], t[1], t[2], t[3], time.clock())
320 320 return t
321 321 s = get_times()
322 322 def print_time():
323 323 t = get_times()
324 324 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
325 325 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
326 326 atexit.register(print_time)
327 327
328 328 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
329 329 not options["noninteractive"], options["traceback"])
330 330
331 331 if options['help']:
332 332 return commands.help_(ui, cmd, options['version'])
333 333 elif options['version']:
334 334 return commands.version_(ui)
335 335 elif not cmd:
336 336 return commands.help_(ui, 'shortlist')
337 337
338 338 repo = None
339 339 if cmd not in commands.norepo.split():
340 340 try:
341 341 repo = hg.repository(ui, path=path)
342 342 ui = repo.ui
343 343 ui.setconfig("bundle", "mainreporoot", repo.root)
344 344 if not repo.local():
345 345 raise util.Abort(_("repository '%s' is not local") % path)
346 346 except hg.RepoError:
347 347 if cmd not in commands.optionalrepo.split():
348 348 if not path:
349 349 raise hg.RepoError(_("There is no Mercurial repository here"
350 350 " (.hg not found)"))
351 351 raise
352 352 d = lambda: func(ui, repo, *args, **cmdoptions)
353 353 else:
354 354 d = lambda: func(ui, *args, **cmdoptions)
355 355
356 356 # run pre-hook, and abort if it fails
357 357 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
358 358 if ret:
359 359 return ret
360 360 ret = _runcommand(ui, options, cmd, d)
361 361 # run post-hook, passing command result
362 362 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
363 363 result = ret)
364 364 return ret
365 365
366 366 def _runcommand(ui, options, cmd, cmdfunc):
367 367 def checkargs():
368 368 try:
369 369 return cmdfunc()
370 370 except TypeError, inst:
371 371 # was this an argument error?
372 372 tb = traceback.extract_tb(sys.exc_info()[2])
373 373 if len(tb) != 2: # no
374 374 raise
375 375 raise ParseError(cmd, _("invalid arguments"))
376 return profiled(ui, checkargs, options)
377 376
378 def profiled(ui, func, options={}):
379 def profile_fp():
380 outfile = ui.config('profile', 'output', untrusted=True)
381 if outfile:
382 return open(outfile, 'w')
383 else:
384 return sys.stderr
385
386 if options.get('profile') or ui.config('profile', 'enable') == 'hotshot':
377 if options['profile']:
387 378 import hotshot, hotshot.stats
388 379 prof = hotshot.Profile("hg.prof")
389 380 try:
390 381 try:
391 382 return prof.runcall(checkargs)
392 383 except:
393 384 try:
394 385 ui.warn(_('exception raised - generating '
395 386 'profile anyway\n'))
396 387 except:
397 388 pass
398 389 raise
399 390 finally:
400 391 prof.close()
401 392 stats = hotshot.stats.load("hg.prof")
402 stats.stream = profile_fp()
403 393 stats.strip_dirs()
404 394 stats.sort_stats('time', 'calls')
405 395 stats.print_stats(40)
406 elif options.get('lsprof') or ui.config('profile', 'enable') == 'lsprof':
396 elif options['lsprof']:
407 397 try:
408 398 from mercurial import lsprof
409 399 except ImportError:
410 400 raise util.Abort(_(
411 401 'lsprof not available - install from '
412 402 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
413 403 p = lsprof.Profiler()
414 404 p.enable(subcalls=True)
415 405 try:
416 return func()
406 return checkargs()
417 407 finally:
418 408 p.disable()
419 409 stats = lsprof.Stats(p.getstats())
420 410 stats.sort()
421 stats.pprint(top=10, file=profile_fp(), climit=5)
411 stats.pprint(top=10, file=sys.stderr, climit=5)
422 412 else:
423 return func()
413 return checkargs()
@@ -1,911 +1,910 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, mimetypes, re
10 10 from mercurial.node import *
11 11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 12 from mercurial import revlog, templater, templatefilters
13 13 from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
14 14 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from request import wsgirequest
16 16 import webcommands, protocol
17 17
18 18 shortcuts = {
19 19 'cl': [('cmd', ['changelog']), ('rev', None)],
20 20 'sl': [('cmd', ['shortlog']), ('rev', None)],
21 21 'cs': [('cmd', ['changeset']), ('node', None)],
22 22 'f': [('cmd', ['file']), ('filenode', None)],
23 23 'fl': [('cmd', ['filelog']), ('filenode', None)],
24 24 'fd': [('cmd', ['filediff']), ('node', None)],
25 25 'fa': [('cmd', ['annotate']), ('filenode', None)],
26 26 'mf': [('cmd', ['manifest']), ('manifest', None)],
27 27 'ca': [('cmd', ['archive']), ('node', None)],
28 28 'tags': [('cmd', ['tags'])],
29 29 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
30 30 'static': [('cmd', ['static']), ('file', None)]
31 31 }
32 32
33 33 def _up(p):
34 34 if p[0] != "/":
35 35 p = "/" + p
36 36 if p[-1] == "/":
37 37 p = p[:-1]
38 38 up = os.path.dirname(p)
39 39 if up == "/":
40 40 return "/"
41 41 return up + "/"
42 42
43 43 def revnavgen(pos, pagelen, limit, nodefunc):
44 44 def seq(factor, limit=None):
45 45 if limit:
46 46 yield limit
47 47 if limit >= 20 and limit <= 40:
48 48 yield 50
49 49 else:
50 50 yield 1 * factor
51 51 yield 3 * factor
52 52 for f in seq(factor * 10):
53 53 yield f
54 54
55 55 def nav(**map):
56 56 l = []
57 57 last = 0
58 58 for f in seq(1, pagelen):
59 59 if f < pagelen or f <= last:
60 60 continue
61 61 if f > limit:
62 62 break
63 63 last = f
64 64 if pos + f < limit:
65 65 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
66 66 if pos - f >= 0:
67 67 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
68 68
69 69 try:
70 70 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
71 71
72 72 for label, node in l:
73 73 yield {"label": label, "node": node}
74 74
75 75 yield {"label": "tip", "node": "tip"}
76 76 except hg.RepoError:
77 77 pass
78 78
79 79 return nav
80 80
81 81 class hgweb(object):
82 def __init__(self, repo, name=None, parentui=None):
82 def __init__(self, repo, name=None):
83 83 if isinstance(repo, str):
84 parentui = (parentui or
85 ui.ui(report_untrusted=False, interactive=False))
84 parentui = ui.ui(report_untrusted=False, interactive=False)
86 85 self.repo = hg.repository(parentui, repo)
87 86 else:
88 87 self.repo = repo
89 88
90 89 hook.redirect(True)
91 90 self.mtime = -1
92 91 self.reponame = name
93 92 self.archives = 'zip', 'gz', 'bz2'
94 93 self.stripecount = 1
95 94 # a repo owner may set web.templates in .hg/hgrc to get any file
96 95 # readable by the user running the CGI script
97 96 self.templatepath = self.config("web", "templates",
98 97 templater.templatepath(),
99 98 untrusted=False)
100 99
101 100 # The CGI scripts are often run by a user different from the repo owner.
102 101 # Trust the settings from the .hg/hgrc files by default.
103 102 def config(self, section, name, default=None, untrusted=True):
104 103 return self.repo.ui.config(section, name, default,
105 104 untrusted=untrusted)
106 105
107 106 def configbool(self, section, name, default=False, untrusted=True):
108 107 return self.repo.ui.configbool(section, name, default,
109 108 untrusted=untrusted)
110 109
111 110 def configlist(self, section, name, default=None, untrusted=True):
112 111 return self.repo.ui.configlist(section, name, default,
113 112 untrusted=untrusted)
114 113
115 114 def refresh(self):
116 115 mtime = get_mtime(self.repo.root)
117 116 if mtime != self.mtime:
118 117 self.mtime = mtime
119 118 self.repo = hg.repository(self.repo.ui, self.repo.root)
120 119 self.maxchanges = int(self.config("web", "maxchanges", 10))
121 120 self.stripecount = int(self.config("web", "stripes", 1))
122 121 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
123 122 self.maxfiles = int(self.config("web", "maxfiles", 10))
124 123 self.allowpull = self.configbool("web", "allowpull", True)
125 124 self.encoding = self.config("web", "encoding", util._encoding)
126 125
127 126 def run(self):
128 127 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
129 128 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
130 129 import mercurial.hgweb.wsgicgi as wsgicgi
131 130 wsgicgi.launch(self)
132 131
133 132 def __call__(self, env, respond):
134 133 req = wsgirequest(env, respond)
135 134 self.run_wsgi(req)
136 135 return req
137 136
138 137 def run_wsgi(self, req):
139 138
140 139 self.refresh()
141 140
142 141 # expand form shortcuts
143 142
144 143 for k in shortcuts.iterkeys():
145 144 if k in req.form:
146 145 for name, value in shortcuts[k]:
147 146 if value is None:
148 147 value = req.form[k]
149 148 req.form[name] = value
150 149 del req.form[k]
151 150
152 151 # work with CGI variables to create coherent structure
153 152 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
154 153
155 154 req.url = req.env['SCRIPT_NAME']
156 155 if not req.url.endswith('/'):
157 156 req.url += '/'
158 157 if 'REPO_NAME' in req.env:
159 158 req.url += req.env['REPO_NAME'] + '/'
160 159
161 160 if req.env.get('PATH_INFO'):
162 161 parts = req.env.get('PATH_INFO').strip('/').split('/')
163 162 repo_parts = req.env.get('REPO_NAME', '').split('/')
164 163 if parts[:len(repo_parts)] == repo_parts:
165 164 parts = parts[len(repo_parts):]
166 165 query = '/'.join(parts)
167 166 else:
168 167 query = req.env['QUERY_STRING'].split('&', 1)[0]
169 168 query = query.split(';', 1)[0]
170 169
171 170 # translate user-visible url structure to internal structure
172 171
173 172 args = query.split('/', 2)
174 173 if 'cmd' not in req.form and args and args[0]:
175 174
176 175 cmd = args.pop(0)
177 176 style = cmd.rfind('-')
178 177 if style != -1:
179 178 req.form['style'] = [cmd[:style]]
180 179 cmd = cmd[style+1:]
181 180
182 181 # avoid accepting e.g. style parameter as command
183 182 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
184 183 req.form['cmd'] = [cmd]
185 184
186 185 if args and args[0]:
187 186 node = args.pop(0)
188 187 req.form['node'] = [node]
189 188 if args:
190 189 req.form['file'] = args
191 190
192 191 if cmd == 'static':
193 192 req.form['file'] = req.form['node']
194 193 elif cmd == 'archive':
195 194 fn = req.form['node'][0]
196 195 for type_, spec in self.archive_specs.iteritems():
197 196 ext = spec[2]
198 197 if fn.endswith(ext):
199 198 req.form['node'] = [fn[:-len(ext)]]
200 199 req.form['type'] = [type_]
201 200
202 201 # actually process the request
203 202
204 203 try:
205 204
206 205 cmd = req.form.get('cmd', [''])[0]
207 206 if cmd in protocol.__all__:
208 207 method = getattr(protocol, cmd)
209 208 method(self, req)
210 209 else:
211 210 tmpl = self.templater(req)
212 211 ctype = tmpl('mimetype', encoding=self.encoding)
213 212 ctype = templater.stringify(ctype)
214 213
215 214 if cmd == '':
216 215 req.form['cmd'] = [tmpl.cache['default']]
217 216 cmd = req.form['cmd'][0]
218 217
219 218 if cmd not in webcommands.__all__:
220 219 msg = 'No such method: %s' % cmd
221 220 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
222 221 elif cmd == 'file' and 'raw' in req.form.get('style', []):
223 222 self.ctype = ctype
224 223 content = webcommands.rawfile(self, req, tmpl)
225 224 else:
226 225 content = getattr(webcommands, cmd)(self, req, tmpl)
227 226 req.respond(HTTP_OK, ctype)
228 227
229 228 req.write(content)
230 229 del tmpl
231 230
232 231 except revlog.LookupError, err:
233 232 req.respond(HTTP_NOT_FOUND, ctype)
234 233 req.write(tmpl('error', error='revision not found: %s' % err.name))
235 234 except (hg.RepoError, revlog.RevlogError), inst:
236 235 req.respond(HTTP_SERVER_ERROR, ctype)
237 236 req.write(tmpl('error', error=str(inst)))
238 237 except ErrorResponse, inst:
239 238 req.respond(inst.code, ctype)
240 239 req.write(tmpl('error', error=inst.message))
241 240
242 241 def templater(self, req):
243 242
244 243 # determine scheme, port and server name
245 244 # this is needed to create absolute urls
246 245
247 246 proto = req.env.get('wsgi.url_scheme')
248 247 if proto == 'https':
249 248 proto = 'https'
250 249 default_port = "443"
251 250 else:
252 251 proto = 'http'
253 252 default_port = "80"
254 253
255 254 port = req.env["SERVER_PORT"]
256 255 port = port != default_port and (":" + port) or ""
257 256 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
258 257 staticurl = self.config("web", "staticurl") or req.url + 'static/'
259 258 if not staticurl.endswith('/'):
260 259 staticurl += '/'
261 260
262 261 # some functions for the templater
263 262
264 263 def header(**map):
265 264 yield tmpl('header', encoding=self.encoding, **map)
266 265
267 266 def footer(**map):
268 267 yield tmpl("footer", **map)
269 268
270 269 def motd(**map):
271 270 yield self.config("web", "motd", "")
272 271
273 272 def sessionvars(**map):
274 273 fields = []
275 274 if 'style' in req.form:
276 275 style = req.form['style'][0]
277 276 if style != self.config('web', 'style', ''):
278 277 fields.append(('style', style))
279 278
280 279 separator = req.url[-1] == '?' and ';' or '?'
281 280 for name, value in fields:
282 281 yield dict(name=name, value=value, separator=separator)
283 282 separator = ';'
284 283
285 284 # figure out which style to use
286 285
287 286 style = self.config("web", "style", "")
288 287 if 'style' in req.form:
289 288 style = req.form['style'][0]
290 289 mapfile = style_map(self.templatepath, style)
291 290
292 291 if not self.reponame:
293 292 self.reponame = (self.config("web", "name")
294 293 or req.env.get('REPO_NAME')
295 294 or req.url.strip('/') or self.repo.root)
296 295
297 296 # create the templater
298 297
299 298 tmpl = templater.templater(mapfile, templatefilters.filters,
300 299 defaults={"url": req.url,
301 300 "staticurl": staticurl,
302 301 "urlbase": urlbase,
303 302 "repo": self.reponame,
304 303 "header": header,
305 304 "footer": footer,
306 305 "motd": motd,
307 306 "sessionvars": sessionvars
308 307 })
309 308 return tmpl
310 309
311 310 def archivelist(self, nodeid):
312 311 allowed = self.configlist("web", "allow_archive")
313 312 for i, spec in self.archive_specs.iteritems():
314 313 if i in allowed or self.configbool("web", "allow" + i):
315 314 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
316 315
317 316 def listfilediffs(self, tmpl, files, changeset):
318 317 for f in files[:self.maxfiles]:
319 318 yield tmpl("filedifflink", node=hex(changeset), file=f)
320 319 if len(files) > self.maxfiles:
321 320 yield tmpl("fileellipses")
322 321
323 322 def siblings(self, siblings=[], hiderev=None, **args):
324 323 siblings = [s for s in siblings if s.node() != nullid]
325 324 if len(siblings) == 1 and siblings[0].rev() == hiderev:
326 325 return
327 326 for s in siblings:
328 327 d = {'node': hex(s.node()), 'rev': s.rev()}
329 328 if hasattr(s, 'path'):
330 329 d['file'] = s.path()
331 330 d.update(args)
332 331 yield d
333 332
334 333 def renamelink(self, fl, node):
335 334 r = fl.renamed(node)
336 335 if r:
337 336 return [dict(file=r[0], node=hex(r[1]))]
338 337 return []
339 338
340 339 def nodetagsdict(self, node):
341 340 return [{"name": i} for i in self.repo.nodetags(node)]
342 341
343 342 def nodebranchdict(self, ctx):
344 343 branches = []
345 344 branch = ctx.branch()
346 345 # If this is an empty repo, ctx.node() == nullid,
347 346 # ctx.branch() == 'default', but branchtags() is
348 347 # an empty dict. Using dict.get avoids a traceback.
349 348 if self.repo.branchtags().get(branch) == ctx.node():
350 349 branches.append({"name": branch})
351 350 return branches
352 351
353 352 def showtag(self, tmpl, t1, node=nullid, **args):
354 353 for t in self.repo.nodetags(node):
355 354 yield tmpl(t1, tag=t, **args)
356 355
357 356 def diff(self, tmpl, node1, node2, files):
358 357 def filterfiles(filters, files):
359 358 l = [x for x in files if x in filters]
360 359
361 360 for t in filters:
362 361 if t and t[-1] != os.sep:
363 362 t += os.sep
364 363 l += [x for x in files if x.startswith(t)]
365 364 return l
366 365
367 366 parity = paritygen(self.stripecount)
368 367 def diffblock(diff, f, fn):
369 368 yield tmpl("diffblock",
370 369 lines=prettyprintlines(diff),
371 370 parity=parity.next(),
372 371 file=f,
373 372 filenode=hex(fn or nullid))
374 373
375 374 def prettyprintlines(diff):
376 375 for l in diff.splitlines(1):
377 376 if l.startswith('+'):
378 377 yield tmpl("difflineplus", line=l)
379 378 elif l.startswith('-'):
380 379 yield tmpl("difflineminus", line=l)
381 380 elif l.startswith('@'):
382 381 yield tmpl("difflineat", line=l)
383 382 else:
384 383 yield tmpl("diffline", line=l)
385 384
386 385 r = self.repo
387 386 c1 = r.changectx(node1)
388 387 c2 = r.changectx(node2)
389 388 date1 = util.datestr(c1.date())
390 389 date2 = util.datestr(c2.date())
391 390
392 391 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
393 392 if files:
394 393 modified, added, removed = map(lambda x: filterfiles(files, x),
395 394 (modified, added, removed))
396 395
397 396 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
398 397 for f in modified:
399 398 to = c1.filectx(f).data()
400 399 tn = c2.filectx(f).data()
401 400 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
402 401 opts=diffopts), f, tn)
403 402 for f in added:
404 403 to = None
405 404 tn = c2.filectx(f).data()
406 405 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
407 406 opts=diffopts), f, tn)
408 407 for f in removed:
409 408 to = c1.filectx(f).data()
410 409 tn = None
411 410 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
412 411 opts=diffopts), f, tn)
413 412
414 413 def changelog(self, tmpl, ctx, shortlog=False):
415 414 def changelist(limit=0,**map):
416 415 cl = self.repo.changelog
417 416 l = [] # build a list in forward order for efficiency
418 417 for i in xrange(start, end):
419 418 ctx = self.repo.changectx(i)
420 419 n = ctx.node()
421 420
422 421 l.insert(0, {"parity": parity.next(),
423 422 "author": ctx.user(),
424 423 "parent": self.siblings(ctx.parents(), i - 1),
425 424 "child": self.siblings(ctx.children(), i + 1),
426 425 "changelogtag": self.showtag("changelogtag",n),
427 426 "desc": ctx.description(),
428 427 "date": ctx.date(),
429 428 "files": self.listfilediffs(tmpl, ctx.files(), n),
430 429 "rev": i,
431 430 "node": hex(n),
432 431 "tags": self.nodetagsdict(n),
433 432 "branches": self.nodebranchdict(ctx)})
434 433
435 434 if limit > 0:
436 435 l = l[:limit]
437 436
438 437 for e in l:
439 438 yield e
440 439
441 440 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
442 441 cl = self.repo.changelog
443 442 count = cl.count()
444 443 pos = ctx.rev()
445 444 start = max(0, pos - maxchanges + 1)
446 445 end = min(count, start + maxchanges)
447 446 pos = end - 1
448 447 parity = paritygen(self.stripecount, offset=start-end)
449 448
450 449 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
451 450
452 451 return tmpl(shortlog and 'shortlog' or 'changelog',
453 452 changenav=changenav,
454 453 node=hex(cl.tip()),
455 454 rev=pos, changesets=count,
456 455 entries=lambda **x: changelist(limit=0,**x),
457 456 latestentry=lambda **x: changelist(limit=1,**x),
458 457 archives=self.archivelist("tip"))
459 458
460 459 def search(self, tmpl, query):
461 460
462 461 def changelist(**map):
463 462 cl = self.repo.changelog
464 463 count = 0
465 464 qw = query.lower().split()
466 465
467 466 def revgen():
468 467 for i in xrange(cl.count() - 1, 0, -100):
469 468 l = []
470 469 for j in xrange(max(0, i - 100), i):
471 470 ctx = self.repo.changectx(j)
472 471 l.append(ctx)
473 472 l.reverse()
474 473 for e in l:
475 474 yield e
476 475
477 476 for ctx in revgen():
478 477 miss = 0
479 478 for q in qw:
480 479 if not (q in ctx.user().lower() or
481 480 q in ctx.description().lower() or
482 481 q in " ".join(ctx.files()).lower()):
483 482 miss = 1
484 483 break
485 484 if miss:
486 485 continue
487 486
488 487 count += 1
489 488 n = ctx.node()
490 489
491 490 yield tmpl('searchentry',
492 491 parity=parity.next(),
493 492 author=ctx.user(),
494 493 parent=self.siblings(ctx.parents()),
495 494 child=self.siblings(ctx.children()),
496 495 changelogtag=self.showtag("changelogtag",n),
497 496 desc=ctx.description(),
498 497 date=ctx.date(),
499 498 files=self.listfilediffs(tmpl, ctx.files(), n),
500 499 rev=ctx.rev(),
501 500 node=hex(n),
502 501 tags=self.nodetagsdict(n),
503 502 branches=self.nodebranchdict(ctx))
504 503
505 504 if count >= self.maxchanges:
506 505 break
507 506
508 507 cl = self.repo.changelog
509 508 parity = paritygen(self.stripecount)
510 509
511 510 return tmpl('search',
512 511 query=query,
513 512 node=hex(cl.tip()),
514 513 entries=changelist,
515 514 archives=self.archivelist("tip"))
516 515
517 516 def changeset(self, tmpl, ctx):
518 517 n = ctx.node()
519 518 parents = ctx.parents()
520 519 p1 = parents[0].node()
521 520
522 521 files = []
523 522 parity = paritygen(self.stripecount)
524 523 for f in ctx.files():
525 524 files.append(tmpl("filenodelink",
526 525 node=hex(n), file=f,
527 526 parity=parity.next()))
528 527
529 528 def diff(**map):
530 529 yield self.diff(tmpl, p1, n, None)
531 530
532 531 return tmpl('changeset',
533 532 diff=diff,
534 533 rev=ctx.rev(),
535 534 node=hex(n),
536 535 parent=self.siblings(parents),
537 536 child=self.siblings(ctx.children()),
538 537 changesettag=self.showtag("changesettag",n),
539 538 author=ctx.user(),
540 539 desc=ctx.description(),
541 540 date=ctx.date(),
542 541 files=files,
543 542 archives=self.archivelist(hex(n)),
544 543 tags=self.nodetagsdict(n),
545 544 branches=self.nodebranchdict(ctx))
546 545
547 546 def filelog(self, tmpl, fctx):
548 547 f = fctx.path()
549 548 fl = fctx.filelog()
550 549 count = fl.count()
551 550 pagelen = self.maxshortchanges
552 551 pos = fctx.filerev()
553 552 start = max(0, pos - pagelen + 1)
554 553 end = min(count, start + pagelen)
555 554 pos = end - 1
556 555 parity = paritygen(self.stripecount, offset=start-end)
557 556
558 557 def entries(limit=0, **map):
559 558 l = []
560 559
561 560 for i in xrange(start, end):
562 561 ctx = fctx.filectx(i)
563 562 n = fl.node(i)
564 563
565 564 l.insert(0, {"parity": parity.next(),
566 565 "filerev": i,
567 566 "file": f,
568 567 "node": hex(ctx.node()),
569 568 "author": ctx.user(),
570 569 "date": ctx.date(),
571 570 "rename": self.renamelink(fl, n),
572 571 "parent": self.siblings(fctx.parents()),
573 572 "child": self.siblings(fctx.children()),
574 573 "desc": ctx.description()})
575 574
576 575 if limit > 0:
577 576 l = l[:limit]
578 577
579 578 for e in l:
580 579 yield e
581 580
582 581 nodefunc = lambda x: fctx.filectx(fileid=x)
583 582 nav = revnavgen(pos, pagelen, count, nodefunc)
584 583 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
585 584 entries=lambda **x: entries(limit=0, **x),
586 585 latestentry=lambda **x: entries(limit=1, **x))
587 586
588 587 def filerevision(self, tmpl, fctx):
589 588 f = fctx.path()
590 589 text = fctx.data()
591 590 fl = fctx.filelog()
592 591 n = fctx.filenode()
593 592 parity = paritygen(self.stripecount)
594 593
595 594 if util.binary(text):
596 595 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
597 596 text = '(binary:%s)' % mt
598 597
599 598 def lines():
600 599 for l, t in enumerate(text.splitlines(1)):
601 600 yield {"line": t,
602 601 "linenumber": "% 6d" % (l + 1),
603 602 "parity": parity.next()}
604 603
605 604 return tmpl("filerevision",
606 605 file=f,
607 606 path=_up(f),
608 607 text=lines(),
609 608 rev=fctx.rev(),
610 609 node=hex(fctx.node()),
611 610 author=fctx.user(),
612 611 date=fctx.date(),
613 612 desc=fctx.description(),
614 613 parent=self.siblings(fctx.parents()),
615 614 child=self.siblings(fctx.children()),
616 615 rename=self.renamelink(fl, n),
617 616 permissions=fctx.manifest().flags(f))
618 617
619 618 def fileannotate(self, tmpl, fctx):
620 619 f = fctx.path()
621 620 n = fctx.filenode()
622 621 fl = fctx.filelog()
623 622 parity = paritygen(self.stripecount)
624 623
625 624 def annotate(**map):
626 625 last = None
627 626 for f, l in fctx.annotate(follow=True):
628 627 fnode = f.filenode()
629 628 name = self.repo.ui.shortuser(f.user())
630 629
631 630 if last != fnode:
632 631 last = fnode
633 632
634 633 yield {"parity": parity.next(),
635 634 "node": hex(f.node()),
636 635 "rev": f.rev(),
637 636 "author": name,
638 637 "file": f.path(),
639 638 "line": l}
640 639
641 640 return tmpl("fileannotate",
642 641 file=f,
643 642 annotate=annotate,
644 643 path=_up(f),
645 644 rev=fctx.rev(),
646 645 node=hex(fctx.node()),
647 646 author=fctx.user(),
648 647 date=fctx.date(),
649 648 desc=fctx.description(),
650 649 rename=self.renamelink(fl, n),
651 650 parent=self.siblings(fctx.parents()),
652 651 child=self.siblings(fctx.children()),
653 652 permissions=fctx.manifest().flags(f))
654 653
655 654 def manifest(self, tmpl, ctx, path):
656 655 mf = ctx.manifest()
657 656 node = ctx.node()
658 657
659 658 files = {}
660 659 parity = paritygen(self.stripecount)
661 660
662 661 if path and path[-1] != "/":
663 662 path += "/"
664 663 l = len(path)
665 664 abspath = "/" + path
666 665
667 666 for f, n in mf.items():
668 667 if f[:l] != path:
669 668 continue
670 669 remain = f[l:]
671 670 if "/" in remain:
672 671 short = remain[:remain.index("/") + 1] # bleah
673 672 files[short] = (f, None)
674 673 else:
675 674 short = os.path.basename(remain)
676 675 files[short] = (f, n)
677 676
678 677 if not files:
679 678 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
680 679
681 680 def filelist(**map):
682 681 fl = files.keys()
683 682 fl.sort()
684 683 for f in fl:
685 684 full, fnode = files[f]
686 685 if not fnode:
687 686 continue
688 687
689 688 fctx = ctx.filectx(full)
690 689 yield {"file": full,
691 690 "parity": parity.next(),
692 691 "basename": f,
693 692 "date": fctx.changectx().date(),
694 693 "size": fctx.size(),
695 694 "permissions": mf.flags(full)}
696 695
697 696 def dirlist(**map):
698 697 fl = files.keys()
699 698 fl.sort()
700 699 for f in fl:
701 700 full, fnode = files[f]
702 701 if fnode:
703 702 continue
704 703
705 704 yield {"parity": parity.next(),
706 705 "path": "%s%s" % (abspath, f),
707 706 "basename": f[:-1]}
708 707
709 708 return tmpl("manifest",
710 709 rev=ctx.rev(),
711 710 node=hex(node),
712 711 path=abspath,
713 712 up=_up(abspath),
714 713 upparity=parity.next(),
715 714 fentries=filelist,
716 715 dentries=dirlist,
717 716 archives=self.archivelist(hex(node)),
718 717 tags=self.nodetagsdict(node),
719 718 branches=self.nodebranchdict(ctx))
720 719
721 720 def tags(self, tmpl):
722 721 i = self.repo.tagslist()
723 722 i.reverse()
724 723 parity = paritygen(self.stripecount)
725 724
726 725 def entries(notip=False,limit=0, **map):
727 726 count = 0
728 727 for k, n in i:
729 728 if notip and k == "tip":
730 729 continue
731 730 if limit > 0 and count >= limit:
732 731 continue
733 732 count = count + 1
734 733 yield {"parity": parity.next(),
735 734 "tag": k,
736 735 "date": self.repo.changectx(n).date(),
737 736 "node": hex(n)}
738 737
739 738 return tmpl("tags",
740 739 node=hex(self.repo.changelog.tip()),
741 740 entries=lambda **x: entries(False,0, **x),
742 741 entriesnotip=lambda **x: entries(True,0, **x),
743 742 latestentry=lambda **x: entries(True,1, **x))
744 743
745 744 def summary(self, tmpl):
746 745 i = self.repo.tagslist()
747 746 i.reverse()
748 747
749 748 def tagentries(**map):
750 749 parity = paritygen(self.stripecount)
751 750 count = 0
752 751 for k, n in i:
753 752 if k == "tip": # skip tip
754 753 continue;
755 754
756 755 count += 1
757 756 if count > 10: # limit to 10 tags
758 757 break;
759 758
760 759 yield tmpl("tagentry",
761 760 parity=parity.next(),
762 761 tag=k,
763 762 node=hex(n),
764 763 date=self.repo.changectx(n).date())
765 764
766 765
767 766 def branches(**map):
768 767 parity = paritygen(self.stripecount)
769 768
770 769 b = self.repo.branchtags()
771 770 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
772 771 l.sort()
773 772
774 773 for r,n,t in l:
775 774 ctx = self.repo.changectx(n)
776 775
777 776 yield {'parity': parity.next(),
778 777 'branch': t,
779 778 'node': hex(n),
780 779 'date': ctx.date()}
781 780
782 781 def changelist(**map):
783 782 parity = paritygen(self.stripecount, offset=start-end)
784 783 l = [] # build a list in forward order for efficiency
785 784 for i in xrange(start, end):
786 785 ctx = self.repo.changectx(i)
787 786 n = ctx.node()
788 787 hn = hex(n)
789 788
790 789 l.insert(0, tmpl(
791 790 'shortlogentry',
792 791 parity=parity.next(),
793 792 author=ctx.user(),
794 793 desc=ctx.description(),
795 794 date=ctx.date(),
796 795 rev=i,
797 796 node=hn,
798 797 tags=self.nodetagsdict(n),
799 798 branches=self.nodebranchdict(ctx)))
800 799
801 800 yield l
802 801
803 802 cl = self.repo.changelog
804 803 count = cl.count()
805 804 start = max(0, count - self.maxchanges)
806 805 end = min(count, start + self.maxchanges)
807 806
808 807 return tmpl("summary",
809 808 desc=self.config("web", "description", "unknown"),
810 809 owner=get_contact(self.config) or "unknown",
811 810 lastchange=cl.read(cl.tip())[2],
812 811 tags=tagentries,
813 812 branches=branches,
814 813 shortlog=changelist,
815 814 node=hex(cl.tip()),
816 815 archives=self.archivelist("tip"))
817 816
818 817 def filediff(self, tmpl, fctx):
819 818 n = fctx.node()
820 819 path = fctx.path()
821 820 parents = fctx.parents()
822 821 p1 = parents and parents[0].node() or nullid
823 822
824 823 def diff(**map):
825 824 yield self.diff(tmpl, p1, n, [path])
826 825
827 826 return tmpl("filediff",
828 827 file=path,
829 828 node=hex(n),
830 829 rev=fctx.rev(),
831 830 parent=self.siblings(parents),
832 831 child=self.siblings(fctx.children()),
833 832 diff=diff)
834 833
835 834 archive_specs = {
836 835 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
837 836 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
838 837 'zip': ('application/zip', 'zip', '.zip', None),
839 838 }
840 839
841 840 def archive(self, tmpl, req, key, type_):
842 841 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
843 842 cnode = self.repo.lookup(key)
844 843 arch_version = key
845 844 if cnode == key or key == 'tip':
846 845 arch_version = short(cnode)
847 846 name = "%s-%s" % (reponame, arch_version)
848 847 mimetype, artype, extension, encoding = self.archive_specs[type_]
849 848 headers = [
850 849 ('Content-Type', mimetype),
851 850 ('Content-Disposition', 'attachment; filename=%s%s' %
852 851 (name, extension))
853 852 ]
854 853 if encoding:
855 854 headers.append(('Content-Encoding', encoding))
856 855 req.header(headers)
857 856 req.respond(HTTP_OK)
858 857 archival.archive(self.repo, req, cnode, artype, prefix=name)
859 858
860 859 # add tags to things
861 860 # tags -> list of changesets corresponding to tags
862 861 # find tag, changeset, file
863 862
864 863 def cleanpath(self, path):
865 864 path = path.lstrip('/')
866 865 return util.canonpath(self.repo.root, '', path)
867 866
868 867 def changectx(self, req):
869 868 if 'node' in req.form:
870 869 changeid = req.form['node'][0]
871 870 elif 'manifest' in req.form:
872 871 changeid = req.form['manifest'][0]
873 872 else:
874 873 changeid = self.repo.changelog.count() - 1
875 874
876 875 try:
877 876 ctx = self.repo.changectx(changeid)
878 877 except hg.RepoError:
879 878 man = self.repo.manifest
880 879 mn = man.lookup(changeid)
881 880 ctx = self.repo.changectx(man.linkrev(mn))
882 881
883 882 return ctx
884 883
885 884 def filectx(self, req):
886 885 path = self.cleanpath(req.form['file'][0])
887 886 if 'node' in req.form:
888 887 changeid = req.form['node'][0]
889 888 else:
890 889 changeid = req.form['filenode'][0]
891 890 try:
892 891 ctx = self.repo.changectx(changeid)
893 892 fctx = ctx.filectx(path)
894 893 except hg.RepoError:
895 894 fctx = self.repo.filectx(path, fileid=changeid)
896 895
897 896 return fctx
898 897
899 898 def check_perm(self, req, op, default):
900 899 '''check permission for operation based on user auth.
901 900 return true if op allowed, else false.
902 901 default is policy to use if no config given.'''
903 902
904 903 user = req.env.get('REMOTE_USER')
905 904
906 905 deny = self.configlist('web', 'deny_' + op)
907 906 if deny and (not user or deny == ['*'] or user in deny):
908 907 return False
909 908
910 909 allow = self.configlist('web', 'allow_' + op)
911 910 return (allow and (allow == ['*'] or user in allow)) or default
General Comments 0
You need to be logged in to leave comments. Login now