##// END OF EJS Templates
Backed out changeset b913d3aacddc (see issue971/msg5317)
Thomas Arendsen Hein -
r6141:90e5c82a default
parent child Browse files
Show More
@@ -1,52 +1,50 b''
1 #!/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
27 from flup.server.fcgi import WSGIServer
26 from flup.server.fcgi import WSGIServer
28
27
29 # 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
30 # repos, collections of repos in a directory tree, or both.
29 # repos, collections of repos in a directory tree, or both.
31 #
30 #
32 # [paths]
31 # [paths]
33 # virtual/path = /real/path
32 # virtual/path = /real/path
34 # virtual/path = /real/path
33 # virtual/path = /real/path
35 #
34 #
36 # [collections]
35 # [collections]
37 # /prefix/to/strip/off = /root/of/tree/full/of/repos
36 # /prefix/to/strip/off = /root/of/tree/full/of/repos
38 #
37 #
39 # collections example: say directory tree /foo contains repos /foo/bar,
38 # collections example: say directory tree /foo contains repos /foo/bar,
40 # /foo/quux/baz. Give this config section:
39 # /foo/quux/baz. Give this config section:
41 # [collections]
40 # [collections]
42 # /foo = /foo
41 # /foo = /foo
43 # Then repos will list as bar and quux/baz.
42 # Then repos will list as bar and quux/baz.
44 #
43 #
45 # 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
46 # or use a dictionary with entries like 'virtual/path': '/real/path'
45 # or use a dictionary with entries like 'virtual/path': '/real/path'
47
46
48 def web_app(ui):
47 def make_web_app():
49 return lambda: hgwebdir("hgweb.config", ui)
48 return hgwebdir("hgweb.config")
50
49
51 u = ui.ui(report_untrusted=False, interactive=False)
50 WSGIServer(wsgiapplication(make_web_app)).run()
52 dispatch.profiled(u, lambda: WSGIServer(wsgiapplication(web_app(u))).run())
@@ -1,601 +1,590 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
417 server::
406 server::
418 Controls generic server settings.
407 Controls generic server settings.
419 uncompressed;;
408 uncompressed;;
420 Whether to allow clients to clone a repo using the uncompressed
409 Whether to allow clients to clone a repo using the uncompressed
421 streaming protocol. This transfers about 40% more data than a
410 streaming protocol. This transfers about 40% more data than a
422 regular clone, but uses less memory and CPU on both server and
411 regular clone, but uses less memory and CPU on both server and
423 client. Over a LAN (100Mbps or better) or a very fast WAN, an
412 client. Over a LAN (100Mbps or better) or a very fast WAN, an
424 uncompressed streaming clone is a lot faster (~10x) than a regular
413 uncompressed streaming clone is a lot faster (~10x) than a regular
425 clone. Over most WAN connections (anything slower than about
414 clone. Over most WAN connections (anything slower than about
426 6Mbps), uncompressed streaming is slower, because of the extra
415 6Mbps), uncompressed streaming is slower, because of the extra
427 data transfer overhead. Default is False.
416 data transfer overhead. Default is False.
428
417
429 trusted::
418 trusted::
430 For security reasons, Mercurial will not use the settings in
419 For security reasons, Mercurial will not use the settings in
431 the .hg/hgrc file from a repository if it doesn't belong to a
420 the .hg/hgrc file from a repository if it doesn't belong to a
432 trusted user or to a trusted group. The main exception is the
421 trusted user or to a trusted group. The main exception is the
433 web interface, which automatically uses some safe settings, since
422 web interface, which automatically uses some safe settings, since
434 it's common to serve repositories from different users.
423 it's common to serve repositories from different users.
435
424
436 This section specifies what users and groups are trusted. The
425 This section specifies what users and groups are trusted. The
437 current user is always trusted. To trust everybody, list a user
426 current user is always trusted. To trust everybody, list a user
438 or a group with name "*".
427 or a group with name "*".
439
428
440 users;;
429 users;;
441 Comma-separated list of trusted users.
430 Comma-separated list of trusted users.
442 groups;;
431 groups;;
443 Comma-separated list of trusted groups.
432 Comma-separated list of trusted groups.
444
433
445 ui::
434 ui::
446 User interface controls.
435 User interface controls.
447 debug;;
436 debug;;
448 Print debugging information. True or False. Default is False.
437 Print debugging information. True or False. Default is False.
449 editor;;
438 editor;;
450 The editor to use during a commit. Default is $EDITOR or "vi".
439 The editor to use during a commit. Default is $EDITOR or "vi".
451 fallbackencoding;;
440 fallbackencoding;;
452 Encoding to try if it's not possible to decode the changelog using
441 Encoding to try if it's not possible to decode the changelog using
453 UTF-8. Default is ISO-8859-1.
442 UTF-8. Default is ISO-8859-1.
454 ignore;;
443 ignore;;
455 A file to read per-user ignore patterns from. This file should be in
444 A file to read per-user ignore patterns from. This file should be in
456 the same format as a repository-wide .hgignore file. This option
445 the same format as a repository-wide .hgignore file. This option
457 supports hook syntax, so if you want to specify multiple ignore
446 supports hook syntax, so if you want to specify multiple ignore
458 files, you can do so by setting something like
447 files, you can do so by setting something like
459 "ignore.other = ~/.hgignore2". For details of the ignore file
448 "ignore.other = ~/.hgignore2". For details of the ignore file
460 format, see the hgignore(5) man page.
449 format, see the hgignore(5) man page.
461 interactive;;
450 interactive;;
462 Allow to prompt the user. True or False. Default is True.
451 Allow to prompt the user. True or False. Default is True.
463 logtemplate;;
452 logtemplate;;
464 Template string for commands that print changesets.
453 Template string for commands that print changesets.
465 merge;;
454 merge;;
466 The conflict resolution program to use during a manual merge.
455 The conflict resolution program to use during a manual merge.
467 Default is "hgmerge".
456 Default is "hgmerge".
468 patch;;
457 patch;;
469 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
458 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
470 unset.
459 unset.
471 quiet;;
460 quiet;;
472 Reduce the amount of output printed. True or False. Default is False.
461 Reduce the amount of output printed. True or False. Default is False.
473 remotecmd;;
462 remotecmd;;
474 remote command to use for clone/push/pull operations. Default is 'hg'.
463 remote command to use for clone/push/pull operations. Default is 'hg'.
475 report_untrusted;;
464 report_untrusted;;
476 Warn if a .hg/hgrc file is ignored due to not being owned by a
465 Warn if a .hg/hgrc file is ignored due to not being owned by a
477 trusted user or group. True or False. Default is True.
466 trusted user or group. True or False. Default is True.
478 slash;;
467 slash;;
479 Display paths using a slash ("/") as the path separator. This only
468 Display paths using a slash ("/") as the path separator. This only
480 makes a difference on systems where the default path separator is not
469 makes a difference on systems where the default path separator is not
481 the slash character (e.g. Windows uses the backslash character ("\")).
470 the slash character (e.g. Windows uses the backslash character ("\")).
482 Default is False.
471 Default is False.
483 ssh;;
472 ssh;;
484 command to use for SSH connections. Default is 'ssh'.
473 command to use for SSH connections. Default is 'ssh'.
485 strict;;
474 strict;;
486 Require exact command names, instead of allowing unambiguous
475 Require exact command names, instead of allowing unambiguous
487 abbreviations. True or False. Default is False.
476 abbreviations. True or False. Default is False.
488 style;;
477 style;;
489 Name of style to use for command output.
478 Name of style to use for command output.
490 timeout;;
479 timeout;;
491 The timeout used when a lock is held (in seconds), a negative value
480 The timeout used when a lock is held (in seconds), a negative value
492 means no timeout. Default is 600.
481 means no timeout. Default is 600.
493 username;;
482 username;;
494 The committer of a changeset created when running "commit".
483 The committer of a changeset created when running "commit".
495 Typically a person's name and email address, e.g. "Fred Widget
484 Typically a person's name and email address, e.g. "Fred Widget
496 <fred@example.com>". Default is $EMAIL or username@hostname.
485 <fred@example.com>". Default is $EMAIL or username@hostname.
497 If the username in hgrc is empty, it has to be specified manually or
486 If the username in hgrc is empty, it has to be specified manually or
498 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
487 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
499 in the system hgrc).
488 in the system hgrc).
500 verbose;;
489 verbose;;
501 Increase the amount of output printed. True or False. Default is False.
490 Increase the amount of output printed. True or False. Default is False.
502
491
503
492
504 web::
493 web::
505 Web interface configuration.
494 Web interface configuration.
506 accesslog;;
495 accesslog;;
507 Where to output the access log. Default is stdout.
496 Where to output the access log. Default is stdout.
508 address;;
497 address;;
509 Interface address to bind to. Default is all.
498 Interface address to bind to. Default is all.
510 allow_archive;;
499 allow_archive;;
511 List of archive format (bz2, gz, zip) allowed for downloading.
500 List of archive format (bz2, gz, zip) allowed for downloading.
512 Default is empty.
501 Default is empty.
513 allowbz2;;
502 allowbz2;;
514 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
503 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
515 Default is false.
504 Default is false.
516 allowgz;;
505 allowgz;;
517 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
506 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
518 Default is false.
507 Default is false.
519 allowpull;;
508 allowpull;;
520 Whether to allow pulling from the repository. Default is true.
509 Whether to allow pulling from the repository. Default is true.
521 allow_push;;
510 allow_push;;
522 Whether to allow pushing to the repository. If empty or not set,
511 Whether to allow pushing to the repository. If empty or not set,
523 push is not allowed. If the special value "*", any remote user
512 push is not allowed. If the special value "*", any remote user
524 can push, including unauthenticated users. Otherwise, the remote
513 can push, including unauthenticated users. Otherwise, the remote
525 user must have been authenticated, and the authenticated user name
514 user must have been authenticated, and the authenticated user name
526 must be present in this list (separated by whitespace or ",").
515 must be present in this list (separated by whitespace or ",").
527 The contents of the allow_push list are examined after the
516 The contents of the allow_push list are examined after the
528 deny_push list.
517 deny_push list.
529 allowzip;;
518 allowzip;;
530 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
519 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
531 Default is false. This feature creates temporary files.
520 Default is false. This feature creates temporary files.
532 baseurl;;
521 baseurl;;
533 Base URL to use when publishing URLs in other locations, so
522 Base URL to use when publishing URLs in other locations, so
534 third-party tools like email notification hooks can construct URLs.
523 third-party tools like email notification hooks can construct URLs.
535 Example: "http://hgserver/repos/"
524 Example: "http://hgserver/repos/"
536 contact;;
525 contact;;
537 Name or email address of the person in charge of the repository.
526 Name or email address of the person in charge of the repository.
538 Defaults to ui.username or $EMAIL or "unknown" if unset or empty.
527 Defaults to ui.username or $EMAIL or "unknown" if unset or empty.
539 deny_push;;
528 deny_push;;
540 Whether to deny pushing to the repository. If empty or not set,
529 Whether to deny pushing to the repository. If empty or not set,
541 push is not denied. If the special value "*", all remote users
530 push is not denied. If the special value "*", all remote users
542 are denied push. Otherwise, unauthenticated users are all denied,
531 are denied push. Otherwise, unauthenticated users are all denied,
543 and any authenticated user name present in this list (separated by
532 and any authenticated user name present in this list (separated by
544 whitespace or ",") is also denied. The contents of the deny_push
533 whitespace or ",") is also denied. The contents of the deny_push
545 list are examined before the allow_push list.
534 list are examined before the allow_push list.
546 description;;
535 description;;
547 Textual description of the repository's purpose or contents.
536 Textual description of the repository's purpose or contents.
548 Default is "unknown".
537 Default is "unknown".
549 encoding;;
538 encoding;;
550 Character encoding name.
539 Character encoding name.
551 Example: "UTF-8"
540 Example: "UTF-8"
552 errorlog;;
541 errorlog;;
553 Where to output the error log. Default is stderr.
542 Where to output the error log. Default is stderr.
554 hidden;;
543 hidden;;
555 Whether to hide the repository in the hgwebdir index. Default is false.
544 Whether to hide the repository in the hgwebdir index. Default is false.
556 ipv6;;
545 ipv6;;
557 Whether to use IPv6. Default is false.
546 Whether to use IPv6. Default is false.
558 name;;
547 name;;
559 Repository name to use in the web interface. Default is current
548 Repository name to use in the web interface. Default is current
560 working directory.
549 working directory.
561 maxchanges;;
550 maxchanges;;
562 Maximum number of changes to list on the changelog. Default is 10.
551 Maximum number of changes to list on the changelog. Default is 10.
563 maxfiles;;
552 maxfiles;;
564 Maximum number of files to list per changeset. Default is 10.
553 Maximum number of files to list per changeset. Default is 10.
565 port;;
554 port;;
566 Port to listen on. Default is 8000.
555 Port to listen on. Default is 8000.
567 prefix;;
556 prefix;;
568 Prefix path to serve from. Default is '' (server root).
557 Prefix path to serve from. Default is '' (server root).
569 push_ssl;;
558 push_ssl;;
570 Whether to require that inbound pushes be transported over SSL to
559 Whether to require that inbound pushes be transported over SSL to
571 prevent password sniffing. Default is true.
560 prevent password sniffing. Default is true.
572 staticurl;;
561 staticurl;;
573 Base URL to use for static files. If unset, static files (e.g.
562 Base URL to use for static files. If unset, static files (e.g.
574 the hgicon.png favicon) will be served by the CGI script itself.
563 the hgicon.png favicon) will be served by the CGI script itself.
575 Use this setting to serve them directly with the HTTP server.
564 Use this setting to serve them directly with the HTTP server.
576 Example: "http://hgserver/static/"
565 Example: "http://hgserver/static/"
577 stripes;;
566 stripes;;
578 How many lines a "zebra stripe" should span in multiline output.
567 How many lines a "zebra stripe" should span in multiline output.
579 Default is 1; set to 0 to disable.
568 Default is 1; set to 0 to disable.
580 style;;
569 style;;
581 Which template map style to use.
570 Which template map style to use.
582 templates;;
571 templates;;
583 Where to find the HTML templates. Default is install path.
572 Where to find the HTML templates. Default is install path.
584
573
585
574
586 AUTHOR
575 AUTHOR
587 ------
576 ------
588 Bryan O'Sullivan <bos@serpentine.com>.
577 Bryan O'Sullivan <bos@serpentine.com>.
589
578
590 Mercurial was written by Matt Mackall <mpm@selenic.com>.
579 Mercurial was written by Matt Mackall <mpm@selenic.com>.
591
580
592 SEE ALSO
581 SEE ALSO
593 --------
582 --------
594 hg(1), hgignore(5)
583 hg(1), hgignore(5)
595
584
596 COPYING
585 COPYING
597 -------
586 -------
598 This manual page is copyright 2005 Bryan O'Sullivan.
587 This manual page is copyright 2005 Bryan O'Sullivan.
599 Mercurial is copyright 2005-2007 Matt Mackall.
588 Mercurial is copyright 2005-2007 Matt Mackall.
600 Free use of this software is granted under the terms of the GNU General
589 Free use of this software is granted under the terms of the GNU General
601 Public License (GPL).
590 Public License (GPL).
@@ -1,30 +1,28 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
26 import mercurial.hgweb.wsgicgi as wsgicgi
25 import mercurial.hgweb.wsgicgi as wsgicgi
27
26
28 u = ui.ui(report_untrusted=False, interactive=False)
27 application = hgweb("/path/to/repo", "repository name")
29 dispatch.profiled(u, lambda: wsgicgi.launch(hgweb("/path/to/repo",
28 wsgicgi.launch(application)
30 "repository name", u)))
@@ -1,48 +1,47 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
26 import mercurial.hgweb.wsgicgi as wsgicgi
25 import mercurial.hgweb.wsgicgi as wsgicgi
27
26
28 # The config file looks like this. You can have paths to individual
27 # The config file looks like this. You can have paths to individual
29 # repos, collections of repos in a directory tree, or both.
28 # repos, collections of repos in a directory tree, or both.
30 #
29 #
31 # [paths]
30 # [paths]
32 # virtual/path = /real/path
31 # virtual/path = /real/path
33 # virtual/path = /real/path
32 # virtual/path = /real/path
34 #
33 #
35 # [collections]
34 # [collections]
36 # /prefix/to/strip/off = /root/of/tree/full/of/repos
35 # /prefix/to/strip/off = /root/of/tree/full/of/repos
37 #
36 #
38 # collections example: say directory tree /foo contains repos /foo/bar,
37 # collections example: say directory tree /foo contains repos /foo/bar,
39 # /foo/quux/baz. Give this config section:
38 # /foo/quux/baz. Give this config section:
40 # [collections]
39 # [collections]
41 # /foo = /foo
40 # /foo = /foo
42 # Then repos will list as bar and quux/baz.
41 # Then repos will list as bar and quux/baz.
43 #
42 #
44 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
43 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
45 # or use a dictionary with entries like 'virtual/path': '/real/path'
44 # or use a dictionary with entries like 'virtual/path': '/real/path'
46
45
47 u = ui.ui(report_untrusted=False, interactive=False)
46 application = hgwebdir('hgweb.config')
48 dispatch.profiled(u, lambda: wsgicgi.launch(hgwebdir('hgweb.config', u)))
47 wsgicgi.launch(application)
@@ -1,423 +1,413 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)
377
376
378 def profiled(ui, func, options={}):
377 if options['profile']:
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':
387 import hotshot, hotshot.stats
378 import hotshot, hotshot.stats
388 prof = hotshot.Profile("hg.prof")
379 prof = hotshot.Profile("hg.prof")
389 try:
380 try:
390 try:
381 try:
391 return prof.runcall(checkargs)
382 return prof.runcall(checkargs)
392 except:
383 except:
393 try:
384 try:
394 ui.warn(_('exception raised - generating '
385 ui.warn(_('exception raised - generating '
395 'profile anyway\n'))
386 'profile anyway\n'))
396 except:
387 except:
397 pass
388 pass
398 raise
389 raise
399 finally:
390 finally:
400 prof.close()
391 prof.close()
401 stats = hotshot.stats.load("hg.prof")
392 stats = hotshot.stats.load("hg.prof")
402 stats.stream = profile_fp()
403 stats.strip_dirs()
393 stats.strip_dirs()
404 stats.sort_stats('time', 'calls')
394 stats.sort_stats('time', 'calls')
405 stats.print_stats(40)
395 stats.print_stats(40)
406 elif options.get('lsprof') or ui.config('profile', 'enable') == 'lsprof':
396 elif options['lsprof']:
407 try:
397 try:
408 from mercurial import lsprof
398 from mercurial import lsprof
409 except ImportError:
399 except ImportError:
410 raise util.Abort(_(
400 raise util.Abort(_(
411 'lsprof not available - install from '
401 'lsprof not available - install from '
412 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
402 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
413 p = lsprof.Profiler()
403 p = lsprof.Profiler()
414 p.enable(subcalls=True)
404 p.enable(subcalls=True)
415 try:
405 try:
416 return func()
406 return checkargs()
417 finally:
407 finally:
418 p.disable()
408 p.disable()
419 stats = lsprof.Stats(p.getstats())
409 stats = lsprof.Stats(p.getstats())
420 stats.sort()
410 stats.sort()
421 stats.pprint(top=10, file=profile_fp(), climit=5)
411 stats.pprint(top=10, file=sys.stderr, climit=5)
422 else:
412 else:
423 return func()
413 return checkargs()
@@ -1,911 +1,910 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, parentui=None):
82 def __init__(self, repo, name=None):
83 if isinstance(repo, str):
83 if isinstance(repo, str):
84 parentui = (parentui or
84 parentui = ui.ui(report_untrusted=False, interactive=False)
85 ui.ui(report_untrusted=False, interactive=False))
86 self.repo = hg.repository(parentui, repo)
85 self.repo = hg.repository(parentui, repo)
87 else:
86 else:
88 self.repo = repo
87 self.repo = repo
89
88
90 hook.redirect(True)
89 hook.redirect(True)
91 self.mtime = -1
90 self.mtime = -1
92 self.reponame = name
91 self.reponame = name
93 self.archives = 'zip', 'gz', 'bz2'
92 self.archives = 'zip', 'gz', 'bz2'
94 self.stripecount = 1
93 self.stripecount = 1
95 # a repo owner may set web.templates in .hg/hgrc to get any file
94 # a repo owner may set web.templates in .hg/hgrc to get any file
96 # readable by the user running the CGI script
95 # readable by the user running the CGI script
97 self.templatepath = self.config("web", "templates",
96 self.templatepath = self.config("web", "templates",
98 templater.templatepath(),
97 templater.templatepath(),
99 untrusted=False)
98 untrusted=False)
100
99
101 # The CGI scripts are often run by a user different from the repo owner.
100 # The CGI scripts are often run by a user different from the repo owner.
102 # Trust the settings from the .hg/hgrc files by default.
101 # Trust the settings from the .hg/hgrc files by default.
103 def config(self, section, name, default=None, untrusted=True):
102 def config(self, section, name, default=None, untrusted=True):
104 return self.repo.ui.config(section, name, default,
103 return self.repo.ui.config(section, name, default,
105 untrusted=untrusted)
104 untrusted=untrusted)
106
105
107 def configbool(self, section, name, default=False, untrusted=True):
106 def configbool(self, section, name, default=False, untrusted=True):
108 return self.repo.ui.configbool(section, name, default,
107 return self.repo.ui.configbool(section, name, default,
109 untrusted=untrusted)
108 untrusted=untrusted)
110
109
111 def configlist(self, section, name, default=None, untrusted=True):
110 def configlist(self, section, name, default=None, untrusted=True):
112 return self.repo.ui.configlist(section, name, default,
111 return self.repo.ui.configlist(section, name, default,
113 untrusted=untrusted)
112 untrusted=untrusted)
114
113
115 def refresh(self):
114 def refresh(self):
116 mtime = get_mtime(self.repo.root)
115 mtime = get_mtime(self.repo.root)
117 if mtime != self.mtime:
116 if mtime != self.mtime:
118 self.mtime = mtime
117 self.mtime = mtime
119 self.repo = hg.repository(self.repo.ui, self.repo.root)
118 self.repo = hg.repository(self.repo.ui, self.repo.root)
120 self.maxchanges = int(self.config("web", "maxchanges", 10))
119 self.maxchanges = int(self.config("web", "maxchanges", 10))
121 self.stripecount = int(self.config("web", "stripes", 1))
120 self.stripecount = int(self.config("web", "stripes", 1))
122 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
121 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
123 self.maxfiles = int(self.config("web", "maxfiles", 10))
122 self.maxfiles = int(self.config("web", "maxfiles", 10))
124 self.allowpull = self.configbool("web", "allowpull", True)
123 self.allowpull = self.configbool("web", "allowpull", True)
125 self.encoding = self.config("web", "encoding", util._encoding)
124 self.encoding = self.config("web", "encoding", util._encoding)
126
125
127 def run(self):
126 def run(self):
128 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
127 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
129 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
128 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
130 import mercurial.hgweb.wsgicgi as wsgicgi
129 import mercurial.hgweb.wsgicgi as wsgicgi
131 wsgicgi.launch(self)
130 wsgicgi.launch(self)
132
131
133 def __call__(self, env, respond):
132 def __call__(self, env, respond):
134 req = wsgirequest(env, respond)
133 req = wsgirequest(env, respond)
135 self.run_wsgi(req)
134 self.run_wsgi(req)
136 return req
135 return req
137
136
138 def run_wsgi(self, req):
137 def run_wsgi(self, req):
139
138
140 self.refresh()
139 self.refresh()
141
140
142 # expand form shortcuts
141 # expand form shortcuts
143
142
144 for k in shortcuts.iterkeys():
143 for k in shortcuts.iterkeys():
145 if k in req.form:
144 if k in req.form:
146 for name, value in shortcuts[k]:
145 for name, value in shortcuts[k]:
147 if value is None:
146 if value is None:
148 value = req.form[k]
147 value = req.form[k]
149 req.form[name] = value
148 req.form[name] = value
150 del req.form[k]
149 del req.form[k]
151
150
152 # work with CGI variables to create coherent structure
151 # work with CGI variables to create coherent structure
153 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
152 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
154
153
155 req.url = req.env['SCRIPT_NAME']
154 req.url = req.env['SCRIPT_NAME']
156 if not req.url.endswith('/'):
155 if not req.url.endswith('/'):
157 req.url += '/'
156 req.url += '/'
158 if 'REPO_NAME' in req.env:
157 if 'REPO_NAME' in req.env:
159 req.url += req.env['REPO_NAME'] + '/'
158 req.url += req.env['REPO_NAME'] + '/'
160
159
161 if req.env.get('PATH_INFO'):
160 if req.env.get('PATH_INFO'):
162 parts = req.env.get('PATH_INFO').strip('/').split('/')
161 parts = req.env.get('PATH_INFO').strip('/').split('/')
163 repo_parts = req.env.get('REPO_NAME', '').split('/')
162 repo_parts = req.env.get('REPO_NAME', '').split('/')
164 if parts[:len(repo_parts)] == repo_parts:
163 if parts[:len(repo_parts)] == repo_parts:
165 parts = parts[len(repo_parts):]
164 parts = parts[len(repo_parts):]
166 query = '/'.join(parts)
165 query = '/'.join(parts)
167 else:
166 else:
168 query = req.env['QUERY_STRING'].split('&', 1)[0]
167 query = req.env['QUERY_STRING'].split('&', 1)[0]
169 query = query.split(';', 1)[0]
168 query = query.split(';', 1)[0]
170
169
171 # translate user-visible url structure to internal structure
170 # translate user-visible url structure to internal structure
172
171
173 args = query.split('/', 2)
172 args = query.split('/', 2)
174 if 'cmd' not in req.form and args and args[0]:
173 if 'cmd' not in req.form and args and args[0]:
175
174
176 cmd = args.pop(0)
175 cmd = args.pop(0)
177 style = cmd.rfind('-')
176 style = cmd.rfind('-')
178 if style != -1:
177 if style != -1:
179 req.form['style'] = [cmd[:style]]
178 req.form['style'] = [cmd[:style]]
180 cmd = cmd[style+1:]
179 cmd = cmd[style+1:]
181
180
182 # avoid accepting e.g. style parameter as command
181 # avoid accepting e.g. style parameter as command
183 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
182 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
184 req.form['cmd'] = [cmd]
183 req.form['cmd'] = [cmd]
185
184
186 if args and args[0]:
185 if args and args[0]:
187 node = args.pop(0)
186 node = args.pop(0)
188 req.form['node'] = [node]
187 req.form['node'] = [node]
189 if args:
188 if args:
190 req.form['file'] = args
189 req.form['file'] = args
191
190
192 if cmd == 'static':
191 if cmd == 'static':
193 req.form['file'] = req.form['node']
192 req.form['file'] = req.form['node']
194 elif cmd == 'archive':
193 elif cmd == 'archive':
195 fn = req.form['node'][0]
194 fn = req.form['node'][0]
196 for type_, spec in self.archive_specs.iteritems():
195 for type_, spec in self.archive_specs.iteritems():
197 ext = spec[2]
196 ext = spec[2]
198 if fn.endswith(ext):
197 if fn.endswith(ext):
199 req.form['node'] = [fn[:-len(ext)]]
198 req.form['node'] = [fn[:-len(ext)]]
200 req.form['type'] = [type_]
199 req.form['type'] = [type_]
201
200
202 # actually process the request
201 # actually process the request
203
202
204 try:
203 try:
205
204
206 cmd = req.form.get('cmd', [''])[0]
205 cmd = req.form.get('cmd', [''])[0]
207 if cmd in protocol.__all__:
206 if cmd in protocol.__all__:
208 method = getattr(protocol, cmd)
207 method = getattr(protocol, cmd)
209 method(self, req)
208 method(self, req)
210 else:
209 else:
211 tmpl = self.templater(req)
210 tmpl = self.templater(req)
212 ctype = tmpl('mimetype', encoding=self.encoding)
211 ctype = tmpl('mimetype', encoding=self.encoding)
213 ctype = templater.stringify(ctype)
212 ctype = templater.stringify(ctype)
214
213
215 if cmd == '':
214 if cmd == '':
216 req.form['cmd'] = [tmpl.cache['default']]
215 req.form['cmd'] = [tmpl.cache['default']]
217 cmd = req.form['cmd'][0]
216 cmd = req.form['cmd'][0]
218
217
219 if cmd not in webcommands.__all__:
218 if cmd not in webcommands.__all__:
220 msg = 'No such method: %s' % cmd
219 msg = 'No such method: %s' % cmd
221 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
220 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
222 elif cmd == 'file' and 'raw' in req.form.get('style', []):
221 elif cmd == 'file' and 'raw' in req.form.get('style', []):
223 self.ctype = ctype
222 self.ctype = ctype
224 content = webcommands.rawfile(self, req, tmpl)
223 content = webcommands.rawfile(self, req, tmpl)
225 else:
224 else:
226 content = getattr(webcommands, cmd)(self, req, tmpl)
225 content = getattr(webcommands, cmd)(self, req, tmpl)
227 req.respond(HTTP_OK, ctype)
226 req.respond(HTTP_OK, ctype)
228
227
229 req.write(content)
228 req.write(content)
230 del tmpl
229 del tmpl
231
230
232 except revlog.LookupError, err:
231 except revlog.LookupError, err:
233 req.respond(HTTP_NOT_FOUND, ctype)
232 req.respond(HTTP_NOT_FOUND, ctype)
234 req.write(tmpl('error', error='revision not found: %s' % err.name))
233 req.write(tmpl('error', error='revision not found: %s' % err.name))
235 except (hg.RepoError, revlog.RevlogError), inst:
234 except (hg.RepoError, revlog.RevlogError), inst:
236 req.respond(HTTP_SERVER_ERROR, ctype)
235 req.respond(HTTP_SERVER_ERROR, ctype)
237 req.write(tmpl('error', error=str(inst)))
236 req.write(tmpl('error', error=str(inst)))
238 except ErrorResponse, inst:
237 except ErrorResponse, inst:
239 req.respond(inst.code, ctype)
238 req.respond(inst.code, ctype)
240 req.write(tmpl('error', error=inst.message))
239 req.write(tmpl('error', error=inst.message))
241
240
242 def templater(self, req):
241 def templater(self, req):
243
242
244 # determine scheme, port and server name
243 # determine scheme, port and server name
245 # this is needed to create absolute urls
244 # this is needed to create absolute urls
246
245
247 proto = req.env.get('wsgi.url_scheme')
246 proto = req.env.get('wsgi.url_scheme')
248 if proto == 'https':
247 if proto == 'https':
249 proto = 'https'
248 proto = 'https'
250 default_port = "443"
249 default_port = "443"
251 else:
250 else:
252 proto = 'http'
251 proto = 'http'
253 default_port = "80"
252 default_port = "80"
254
253
255 port = req.env["SERVER_PORT"]
254 port = req.env["SERVER_PORT"]
256 port = port != default_port and (":" + port) or ""
255 port = port != default_port and (":" + port) or ""
257 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
256 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
258 staticurl = self.config("web", "staticurl") or req.url + 'static/'
257 staticurl = self.config("web", "staticurl") or req.url + 'static/'
259 if not staticurl.endswith('/'):
258 if not staticurl.endswith('/'):
260 staticurl += '/'
259 staticurl += '/'
261
260
262 # some functions for the templater
261 # some functions for the templater
263
262
264 def header(**map):
263 def header(**map):
265 yield tmpl('header', encoding=self.encoding, **map)
264 yield tmpl('header', encoding=self.encoding, **map)
266
265
267 def footer(**map):
266 def footer(**map):
268 yield tmpl("footer", **map)
267 yield tmpl("footer", **map)
269
268
270 def motd(**map):
269 def motd(**map):
271 yield self.config("web", "motd", "")
270 yield self.config("web", "motd", "")
272
271
273 def sessionvars(**map):
272 def sessionvars(**map):
274 fields = []
273 fields = []
275 if 'style' in req.form:
274 if 'style' in req.form:
276 style = req.form['style'][0]
275 style = req.form['style'][0]
277 if style != self.config('web', 'style', ''):
276 if style != self.config('web', 'style', ''):
278 fields.append(('style', style))
277 fields.append(('style', style))
279
278
280 separator = req.url[-1] == '?' and ';' or '?'
279 separator = req.url[-1] == '?' and ';' or '?'
281 for name, value in fields:
280 for name, value in fields:
282 yield dict(name=name, value=value, separator=separator)
281 yield dict(name=name, value=value, separator=separator)
283 separator = ';'
282 separator = ';'
284
283
285 # figure out which style to use
284 # figure out which style to use
286
285
287 style = self.config("web", "style", "")
286 style = self.config("web", "style", "")
288 if 'style' in req.form:
287 if 'style' in req.form:
289 style = req.form['style'][0]
288 style = req.form['style'][0]
290 mapfile = style_map(self.templatepath, style)
289 mapfile = style_map(self.templatepath, style)
291
290
292 if not self.reponame:
291 if not self.reponame:
293 self.reponame = (self.config("web", "name")
292 self.reponame = (self.config("web", "name")
294 or req.env.get('REPO_NAME')
293 or req.env.get('REPO_NAME')
295 or req.url.strip('/') or self.repo.root)
294 or req.url.strip('/') or self.repo.root)
296
295
297 # create the templater
296 # create the templater
298
297
299 tmpl = templater.templater(mapfile, templatefilters.filters,
298 tmpl = templater.templater(mapfile, templatefilters.filters,
300 defaults={"url": req.url,
299 defaults={"url": req.url,
301 "staticurl": staticurl,
300 "staticurl": staticurl,
302 "urlbase": urlbase,
301 "urlbase": urlbase,
303 "repo": self.reponame,
302 "repo": self.reponame,
304 "header": header,
303 "header": header,
305 "footer": footer,
304 "footer": footer,
306 "motd": motd,
305 "motd": motd,
307 "sessionvars": sessionvars
306 "sessionvars": sessionvars
308 })
307 })
309 return tmpl
308 return tmpl
310
309
311 def archivelist(self, nodeid):
310 def archivelist(self, nodeid):
312 allowed = self.configlist("web", "allow_archive")
311 allowed = self.configlist("web", "allow_archive")
313 for i, spec in self.archive_specs.iteritems():
312 for i, spec in self.archive_specs.iteritems():
314 if i in allowed or self.configbool("web", "allow" + i):
313 if i in allowed or self.configbool("web", "allow" + i):
315 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
314 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
316
315
317 def listfilediffs(self, tmpl, files, changeset):
316 def listfilediffs(self, tmpl, files, changeset):
318 for f in files[:self.maxfiles]:
317 for f in files[:self.maxfiles]:
319 yield tmpl("filedifflink", node=hex(changeset), file=f)
318 yield tmpl("filedifflink", node=hex(changeset), file=f)
320 if len(files) > self.maxfiles:
319 if len(files) > self.maxfiles:
321 yield tmpl("fileellipses")
320 yield tmpl("fileellipses")
322
321
323 def siblings(self, siblings=[], hiderev=None, **args):
322 def siblings(self, siblings=[], hiderev=None, **args):
324 siblings = [s for s in siblings if s.node() != nullid]
323 siblings = [s for s in siblings if s.node() != nullid]
325 if len(siblings) == 1 and siblings[0].rev() == hiderev:
324 if len(siblings) == 1 and siblings[0].rev() == hiderev:
326 return
325 return
327 for s in siblings:
326 for s in siblings:
328 d = {'node': hex(s.node()), 'rev': s.rev()}
327 d = {'node': hex(s.node()), 'rev': s.rev()}
329 if hasattr(s, 'path'):
328 if hasattr(s, 'path'):
330 d['file'] = s.path()
329 d['file'] = s.path()
331 d.update(args)
330 d.update(args)
332 yield d
331 yield d
333
332
334 def renamelink(self, fl, node):
333 def renamelink(self, fl, node):
335 r = fl.renamed(node)
334 r = fl.renamed(node)
336 if r:
335 if r:
337 return [dict(file=r[0], node=hex(r[1]))]
336 return [dict(file=r[0], node=hex(r[1]))]
338 return []
337 return []
339
338
340 def nodetagsdict(self, node):
339 def nodetagsdict(self, node):
341 return [{"name": i} for i in self.repo.nodetags(node)]
340 return [{"name": i} for i in self.repo.nodetags(node)]
342
341
343 def nodebranchdict(self, ctx):
342 def nodebranchdict(self, ctx):
344 branches = []
343 branches = []
345 branch = ctx.branch()
344 branch = ctx.branch()
346 # If this is an empty repo, ctx.node() == nullid,
345 # If this is an empty repo, ctx.node() == nullid,
347 # ctx.branch() == 'default', but branchtags() is
346 # ctx.branch() == 'default', but branchtags() is
348 # an empty dict. Using dict.get avoids a traceback.
347 # an empty dict. Using dict.get avoids a traceback.
349 if self.repo.branchtags().get(branch) == ctx.node():
348 if self.repo.branchtags().get(branch) == ctx.node():
350 branches.append({"name": branch})
349 branches.append({"name": branch})
351 return branches
350 return branches
352
351
353 def showtag(self, tmpl, t1, node=nullid, **args):
352 def showtag(self, tmpl, t1, node=nullid, **args):
354 for t in self.repo.nodetags(node):
353 for t in self.repo.nodetags(node):
355 yield tmpl(t1, tag=t, **args)
354 yield tmpl(t1, tag=t, **args)
356
355
357 def diff(self, tmpl, node1, node2, files):
356 def diff(self, tmpl, node1, node2, files):
358 def filterfiles(filters, files):
357 def filterfiles(filters, files):
359 l = [x for x in files if x in filters]
358 l = [x for x in files if x in filters]
360
359
361 for t in filters:
360 for t in filters:
362 if t and t[-1] != os.sep:
361 if t and t[-1] != os.sep:
363 t += os.sep
362 t += os.sep
364 l += [x for x in files if x.startswith(t)]
363 l += [x for x in files if x.startswith(t)]
365 return l
364 return l
366
365
367 parity = paritygen(self.stripecount)
366 parity = paritygen(self.stripecount)
368 def diffblock(diff, f, fn):
367 def diffblock(diff, f, fn):
369 yield tmpl("diffblock",
368 yield tmpl("diffblock",
370 lines=prettyprintlines(diff),
369 lines=prettyprintlines(diff),
371 parity=parity.next(),
370 parity=parity.next(),
372 file=f,
371 file=f,
373 filenode=hex(fn or nullid))
372 filenode=hex(fn or nullid))
374
373
375 def prettyprintlines(diff):
374 def prettyprintlines(diff):
376 for l in diff.splitlines(1):
375 for l in diff.splitlines(1):
377 if l.startswith('+'):
376 if l.startswith('+'):
378 yield tmpl("difflineplus", line=l)
377 yield tmpl("difflineplus", line=l)
379 elif l.startswith('-'):
378 elif l.startswith('-'):
380 yield tmpl("difflineminus", line=l)
379 yield tmpl("difflineminus", line=l)
381 elif l.startswith('@'):
380 elif l.startswith('@'):
382 yield tmpl("difflineat", line=l)
381 yield tmpl("difflineat", line=l)
383 else:
382 else:
384 yield tmpl("diffline", line=l)
383 yield tmpl("diffline", line=l)
385
384
386 r = self.repo
385 r = self.repo
387 c1 = r.changectx(node1)
386 c1 = r.changectx(node1)
388 c2 = r.changectx(node2)
387 c2 = r.changectx(node2)
389 date1 = util.datestr(c1.date())
388 date1 = util.datestr(c1.date())
390 date2 = util.datestr(c2.date())
389 date2 = util.datestr(c2.date())
391
390
392 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
391 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
393 if files:
392 if files:
394 modified, added, removed = map(lambda x: filterfiles(files, x),
393 modified, added, removed = map(lambda x: filterfiles(files, x),
395 (modified, added, removed))
394 (modified, added, removed))
396
395
397 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
396 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
398 for f in modified:
397 for f in modified:
399 to = c1.filectx(f).data()
398 to = c1.filectx(f).data()
400 tn = c2.filectx(f).data()
399 tn = c2.filectx(f).data()
401 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
400 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
402 opts=diffopts), f, tn)
401 opts=diffopts), f, tn)
403 for f in added:
402 for f in added:
404 to = None
403 to = None
405 tn = c2.filectx(f).data()
404 tn = c2.filectx(f).data()
406 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
405 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
407 opts=diffopts), f, tn)
406 opts=diffopts), f, tn)
408 for f in removed:
407 for f in removed:
409 to = c1.filectx(f).data()
408 to = c1.filectx(f).data()
410 tn = None
409 tn = None
411 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
410 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
412 opts=diffopts), f, tn)
411 opts=diffopts), f, tn)
413
412
414 def changelog(self, tmpl, ctx, shortlog=False):
413 def changelog(self, tmpl, ctx, shortlog=False):
415 def changelist(limit=0,**map):
414 def changelist(limit=0,**map):
416 cl = self.repo.changelog
415 cl = self.repo.changelog
417 l = [] # build a list in forward order for efficiency
416 l = [] # build a list in forward order for efficiency
418 for i in xrange(start, end):
417 for i in xrange(start, end):
419 ctx = self.repo.changectx(i)
418 ctx = self.repo.changectx(i)
420 n = ctx.node()
419 n = ctx.node()
421
420
422 l.insert(0, {"parity": parity.next(),
421 l.insert(0, {"parity": parity.next(),
423 "author": ctx.user(),
422 "author": ctx.user(),
424 "parent": self.siblings(ctx.parents(), i - 1),
423 "parent": self.siblings(ctx.parents(), i - 1),
425 "child": self.siblings(ctx.children(), i + 1),
424 "child": self.siblings(ctx.children(), i + 1),
426 "changelogtag": self.showtag("changelogtag",n),
425 "changelogtag": self.showtag("changelogtag",n),
427 "desc": ctx.description(),
426 "desc": ctx.description(),
428 "date": ctx.date(),
427 "date": ctx.date(),
429 "files": self.listfilediffs(tmpl, ctx.files(), n),
428 "files": self.listfilediffs(tmpl, ctx.files(), n),
430 "rev": i,
429 "rev": i,
431 "node": hex(n),
430 "node": hex(n),
432 "tags": self.nodetagsdict(n),
431 "tags": self.nodetagsdict(n),
433 "branches": self.nodebranchdict(ctx)})
432 "branches": self.nodebranchdict(ctx)})
434
433
435 if limit > 0:
434 if limit > 0:
436 l = l[:limit]
435 l = l[:limit]
437
436
438 for e in l:
437 for e in l:
439 yield e
438 yield e
440
439
441 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
440 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
442 cl = self.repo.changelog
441 cl = self.repo.changelog
443 count = cl.count()
442 count = cl.count()
444 pos = ctx.rev()
443 pos = ctx.rev()
445 start = max(0, pos - maxchanges + 1)
444 start = max(0, pos - maxchanges + 1)
446 end = min(count, start + maxchanges)
445 end = min(count, start + maxchanges)
447 pos = end - 1
446 pos = end - 1
448 parity = paritygen(self.stripecount, offset=start-end)
447 parity = paritygen(self.stripecount, offset=start-end)
449
448
450 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
449 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
451
450
452 return tmpl(shortlog and 'shortlog' or 'changelog',
451 return tmpl(shortlog and 'shortlog' or 'changelog',
453 changenav=changenav,
452 changenav=changenav,
454 node=hex(cl.tip()),
453 node=hex(cl.tip()),
455 rev=pos, changesets=count,
454 rev=pos, changesets=count,
456 entries=lambda **x: changelist(limit=0,**x),
455 entries=lambda **x: changelist(limit=0,**x),
457 latestentry=lambda **x: changelist(limit=1,**x),
456 latestentry=lambda **x: changelist(limit=1,**x),
458 archives=self.archivelist("tip"))
457 archives=self.archivelist("tip"))
459
458
460 def search(self, tmpl, query):
459 def search(self, tmpl, query):
461
460
462 def changelist(**map):
461 def changelist(**map):
463 cl = self.repo.changelog
462 cl = self.repo.changelog
464 count = 0
463 count = 0
465 qw = query.lower().split()
464 qw = query.lower().split()
466
465
467 def revgen():
466 def revgen():
468 for i in xrange(cl.count() - 1, 0, -100):
467 for i in xrange(cl.count() - 1, 0, -100):
469 l = []
468 l = []
470 for j in xrange(max(0, i - 100), i):
469 for j in xrange(max(0, i - 100), i):
471 ctx = self.repo.changectx(j)
470 ctx = self.repo.changectx(j)
472 l.append(ctx)
471 l.append(ctx)
473 l.reverse()
472 l.reverse()
474 for e in l:
473 for e in l:
475 yield e
474 yield e
476
475
477 for ctx in revgen():
476 for ctx in revgen():
478 miss = 0
477 miss = 0
479 for q in qw:
478 for q in qw:
480 if not (q in ctx.user().lower() or
479 if not (q in ctx.user().lower() or
481 q in ctx.description().lower() or
480 q in ctx.description().lower() or
482 q in " ".join(ctx.files()).lower()):
481 q in " ".join(ctx.files()).lower()):
483 miss = 1
482 miss = 1
484 break
483 break
485 if miss:
484 if miss:
486 continue
485 continue
487
486
488 count += 1
487 count += 1
489 n = ctx.node()
488 n = ctx.node()
490
489
491 yield tmpl('searchentry',
490 yield tmpl('searchentry',
492 parity=parity.next(),
491 parity=parity.next(),
493 author=ctx.user(),
492 author=ctx.user(),
494 parent=self.siblings(ctx.parents()),
493 parent=self.siblings(ctx.parents()),
495 child=self.siblings(ctx.children()),
494 child=self.siblings(ctx.children()),
496 changelogtag=self.showtag("changelogtag",n),
495 changelogtag=self.showtag("changelogtag",n),
497 desc=ctx.description(),
496 desc=ctx.description(),
498 date=ctx.date(),
497 date=ctx.date(),
499 files=self.listfilediffs(tmpl, ctx.files(), n),
498 files=self.listfilediffs(tmpl, ctx.files(), n),
500 rev=ctx.rev(),
499 rev=ctx.rev(),
501 node=hex(n),
500 node=hex(n),
502 tags=self.nodetagsdict(n),
501 tags=self.nodetagsdict(n),
503 branches=self.nodebranchdict(ctx))
502 branches=self.nodebranchdict(ctx))
504
503
505 if count >= self.maxchanges:
504 if count >= self.maxchanges:
506 break
505 break
507
506
508 cl = self.repo.changelog
507 cl = self.repo.changelog
509 parity = paritygen(self.stripecount)
508 parity = paritygen(self.stripecount)
510
509
511 return tmpl('search',
510 return tmpl('search',
512 query=query,
511 query=query,
513 node=hex(cl.tip()),
512 node=hex(cl.tip()),
514 entries=changelist,
513 entries=changelist,
515 archives=self.archivelist("tip"))
514 archives=self.archivelist("tip"))
516
515
517 def changeset(self, tmpl, ctx):
516 def changeset(self, tmpl, ctx):
518 n = ctx.node()
517 n = ctx.node()
519 parents = ctx.parents()
518 parents = ctx.parents()
520 p1 = parents[0].node()
519 p1 = parents[0].node()
521
520
522 files = []
521 files = []
523 parity = paritygen(self.stripecount)
522 parity = paritygen(self.stripecount)
524 for f in ctx.files():
523 for f in ctx.files():
525 files.append(tmpl("filenodelink",
524 files.append(tmpl("filenodelink",
526 node=hex(n), file=f,
525 node=hex(n), file=f,
527 parity=parity.next()))
526 parity=parity.next()))
528
527
529 def diff(**map):
528 def diff(**map):
530 yield self.diff(tmpl, p1, n, None)
529 yield self.diff(tmpl, p1, n, None)
531
530
532 return tmpl('changeset',
531 return tmpl('changeset',
533 diff=diff,
532 diff=diff,
534 rev=ctx.rev(),
533 rev=ctx.rev(),
535 node=hex(n),
534 node=hex(n),
536 parent=self.siblings(parents),
535 parent=self.siblings(parents),
537 child=self.siblings(ctx.children()),
536 child=self.siblings(ctx.children()),
538 changesettag=self.showtag("changesettag",n),
537 changesettag=self.showtag("changesettag",n),
539 author=ctx.user(),
538 author=ctx.user(),
540 desc=ctx.description(),
539 desc=ctx.description(),
541 date=ctx.date(),
540 date=ctx.date(),
542 files=files,
541 files=files,
543 archives=self.archivelist(hex(n)),
542 archives=self.archivelist(hex(n)),
544 tags=self.nodetagsdict(n),
543 tags=self.nodetagsdict(n),
545 branches=self.nodebranchdict(ctx))
544 branches=self.nodebranchdict(ctx))
546
545
547 def filelog(self, tmpl, fctx):
546 def filelog(self, tmpl, fctx):
548 f = fctx.path()
547 f = fctx.path()
549 fl = fctx.filelog()
548 fl = fctx.filelog()
550 count = fl.count()
549 count = fl.count()
551 pagelen = self.maxshortchanges
550 pagelen = self.maxshortchanges
552 pos = fctx.filerev()
551 pos = fctx.filerev()
553 start = max(0, pos - pagelen + 1)
552 start = max(0, pos - pagelen + 1)
554 end = min(count, start + pagelen)
553 end = min(count, start + pagelen)
555 pos = end - 1
554 pos = end - 1
556 parity = paritygen(self.stripecount, offset=start-end)
555 parity = paritygen(self.stripecount, offset=start-end)
557
556
558 def entries(limit=0, **map):
557 def entries(limit=0, **map):
559 l = []
558 l = []
560
559
561 for i in xrange(start, end):
560 for i in xrange(start, end):
562 ctx = fctx.filectx(i)
561 ctx = fctx.filectx(i)
563 n = fl.node(i)
562 n = fl.node(i)
564
563
565 l.insert(0, {"parity": parity.next(),
564 l.insert(0, {"parity": parity.next(),
566 "filerev": i,
565 "filerev": i,
567 "file": f,
566 "file": f,
568 "node": hex(ctx.node()),
567 "node": hex(ctx.node()),
569 "author": ctx.user(),
568 "author": ctx.user(),
570 "date": ctx.date(),
569 "date": ctx.date(),
571 "rename": self.renamelink(fl, n),
570 "rename": self.renamelink(fl, n),
572 "parent": self.siblings(fctx.parents()),
571 "parent": self.siblings(fctx.parents()),
573 "child": self.siblings(fctx.children()),
572 "child": self.siblings(fctx.children()),
574 "desc": ctx.description()})
573 "desc": ctx.description()})
575
574
576 if limit > 0:
575 if limit > 0:
577 l = l[:limit]
576 l = l[:limit]
578
577
579 for e in l:
578 for e in l:
580 yield e
579 yield e
581
580
582 nodefunc = lambda x: fctx.filectx(fileid=x)
581 nodefunc = lambda x: fctx.filectx(fileid=x)
583 nav = revnavgen(pos, pagelen, count, nodefunc)
582 nav = revnavgen(pos, pagelen, count, nodefunc)
584 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
583 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
585 entries=lambda **x: entries(limit=0, **x),
584 entries=lambda **x: entries(limit=0, **x),
586 latestentry=lambda **x: entries(limit=1, **x))
585 latestentry=lambda **x: entries(limit=1, **x))
587
586
588 def filerevision(self, tmpl, fctx):
587 def filerevision(self, tmpl, fctx):
589 f = fctx.path()
588 f = fctx.path()
590 text = fctx.data()
589 text = fctx.data()
591 fl = fctx.filelog()
590 fl = fctx.filelog()
592 n = fctx.filenode()
591 n = fctx.filenode()
593 parity = paritygen(self.stripecount)
592 parity = paritygen(self.stripecount)
594
593
595 if util.binary(text):
594 if util.binary(text):
596 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
595 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
597 text = '(binary:%s)' % mt
596 text = '(binary:%s)' % mt
598
597
599 def lines():
598 def lines():
600 for l, t in enumerate(text.splitlines(1)):
599 for l, t in enumerate(text.splitlines(1)):
601 yield {"line": t,
600 yield {"line": t,
602 "linenumber": "% 6d" % (l + 1),
601 "linenumber": "% 6d" % (l + 1),
603 "parity": parity.next()}
602 "parity": parity.next()}
604
603
605 return tmpl("filerevision",
604 return tmpl("filerevision",
606 file=f,
605 file=f,
607 path=_up(f),
606 path=_up(f),
608 text=lines(),
607 text=lines(),
609 rev=fctx.rev(),
608 rev=fctx.rev(),
610 node=hex(fctx.node()),
609 node=hex(fctx.node()),
611 author=fctx.user(),
610 author=fctx.user(),
612 date=fctx.date(),
611 date=fctx.date(),
613 desc=fctx.description(),
612 desc=fctx.description(),
614 parent=self.siblings(fctx.parents()),
613 parent=self.siblings(fctx.parents()),
615 child=self.siblings(fctx.children()),
614 child=self.siblings(fctx.children()),
616 rename=self.renamelink(fl, n),
615 rename=self.renamelink(fl, n),
617 permissions=fctx.manifest().flags(f))
616 permissions=fctx.manifest().flags(f))
618
617
619 def fileannotate(self, tmpl, fctx):
618 def fileannotate(self, tmpl, fctx):
620 f = fctx.path()
619 f = fctx.path()
621 n = fctx.filenode()
620 n = fctx.filenode()
622 fl = fctx.filelog()
621 fl = fctx.filelog()
623 parity = paritygen(self.stripecount)
622 parity = paritygen(self.stripecount)
624
623
625 def annotate(**map):
624 def annotate(**map):
626 last = None
625 last = None
627 for f, l in fctx.annotate(follow=True):
626 for f, l in fctx.annotate(follow=True):
628 fnode = f.filenode()
627 fnode = f.filenode()
629 name = self.repo.ui.shortuser(f.user())
628 name = self.repo.ui.shortuser(f.user())
630
629
631 if last != fnode:
630 if last != fnode:
632 last = fnode
631 last = fnode
633
632
634 yield {"parity": parity.next(),
633 yield {"parity": parity.next(),
635 "node": hex(f.node()),
634 "node": hex(f.node()),
636 "rev": f.rev(),
635 "rev": f.rev(),
637 "author": name,
636 "author": name,
638 "file": f.path(),
637 "file": f.path(),
639 "line": l}
638 "line": l}
640
639
641 return tmpl("fileannotate",
640 return tmpl("fileannotate",
642 file=f,
641 file=f,
643 annotate=annotate,
642 annotate=annotate,
644 path=_up(f),
643 path=_up(f),
645 rev=fctx.rev(),
644 rev=fctx.rev(),
646 node=hex(fctx.node()),
645 node=hex(fctx.node()),
647 author=fctx.user(),
646 author=fctx.user(),
648 date=fctx.date(),
647 date=fctx.date(),
649 desc=fctx.description(),
648 desc=fctx.description(),
650 rename=self.renamelink(fl, n),
649 rename=self.renamelink(fl, n),
651 parent=self.siblings(fctx.parents()),
650 parent=self.siblings(fctx.parents()),
652 child=self.siblings(fctx.children()),
651 child=self.siblings(fctx.children()),
653 permissions=fctx.manifest().flags(f))
652 permissions=fctx.manifest().flags(f))
654
653
655 def manifest(self, tmpl, ctx, path):
654 def manifest(self, tmpl, ctx, path):
656 mf = ctx.manifest()
655 mf = ctx.manifest()
657 node = ctx.node()
656 node = ctx.node()
658
657
659 files = {}
658 files = {}
660 parity = paritygen(self.stripecount)
659 parity = paritygen(self.stripecount)
661
660
662 if path and path[-1] != "/":
661 if path and path[-1] != "/":
663 path += "/"
662 path += "/"
664 l = len(path)
663 l = len(path)
665 abspath = "/" + path
664 abspath = "/" + path
666
665
667 for f, n in mf.items():
666 for f, n in mf.items():
668 if f[:l] != path:
667 if f[:l] != path:
669 continue
668 continue
670 remain = f[l:]
669 remain = f[l:]
671 if "/" in remain:
670 if "/" in remain:
672 short = remain[:remain.index("/") + 1] # bleah
671 short = remain[:remain.index("/") + 1] # bleah
673 files[short] = (f, None)
672 files[short] = (f, None)
674 else:
673 else:
675 short = os.path.basename(remain)
674 short = os.path.basename(remain)
676 files[short] = (f, n)
675 files[short] = (f, n)
677
676
678 if not files:
677 if not files:
679 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
678 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
680
679
681 def filelist(**map):
680 def filelist(**map):
682 fl = files.keys()
681 fl = files.keys()
683 fl.sort()
682 fl.sort()
684 for f in fl:
683 for f in fl:
685 full, fnode = files[f]
684 full, fnode = files[f]
686 if not fnode:
685 if not fnode:
687 continue
686 continue
688
687
689 fctx = ctx.filectx(full)
688 fctx = ctx.filectx(full)
690 yield {"file": full,
689 yield {"file": full,
691 "parity": parity.next(),
690 "parity": parity.next(),
692 "basename": f,
691 "basename": f,
693 "date": fctx.changectx().date(),
692 "date": fctx.changectx().date(),
694 "size": fctx.size(),
693 "size": fctx.size(),
695 "permissions": mf.flags(full)}
694 "permissions": mf.flags(full)}
696
695
697 def dirlist(**map):
696 def dirlist(**map):
698 fl = files.keys()
697 fl = files.keys()
699 fl.sort()
698 fl.sort()
700 for f in fl:
699 for f in fl:
701 full, fnode = files[f]
700 full, fnode = files[f]
702 if fnode:
701 if fnode:
703 continue
702 continue
704
703
705 yield {"parity": parity.next(),
704 yield {"parity": parity.next(),
706 "path": "%s%s" % (abspath, f),
705 "path": "%s%s" % (abspath, f),
707 "basename": f[:-1]}
706 "basename": f[:-1]}
708
707
709 return tmpl("manifest",
708 return tmpl("manifest",
710 rev=ctx.rev(),
709 rev=ctx.rev(),
711 node=hex(node),
710 node=hex(node),
712 path=abspath,
711 path=abspath,
713 up=_up(abspath),
712 up=_up(abspath),
714 upparity=parity.next(),
713 upparity=parity.next(),
715 fentries=filelist,
714 fentries=filelist,
716 dentries=dirlist,
715 dentries=dirlist,
717 archives=self.archivelist(hex(node)),
716 archives=self.archivelist(hex(node)),
718 tags=self.nodetagsdict(node),
717 tags=self.nodetagsdict(node),
719 branches=self.nodebranchdict(ctx))
718 branches=self.nodebranchdict(ctx))
720
719
721 def tags(self, tmpl):
720 def tags(self, tmpl):
722 i = self.repo.tagslist()
721 i = self.repo.tagslist()
723 i.reverse()
722 i.reverse()
724 parity = paritygen(self.stripecount)
723 parity = paritygen(self.stripecount)
725
724
726 def entries(notip=False,limit=0, **map):
725 def entries(notip=False,limit=0, **map):
727 count = 0
726 count = 0
728 for k, n in i:
727 for k, n in i:
729 if notip and k == "tip":
728 if notip and k == "tip":
730 continue
729 continue
731 if limit > 0 and count >= limit:
730 if limit > 0 and count >= limit:
732 continue
731 continue
733 count = count + 1
732 count = count + 1
734 yield {"parity": parity.next(),
733 yield {"parity": parity.next(),
735 "tag": k,
734 "tag": k,
736 "date": self.repo.changectx(n).date(),
735 "date": self.repo.changectx(n).date(),
737 "node": hex(n)}
736 "node": hex(n)}
738
737
739 return tmpl("tags",
738 return tmpl("tags",
740 node=hex(self.repo.changelog.tip()),
739 node=hex(self.repo.changelog.tip()),
741 entries=lambda **x: entries(False,0, **x),
740 entries=lambda **x: entries(False,0, **x),
742 entriesnotip=lambda **x: entries(True,0, **x),
741 entriesnotip=lambda **x: entries(True,0, **x),
743 latestentry=lambda **x: entries(True,1, **x))
742 latestentry=lambda **x: entries(True,1, **x))
744
743
745 def summary(self, tmpl):
744 def summary(self, tmpl):
746 i = self.repo.tagslist()
745 i = self.repo.tagslist()
747 i.reverse()
746 i.reverse()
748
747
749 def tagentries(**map):
748 def tagentries(**map):
750 parity = paritygen(self.stripecount)
749 parity = paritygen(self.stripecount)
751 count = 0
750 count = 0
752 for k, n in i:
751 for k, n in i:
753 if k == "tip": # skip tip
752 if k == "tip": # skip tip
754 continue;
753 continue;
755
754
756 count += 1
755 count += 1
757 if count > 10: # limit to 10 tags
756 if count > 10: # limit to 10 tags
758 break;
757 break;
759
758
760 yield tmpl("tagentry",
759 yield tmpl("tagentry",
761 parity=parity.next(),
760 parity=parity.next(),
762 tag=k,
761 tag=k,
763 node=hex(n),
762 node=hex(n),
764 date=self.repo.changectx(n).date())
763 date=self.repo.changectx(n).date())
765
764
766
765
767 def branches(**map):
766 def branches(**map):
768 parity = paritygen(self.stripecount)
767 parity = paritygen(self.stripecount)
769
768
770 b = self.repo.branchtags()
769 b = self.repo.branchtags()
771 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
770 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
772 l.sort()
771 l.sort()
773
772
774 for r,n,t in l:
773 for r,n,t in l:
775 ctx = self.repo.changectx(n)
774 ctx = self.repo.changectx(n)
776
775
777 yield {'parity': parity.next(),
776 yield {'parity': parity.next(),
778 'branch': t,
777 'branch': t,
779 'node': hex(n),
778 'node': hex(n),
780 'date': ctx.date()}
779 'date': ctx.date()}
781
780
782 def changelist(**map):
781 def changelist(**map):
783 parity = paritygen(self.stripecount, offset=start-end)
782 parity = paritygen(self.stripecount, offset=start-end)
784 l = [] # build a list in forward order for efficiency
783 l = [] # build a list in forward order for efficiency
785 for i in xrange(start, end):
784 for i in xrange(start, end):
786 ctx = self.repo.changectx(i)
785 ctx = self.repo.changectx(i)
787 n = ctx.node()
786 n = ctx.node()
788 hn = hex(n)
787 hn = hex(n)
789
788
790 l.insert(0, tmpl(
789 l.insert(0, tmpl(
791 'shortlogentry',
790 'shortlogentry',
792 parity=parity.next(),
791 parity=parity.next(),
793 author=ctx.user(),
792 author=ctx.user(),
794 desc=ctx.description(),
793 desc=ctx.description(),
795 date=ctx.date(),
794 date=ctx.date(),
796 rev=i,
795 rev=i,
797 node=hn,
796 node=hn,
798 tags=self.nodetagsdict(n),
797 tags=self.nodetagsdict(n),
799 branches=self.nodebranchdict(ctx)))
798 branches=self.nodebranchdict(ctx)))
800
799
801 yield l
800 yield l
802
801
803 cl = self.repo.changelog
802 cl = self.repo.changelog
804 count = cl.count()
803 count = cl.count()
805 start = max(0, count - self.maxchanges)
804 start = max(0, count - self.maxchanges)
806 end = min(count, start + self.maxchanges)
805 end = min(count, start + self.maxchanges)
807
806
808 return tmpl("summary",
807 return tmpl("summary",
809 desc=self.config("web", "description", "unknown"),
808 desc=self.config("web", "description", "unknown"),
810 owner=get_contact(self.config) or "unknown",
809 owner=get_contact(self.config) or "unknown",
811 lastchange=cl.read(cl.tip())[2],
810 lastchange=cl.read(cl.tip())[2],
812 tags=tagentries,
811 tags=tagentries,
813 branches=branches,
812 branches=branches,
814 shortlog=changelist,
813 shortlog=changelist,
815 node=hex(cl.tip()),
814 node=hex(cl.tip()),
816 archives=self.archivelist("tip"))
815 archives=self.archivelist("tip"))
817
816
818 def filediff(self, tmpl, fctx):
817 def filediff(self, tmpl, fctx):
819 n = fctx.node()
818 n = fctx.node()
820 path = fctx.path()
819 path = fctx.path()
821 parents = fctx.parents()
820 parents = fctx.parents()
822 p1 = parents and parents[0].node() or nullid
821 p1 = parents and parents[0].node() or nullid
823
822
824 def diff(**map):
823 def diff(**map):
825 yield self.diff(tmpl, p1, n, [path])
824 yield self.diff(tmpl, p1, n, [path])
826
825
827 return tmpl("filediff",
826 return tmpl("filediff",
828 file=path,
827 file=path,
829 node=hex(n),
828 node=hex(n),
830 rev=fctx.rev(),
829 rev=fctx.rev(),
831 parent=self.siblings(parents),
830 parent=self.siblings(parents),
832 child=self.siblings(fctx.children()),
831 child=self.siblings(fctx.children()),
833 diff=diff)
832 diff=diff)
834
833
835 archive_specs = {
834 archive_specs = {
836 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
835 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
837 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
836 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
838 'zip': ('application/zip', 'zip', '.zip', None),
837 'zip': ('application/zip', 'zip', '.zip', None),
839 }
838 }
840
839
841 def archive(self, tmpl, req, key, type_):
840 def archive(self, tmpl, req, key, type_):
842 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
841 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
843 cnode = self.repo.lookup(key)
842 cnode = self.repo.lookup(key)
844 arch_version = key
843 arch_version = key
845 if cnode == key or key == 'tip':
844 if cnode == key or key == 'tip':
846 arch_version = short(cnode)
845 arch_version = short(cnode)
847 name = "%s-%s" % (reponame, arch_version)
846 name = "%s-%s" % (reponame, arch_version)
848 mimetype, artype, extension, encoding = self.archive_specs[type_]
847 mimetype, artype, extension, encoding = self.archive_specs[type_]
849 headers = [
848 headers = [
850 ('Content-Type', mimetype),
849 ('Content-Type', mimetype),
851 ('Content-Disposition', 'attachment; filename=%s%s' %
850 ('Content-Disposition', 'attachment; filename=%s%s' %
852 (name, extension))
851 (name, extension))
853 ]
852 ]
854 if encoding:
853 if encoding:
855 headers.append(('Content-Encoding', encoding))
854 headers.append(('Content-Encoding', encoding))
856 req.header(headers)
855 req.header(headers)
857 req.respond(HTTP_OK)
856 req.respond(HTTP_OK)
858 archival.archive(self.repo, req, cnode, artype, prefix=name)
857 archival.archive(self.repo, req, cnode, artype, prefix=name)
859
858
860 # add tags to things
859 # add tags to things
861 # tags -> list of changesets corresponding to tags
860 # tags -> list of changesets corresponding to tags
862 # find tag, changeset, file
861 # find tag, changeset, file
863
862
864 def cleanpath(self, path):
863 def cleanpath(self, path):
865 path = path.lstrip('/')
864 path = path.lstrip('/')
866 return util.canonpath(self.repo.root, '', path)
865 return util.canonpath(self.repo.root, '', path)
867
866
868 def changectx(self, req):
867 def changectx(self, req):
869 if 'node' in req.form:
868 if 'node' in req.form:
870 changeid = req.form['node'][0]
869 changeid = req.form['node'][0]
871 elif 'manifest' in req.form:
870 elif 'manifest' in req.form:
872 changeid = req.form['manifest'][0]
871 changeid = req.form['manifest'][0]
873 else:
872 else:
874 changeid = self.repo.changelog.count() - 1
873 changeid = self.repo.changelog.count() - 1
875
874
876 try:
875 try:
877 ctx = self.repo.changectx(changeid)
876 ctx = self.repo.changectx(changeid)
878 except hg.RepoError:
877 except hg.RepoError:
879 man = self.repo.manifest
878 man = self.repo.manifest
880 mn = man.lookup(changeid)
879 mn = man.lookup(changeid)
881 ctx = self.repo.changectx(man.linkrev(mn))
880 ctx = self.repo.changectx(man.linkrev(mn))
882
881
883 return ctx
882 return ctx
884
883
885 def filectx(self, req):
884 def filectx(self, req):
886 path = self.cleanpath(req.form['file'][0])
885 path = self.cleanpath(req.form['file'][0])
887 if 'node' in req.form:
886 if 'node' in req.form:
888 changeid = req.form['node'][0]
887 changeid = req.form['node'][0]
889 else:
888 else:
890 changeid = req.form['filenode'][0]
889 changeid = req.form['filenode'][0]
891 try:
890 try:
892 ctx = self.repo.changectx(changeid)
891 ctx = self.repo.changectx(changeid)
893 fctx = ctx.filectx(path)
892 fctx = ctx.filectx(path)
894 except hg.RepoError:
893 except hg.RepoError:
895 fctx = self.repo.filectx(path, fileid=changeid)
894 fctx = self.repo.filectx(path, fileid=changeid)
896
895
897 return fctx
896 return fctx
898
897
899 def check_perm(self, req, op, default):
898 def check_perm(self, req, op, default):
900 '''check permission for operation based on user auth.
899 '''check permission for operation based on user auth.
901 return true if op allowed, else false.
900 return true if op allowed, else false.
902 default is policy to use if no config given.'''
901 default is policy to use if no config given.'''
903
902
904 user = req.env.get('REMOTE_USER')
903 user = req.env.get('REMOTE_USER')
905
904
906 deny = self.configlist('web', 'deny_' + op)
905 deny = self.configlist('web', 'deny_' + op)
907 if deny and (not user or deny == ['*'] or user in deny):
906 if deny and (not user or deny == ['*'] or user in deny):
908 return False
907 return False
909
908
910 allow = self.configlist('web', 'allow_' + op)
909 allow = self.configlist('web', 'allow_' + op)
911 return (allow and (allow == ['*'] or user in allow)) or default
910 return (allow and (allow == ['*'] or user in allow)) or default
General Comments 0
You need to be logged in to leave comments. Login now