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