##// END OF EJS Templates
Enable to select encoding in hgrc web section...
OHASHI Hideya <ohachige at gmail.com> -
r4690:ecea4de3 default
parent child Browse files
Show More
@@ -1,571 +1,574 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.
21 21
22 22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
23 23 (Unix) <install-root>/etc/mercurial/hgrc::
24 24 Per-installation configuration files, searched for in the
25 25 directory where Mercurial is installed. For example, if installed
26 26 in /shared/tools, Mercurial will look in
27 27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
28 28 all Mercurial commands executed by any user in any directory.
29 29
30 30 (Unix) /etc/mercurial/hgrc.d/*.rc::
31 31 (Unix) /etc/mercurial/hgrc::
32 32 (Windows) C:\Mercurial\Mercurial.ini::
33 33 Per-system configuration files, for the system on which Mercurial
34 34 is running. Options in these files apply to all Mercurial
35 35 commands executed by any user in any directory. Options in these
36 36 files override per-installation options.
37 37
38 38 (Unix) $HOME/.hgrc::
39 39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
40 40 (Windows) $HOME\Mercurial.ini::
41 41 Per-user configuration file, for the user running Mercurial.
42 42 Options in this file apply to all Mercurial commands executed by
43 43 any user in any directory. Options in this file override
44 44 per-installation and per-system options.
45 45 On Windows system, one of these is chosen exclusively according
46 46 to definition of HOME environment variable.
47 47
48 48 (Unix, Windows) <repo>/.hg/hgrc::
49 49 Per-repository configuration options that only apply in a
50 50 particular repository. This file is not version-controlled, and
51 51 will not get transferred during a "clone" operation. Options in
52 52 this file override options in all other configuration files.
53 53 On Unix, most of this file will be ignored if it doesn't belong
54 54 to a trusted user or to a trusted group. See the documentation
55 55 for the trusted section below for more details.
56 56
57 57 SYNTAX
58 58 ------
59 59
60 60 A configuration file consists of sections, led by a "[section]" header
61 61 and followed by "name: value" entries; "name=value" is also accepted.
62 62
63 63 [spam]
64 64 eggs=ham
65 65 green=
66 66 eggs
67 67
68 68 Each line contains one entry. If the lines that follow are indented,
69 69 they are treated as continuations of that entry.
70 70
71 71 Leading whitespace is removed from values. Empty lines are skipped.
72 72
73 73 The optional values can contain format strings which refer to other
74 74 values in the same section, or values in a special DEFAULT section.
75 75
76 76 Lines beginning with "#" or ";" are ignored and may be used to provide
77 77 comments.
78 78
79 79 SECTIONS
80 80 --------
81 81
82 82 This section describes the different sections that may appear in a
83 83 Mercurial "hgrc" file, the purpose of each section, its possible
84 84 keys, and their possible values.
85 85
86 86 decode/encode::
87 87 Filters for transforming files on checkout/checkin. This would
88 88 typically be used for newline processing or other
89 89 localization/canonicalization of files.
90 90
91 91 Filters consist of a filter pattern followed by a filter command.
92 92 Filter patterns are globs by default, rooted at the repository
93 93 root. For example, to match any file ending in ".txt" in the root
94 94 directory only, use the pattern "*.txt". To match any file ending
95 95 in ".c" anywhere in the repository, use the pattern "**.c".
96 96
97 97 The filter command can start with a specifier, either "pipe:" or
98 98 "tempfile:". If no specifier is given, "pipe:" is used by default.
99 99
100 100 A "pipe:" command must accept data on stdin and return the
101 101 transformed data on stdout.
102 102
103 103 Pipe example:
104 104
105 105 [encode]
106 106 # uncompress gzip files on checkin to improve delta compression
107 107 # note: not necessarily a good idea, just an example
108 108 *.gz = pipe: gunzip
109 109
110 110 [decode]
111 111 # recompress gzip files when writing them to the working dir (we
112 112 # can safely omit "pipe:", because it's the default)
113 113 *.gz = gzip
114 114
115 115 A "tempfile:" command is a template. The string INFILE is replaced
116 116 with the name of a temporary file that contains the data to be
117 117 filtered by the command. The string OUTFILE is replaced with the
118 118 name of an empty temporary file, where the filtered data must be
119 119 written by the command.
120 120
121 121 NOTE: the tempfile mechanism is recommended for Windows systems,
122 122 where the standard shell I/O redirection operators often have
123 123 strange effects. In particular, if you are doing line ending
124 124 conversion on Windows using the popular dos2unix and unix2dos
125 125 programs, you *must* use the tempfile mechanism, as using pipes will
126 126 corrupt the contents of your files.
127 127
128 128 Tempfile example:
129 129
130 130 [encode]
131 131 # convert files to unix line ending conventions on checkin
132 132 **.txt = tempfile: dos2unix -n INFILE OUTFILE
133 133
134 134 [decode]
135 135 # convert files to windows line ending conventions when writing
136 136 # them to the working dir
137 137 **.txt = tempfile: unix2dos -n INFILE OUTFILE
138 138
139 139 defaults::
140 140 Use the [defaults] section to define command defaults, i.e. the
141 141 default options/arguments to pass to the specified commands.
142 142
143 143 The following example makes 'hg log' run in verbose mode, and
144 144 'hg status' show only the modified files, by default.
145 145
146 146 [defaults]
147 147 log = -v
148 148 status = -m
149 149
150 150 The actual commands, instead of their aliases, must be used when
151 151 defining command defaults. The command defaults will also be
152 152 applied to the aliases of the commands defined.
153 153
154 154 diff::
155 155 Settings used when displaying diffs. They are all boolean and
156 156 defaults to False.
157 157 git;;
158 158 Use git extended diff format.
159 159 nodates;;
160 160 Don't include dates in diff headers.
161 161 showfunc;;
162 162 Show which function each change is in.
163 163 ignorews;;
164 164 Ignore white space when comparing lines.
165 165 ignorewsamount;;
166 166 Ignore changes in the amount of white space.
167 167 ignoreblanklines;;
168 168 Ignore changes whose lines are all blank.
169 169
170 170 email::
171 171 Settings for extensions that send email messages.
172 172 from;;
173 173 Optional. Email address to use in "From" header and SMTP envelope
174 174 of outgoing messages.
175 175 to;;
176 176 Optional. Comma-separated list of recipients' email addresses.
177 177 cc;;
178 178 Optional. Comma-separated list of carbon copy recipients'
179 179 email addresses.
180 180 bcc;;
181 181 Optional. Comma-separated list of blind carbon copy
182 182 recipients' email addresses. Cannot be set interactively.
183 183 method;;
184 184 Optional. Method to use to send email messages. If value is
185 185 "smtp" (default), use SMTP (see section "[smtp]" for
186 186 configuration). Otherwise, use as name of program to run that
187 187 acts like sendmail (takes "-f" option for sender, list of
188 188 recipients on command line, message on stdin). Normally, setting
189 189 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
190 190 sendmail to send messages.
191 191
192 192 Email example:
193 193
194 194 [email]
195 195 from = Joseph User <joe.user@example.com>
196 196 method = /usr/sbin/sendmail
197 197
198 198 extensions::
199 199 Mercurial has an extension mechanism for adding new features. To
200 200 enable an extension, create an entry for it in this section.
201 201
202 202 If you know that the extension is already in Python's search path,
203 203 you can give the name of the module, followed by "=", with nothing
204 204 after the "=".
205 205
206 206 Otherwise, give a name that you choose, followed by "=", followed by
207 207 the path to the ".py" file (including the file name extension) that
208 208 defines the extension.
209 209
210 210 Example for ~/.hgrc:
211 211
212 212 [extensions]
213 213 # (the mq extension will get loaded from mercurial's path)
214 214 hgext.mq =
215 215 # (this extension will get loaded from the file specified)
216 216 myfeature = ~/.hgext/myfeature.py
217 217
218 218 format::
219 219
220 220 usestore;;
221 221 Enable or disable the "store" repository format which improves
222 222 compatibility with systems that fold case or otherwise mangle
223 223 filenames. Enabled by default. Disabling this option will allow
224 224 you to store longer filenames in some situations at the expense of
225 225 compatibility.
226 226
227 227 hooks::
228 228 Commands or Python functions that get automatically executed by
229 229 various actions such as starting or finishing a commit. Multiple
230 230 hooks can be run for the same action by appending a suffix to the
231 231 action. Overriding a site-wide hook can be done by changing its
232 232 value or setting it to an empty string.
233 233
234 234 Example .hg/hgrc:
235 235
236 236 [hooks]
237 237 # do not use the site-wide hook
238 238 incoming =
239 239 incoming.email = /my/email/hook
240 240 incoming.autobuild = /my/build/hook
241 241
242 242 Most hooks are run with environment variables set that give added
243 243 useful information. For each hook below, the environment variables
244 244 it is passed are listed with names of the form "$HG_foo".
245 245
246 246 changegroup;;
247 247 Run after a changegroup has been added via push, pull or
248 248 unbundle. ID of the first new changeset is in $HG_NODE. URL from
249 249 which changes came is in $HG_URL.
250 250 commit;;
251 251 Run after a changeset has been created in the local repository.
252 252 ID of the newly created changeset is in $HG_NODE. Parent
253 253 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
254 254 incoming;;
255 255 Run after a changeset has been pulled, pushed, or unbundled into
256 256 the local repository. The ID of the newly arrived changeset is in
257 257 $HG_NODE. URL that was source of changes came is in $HG_URL.
258 258 outgoing;;
259 259 Run after sending changes from local repository to another. ID of
260 260 first changeset sent is in $HG_NODE. Source of operation is in
261 261 $HG_SOURCE; see "preoutgoing" hook for description.
262 262 prechangegroup;;
263 263 Run before a changegroup is added via push, pull or unbundle.
264 264 Exit status 0 allows the changegroup to proceed. Non-zero status
265 265 will cause the push, pull or unbundle to fail. URL from which
266 266 changes will come is in $HG_URL.
267 267 precommit;;
268 268 Run before starting a local commit. Exit status 0 allows the
269 269 commit to proceed. Non-zero status will cause the commit to fail.
270 270 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
271 271 preoutgoing;;
272 272 Run before computing changes to send from the local repository to
273 273 another. Non-zero status will cause failure. This lets you
274 274 prevent pull over http or ssh. Also prevents against local pull,
275 275 push (outbound) or bundle commands, but not effective, since you
276 276 can just copy files instead then. Source of operation is in
277 277 $HG_SOURCE. If "serve", operation is happening on behalf of
278 278 remote ssh or http repository. If "push", "pull" or "bundle",
279 279 operation is happening on behalf of repository on same system.
280 280 pretag;;
281 281 Run before creating a tag. Exit status 0 allows the tag to be
282 282 created. Non-zero status will cause the tag to fail. ID of
283 283 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
284 284 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
285 285 pretxnchangegroup;;
286 286 Run after a changegroup has been added via push, pull or unbundle,
287 287 but before the transaction has been committed. Changegroup is
288 288 visible to hook program. This lets you validate incoming changes
289 289 before accepting them. Passed the ID of the first new changeset
290 290 in $HG_NODE. Exit status 0 allows the transaction to commit.
291 291 Non-zero status will cause the transaction to be rolled back and
292 292 the push, pull or unbundle will fail. URL that was source of
293 293 changes is in $HG_URL.
294 294 pretxncommit;;
295 295 Run after a changeset has been created but the transaction not yet
296 296 committed. Changeset is visible to hook program. This lets you
297 297 validate commit message and changes. Exit status 0 allows the
298 298 commit to proceed. Non-zero status will cause the transaction to
299 299 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
300 300 IDs are in $HG_PARENT1 and $HG_PARENT2.
301 301 preupdate;;
302 302 Run before updating the working directory. Exit status 0 allows
303 303 the update to proceed. Non-zero status will prevent the update.
304 304 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
305 305 of second new parent is in $HG_PARENT2.
306 306 tag;;
307 307 Run after a tag is created. ID of tagged changeset is in
308 308 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
309 309 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
310 310 update;;
311 311 Run after updating the working directory. Changeset ID of first
312 312 new parent is in $HG_PARENT1. If merge, ID of second new parent
313 313 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
314 314 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
315 315 pre-<command>;;
316 316 Run before executing the associated command. The contents of the
317 317 command line are passed as $HG_ARGS. If the hook returns failure,
318 318 the command doesn't execute and Mercurial returns the failure code.
319 319 post-<command>;;
320 320 Run after successful invocations of the associated command. The
321 321 contents of the command line are passed as $HG_ARGS and the result
322 322 code in $HG_RESULT. Hook failure is ignored.
323 323
324 324 Note: it is generally better to use standard hooks rather than the
325 325 generic pre- and post- command hooks as they are guaranteed to be
326 326 called in the appropriate contexts for influencing transactions.
327 327 Also, hooks like "commit" will be called in all contexts that
328 328 generate a commit (eg. tag) and not just the commit command.
329 329
330 330 Note2: Environment variables with empty values may not be passed to
331 331 hooks on platforms like Windows. For instance, $HG_PARENT2 will
332 332 not be available under Windows for non-merge changesets while being
333 333 set to an empty value under Unix-like systems.
334 334
335 335 The syntax for Python hooks is as follows:
336 336
337 337 hookname = python:modulename.submodule.callable
338 338
339 339 Python hooks are run within the Mercurial process. Each hook is
340 340 called with at least three keyword arguments: a ui object (keyword
341 341 "ui"), a repository object (keyword "repo"), and a "hooktype"
342 342 keyword that tells what kind of hook is used. Arguments listed as
343 343 environment variables above are passed as keyword arguments, with no
344 344 "HG_" prefix, and names in lower case.
345 345
346 346 If a Python hook returns a "true" value or raises an exception, this
347 347 is treated as failure of the hook.
348 348
349 349 http_proxy::
350 350 Used to access web-based Mercurial repositories through a HTTP
351 351 proxy.
352 352 host;;
353 353 Host name and (optional) port of the proxy server, for example
354 354 "myproxy:8000".
355 355 no;;
356 356 Optional. Comma-separated list of host names that should bypass
357 357 the proxy.
358 358 passwd;;
359 359 Optional. Password to authenticate with at the proxy server.
360 360 user;;
361 361 Optional. User name to authenticate with at the proxy server.
362 362
363 363 smtp::
364 364 Configuration for extensions that need to send email messages.
365 365 host;;
366 366 Host name of mail server, e.g. "mail.example.com".
367 367 port;;
368 368 Optional. Port to connect to on mail server. Default: 25.
369 369 tls;;
370 370 Optional. Whether to connect to mail server using TLS. True or
371 371 False. Default: False.
372 372 username;;
373 373 Optional. User name to authenticate to SMTP server with.
374 374 If username is specified, password must also be specified.
375 375 Default: none.
376 376 password;;
377 377 Optional. Password to authenticate to SMTP server with.
378 378 If username is specified, password must also be specified.
379 379 Default: none.
380 380 local_hostname;;
381 381 Optional. It's the hostname that the sender can use to identify itself
382 382 to the MTA.
383 383
384 384 paths::
385 385 Assigns symbolic names to repositories. The left side is the
386 386 symbolic name, and the right gives the directory or URL that is the
387 387 location of the repository. Default paths can be declared by
388 388 setting the following entries.
389 389 default;;
390 390 Directory or URL to use when pulling if no source is specified.
391 391 Default is set to repository from which the current repository
392 392 was cloned.
393 393 default-push;;
394 394 Optional. Directory or URL to use when pushing if no destination
395 395 is specified.
396 396
397 397 server::
398 398 Controls generic server settings.
399 399 uncompressed;;
400 400 Whether to allow clients to clone a repo using the uncompressed
401 401 streaming protocol. This transfers about 40% more data than a
402 402 regular clone, but uses less memory and CPU on both server and
403 403 client. Over a LAN (100Mbps or better) or a very fast WAN, an
404 404 uncompressed streaming clone is a lot faster (~10x) than a regular
405 405 clone. Over most WAN connections (anything slower than about
406 406 6Mbps), uncompressed streaming is slower, because of the extra
407 407 data transfer overhead. Default is False.
408 408
409 409 trusted::
410 410 For security reasons, Mercurial will not use the settings in
411 411 the .hg/hgrc file from a repository if it doesn't belong to a
412 412 trusted user or to a trusted group. The main exception is the
413 413 web interface, which automatically uses some safe settings, since
414 414 it's common to serve repositories from different users.
415 415
416 416 This section specifies what users and groups are trusted. The
417 417 current user is always trusted. To trust everybody, list a user
418 418 or a group with name "*".
419 419
420 420 users;;
421 421 Comma-separated list of trusted users.
422 422 groups;;
423 423 Comma-separated list of trusted groups.
424 424
425 425 ui::
426 426 User interface controls.
427 427 debug;;
428 428 Print debugging information. True or False. Default is False.
429 429 editor;;
430 430 The editor to use during a commit. Default is $EDITOR or "vi".
431 431 fallbackencoding;;
432 432 Encoding to try if it's not possible to decode the changelog using
433 433 UTF-8. Default is ISO-8859-1.
434 434 ignore;;
435 435 A file to read per-user ignore patterns from. This file should be in
436 436 the same format as a repository-wide .hgignore file. This option
437 437 supports hook syntax, so if you want to specify multiple ignore
438 438 files, you can do so by setting something like
439 439 "ignore.other = ~/.hgignore2". For details of the ignore file
440 440 format, see the hgignore(5) man page.
441 441 interactive;;
442 442 Allow to prompt the user. True or False. Default is True.
443 443 logtemplate;;
444 444 Template string for commands that print changesets.
445 445 style;;
446 446 Name of style to use for command output.
447 447 merge;;
448 448 The conflict resolution program to use during a manual merge.
449 449 Default is "hgmerge".
450 450 patch;;
451 451 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
452 452 unset.
453 453 quiet;;
454 454 Reduce the amount of output printed. True or False. Default is False.
455 455 remotecmd;;
456 456 remote command to use for clone/push/pull operations. Default is 'hg'.
457 457 slash;;
458 458 Display paths using a slash ("/") as the path separator. This only
459 459 makes a difference on systems where the default path separator is not
460 460 the slash character (e.g. Windows uses the backslash character ("\")).
461 461 Default is False.
462 462 ssh;;
463 463 command to use for SSH connections. Default is 'ssh'.
464 464 strict;;
465 465 Require exact command names, instead of allowing unambiguous
466 466 abbreviations. True or False. Default is False.
467 467 timeout;;
468 468 The timeout used when a lock is held (in seconds), a negative value
469 469 means no timeout. Default is 600.
470 470 username;;
471 471 The committer of a changeset created when running "commit".
472 472 Typically a person's name and email address, e.g. "Fred Widget
473 473 <fred@example.com>". Default is $EMAIL or username@hostname.
474 474 If the username in hgrc is empty, it has to be specified manually or
475 475 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
476 476 in the system hgrc).
477 477 verbose;;
478 478 Increase the amount of output printed. True or False. Default is False.
479 479
480 480
481 481 web::
482 482 Web interface configuration.
483 483 accesslog;;
484 484 Where to output the access log. Default is stdout.
485 485 address;;
486 486 Interface address to bind to. Default is all.
487 487 allow_archive;;
488 488 List of archive format (bz2, gz, zip) allowed for downloading.
489 489 Default is empty.
490 490 allowbz2;;
491 491 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
492 492 Default is false.
493 493 allowgz;;
494 494 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
495 495 Default is false.
496 496 allowpull;;
497 497 Whether to allow pulling from the repository. Default is true.
498 498 allow_push;;
499 499 Whether to allow pushing to the repository. If empty or not set,
500 500 push is not allowed. If the special value "*", any remote user
501 501 can push, including unauthenticated users. Otherwise, the remote
502 502 user must have been authenticated, and the authenticated user name
503 503 must be present in this list (separated by whitespace or ",").
504 504 The contents of the allow_push list are examined after the
505 505 deny_push list.
506 506 allowzip;;
507 507 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
508 508 Default is false. This feature creates temporary files.
509 509 baseurl;;
510 510 Base URL to use when publishing URLs in other locations, so
511 511 third-party tools like email notification hooks can construct URLs.
512 512 Example: "http://hgserver/repos/"
513 513 contact;;
514 514 Name or email address of the person in charge of the repository.
515 515 Default is "unknown".
516 516 deny_push;;
517 517 Whether to deny pushing to the repository. If empty or not set,
518 518 push is not denied. If the special value "*", all remote users
519 519 are denied push. Otherwise, unauthenticated users are all denied,
520 520 and any authenticated user name present in this list (separated by
521 521 whitespace or ",") is also denied. The contents of the deny_push
522 522 list are examined before the allow_push list.
523 523 description;;
524 524 Textual description of the repository's purpose or contents.
525 525 Default is "unknown".
526 526 errorlog;;
527 527 Where to output the error log. Default is stderr.
528 528 ipv6;;
529 529 Whether to use IPv6. Default is false.
530 530 name;;
531 531 Repository name to use in the web interface. Default is current
532 532 working directory.
533 533 maxchanges;;
534 534 Maximum number of changes to list on the changelog. Default is 10.
535 535 maxfiles;;
536 536 Maximum number of files to list per changeset. Default is 10.
537 537 port;;
538 538 Port to listen on. Default is 8000.
539 539 push_ssl;;
540 540 Whether to require that inbound pushes be transported over SSL to
541 541 prevent password sniffing. Default is true.
542 542 staticurl;;
543 543 Base URL to use for static files. If unset, static files (e.g.
544 544 the hgicon.png favicon) will be served by the CGI script itself.
545 545 Use this setting to serve them directly with the HTTP server.
546 546 Example: "http://hgserver/static/"
547 547 stripes;;
548 548 How many lines a "zebra stripe" should span in multiline output.
549 549 Default is 1; set to 0 to disable.
550 550 style;;
551 551 Which template map style to use.
552 552 templates;;
553 553 Where to find the HTML templates. Default is install path.
554 encoding;;
555 Character encoding name.
556 Example: "UTF-8"
554 557
555 558
556 559 AUTHOR
557 560 ------
558 561 Bryan O'Sullivan <bos@serpentine.com>.
559 562
560 563 Mercurial was written by Matt Mackall <mpm@selenic.com>.
561 564
562 565 SEE ALSO
563 566 --------
564 567 hg(1), hgignore(5)
565 568
566 569 COPYING
567 570 -------
568 571 This manual page is copyright 2005 Bryan O'Sullivan.
569 572 Mercurial is copyright 2005-2007 Matt Mackall.
570 573 Free use of this software is granted under the terms of the GNU General
571 574 Public License (GPL).
@@ -1,1179 +1,1180 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, zlib, mimetools, cStringIO, sys
10 10 import tempfile, urllib, bz2
11 11 from mercurial.node import *
12 12 from mercurial.i18n import gettext as _
13 13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 14 from mercurial import revlog, templater
15 15 from common import get_mtime, staticfile, style_map, paritygen
16 16
17 17 def _up(p):
18 18 if p[0] != "/":
19 19 p = "/" + p
20 20 if p[-1] == "/":
21 21 p = p[:-1]
22 22 up = os.path.dirname(p)
23 23 if up == "/":
24 24 return "/"
25 25 return up + "/"
26 26
27 27 def revnavgen(pos, pagelen, limit, nodefunc):
28 28 def seq(factor, limit=None):
29 29 if limit:
30 30 yield limit
31 31 if limit >= 20 and limit <= 40:
32 32 yield 50
33 33 else:
34 34 yield 1 * factor
35 35 yield 3 * factor
36 36 for f in seq(factor * 10):
37 37 yield f
38 38
39 39 def nav(**map):
40 40 l = []
41 41 last = 0
42 42 for f in seq(1, pagelen):
43 43 if f < pagelen or f <= last:
44 44 continue
45 45 if f > limit:
46 46 break
47 47 last = f
48 48 if pos + f < limit:
49 49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 50 if pos - f >= 0:
51 51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52 52
53 53 try:
54 54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55 55
56 56 for label, node in l:
57 57 yield {"label": label, "node": node}
58 58
59 59 yield {"label": "tip", "node": "tip"}
60 60 except hg.RepoError:
61 61 pass
62 62
63 63 return nav
64 64
65 65 class hgweb(object):
66 66 def __init__(self, repo, name=None):
67 67 if type(repo) == type(""):
68 68 self.repo = hg.repository(ui.ui(report_untrusted=False), repo)
69 69 else:
70 70 self.repo = repo
71 71
72 72 self.mtime = -1
73 73 self.reponame = name
74 74 self.archives = 'zip', 'gz', 'bz2'
75 75 self.stripecount = 1
76 76 # a repo owner may set web.templates in .hg/hgrc to get any file
77 77 # readable by the user running the CGI script
78 78 self.templatepath = self.config("web", "templates",
79 79 templater.templatepath(),
80 80 untrusted=False)
81 81
82 82 # The CGI scripts are often run by a user different from the repo owner.
83 83 # Trust the settings from the .hg/hgrc files by default.
84 84 def config(self, section, name, default=None, untrusted=True):
85 85 return self.repo.ui.config(section, name, default,
86 86 untrusted=untrusted)
87 87
88 88 def configbool(self, section, name, default=False, untrusted=True):
89 89 return self.repo.ui.configbool(section, name, default,
90 90 untrusted=untrusted)
91 91
92 92 def configlist(self, section, name, default=None, untrusted=True):
93 93 return self.repo.ui.configlist(section, name, default,
94 94 untrusted=untrusted)
95 95
96 96 def refresh(self):
97 97 mtime = get_mtime(self.repo.root)
98 98 if mtime != self.mtime:
99 99 self.mtime = mtime
100 100 self.repo = hg.repository(self.repo.ui, self.repo.root)
101 101 self.maxchanges = int(self.config("web", "maxchanges", 10))
102 102 self.stripecount = int(self.config("web", "stripes", 1))
103 103 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
104 104 self.maxfiles = int(self.config("web", "maxfiles", 10))
105 105 self.allowpull = self.configbool("web", "allowpull", True)
106 self.encoding = self.config("web", "encoding", util._encoding)
106 107
107 108 def archivelist(self, nodeid):
108 109 allowed = self.configlist("web", "allow_archive")
109 110 for i, spec in self.archive_specs.iteritems():
110 111 if i in allowed or self.configbool("web", "allow" + i):
111 112 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
112 113
113 114 def listfilediffs(self, files, changeset):
114 115 for f in files[:self.maxfiles]:
115 116 yield self.t("filedifflink", node=hex(changeset), file=f)
116 117 if len(files) > self.maxfiles:
117 118 yield self.t("fileellipses")
118 119
119 120 def siblings(self, siblings=[], hiderev=None, **args):
120 121 siblings = [s for s in siblings if s.node() != nullid]
121 122 if len(siblings) == 1 and siblings[0].rev() == hiderev:
122 123 return
123 124 for s in siblings:
124 125 d = {'node': hex(s.node()), 'rev': s.rev()}
125 126 if hasattr(s, 'path'):
126 127 d['file'] = s.path()
127 128 d.update(args)
128 129 yield d
129 130
130 131 def renamelink(self, fl, node):
131 132 r = fl.renamed(node)
132 133 if r:
133 134 return [dict(file=r[0], node=hex(r[1]))]
134 135 return []
135 136
136 137 def nodetagsdict(self, node):
137 138 return [{"name": i} for i in self.repo.nodetags(node)]
138 139
139 140 def nodebranchdict(self, ctx):
140 141 branches = []
141 142 branch = ctx.branch()
142 143 if self.repo.branchtags()[branch] == ctx.node():
143 144 branches.append({"name": branch})
144 145 return branches
145 146
146 147 def showtag(self, t1, node=nullid, **args):
147 148 for t in self.repo.nodetags(node):
148 149 yield self.t(t1, tag=t, **args)
149 150
150 151 def diff(self, node1, node2, files):
151 152 def filterfiles(filters, files):
152 153 l = [x for x in files if x in filters]
153 154
154 155 for t in filters:
155 156 if t and t[-1] != os.sep:
156 157 t += os.sep
157 158 l += [x for x in files if x.startswith(t)]
158 159 return l
159 160
160 161 parity = paritygen(self.stripecount)
161 162 def diffblock(diff, f, fn):
162 163 yield self.t("diffblock",
163 164 lines=prettyprintlines(diff),
164 165 parity=parity.next(),
165 166 file=f,
166 167 filenode=hex(fn or nullid))
167 168
168 169 def prettyprintlines(diff):
169 170 for l in diff.splitlines(1):
170 171 if l.startswith('+'):
171 172 yield self.t("difflineplus", line=l)
172 173 elif l.startswith('-'):
173 174 yield self.t("difflineminus", line=l)
174 175 elif l.startswith('@'):
175 176 yield self.t("difflineat", line=l)
176 177 else:
177 178 yield self.t("diffline", line=l)
178 179
179 180 r = self.repo
180 181 c1 = r.changectx(node1)
181 182 c2 = r.changectx(node2)
182 183 date1 = util.datestr(c1.date())
183 184 date2 = util.datestr(c2.date())
184 185
185 186 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
186 187 if files:
187 188 modified, added, removed = map(lambda x: filterfiles(files, x),
188 189 (modified, added, removed))
189 190
190 191 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
191 192 for f in modified:
192 193 to = c1.filectx(f).data()
193 194 tn = c2.filectx(f).data()
194 195 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
195 196 opts=diffopts), f, tn)
196 197 for f in added:
197 198 to = None
198 199 tn = c2.filectx(f).data()
199 200 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
200 201 opts=diffopts), f, tn)
201 202 for f in removed:
202 203 to = c1.filectx(f).data()
203 204 tn = None
204 205 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
205 206 opts=diffopts), f, tn)
206 207
207 208 def changelog(self, ctx, shortlog=False):
208 209 def changelist(**map):
209 210 cl = self.repo.changelog
210 211 l = [] # build a list in forward order for efficiency
211 212 for i in xrange(start, end):
212 213 ctx = self.repo.changectx(i)
213 214 n = ctx.node()
214 215
215 216 l.insert(0, {"parity": parity.next(),
216 217 "author": ctx.user(),
217 218 "parent": self.siblings(ctx.parents(), i - 1),
218 219 "child": self.siblings(ctx.children(), i + 1),
219 220 "changelogtag": self.showtag("changelogtag",n),
220 221 "desc": ctx.description(),
221 222 "date": ctx.date(),
222 223 "files": self.listfilediffs(ctx.files(), n),
223 224 "rev": i,
224 225 "node": hex(n),
225 226 "tags": self.nodetagsdict(n),
226 227 "branches": self.nodebranchdict(ctx)})
227 228
228 229 for e in l:
229 230 yield e
230 231
231 232 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
232 233 cl = self.repo.changelog
233 234 count = cl.count()
234 235 pos = ctx.rev()
235 236 start = max(0, pos - maxchanges + 1)
236 237 end = min(count, start + maxchanges)
237 238 pos = end - 1
238 239 parity = paritygen(self.stripecount, offset=start-end)
239 240
240 241 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
241 242
242 243 yield self.t(shortlog and 'shortlog' or 'changelog',
243 244 changenav=changenav,
244 245 node=hex(cl.tip()),
245 246 rev=pos, changesets=count, entries=changelist,
246 247 archives=self.archivelist("tip"))
247 248
248 249 def search(self, query):
249 250
250 251 def changelist(**map):
251 252 cl = self.repo.changelog
252 253 count = 0
253 254 qw = query.lower().split()
254 255
255 256 def revgen():
256 257 for i in xrange(cl.count() - 1, 0, -100):
257 258 l = []
258 259 for j in xrange(max(0, i - 100), i):
259 260 ctx = self.repo.changectx(j)
260 261 l.append(ctx)
261 262 l.reverse()
262 263 for e in l:
263 264 yield e
264 265
265 266 for ctx in revgen():
266 267 miss = 0
267 268 for q in qw:
268 269 if not (q in ctx.user().lower() or
269 270 q in ctx.description().lower() or
270 271 q in " ".join(ctx.files()).lower()):
271 272 miss = 1
272 273 break
273 274 if miss:
274 275 continue
275 276
276 277 count += 1
277 278 n = ctx.node()
278 279
279 280 yield self.t('searchentry',
280 281 parity=parity.next(),
281 282 author=ctx.user(),
282 283 parent=self.siblings(ctx.parents()),
283 284 child=self.siblings(ctx.children()),
284 285 changelogtag=self.showtag("changelogtag",n),
285 286 desc=ctx.description(),
286 287 date=ctx.date(),
287 288 files=self.listfilediffs(ctx.files(), n),
288 289 rev=ctx.rev(),
289 290 node=hex(n),
290 291 tags=self.nodetagsdict(n),
291 292 branches=self.nodebranchdict(ctx))
292 293
293 294 if count >= self.maxchanges:
294 295 break
295 296
296 297 cl = self.repo.changelog
297 298 parity = paritygen(self.stripecount)
298 299
299 300 yield self.t('search',
300 301 query=query,
301 302 node=hex(cl.tip()),
302 303 entries=changelist,
303 304 archives=self.archivelist("tip"))
304 305
305 306 def changeset(self, ctx):
306 307 n = ctx.node()
307 308 parents = ctx.parents()
308 309 p1 = parents[0].node()
309 310
310 311 files = []
311 312 parity = paritygen(self.stripecount)
312 313 for f in ctx.files():
313 314 files.append(self.t("filenodelink",
314 315 node=hex(n), file=f,
315 316 parity=parity.next()))
316 317
317 318 def diff(**map):
318 319 yield self.diff(p1, n, None)
319 320
320 321 yield self.t('changeset',
321 322 diff=diff,
322 323 rev=ctx.rev(),
323 324 node=hex(n),
324 325 parent=self.siblings(parents),
325 326 child=self.siblings(ctx.children()),
326 327 changesettag=self.showtag("changesettag",n),
327 328 author=ctx.user(),
328 329 desc=ctx.description(),
329 330 date=ctx.date(),
330 331 files=files,
331 332 archives=self.archivelist(hex(n)),
332 333 tags=self.nodetagsdict(n),
333 334 branches=self.nodebranchdict(ctx))
334 335
335 336 def filelog(self, fctx):
336 337 f = fctx.path()
337 338 fl = fctx.filelog()
338 339 count = fl.count()
339 340 pagelen = self.maxshortchanges
340 341 pos = fctx.filerev()
341 342 start = max(0, pos - pagelen + 1)
342 343 end = min(count, start + pagelen)
343 344 pos = end - 1
344 345 parity = paritygen(self.stripecount, offset=start-end)
345 346
346 347 def entries(**map):
347 348 l = []
348 349
349 350 for i in xrange(start, end):
350 351 ctx = fctx.filectx(i)
351 352 n = fl.node(i)
352 353
353 354 l.insert(0, {"parity": parity.next(),
354 355 "filerev": i,
355 356 "file": f,
356 357 "node": hex(ctx.node()),
357 358 "author": ctx.user(),
358 359 "date": ctx.date(),
359 360 "rename": self.renamelink(fl, n),
360 361 "parent": self.siblings(fctx.parents()),
361 362 "child": self.siblings(fctx.children()),
362 363 "desc": ctx.description()})
363 364
364 365 for e in l:
365 366 yield e
366 367
367 368 nodefunc = lambda x: fctx.filectx(fileid=x)
368 369 nav = revnavgen(pos, pagelen, count, nodefunc)
369 370 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
370 371 entries=entries)
371 372
372 373 def filerevision(self, fctx):
373 374 f = fctx.path()
374 375 text = fctx.data()
375 376 fl = fctx.filelog()
376 377 n = fctx.filenode()
377 378 parity = paritygen(self.stripecount)
378 379
379 380 mt = mimetypes.guess_type(f)[0]
380 381 rawtext = text
381 382 if util.binary(text):
382 383 mt = mt or 'application/octet-stream'
383 384 text = "(binary:%s)" % mt
384 385 mt = mt or 'text/plain'
385 386
386 387 def lines():
387 388 for l, t in enumerate(text.splitlines(1)):
388 389 yield {"line": t,
389 390 "linenumber": "% 6d" % (l + 1),
390 391 "parity": parity.next()}
391 392
392 393 yield self.t("filerevision",
393 394 file=f,
394 395 path=_up(f),
395 396 text=lines(),
396 397 raw=rawtext,
397 398 mimetype=mt,
398 399 rev=fctx.rev(),
399 400 node=hex(fctx.node()),
400 401 author=fctx.user(),
401 402 date=fctx.date(),
402 403 desc=fctx.description(),
403 404 parent=self.siblings(fctx.parents()),
404 405 child=self.siblings(fctx.children()),
405 406 rename=self.renamelink(fl, n),
406 407 permissions=fctx.manifest().execf(f))
407 408
408 409 def fileannotate(self, fctx):
409 410 f = fctx.path()
410 411 n = fctx.filenode()
411 412 fl = fctx.filelog()
412 413 parity = paritygen(self.stripecount)
413 414
414 415 def annotate(**map):
415 416 last = None
416 417 for f, l in fctx.annotate(follow=True):
417 418 fnode = f.filenode()
418 419 name = self.repo.ui.shortuser(f.user())
419 420
420 421 if last != fnode:
421 422 last = fnode
422 423
423 424 yield {"parity": parity.next(),
424 425 "node": hex(f.node()),
425 426 "rev": f.rev(),
426 427 "author": name,
427 428 "file": f.path(),
428 429 "line": l}
429 430
430 431 yield self.t("fileannotate",
431 432 file=f,
432 433 annotate=annotate,
433 434 path=_up(f),
434 435 rev=fctx.rev(),
435 436 node=hex(fctx.node()),
436 437 author=fctx.user(),
437 438 date=fctx.date(),
438 439 desc=fctx.description(),
439 440 rename=self.renamelink(fl, n),
440 441 parent=self.siblings(fctx.parents()),
441 442 child=self.siblings(fctx.children()),
442 443 permissions=fctx.manifest().execf(f))
443 444
444 445 def manifest(self, ctx, path):
445 446 mf = ctx.manifest()
446 447 node = ctx.node()
447 448
448 449 files = {}
449 450 parity = paritygen(self.stripecount)
450 451
451 452 if path and path[-1] != "/":
452 453 path += "/"
453 454 l = len(path)
454 455 abspath = "/" + path
455 456
456 457 for f, n in mf.items():
457 458 if f[:l] != path:
458 459 continue
459 460 remain = f[l:]
460 461 if "/" in remain:
461 462 short = remain[:remain.index("/") + 1] # bleah
462 463 files[short] = (f, None)
463 464 else:
464 465 short = os.path.basename(remain)
465 466 files[short] = (f, n)
466 467
467 468 def filelist(**map):
468 469 fl = files.keys()
469 470 fl.sort()
470 471 for f in fl:
471 472 full, fnode = files[f]
472 473 if not fnode:
473 474 continue
474 475
475 476 yield {"file": full,
476 477 "parity": parity.next(),
477 478 "basename": f,
478 479 "size": ctx.filectx(full).size(),
479 480 "permissions": mf.execf(full)}
480 481
481 482 def dirlist(**map):
482 483 fl = files.keys()
483 484 fl.sort()
484 485 for f in fl:
485 486 full, fnode = files[f]
486 487 if fnode:
487 488 continue
488 489
489 490 yield {"parity": parity.next(),
490 491 "path": os.path.join(abspath, f),
491 492 "basename": f[:-1]}
492 493
493 494 yield self.t("manifest",
494 495 rev=ctx.rev(),
495 496 node=hex(node),
496 497 path=abspath,
497 498 up=_up(abspath),
498 499 upparity=parity.next(),
499 500 fentries=filelist,
500 501 dentries=dirlist,
501 502 archives=self.archivelist(hex(node)),
502 503 tags=self.nodetagsdict(node),
503 504 branches=self.nodebranchdict(ctx))
504 505
505 506 def tags(self):
506 507 i = self.repo.tagslist()
507 508 i.reverse()
508 509 parity = paritygen(self.stripecount)
509 510
510 511 def entries(notip=False, **map):
511 512 for k, n in i:
512 513 if notip and k == "tip":
513 514 continue
514 515 yield {"parity": parity.next(),
515 516 "tag": k,
516 517 "date": self.repo.changectx(n).date(),
517 518 "node": hex(n)}
518 519
519 520 yield self.t("tags",
520 521 node=hex(self.repo.changelog.tip()),
521 522 entries=lambda **x: entries(False, **x),
522 523 entriesnotip=lambda **x: entries(True, **x))
523 524
524 525 def summary(self):
525 526 i = self.repo.tagslist()
526 527 i.reverse()
527 528
528 529 def tagentries(**map):
529 530 parity = paritygen(self.stripecount)
530 531 count = 0
531 532 for k, n in i:
532 533 if k == "tip": # skip tip
533 534 continue;
534 535
535 536 count += 1
536 537 if count > 10: # limit to 10 tags
537 538 break;
538 539
539 540 yield self.t("tagentry",
540 541 parity=parity.next(),
541 542 tag=k,
542 543 node=hex(n),
543 544 date=self.repo.changectx(n).date())
544 545
545 546
546 547 def branches(**map):
547 548 parity = paritygen(self.stripecount)
548 549
549 550 b = self.repo.branchtags()
550 551 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
551 552 l.sort()
552 553
553 554 for r,n,t in l:
554 555 ctx = self.repo.changectx(n)
555 556
556 557 yield {'parity': parity.next(),
557 558 'branch': t,
558 559 'node': hex(n),
559 560 'date': ctx.date()}
560 561
561 562 def changelist(**map):
562 563 parity = paritygen(self.stripecount, offset=start-end)
563 564 l = [] # build a list in forward order for efficiency
564 565 for i in xrange(start, end):
565 566 ctx = self.repo.changectx(i)
566 567 n = ctx.node()
567 568 hn = hex(n)
568 569
569 570 l.insert(0, self.t(
570 571 'shortlogentry',
571 572 parity=parity.next(),
572 573 author=ctx.user(),
573 574 desc=ctx.description(),
574 575 date=ctx.date(),
575 576 rev=i,
576 577 node=hn,
577 578 tags=self.nodetagsdict(n),
578 579 branches=self.nodebranchdict(ctx)))
579 580
580 581 yield l
581 582
582 583 cl = self.repo.changelog
583 584 count = cl.count()
584 585 start = max(0, count - self.maxchanges)
585 586 end = min(count, start + self.maxchanges)
586 587
587 588 yield self.t("summary",
588 589 desc=self.config("web", "description", "unknown"),
589 590 owner=(self.config("ui", "username") or # preferred
590 591 self.config("web", "contact") or # deprecated
591 592 self.config("web", "author", "unknown")), # also
592 593 lastchange=cl.read(cl.tip())[2],
593 594 tags=tagentries,
594 595 branches=branches,
595 596 shortlog=changelist,
596 597 node=hex(cl.tip()),
597 598 archives=self.archivelist("tip"))
598 599
599 600 def filediff(self, fctx):
600 601 n = fctx.node()
601 602 path = fctx.path()
602 603 parents = fctx.parents()
603 604 p1 = parents and parents[0].node() or nullid
604 605
605 606 def diff(**map):
606 607 yield self.diff(p1, n, [path])
607 608
608 609 yield self.t("filediff",
609 610 file=path,
610 611 node=hex(n),
611 612 rev=fctx.rev(),
612 613 parent=self.siblings(parents),
613 614 child=self.siblings(fctx.children()),
614 615 diff=diff)
615 616
616 617 archive_specs = {
617 618 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
618 619 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
619 620 'zip': ('application/zip', 'zip', '.zip', None),
620 621 }
621 622
622 623 def archive(self, req, key, type_):
623 624 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
624 625 cnode = self.repo.lookup(key)
625 626 arch_version = key
626 627 if cnode == key or key == 'tip':
627 628 arch_version = short(cnode)
628 629 name = "%s-%s" % (reponame, arch_version)
629 630 mimetype, artype, extension, encoding = self.archive_specs[type_]
630 631 headers = [('Content-type', mimetype),
631 632 ('Content-disposition', 'attachment; filename=%s%s' %
632 633 (name, extension))]
633 634 if encoding:
634 635 headers.append(('Content-encoding', encoding))
635 636 req.header(headers)
636 637 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
637 638
638 639 # add tags to things
639 640 # tags -> list of changesets corresponding to tags
640 641 # find tag, changeset, file
641 642
642 643 def cleanpath(self, path):
643 644 path = path.lstrip('/')
644 645 return util.canonpath(self.repo.root, '', path)
645 646
646 647 def run(self):
647 648 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
648 649 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
649 650 import mercurial.hgweb.wsgicgi as wsgicgi
650 651 from request import wsgiapplication
651 652 def make_web_app():
652 653 return self
653 654 wsgicgi.launch(wsgiapplication(make_web_app))
654 655
655 656 def run_wsgi(self, req):
656 657 def header(**map):
657 658 header_file = cStringIO.StringIO(
658 ''.join(self.t("header", encoding=util._encoding, **map)))
659 ''.join(self.t("header", encoding=self.encoding, **map)))
659 660 msg = mimetools.Message(header_file, 0)
660 661 req.header(msg.items())
661 662 yield header_file.read()
662 663
663 664 def rawfileheader(**map):
664 665 req.header([('Content-type', map['mimetype']),
665 666 ('Content-disposition', 'filename=%s' % map['file']),
666 667 ('Content-length', str(len(map['raw'])))])
667 668 yield ''
668 669
669 670 def footer(**map):
670 671 yield self.t("footer", **map)
671 672
672 673 def motd(**map):
673 674 yield self.config("web", "motd", "")
674 675
675 676 def expand_form(form):
676 677 shortcuts = {
677 678 'cl': [('cmd', ['changelog']), ('rev', None)],
678 679 'sl': [('cmd', ['shortlog']), ('rev', None)],
679 680 'cs': [('cmd', ['changeset']), ('node', None)],
680 681 'f': [('cmd', ['file']), ('filenode', None)],
681 682 'fl': [('cmd', ['filelog']), ('filenode', None)],
682 683 'fd': [('cmd', ['filediff']), ('node', None)],
683 684 'fa': [('cmd', ['annotate']), ('filenode', None)],
684 685 'mf': [('cmd', ['manifest']), ('manifest', None)],
685 686 'ca': [('cmd', ['archive']), ('node', None)],
686 687 'tags': [('cmd', ['tags'])],
687 688 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
688 689 'static': [('cmd', ['static']), ('file', None)]
689 690 }
690 691
691 692 for k in shortcuts.iterkeys():
692 693 if form.has_key(k):
693 694 for name, value in shortcuts[k]:
694 695 if value is None:
695 696 value = form[k]
696 697 form[name] = value
697 698 del form[k]
698 699
699 700 def rewrite_request(req):
700 701 '''translate new web interface to traditional format'''
701 702
702 703 def spliturl(req):
703 704 def firstitem(query):
704 705 return query.split('&', 1)[0].split(';', 1)[0]
705 706
706 707 def normurl(url):
707 708 inner = '/'.join([x for x in url.split('/') if x])
708 709 tl = len(url) > 1 and url.endswith('/') and '/' or ''
709 710
710 711 return '%s%s%s' % (url.startswith('/') and '/' or '',
711 712 inner, tl)
712 713
713 714 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
714 715 pi = normurl(req.env.get('PATH_INFO', ''))
715 716 if pi:
716 717 # strip leading /
717 718 pi = pi[1:]
718 719 if pi:
719 720 root = root[:root.rfind(pi)]
720 721 if req.env.has_key('REPO_NAME'):
721 722 rn = req.env['REPO_NAME'] + '/'
722 723 root += rn
723 724 query = pi[len(rn):]
724 725 else:
725 726 query = pi
726 727 else:
727 728 root += '?'
728 729 query = firstitem(req.env['QUERY_STRING'])
729 730
730 731 return (root, query)
731 732
732 733 req.url, query = spliturl(req)
733 734
734 735 if req.form.has_key('cmd'):
735 736 # old style
736 737 return
737 738
738 739 args = query.split('/', 2)
739 740 if not args or not args[0]:
740 741 return
741 742
742 743 cmd = args.pop(0)
743 744 style = cmd.rfind('-')
744 745 if style != -1:
745 746 req.form['style'] = [cmd[:style]]
746 747 cmd = cmd[style+1:]
747 748 # avoid accepting e.g. style parameter as command
748 749 if hasattr(self, 'do_' + cmd):
749 750 req.form['cmd'] = [cmd]
750 751
751 752 if args and args[0]:
752 753 node = args.pop(0)
753 754 req.form['node'] = [node]
754 755 if args:
755 756 req.form['file'] = args
756 757
757 758 if cmd == 'static':
758 759 req.form['file'] = req.form['node']
759 760 elif cmd == 'archive':
760 761 fn = req.form['node'][0]
761 762 for type_, spec in self.archive_specs.iteritems():
762 763 ext = spec[2]
763 764 if fn.endswith(ext):
764 765 req.form['node'] = [fn[:-len(ext)]]
765 766 req.form['type'] = [type_]
766 767
767 768 def sessionvars(**map):
768 769 fields = []
769 770 if req.form.has_key('style'):
770 771 style = req.form['style'][0]
771 772 if style != self.config('web', 'style', ''):
772 773 fields.append(('style', style))
773 774
774 775 separator = req.url[-1] == '?' and ';' or '?'
775 776 for name, value in fields:
776 777 yield dict(name=name, value=value, separator=separator)
777 778 separator = ';'
778 779
779 780 self.refresh()
780 781
781 782 expand_form(req.form)
782 783 rewrite_request(req)
783 784
784 785 style = self.config("web", "style", "")
785 786 if req.form.has_key('style'):
786 787 style = req.form['style'][0]
787 788 mapfile = style_map(self.templatepath, style)
788 789
789 790 port = req.env["SERVER_PORT"]
790 791 port = port != "80" and (":" + port) or ""
791 792 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
792 793 staticurl = self.config("web", "staticurl") or req.url + 'static/'
793 794 if not staticurl.endswith('/'):
794 795 staticurl += '/'
795 796
796 797 if not self.reponame:
797 798 self.reponame = (self.config("web", "name")
798 799 or req.env.get('REPO_NAME')
799 800 or req.url.strip('/') or self.repo.root)
800 801
801 802 self.t = templater.templater(mapfile, templater.common_filters,
802 803 defaults={"url": req.url,
803 804 "staticurl": staticurl,
804 805 "urlbase": urlbase,
805 806 "repo": self.reponame,
806 807 "header": header,
807 808 "footer": footer,
808 809 "motd": motd,
809 810 "rawfileheader": rawfileheader,
810 811 "sessionvars": sessionvars
811 812 })
812 813
813 814 try:
814 815 if not req.form.has_key('cmd'):
815 816 req.form['cmd'] = [self.t.cache['default']]
816 817
817 818 cmd = req.form['cmd'][0]
818 819
819 820 method = getattr(self, 'do_' + cmd, None)
820 821 if method:
821 822 try:
822 823 method(req)
823 824 except (hg.RepoError, revlog.RevlogError), inst:
824 825 req.write(self.t("error", error=str(inst)))
825 826 else:
826 827 req.write(self.t("error", error='No such method: ' + cmd))
827 828 finally:
828 829 self.t = None
829 830
830 831 def changectx(self, req):
831 832 if req.form.has_key('node'):
832 833 changeid = req.form['node'][0]
833 834 elif req.form.has_key('manifest'):
834 835 changeid = req.form['manifest'][0]
835 836 else:
836 837 changeid = self.repo.changelog.count() - 1
837 838
838 839 try:
839 840 ctx = self.repo.changectx(changeid)
840 841 except hg.RepoError:
841 842 man = self.repo.manifest
842 843 mn = man.lookup(changeid)
843 844 ctx = self.repo.changectx(man.linkrev(mn))
844 845
845 846 return ctx
846 847
847 848 def filectx(self, req):
848 849 path = self.cleanpath(req.form['file'][0])
849 850 if req.form.has_key('node'):
850 851 changeid = req.form['node'][0]
851 852 else:
852 853 changeid = req.form['filenode'][0]
853 854 try:
854 855 ctx = self.repo.changectx(changeid)
855 856 fctx = ctx.filectx(path)
856 857 except hg.RepoError:
857 858 fctx = self.repo.filectx(path, fileid=changeid)
858 859
859 860 return fctx
860 861
861 862 def do_log(self, req):
862 863 if req.form.has_key('file') and req.form['file'][0]:
863 864 self.do_filelog(req)
864 865 else:
865 866 self.do_changelog(req)
866 867
867 868 def do_rev(self, req):
868 869 self.do_changeset(req)
869 870
870 871 def do_file(self, req):
871 872 path = self.cleanpath(req.form.get('file', [''])[0])
872 873 if path:
873 874 try:
874 875 req.write(self.filerevision(self.filectx(req)))
875 876 return
876 877 except revlog.LookupError:
877 878 pass
878 879
879 880 req.write(self.manifest(self.changectx(req), path))
880 881
881 882 def do_diff(self, req):
882 883 self.do_filediff(req)
883 884
884 885 def do_changelog(self, req, shortlog = False):
885 886 if req.form.has_key('node'):
886 887 ctx = self.changectx(req)
887 888 else:
888 889 if req.form.has_key('rev'):
889 890 hi = req.form['rev'][0]
890 891 else:
891 892 hi = self.repo.changelog.count() - 1
892 893 try:
893 894 ctx = self.repo.changectx(hi)
894 895 except hg.RepoError:
895 896 req.write(self.search(hi)) # XXX redirect to 404 page?
896 897 return
897 898
898 899 req.write(self.changelog(ctx, shortlog = shortlog))
899 900
900 901 def do_shortlog(self, req):
901 902 self.do_changelog(req, shortlog = True)
902 903
903 904 def do_changeset(self, req):
904 905 req.write(self.changeset(self.changectx(req)))
905 906
906 907 def do_manifest(self, req):
907 908 req.write(self.manifest(self.changectx(req),
908 909 self.cleanpath(req.form['path'][0])))
909 910
910 911 def do_tags(self, req):
911 912 req.write(self.tags())
912 913
913 914 def do_summary(self, req):
914 915 req.write(self.summary())
915 916
916 917 def do_filediff(self, req):
917 918 req.write(self.filediff(self.filectx(req)))
918 919
919 920 def do_annotate(self, req):
920 921 req.write(self.fileannotate(self.filectx(req)))
921 922
922 923 def do_filelog(self, req):
923 924 req.write(self.filelog(self.filectx(req)))
924 925
925 926 def do_lookup(self, req):
926 927 try:
927 928 r = hex(self.repo.lookup(req.form['key'][0]))
928 929 success = 1
929 930 except Exception,inst:
930 931 r = str(inst)
931 932 success = 0
932 933 resp = "%s %s\n" % (success, r)
933 934 req.httphdr("application/mercurial-0.1", length=len(resp))
934 935 req.write(resp)
935 936
936 937 def do_heads(self, req):
937 938 resp = " ".join(map(hex, self.repo.heads())) + "\n"
938 939 req.httphdr("application/mercurial-0.1", length=len(resp))
939 940 req.write(resp)
940 941
941 942 def do_branches(self, req):
942 943 nodes = []
943 944 if req.form.has_key('nodes'):
944 945 nodes = map(bin, req.form['nodes'][0].split(" "))
945 946 resp = cStringIO.StringIO()
946 947 for b in self.repo.branches(nodes):
947 948 resp.write(" ".join(map(hex, b)) + "\n")
948 949 resp = resp.getvalue()
949 950 req.httphdr("application/mercurial-0.1", length=len(resp))
950 951 req.write(resp)
951 952
952 953 def do_between(self, req):
953 954 if req.form.has_key('pairs'):
954 955 pairs = [map(bin, p.split("-"))
955 956 for p in req.form['pairs'][0].split(" ")]
956 957 resp = cStringIO.StringIO()
957 958 for b in self.repo.between(pairs):
958 959 resp.write(" ".join(map(hex, b)) + "\n")
959 960 resp = resp.getvalue()
960 961 req.httphdr("application/mercurial-0.1", length=len(resp))
961 962 req.write(resp)
962 963
963 964 def do_changegroup(self, req):
964 965 req.httphdr("application/mercurial-0.1")
965 966 nodes = []
966 967 if not self.allowpull:
967 968 return
968 969
969 970 if req.form.has_key('roots'):
970 971 nodes = map(bin, req.form['roots'][0].split(" "))
971 972
972 973 z = zlib.compressobj()
973 974 f = self.repo.changegroup(nodes, 'serve')
974 975 while 1:
975 976 chunk = f.read(4096)
976 977 if not chunk:
977 978 break
978 979 req.write(z.compress(chunk))
979 980
980 981 req.write(z.flush())
981 982
982 983 def do_changegroupsubset(self, req):
983 984 req.httphdr("application/mercurial-0.1")
984 985 bases = []
985 986 heads = []
986 987 if not self.allowpull:
987 988 return
988 989
989 990 if req.form.has_key('bases'):
990 991 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
991 992 if req.form.has_key('heads'):
992 993 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
993 994
994 995 z = zlib.compressobj()
995 996 f = self.repo.changegroupsubset(bases, heads, 'serve')
996 997 while 1:
997 998 chunk = f.read(4096)
998 999 if not chunk:
999 1000 break
1000 1001 req.write(z.compress(chunk))
1001 1002
1002 1003 req.write(z.flush())
1003 1004
1004 1005 def do_archive(self, req):
1005 1006 type_ = req.form['type'][0]
1006 1007 allowed = self.configlist("web", "allow_archive")
1007 1008 if (type_ in self.archives and (type_ in allowed or
1008 1009 self.configbool("web", "allow" + type_, False))):
1009 1010 self.archive(req, req.form['node'][0], type_)
1010 1011 return
1011 1012
1012 1013 req.write(self.t("error"))
1013 1014
1014 1015 def do_static(self, req):
1015 1016 fname = req.form['file'][0]
1016 1017 # a repo owner may set web.static in .hg/hgrc to get any file
1017 1018 # readable by the user running the CGI script
1018 1019 static = self.config("web", "static",
1019 1020 os.path.join(self.templatepath, "static"),
1020 1021 untrusted=False)
1021 1022 req.write(staticfile(static, fname, req)
1022 1023 or self.t("error", error="%r not found" % fname))
1023 1024
1024 1025 def do_capabilities(self, req):
1025 1026 caps = ['lookup', 'changegroupsubset']
1026 1027 if self.configbool('server', 'uncompressed'):
1027 1028 caps.append('stream=%d' % self.repo.changelog.version)
1028 1029 # XXX: make configurable and/or share code with do_unbundle:
1029 1030 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1030 1031 if unbundleversions:
1031 1032 caps.append('unbundle=%s' % ','.join(unbundleversions))
1032 1033 resp = ' '.join(caps)
1033 1034 req.httphdr("application/mercurial-0.1", length=len(resp))
1034 1035 req.write(resp)
1035 1036
1036 1037 def check_perm(self, req, op, default):
1037 1038 '''check permission for operation based on user auth.
1038 1039 return true if op allowed, else false.
1039 1040 default is policy to use if no config given.'''
1040 1041
1041 1042 user = req.env.get('REMOTE_USER')
1042 1043
1043 1044 deny = self.configlist('web', 'deny_' + op)
1044 1045 if deny and (not user or deny == ['*'] or user in deny):
1045 1046 return False
1046 1047
1047 1048 allow = self.configlist('web', 'allow_' + op)
1048 1049 return (allow and (allow == ['*'] or user in allow)) or default
1049 1050
1050 1051 def do_unbundle(self, req):
1051 1052 def bail(response, headers={}):
1052 1053 length = int(req.env['CONTENT_LENGTH'])
1053 1054 for s in util.filechunkiter(req, limit=length):
1054 1055 # drain incoming bundle, else client will not see
1055 1056 # response when run outside cgi script
1056 1057 pass
1057 1058 req.httphdr("application/mercurial-0.1", headers=headers)
1058 1059 req.write('0\n')
1059 1060 req.write(response)
1060 1061
1061 1062 # require ssl by default, auth info cannot be sniffed and
1062 1063 # replayed
1063 1064 ssl_req = self.configbool('web', 'push_ssl', True)
1064 1065 if ssl_req:
1065 1066 if not req.env.get('HTTPS'):
1066 1067 bail(_('ssl required\n'))
1067 1068 return
1068 1069 proto = 'https'
1069 1070 else:
1070 1071 proto = 'http'
1071 1072
1072 1073 # do not allow push unless explicitly allowed
1073 1074 if not self.check_perm(req, 'push', False):
1074 1075 bail(_('push not authorized\n'),
1075 1076 headers={'status': '401 Unauthorized'})
1076 1077 return
1077 1078
1078 1079 their_heads = req.form['heads'][0].split(' ')
1079 1080
1080 1081 def check_heads():
1081 1082 heads = map(hex, self.repo.heads())
1082 1083 return their_heads == [hex('force')] or their_heads == heads
1083 1084
1084 1085 # fail early if possible
1085 1086 if not check_heads():
1086 1087 bail(_('unsynced changes\n'))
1087 1088 return
1088 1089
1089 1090 req.httphdr("application/mercurial-0.1")
1090 1091
1091 1092 # do not lock repo until all changegroup data is
1092 1093 # streamed. save to temporary file.
1093 1094
1094 1095 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1095 1096 fp = os.fdopen(fd, 'wb+')
1096 1097 try:
1097 1098 length = int(req.env['CONTENT_LENGTH'])
1098 1099 for s in util.filechunkiter(req, limit=length):
1099 1100 fp.write(s)
1100 1101
1101 1102 try:
1102 1103 lock = self.repo.lock()
1103 1104 try:
1104 1105 if not check_heads():
1105 1106 req.write('0\n')
1106 1107 req.write(_('unsynced changes\n'))
1107 1108 return
1108 1109
1109 1110 fp.seek(0)
1110 1111 header = fp.read(6)
1111 1112 if not header.startswith("HG"):
1112 1113 # old client with uncompressed bundle
1113 1114 def generator(f):
1114 1115 yield header
1115 1116 for chunk in f:
1116 1117 yield chunk
1117 1118 elif not header.startswith("HG10"):
1118 1119 req.write("0\n")
1119 1120 req.write(_("unknown bundle version\n"))
1120 1121 return
1121 1122 elif header == "HG10GZ":
1122 1123 def generator(f):
1123 1124 zd = zlib.decompressobj()
1124 1125 for chunk in f:
1125 1126 yield zd.decompress(chunk)
1126 1127 elif header == "HG10BZ":
1127 1128 def generator(f):
1128 1129 zd = bz2.BZ2Decompressor()
1129 1130 zd.decompress("BZ")
1130 1131 for chunk in f:
1131 1132 yield zd.decompress(chunk)
1132 1133 elif header == "HG10UN":
1133 1134 def generator(f):
1134 1135 for chunk in f:
1135 1136 yield chunk
1136 1137 else:
1137 1138 req.write("0\n")
1138 1139 req.write(_("unknown bundle compression type\n"))
1139 1140 return
1140 1141 gen = generator(util.filechunkiter(fp, 4096))
1141 1142
1142 1143 # send addchangegroup output to client
1143 1144
1144 1145 old_stdout = sys.stdout
1145 1146 sys.stdout = cStringIO.StringIO()
1146 1147
1147 1148 try:
1148 1149 url = 'remote:%s:%s' % (proto,
1149 1150 req.env.get('REMOTE_HOST', ''))
1150 1151 try:
1151 1152 ret = self.repo.addchangegroup(
1152 1153 util.chunkbuffer(gen), 'serve', url)
1153 1154 except util.Abort, inst:
1154 1155 sys.stdout.write("abort: %s\n" % inst)
1155 1156 ret = 0
1156 1157 finally:
1157 1158 val = sys.stdout.getvalue()
1158 1159 sys.stdout = old_stdout
1159 1160 req.write('%d\n' % ret)
1160 1161 req.write(val)
1161 1162 finally:
1162 1163 lock.release()
1163 1164 except (OSError, IOError), inst:
1164 1165 req.write('0\n')
1165 1166 filename = getattr(inst, 'filename', '')
1166 1167 # Don't send our filesystem layout to the client
1167 1168 if filename.startswith(self.repo.root):
1168 1169 filename = filename[len(self.repo.root)+1:]
1169 1170 else:
1170 1171 filename = ''
1171 1172 error = getattr(inst, 'strerror', 'Unknown error')
1172 1173 req.write('%s: %s\n' % (error, filename))
1173 1174 finally:
1174 1175 fp.close()
1175 1176 os.unlink(tempname)
1176 1177
1177 1178 def do_stream_out(self, req):
1178 1179 req.httphdr("application/mercurial-0.1")
1179 1180 streamclone.stream_out(self.repo, req)
General Comments 0
You need to be logged in to leave comments. Login now