##// END OF EJS Templates
push over http: server side authorization support....
Vadim Gelfer -
r2466:e1066514 default
parent child Browse files
Show More
@@ -1,431 +1,449 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.
20 installed.
21
21
22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
23 (Unix) <install-root>/etc/mercurial/hgrc::
23 (Unix) <install-root>/etc/mercurial/hgrc::
24 Per-installation configuration files, searched for in the
24 Per-installation configuration files, searched for in the
25 directory where Mercurial is installed. For example, if installed
25 directory where Mercurial is installed. For example, if installed
26 in /shared/tools, Mercurial will look in
26 in /shared/tools, Mercurial will look in
27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
28 all Mercurial commands executed by any user in any directory.
28 all Mercurial commands executed by any user in any directory.
29
29
30 (Unix) /etc/mercurial/hgrc.d/*.rc::
30 (Unix) /etc/mercurial/hgrc.d/*.rc::
31 (Unix) /etc/mercurial/hgrc::
31 (Unix) /etc/mercurial/hgrc::
32 (Windows) C:\Mercurial\Mercurial.ini::
32 (Windows) C:\Mercurial\Mercurial.ini::
33 Per-system configuration files, for the system on which Mercurial
33 Per-system configuration files, for the system on which Mercurial
34 is running. Options in these files apply to all Mercurial
34 is running. Options in these files apply to all Mercurial
35 commands executed by any user in any directory. Options in these
35 commands executed by any user in any directory. Options in these
36 files override per-installation options.
36 files override per-installation options.
37
37
38 (Unix) $HOME/.hgrc::
38 (Unix) $HOME/.hgrc::
39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
40 (Windows) $HOME\Mercurial.ini::
40 (Windows) $HOME\Mercurial.ini::
41 Per-user configuration file, for the user running Mercurial.
41 Per-user configuration file, for the user running Mercurial.
42 Options in this file apply to all Mercurial commands executed by
42 Options in this file apply to all Mercurial commands executed by
43 any user in any directory. Options in this file override
43 any user in any directory. Options in this file override
44 per-installation and per-system options.
44 per-installation and per-system options.
45 On Windows system, one of these is chosen exclusively according
45 On Windows system, one of these is chosen exclusively according
46 to definition of HOME environment variable.
46 to definition of HOME environment variable.
47
47
48 (Unix, Windows) <repo>/.hg/hgrc::
48 (Unix, Windows) <repo>/.hg/hgrc::
49 Per-repository configuration options that only apply in a
49 Per-repository configuration options that only apply in a
50 particular repository. This file is not version-controlled, and
50 particular repository. This file is not version-controlled, and
51 will not get transferred during a "clone" operation. Options in
51 will not get transferred during a "clone" operation. Options in
52 this file override options in all other configuration files.
52 this file override options in all other configuration files.
53
53
54 SYNTAX
54 SYNTAX
55 ------
55 ------
56
56
57 A configuration file consists of sections, led by a "[section]" header
57 A configuration file consists of sections, led by a "[section]" header
58 and followed by "name: value" entries; "name=value" is also accepted.
58 and followed by "name: value" entries; "name=value" is also accepted.
59
59
60 [spam]
60 [spam]
61 eggs=ham
61 eggs=ham
62 green=
62 green=
63 eggs
63 eggs
64
64
65 Each line contains one entry. If the lines that follow are indented,
65 Each line contains one entry. If the lines that follow are indented,
66 they are treated as continuations of that entry.
66 they are treated as continuations of that entry.
67
67
68 Leading whitespace is removed from values. Empty lines are skipped.
68 Leading whitespace is removed from values. Empty lines are skipped.
69
69
70 The optional values can contain format strings which refer to other
70 The optional values can contain format strings which refer to other
71 values in the same section, or values in a special DEFAULT section.
71 values in the same section, or values in a special DEFAULT section.
72
72
73 Lines beginning with "#" or ";" are ignored and may be used to provide
73 Lines beginning with "#" or ";" are ignored and may be used to provide
74 comments.
74 comments.
75
75
76 SECTIONS
76 SECTIONS
77 --------
77 --------
78
78
79 This section describes the different sections that may appear in a
79 This section describes the different sections that may appear in a
80 Mercurial "hgrc" file, the purpose of each section, its possible
80 Mercurial "hgrc" file, the purpose of each section, its possible
81 keys, and their possible values.
81 keys, and their possible values.
82
82
83 decode/encode::
83 decode/encode::
84 Filters for transforming files on checkout/checkin. This would
84 Filters for transforming files on checkout/checkin. This would
85 typically be used for newline processing or other
85 typically be used for newline processing or other
86 localization/canonicalization of files.
86 localization/canonicalization of files.
87
87
88 Filters consist of a filter pattern followed by a filter command.
88 Filters consist of a filter pattern followed by a filter command.
89 Filter patterns are globs by default, rooted at the repository
89 Filter patterns are globs by default, rooted at the repository
90 root. For example, to match any file ending in ".txt" in the root
90 root. For example, to match any file ending in ".txt" in the root
91 directory only, use the pattern "*.txt". To match any file ending
91 directory only, use the pattern "*.txt". To match any file ending
92 in ".c" anywhere in the repository, use the pattern "**.c".
92 in ".c" anywhere in the repository, use the pattern "**.c".
93
93
94 The filter command can start with a specifier, either "pipe:" or
94 The filter command can start with a specifier, either "pipe:" or
95 "tempfile:". If no specifier is given, "pipe:" is used by default.
95 "tempfile:". If no specifier is given, "pipe:" is used by default.
96
96
97 A "pipe:" command must accept data on stdin and return the
97 A "pipe:" command must accept data on stdin and return the
98 transformed data on stdout.
98 transformed data on stdout.
99
99
100 Pipe example:
100 Pipe example:
101
101
102 [encode]
102 [encode]
103 # uncompress gzip files on checkin to improve delta compression
103 # uncompress gzip files on checkin to improve delta compression
104 # note: not necessarily a good idea, just an example
104 # note: not necessarily a good idea, just an example
105 *.gz = pipe: gunzip
105 *.gz = pipe: gunzip
106
106
107 [decode]
107 [decode]
108 # recompress gzip files when writing them to the working dir (we
108 # recompress gzip files when writing them to the working dir (we
109 # can safely omit "pipe:", because it's the default)
109 # can safely omit "pipe:", because it's the default)
110 *.gz = gzip
110 *.gz = gzip
111
111
112 A "tempfile:" command is a template. The string INFILE is replaced
112 A "tempfile:" command is a template. The string INFILE is replaced
113 with the name of a temporary file that contains the data to be
113 with the name of a temporary file that contains the data to be
114 filtered by the command. The string OUTFILE is replaced with the
114 filtered by the command. The string OUTFILE is replaced with the
115 name of an empty temporary file, where the filtered data must be
115 name of an empty temporary file, where the filtered data must be
116 written by the command.
116 written by the command.
117
117
118 NOTE: the tempfile mechanism is recommended for Windows systems,
118 NOTE: the tempfile mechanism is recommended for Windows systems,
119 where the standard shell I/O redirection operators often have
119 where the standard shell I/O redirection operators often have
120 strange effects. In particular, if you are doing line ending
120 strange effects. In particular, if you are doing line ending
121 conversion on Windows using the popular dos2unix and unix2dos
121 conversion on Windows using the popular dos2unix and unix2dos
122 programs, you *must* use the tempfile mechanism, as using pipes will
122 programs, you *must* use the tempfile mechanism, as using pipes will
123 corrupt the contents of your files.
123 corrupt the contents of your files.
124
124
125 Tempfile example:
125 Tempfile example:
126
126
127 [encode]
127 [encode]
128 # convert files to unix line ending conventions on checkin
128 # convert files to unix line ending conventions on checkin
129 **.txt = tempfile: dos2unix -n INFILE OUTFILE
129 **.txt = tempfile: dos2unix -n INFILE OUTFILE
130
130
131 [decode]
131 [decode]
132 # convert files to windows line ending conventions when writing
132 # convert files to windows line ending conventions when writing
133 # them to the working dir
133 # them to the working dir
134 **.txt = tempfile: unix2dos -n INFILE OUTFILE
134 **.txt = tempfile: unix2dos -n INFILE OUTFILE
135
135
136 email::
136 email::
137 Settings for extensions that send email messages.
137 Settings for extensions that send email messages.
138 from;;
138 from;;
139 Optional. Email address to use in "From" header and SMTP envelope
139 Optional. Email address to use in "From" header and SMTP envelope
140 of outgoing messages.
140 of outgoing messages.
141 method;;
141 method;;
142 Optional. Method to use to send email messages. If value is
142 Optional. Method to use to send email messages. If value is
143 "smtp" (default), use SMTP (see section "[mail]" for
143 "smtp" (default), use SMTP (see section "[mail]" for
144 configuration). Otherwise, use as name of program to run that
144 configuration). Otherwise, use as name of program to run that
145 acts like sendmail (takes "-f" option for sender, list of
145 acts like sendmail (takes "-f" option for sender, list of
146 recipients on command line, message on stdin). Normally, setting
146 recipients on command line, message on stdin). Normally, setting
147 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
147 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
148 sendmail to send messages.
148 sendmail to send messages.
149
149
150 Email example:
150 Email example:
151
151
152 [email]
152 [email]
153 from = Joseph User <joe.user@example.com>
153 from = Joseph User <joe.user@example.com>
154 method = /usr/sbin/sendmail
154 method = /usr/sbin/sendmail
155
155
156 extensions::
156 extensions::
157 Mercurial has an extension mechanism for adding new features. To
157 Mercurial has an extension mechanism for adding new features. To
158 enable an extension, create an entry for it in this section.
158 enable an extension, create an entry for it in this section.
159
159
160 If you know that the extension is already in Python's search path,
160 If you know that the extension is already in Python's search path,
161 you can give the name of the module, followed by "=", with nothing
161 you can give the name of the module, followed by "=", with nothing
162 after the "=".
162 after the "=".
163
163
164 Otherwise, give a name that you choose, followed by "=", followed by
164 Otherwise, give a name that you choose, followed by "=", followed by
165 the path to the ".py" file (including the file name extension) that
165 the path to the ".py" file (including the file name extension) that
166 defines the extension.
166 defines the extension.
167
167
168 Example for ~/.hgrc:
168 Example for ~/.hgrc:
169
169
170 [extensions]
170 [extensions]
171 # (the mq extension will get loaded from mercurial's path)
171 # (the mq extension will get loaded from mercurial's path)
172 hgext.mq =
172 hgext.mq =
173 # (this extension will get loaded from the file specified)
173 # (this extension will get loaded from the file specified)
174 myfeature = ~/.hgext/myfeature.py
174 myfeature = ~/.hgext/myfeature.py
175
175
176 hooks::
176 hooks::
177 Commands or Python functions that get automatically executed by
177 Commands or Python functions that get automatically executed by
178 various actions such as starting or finishing a commit. Multiple
178 various actions such as starting or finishing a commit. Multiple
179 hooks can be run for the same action by appending a suffix to the
179 hooks can be run for the same action by appending a suffix to the
180 action. Overriding a site-wide hook can be done by changing its
180 action. Overriding a site-wide hook can be done by changing its
181 value or setting it to an empty string.
181 value or setting it to an empty string.
182
182
183 Example .hg/hgrc:
183 Example .hg/hgrc:
184
184
185 [hooks]
185 [hooks]
186 # do not use the site-wide hook
186 # do not use the site-wide hook
187 incoming =
187 incoming =
188 incoming.email = /my/email/hook
188 incoming.email = /my/email/hook
189 incoming.autobuild = /my/build/hook
189 incoming.autobuild = /my/build/hook
190
190
191 Most hooks are run with environment variables set that give added
191 Most hooks are run with environment variables set that give added
192 useful information. For each hook below, the environment variables
192 useful information. For each hook below, the environment variables
193 it is passed are listed with names of the form "$HG_foo".
193 it is passed are listed with names of the form "$HG_foo".
194
194
195 changegroup;;
195 changegroup;;
196 Run after a changegroup has been added via push, pull or
196 Run after a changegroup has been added via push, pull or
197 unbundle. ID of the first new changeset is in $HG_NODE.
197 unbundle. ID of the first new changeset is in $HG_NODE.
198 commit;;
198 commit;;
199 Run after a changeset has been created in the local repository.
199 Run after a changeset has been created in the local repository.
200 ID of the newly created changeset is in $HG_NODE. Parent
200 ID of the newly created changeset is in $HG_NODE. Parent
201 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
201 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
202 incoming;;
202 incoming;;
203 Run after a changeset has been pulled, pushed, or unbundled into
203 Run after a changeset has been pulled, pushed, or unbundled into
204 the local repository. The ID of the newly arrived changeset is in
204 the local repository. The ID of the newly arrived changeset is in
205 $HG_NODE.
205 $HG_NODE.
206 outgoing;;
206 outgoing;;
207 Run after sending changes from local repository to another. ID of
207 Run after sending changes from local repository to another. ID of
208 first changeset sent is in $HG_NODE. Source of operation is in
208 first changeset sent is in $HG_NODE. Source of operation is in
209 $HG_SOURCE; see "preoutgoing" hook for description.
209 $HG_SOURCE; see "preoutgoing" hook for description.
210 prechangegroup;;
210 prechangegroup;;
211 Run before a changegroup is added via push, pull or unbundle.
211 Run before a changegroup is added via push, pull or unbundle.
212 Exit status 0 allows the changegroup to proceed. Non-zero status
212 Exit status 0 allows the changegroup to proceed. Non-zero status
213 will cause the push, pull or unbundle to fail.
213 will cause the push, pull or unbundle to fail.
214 precommit;;
214 precommit;;
215 Run before starting a local commit. Exit status 0 allows the
215 Run before starting a local commit. Exit status 0 allows the
216 commit to proceed. Non-zero status will cause the commit to fail.
216 commit to proceed. Non-zero status will cause the commit to fail.
217 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
217 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
218 preoutgoing;;
218 preoutgoing;;
219 Run before computing changes to send from the local repository to
219 Run before computing changes to send from the local repository to
220 another. Non-zero status will cause failure. This lets you
220 another. Non-zero status will cause failure. This lets you
221 prevent pull over http or ssh. Also prevents against local pull,
221 prevent pull over http or ssh. Also prevents against local pull,
222 push (outbound) or bundle commands, but not effective, since you
222 push (outbound) or bundle commands, but not effective, since you
223 can just copy files instead then. Source of operation is in
223 can just copy files instead then. Source of operation is in
224 $HG_SOURCE. If "serve", operation is happening on behalf of
224 $HG_SOURCE. If "serve", operation is happening on behalf of
225 remote ssh or http repository. If "push", "pull" or "bundle",
225 remote ssh or http repository. If "push", "pull" or "bundle",
226 operation is happening on behalf of repository on same system.
226 operation is happening on behalf of repository on same system.
227 pretag;;
227 pretag;;
228 Run before creating a tag. Exit status 0 allows the tag to be
228 Run before creating a tag. Exit status 0 allows the tag to be
229 created. Non-zero status will cause the tag to fail. ID of
229 created. Non-zero status will cause the tag to fail. ID of
230 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
230 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
231 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
231 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
232 pretxnchangegroup;;
232 pretxnchangegroup;;
233 Run after a changegroup has been added via push, pull or unbundle,
233 Run after a changegroup has been added via push, pull or unbundle,
234 but before the transaction has been committed. Changegroup is
234 but before the transaction has been committed. Changegroup is
235 visible to hook program. This lets you validate incoming changes
235 visible to hook program. This lets you validate incoming changes
236 before accepting them. Passed the ID of the first new changeset
236 before accepting them. Passed the ID of the first new changeset
237 in $HG_NODE. Exit status 0 allows the transaction to commit.
237 in $HG_NODE. Exit status 0 allows the transaction to commit.
238 Non-zero status will cause the transaction to be rolled back and
238 Non-zero status will cause the transaction to be rolled back and
239 the push, pull or unbundle will fail.
239 the push, pull or unbundle will fail.
240 pretxncommit;;
240 pretxncommit;;
241 Run after a changeset has been created but the transaction not yet
241 Run after a changeset has been created but the transaction not yet
242 committed. Changeset is visible to hook program. This lets you
242 committed. Changeset is visible to hook program. This lets you
243 validate commit message and changes. Exit status 0 allows the
243 validate commit message and changes. Exit status 0 allows the
244 commit to proceed. Non-zero status will cause the transaction to
244 commit to proceed. Non-zero status will cause the transaction to
245 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
245 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
246 IDs are in $HG_PARENT1 and $HG_PARENT2.
246 IDs are in $HG_PARENT1 and $HG_PARENT2.
247 preupdate;;
247 preupdate;;
248 Run before updating the working directory. Exit status 0 allows
248 Run before updating the working directory. Exit status 0 allows
249 the update to proceed. Non-zero status will prevent the update.
249 the update to proceed. Non-zero status will prevent the update.
250 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
250 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
251 of second new parent is in $HG_PARENT2.
251 of second new parent is in $HG_PARENT2.
252 tag;;
252 tag;;
253 Run after a tag is created. ID of tagged changeset is in
253 Run after a tag is created. ID of tagged changeset is in
254 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
254 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
255 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
255 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
256 update;;
256 update;;
257 Run after updating the working directory. Changeset ID of first
257 Run after updating the working directory. Changeset ID of first
258 new parent is in $HG_PARENT1. If merge, ID of second new parent
258 new parent is in $HG_PARENT1. If merge, ID of second new parent
259 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
259 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
260 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
260 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
261
261
262 Note: In earlier releases, the names of hook environment variables
262 Note: In earlier releases, the names of hook environment variables
263 did not have a "HG_" prefix. The old unprefixed names are no longer
263 did not have a "HG_" prefix. The old unprefixed names are no longer
264 provided in the environment.
264 provided in the environment.
265
265
266 The syntax for Python hooks is as follows:
266 The syntax for Python hooks is as follows:
267
267
268 hookname = python:modulename.submodule.callable
268 hookname = python:modulename.submodule.callable
269
269
270 Python hooks are run within the Mercurial process. Each hook is
270 Python hooks are run within the Mercurial process. Each hook is
271 called with at least three keyword arguments: a ui object (keyword
271 called with at least three keyword arguments: a ui object (keyword
272 "ui"), a repository object (keyword "repo"), and a "hooktype"
272 "ui"), a repository object (keyword "repo"), and a "hooktype"
273 keyword that tells what kind of hook is used. Arguments listed as
273 keyword that tells what kind of hook is used. Arguments listed as
274 environment variables above are passed as keyword arguments, with no
274 environment variables above are passed as keyword arguments, with no
275 "HG_" prefix, and names in lower case.
275 "HG_" prefix, and names in lower case.
276
276
277 A Python hook must return a "true" value to succeed. Returning a
277 A Python hook must return a "true" value to succeed. Returning a
278 "false" value or raising an exception is treated as failure of the
278 "false" value or raising an exception is treated as failure of the
279 hook.
279 hook.
280
280
281 http_proxy::
281 http_proxy::
282 Used to access web-based Mercurial repositories through a HTTP
282 Used to access web-based Mercurial repositories through a HTTP
283 proxy.
283 proxy.
284 host;;
284 host;;
285 Host name and (optional) port of the proxy server, for example
285 Host name and (optional) port of the proxy server, for example
286 "myproxy:8000".
286 "myproxy:8000".
287 no;;
287 no;;
288 Optional. Comma-separated list of host names that should bypass
288 Optional. Comma-separated list of host names that should bypass
289 the proxy.
289 the proxy.
290 passwd;;
290 passwd;;
291 Optional. Password to authenticate with at the proxy server.
291 Optional. Password to authenticate with at the proxy server.
292 user;;
292 user;;
293 Optional. User name to authenticate with at the proxy server.
293 Optional. User name to authenticate with at the proxy server.
294
294
295 smtp::
295 smtp::
296 Configuration for extensions that need to send email messages.
296 Configuration for extensions that need to send email messages.
297 host;;
297 host;;
298 Optional. Host name of mail server. Default: "mail".
298 Optional. Host name of mail server. Default: "mail".
299 port;;
299 port;;
300 Optional. Port to connect to on mail server. Default: 25.
300 Optional. Port to connect to on mail server. Default: 25.
301 tls;;
301 tls;;
302 Optional. Whether to connect to mail server using TLS. True or
302 Optional. Whether to connect to mail server using TLS. True or
303 False. Default: False.
303 False. Default: False.
304 username;;
304 username;;
305 Optional. User name to authenticate to SMTP server with.
305 Optional. User name to authenticate to SMTP server with.
306 If username is specified, password must also be specified.
306 If username is specified, password must also be specified.
307 Default: none.
307 Default: none.
308 password;;
308 password;;
309 Optional. Password to authenticate to SMTP server with.
309 Optional. Password to authenticate to SMTP server with.
310 If username is specified, password must also be specified.
310 If username is specified, password must also be specified.
311 Default: none.
311 Default: none.
312
312
313 paths::
313 paths::
314 Assigns symbolic names to repositories. The left side is the
314 Assigns symbolic names to repositories. The left side is the
315 symbolic name, and the right gives the directory or URL that is the
315 symbolic name, and the right gives the directory or URL that is the
316 location of the repository. Default paths can be declared by
316 location of the repository. Default paths can be declared by
317 setting the following entries.
317 setting the following entries.
318 default;;
318 default;;
319 Directory or URL to use when pulling if no source is specified.
319 Directory or URL to use when pulling if no source is specified.
320 Default is set to repository from which the current repository
320 Default is set to repository from which the current repository
321 was cloned.
321 was cloned.
322 default-push;;
322 default-push;;
323 Optional. Directory or URL to use when pushing if no destination
323 Optional. Directory or URL to use when pushing if no destination
324 is specified.
324 is specified.
325
325
326 ui::
326 ui::
327 User interface controls.
327 User interface controls.
328 debug;;
328 debug;;
329 Print debugging information. True or False. Default is False.
329 Print debugging information. True or False. Default is False.
330 editor;;
330 editor;;
331 The editor to use during a commit. Default is $EDITOR or "vi".
331 The editor to use during a commit. Default is $EDITOR or "vi".
332 ignore;;
332 ignore;;
333 A file to read per-user ignore patterns from. This file should be in
333 A file to read per-user ignore patterns from. This file should be in
334 the same format as a repository-wide .hgignore file. This option
334 the same format as a repository-wide .hgignore file. This option
335 supports hook syntax, so if you want to specify multiple ignore
335 supports hook syntax, so if you want to specify multiple ignore
336 files, you can do so by setting something like
336 files, you can do so by setting something like
337 "ignore.other = ~/.hgignore2". For details of the ignore file
337 "ignore.other = ~/.hgignore2". For details of the ignore file
338 format, see the hgignore(5) man page.
338 format, see the hgignore(5) man page.
339 interactive;;
339 interactive;;
340 Allow to prompt the user. True or False. Default is True.
340 Allow to prompt the user. True or False. Default is True.
341 logtemplate;;
341 logtemplate;;
342 Template string for commands that print changesets.
342 Template string for commands that print changesets.
343 style;;
343 style;;
344 Name of style to use for command output.
344 Name of style to use for command output.
345 merge;;
345 merge;;
346 The conflict resolution program to use during a manual merge.
346 The conflict resolution program to use during a manual merge.
347 Default is "hgmerge".
347 Default is "hgmerge".
348 quiet;;
348 quiet;;
349 Reduce the amount of output printed. True or False. Default is False.
349 Reduce the amount of output printed. True or False. Default is False.
350 remotecmd;;
350 remotecmd;;
351 remote command to use for clone/push/pull operations. Default is 'hg'.
351 remote command to use for clone/push/pull operations. Default is 'hg'.
352 ssh;;
352 ssh;;
353 command to use for SSH connections. Default is 'ssh'.
353 command to use for SSH connections. Default is 'ssh'.
354 timeout;;
354 timeout;;
355 The timeout used when a lock is held (in seconds), a negative value
355 The timeout used when a lock is held (in seconds), a negative value
356 means no timeout. Default is 600.
356 means no timeout. Default is 600.
357 username;;
357 username;;
358 The committer of a changeset created when running "commit".
358 The committer of a changeset created when running "commit".
359 Typically a person's name and email address, e.g. "Fred Widget
359 Typically a person's name and email address, e.g. "Fred Widget
360 <fred@example.com>". Default is $EMAIL or username@hostname, unless
360 <fred@example.com>". Default is $EMAIL or username@hostname, unless
361 username is set to an empty string, which enforces specifying the
361 username is set to an empty string, which enforces specifying the
362 username manually.
362 username manually.
363 verbose;;
363 verbose;;
364 Increase the amount of output printed. True or False. Default is False.
364 Increase the amount of output printed. True or False. Default is False.
365
365
366
366
367 web::
367 web::
368 Web interface configuration.
368 Web interface configuration.
369 accesslog;;
369 accesslog;;
370 Where to output the access log. Default is stdout.
370 Where to output the access log. Default is stdout.
371 address;;
371 address;;
372 Interface address to bind to. Default is all.
372 Interface address to bind to. Default is all.
373 allow_archive;;
373 allow_archive;;
374 List of archive format (bz2, gz, zip) allowed for downloading.
374 List of archive format (bz2, gz, zip) allowed for downloading.
375 Default is empty.
375 Default is empty.
376 allowbz2;;
376 allowbz2;;
377 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
377 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
378 Default is false.
378 Default is false.
379 allowgz;;
379 allowgz;;
380 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
380 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
381 Default is false.
381 Default is false.
382 allowpull;;
382 allowpull;;
383 Whether to allow pulling from the repository. Default is true.
383 Whether to allow pulling from the repository. Default is true.
384 allow_push;;
385 Whether to allow pushing to the repository. If empty or not set,
386 push is not allowed. If the special value "*", any remote user
387 can push, including unauthenticated users. Otherwise, the remote
388 user must have been authenticated, and the authenticated user name
389 must be present in this list (separated by whitespace or ",").
390 The contents of the allow_push list are examined after the
391 deny_push list.
384 allowzip;;
392 allowzip;;
385 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
393 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
386 Default is false. This feature creates temporary files.
394 Default is false. This feature creates temporary files.
387 baseurl;;
395 baseurl;;
388 Base URL to use when publishing URLs in other locations, so
396 Base URL to use when publishing URLs in other locations, so
389 third-party tools like email notification hooks can construct URLs.
397 third-party tools like email notification hooks can construct URLs.
390 Example: "http://hgserver/repos/"
398 Example: "http://hgserver/repos/"
391 contact;;
399 contact;;
392 Name or email address of the person in charge of the repository.
400 Name or email address of the person in charge of the repository.
393 Default is "unknown".
401 Default is "unknown".
402 deny_push;;
403 Whether to deny pushing to the repository. If empty or not set,
404 push is not denied. If the special value "*", all remote users
405 are denied push. Otherwise, unauthenticated users are all denied,
406 and any authenticated user name present in this list (separated by
407 whitespace or ",") is also denied. The contents of the deny_push
408 list are examined before the allow_push list.
394 description;;
409 description;;
395 Textual description of the repository's purpose or contents.
410 Textual description of the repository's purpose or contents.
396 Default is "unknown".
411 Default is "unknown".
397 errorlog;;
412 errorlog;;
398 Where to output the error log. Default is stderr.
413 Where to output the error log. Default is stderr.
399 ipv6;;
414 ipv6;;
400 Whether to use IPv6. Default is false.
415 Whether to use IPv6. Default is false.
401 name;;
416 name;;
402 Repository name to use in the web interface. Default is current
417 Repository name to use in the web interface. Default is current
403 working directory.
418 working directory.
404 maxchanges;;
419 maxchanges;;
405 Maximum number of changes to list on the changelog. Default is 10.
420 Maximum number of changes to list on the changelog. Default is 10.
406 maxfiles;;
421 maxfiles;;
407 Maximum number of files to list per changeset. Default is 10.
422 Maximum number of files to list per changeset. Default is 10.
408 port;;
423 port;;
409 Port to listen on. Default is 8000.
424 Port to listen on. Default is 8000.
425 push_ssl;;
426 Whether to require that inbound pushes be transported over SSL to
427 prevent password sniffing. Default is true.
410 style;;
428 style;;
411 Which template map style to use.
429 Which template map style to use.
412 templates;;
430 templates;;
413 Where to find the HTML templates. Default is install path.
431 Where to find the HTML templates. Default is install path.
414
432
415
433
416 AUTHOR
434 AUTHOR
417 ------
435 ------
418 Bryan O'Sullivan <bos@serpentine.com>.
436 Bryan O'Sullivan <bos@serpentine.com>.
419
437
420 Mercurial was written by Matt Mackall <mpm@selenic.com>.
438 Mercurial was written by Matt Mackall <mpm@selenic.com>.
421
439
422 SEE ALSO
440 SEE ALSO
423 --------
441 --------
424 hg(1), hgignore(5)
442 hg(1), hgignore(5)
425
443
426 COPYING
444 COPYING
427 -------
445 -------
428 This manual page is copyright 2005 Bryan O'Sullivan.
446 This manual page is copyright 2005 Bryan O'Sullivan.
429 Mercurial is copyright 2005, 2006 Matt Mackall.
447 Mercurial is copyright 2005, 2006 Matt Mackall.
430 Free use of this software is granted under the terms of the GNU General
448 Free use of this software is granted under the terms of the GNU General
431 Public License (GPL).
449 Public License (GPL).
@@ -1,891 +1,931 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 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 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
9 import os
10 import os.path
10 import os.path
11 import mimetypes
11 import mimetypes
12 from mercurial.demandload import demandload
12 from mercurial.demandload import demandload
13 demandload(globals(), "re zlib ConfigParser cStringIO sys tempfile")
13 demandload(globals(), "re zlib ConfigParser cStringIO sys tempfile")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
17 from mercurial.node import *
17 from mercurial.node import *
18 from mercurial.i18n import gettext as _
18 from mercurial.i18n import gettext as _
19
19
20 def _up(p):
20 def _up(p):
21 if p[0] != "/":
21 if p[0] != "/":
22 p = "/" + p
22 p = "/" + p
23 if p[-1] == "/":
23 if p[-1] == "/":
24 p = p[:-1]
24 p = p[:-1]
25 up = os.path.dirname(p)
25 up = os.path.dirname(p)
26 if up == "/":
26 if up == "/":
27 return "/"
27 return "/"
28 return up + "/"
28 return up + "/"
29
29
30 class hgweb(object):
30 class hgweb(object):
31 def __init__(self, repo, name=None):
31 def __init__(self, repo, name=None):
32 if type(repo) == type(""):
32 if type(repo) == type(""):
33 self.repo = hg.repository(ui.ui(), repo)
33 self.repo = hg.repository(ui.ui(), repo)
34 else:
34 else:
35 self.repo = repo
35 self.repo = repo
36
36
37 self.mtime = -1
37 self.mtime = -1
38 self.reponame = name
38 self.reponame = name
39 self.archives = 'zip', 'gz', 'bz2'
39 self.archives = 'zip', 'gz', 'bz2'
40 self.templatepath = self.repo.ui.config("web", "templates",
40 self.templatepath = self.repo.ui.config("web", "templates",
41 templater.templatepath())
41 templater.templatepath())
42
42
43 def refresh(self):
43 def refresh(self):
44 mtime = get_mtime(self.repo.root)
44 mtime = get_mtime(self.repo.root)
45 if mtime != self.mtime:
45 if mtime != self.mtime:
46 self.mtime = mtime
46 self.mtime = mtime
47 self.repo = hg.repository(self.repo.ui, self.repo.root)
47 self.repo = hg.repository(self.repo.ui, self.repo.root)
48 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
48 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
49 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
49 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
50 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
50 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
51
51
52 def archivelist(self, nodeid):
52 def archivelist(self, nodeid):
53 allowed = (self.repo.ui.config("web", "allow_archive", "")
53 allowed = (self.repo.ui.config("web", "allow_archive", "")
54 .replace(",", " ").split())
54 .replace(",", " ").split())
55 for i in self.archives:
55 for i in self.archives:
56 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
56 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
57 yield {"type" : i, "node" : nodeid, "url": ""}
57 yield {"type" : i, "node" : nodeid, "url": ""}
58
58
59 def listfiles(self, files, mf):
59 def listfiles(self, files, mf):
60 for f in files[:self.maxfiles]:
60 for f in files[:self.maxfiles]:
61 yield self.t("filenodelink", node=hex(mf[f]), file=f)
61 yield self.t("filenodelink", node=hex(mf[f]), file=f)
62 if len(files) > self.maxfiles:
62 if len(files) > self.maxfiles:
63 yield self.t("fileellipses")
63 yield self.t("fileellipses")
64
64
65 def listfilediffs(self, files, changeset):
65 def listfilediffs(self, files, changeset):
66 for f in files[:self.maxfiles]:
66 for f in files[:self.maxfiles]:
67 yield self.t("filedifflink", node=hex(changeset), file=f)
67 yield self.t("filedifflink", node=hex(changeset), file=f)
68 if len(files) > self.maxfiles:
68 if len(files) > self.maxfiles:
69 yield self.t("fileellipses")
69 yield self.t("fileellipses")
70
70
71 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
71 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
72 if not rev:
72 if not rev:
73 rev = lambda x: ""
73 rev = lambda x: ""
74 siblings = [s for s in siblings if s != nullid]
74 siblings = [s for s in siblings if s != nullid]
75 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
75 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
76 return
76 return
77 for s in siblings:
77 for s in siblings:
78 yield dict(node=hex(s), rev=rev(s), **args)
78 yield dict(node=hex(s), rev=rev(s), **args)
79
79
80 def renamelink(self, fl, node):
80 def renamelink(self, fl, node):
81 r = fl.renamed(node)
81 r = fl.renamed(node)
82 if r:
82 if r:
83 return [dict(file=r[0], node=hex(r[1]))]
83 return [dict(file=r[0], node=hex(r[1]))]
84 return []
84 return []
85
85
86 def showtag(self, t1, node=nullid, **args):
86 def showtag(self, t1, node=nullid, **args):
87 for t in self.repo.nodetags(node):
87 for t in self.repo.nodetags(node):
88 yield self.t(t1, tag=t, **args)
88 yield self.t(t1, tag=t, **args)
89
89
90 def diff(self, node1, node2, files):
90 def diff(self, node1, node2, files):
91 def filterfiles(filters, files):
91 def filterfiles(filters, files):
92 l = [x for x in files if x in filters]
92 l = [x for x in files if x in filters]
93
93
94 for t in filters:
94 for t in filters:
95 if t and t[-1] != os.sep:
95 if t and t[-1] != os.sep:
96 t += os.sep
96 t += os.sep
97 l += [x for x in files if x.startswith(t)]
97 l += [x for x in files if x.startswith(t)]
98 return l
98 return l
99
99
100 parity = [0]
100 parity = [0]
101 def diffblock(diff, f, fn):
101 def diffblock(diff, f, fn):
102 yield self.t("diffblock",
102 yield self.t("diffblock",
103 lines=prettyprintlines(diff),
103 lines=prettyprintlines(diff),
104 parity=parity[0],
104 parity=parity[0],
105 file=f,
105 file=f,
106 filenode=hex(fn or nullid))
106 filenode=hex(fn or nullid))
107 parity[0] = 1 - parity[0]
107 parity[0] = 1 - parity[0]
108
108
109 def prettyprintlines(diff):
109 def prettyprintlines(diff):
110 for l in diff.splitlines(1):
110 for l in diff.splitlines(1):
111 if l.startswith('+'):
111 if l.startswith('+'):
112 yield self.t("difflineplus", line=l)
112 yield self.t("difflineplus", line=l)
113 elif l.startswith('-'):
113 elif l.startswith('-'):
114 yield self.t("difflineminus", line=l)
114 yield self.t("difflineminus", line=l)
115 elif l.startswith('@'):
115 elif l.startswith('@'):
116 yield self.t("difflineat", line=l)
116 yield self.t("difflineat", line=l)
117 else:
117 else:
118 yield self.t("diffline", line=l)
118 yield self.t("diffline", line=l)
119
119
120 r = self.repo
120 r = self.repo
121 cl = r.changelog
121 cl = r.changelog
122 mf = r.manifest
122 mf = r.manifest
123 change1 = cl.read(node1)
123 change1 = cl.read(node1)
124 change2 = cl.read(node2)
124 change2 = cl.read(node2)
125 mmap1 = mf.read(change1[0])
125 mmap1 = mf.read(change1[0])
126 mmap2 = mf.read(change2[0])
126 mmap2 = mf.read(change2[0])
127 date1 = util.datestr(change1[2])
127 date1 = util.datestr(change1[2])
128 date2 = util.datestr(change2[2])
128 date2 = util.datestr(change2[2])
129
129
130 modified, added, removed, deleted, unknown = r.changes(node1, node2)
130 modified, added, removed, deleted, unknown = r.changes(node1, node2)
131 if files:
131 if files:
132 modified, added, removed = map(lambda x: filterfiles(files, x),
132 modified, added, removed = map(lambda x: filterfiles(files, x),
133 (modified, added, removed))
133 (modified, added, removed))
134
134
135 diffopts = self.repo.ui.diffopts()
135 diffopts = self.repo.ui.diffopts()
136 showfunc = diffopts['showfunc']
136 showfunc = diffopts['showfunc']
137 ignorews = diffopts['ignorews']
137 ignorews = diffopts['ignorews']
138 for f in modified:
138 for f in modified:
139 to = r.file(f).read(mmap1[f])
139 to = r.file(f).read(mmap1[f])
140 tn = r.file(f).read(mmap2[f])
140 tn = r.file(f).read(mmap2[f])
141 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
141 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
142 showfunc=showfunc, ignorews=ignorews), f, tn)
142 showfunc=showfunc, ignorews=ignorews), f, tn)
143 for f in added:
143 for f in added:
144 to = None
144 to = None
145 tn = r.file(f).read(mmap2[f])
145 tn = r.file(f).read(mmap2[f])
146 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
146 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
147 showfunc=showfunc, ignorews=ignorews), f, tn)
147 showfunc=showfunc, ignorews=ignorews), f, tn)
148 for f in removed:
148 for f in removed:
149 to = r.file(f).read(mmap1[f])
149 to = r.file(f).read(mmap1[f])
150 tn = None
150 tn = None
151 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
151 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
152 showfunc=showfunc, ignorews=ignorews), f, tn)
152 showfunc=showfunc, ignorews=ignorews), f, tn)
153
153
154 def changelog(self, pos):
154 def changelog(self, pos):
155 def changenav(**map):
155 def changenav(**map):
156 def seq(factor, maxchanges=None):
156 def seq(factor, maxchanges=None):
157 if maxchanges:
157 if maxchanges:
158 yield maxchanges
158 yield maxchanges
159 if maxchanges >= 20 and maxchanges <= 40:
159 if maxchanges >= 20 and maxchanges <= 40:
160 yield 50
160 yield 50
161 else:
161 else:
162 yield 1 * factor
162 yield 1 * factor
163 yield 3 * factor
163 yield 3 * factor
164 for f in seq(factor * 10):
164 for f in seq(factor * 10):
165 yield f
165 yield f
166
166
167 l = []
167 l = []
168 last = 0
168 last = 0
169 for f in seq(1, self.maxchanges):
169 for f in seq(1, self.maxchanges):
170 if f < self.maxchanges or f <= last:
170 if f < self.maxchanges or f <= last:
171 continue
171 continue
172 if f > count:
172 if f > count:
173 break
173 break
174 last = f
174 last = f
175 r = "%d" % f
175 r = "%d" % f
176 if pos + f < count:
176 if pos + f < count:
177 l.append(("+" + r, pos + f))
177 l.append(("+" + r, pos + f))
178 if pos - f >= 0:
178 if pos - f >= 0:
179 l.insert(0, ("-" + r, pos - f))
179 l.insert(0, ("-" + r, pos - f))
180
180
181 yield {"rev": 0, "label": "(0)"}
181 yield {"rev": 0, "label": "(0)"}
182
182
183 for label, rev in l:
183 for label, rev in l:
184 yield {"label": label, "rev": rev}
184 yield {"label": label, "rev": rev}
185
185
186 yield {"label": "tip", "rev": "tip"}
186 yield {"label": "tip", "rev": "tip"}
187
187
188 def changelist(**map):
188 def changelist(**map):
189 parity = (start - end) & 1
189 parity = (start - end) & 1
190 cl = self.repo.changelog
190 cl = self.repo.changelog
191 l = [] # build a list in forward order for efficiency
191 l = [] # build a list in forward order for efficiency
192 for i in range(start, end):
192 for i in range(start, end):
193 n = cl.node(i)
193 n = cl.node(i)
194 changes = cl.read(n)
194 changes = cl.read(n)
195 hn = hex(n)
195 hn = hex(n)
196
196
197 l.insert(0, {"parity": parity,
197 l.insert(0, {"parity": parity,
198 "author": changes[1],
198 "author": changes[1],
199 "parent": self.siblings(cl.parents(n), cl.rev,
199 "parent": self.siblings(cl.parents(n), cl.rev,
200 cl.rev(n) - 1),
200 cl.rev(n) - 1),
201 "child": self.siblings(cl.children(n), cl.rev,
201 "child": self.siblings(cl.children(n), cl.rev,
202 cl.rev(n) + 1),
202 cl.rev(n) + 1),
203 "changelogtag": self.showtag("changelogtag",n),
203 "changelogtag": self.showtag("changelogtag",n),
204 "manifest": hex(changes[0]),
204 "manifest": hex(changes[0]),
205 "desc": changes[4],
205 "desc": changes[4],
206 "date": changes[2],
206 "date": changes[2],
207 "files": self.listfilediffs(changes[3], n),
207 "files": self.listfilediffs(changes[3], n),
208 "rev": i,
208 "rev": i,
209 "node": hn})
209 "node": hn})
210 parity = 1 - parity
210 parity = 1 - parity
211
211
212 for e in l:
212 for e in l:
213 yield e
213 yield e
214
214
215 cl = self.repo.changelog
215 cl = self.repo.changelog
216 mf = cl.read(cl.tip())[0]
216 mf = cl.read(cl.tip())[0]
217 count = cl.count()
217 count = cl.count()
218 start = max(0, pos - self.maxchanges + 1)
218 start = max(0, pos - self.maxchanges + 1)
219 end = min(count, start + self.maxchanges)
219 end = min(count, start + self.maxchanges)
220 pos = end - 1
220 pos = end - 1
221
221
222 yield self.t('changelog',
222 yield self.t('changelog',
223 changenav=changenav,
223 changenav=changenav,
224 manifest=hex(mf),
224 manifest=hex(mf),
225 rev=pos, changesets=count, entries=changelist,
225 rev=pos, changesets=count, entries=changelist,
226 archives=self.archivelist("tip"))
226 archives=self.archivelist("tip"))
227
227
228 def search(self, query):
228 def search(self, query):
229
229
230 def changelist(**map):
230 def changelist(**map):
231 cl = self.repo.changelog
231 cl = self.repo.changelog
232 count = 0
232 count = 0
233 qw = query.lower().split()
233 qw = query.lower().split()
234
234
235 def revgen():
235 def revgen():
236 for i in range(cl.count() - 1, 0, -100):
236 for i in range(cl.count() - 1, 0, -100):
237 l = []
237 l = []
238 for j in range(max(0, i - 100), i):
238 for j in range(max(0, i - 100), i):
239 n = cl.node(j)
239 n = cl.node(j)
240 changes = cl.read(n)
240 changes = cl.read(n)
241 l.append((n, j, changes))
241 l.append((n, j, changes))
242 l.reverse()
242 l.reverse()
243 for e in l:
243 for e in l:
244 yield e
244 yield e
245
245
246 for n, i, changes in revgen():
246 for n, i, changes in revgen():
247 miss = 0
247 miss = 0
248 for q in qw:
248 for q in qw:
249 if not (q in changes[1].lower() or
249 if not (q in changes[1].lower() or
250 q in changes[4].lower() or
250 q in changes[4].lower() or
251 q in " ".join(changes[3][:20]).lower()):
251 q in " ".join(changes[3][:20]).lower()):
252 miss = 1
252 miss = 1
253 break
253 break
254 if miss:
254 if miss:
255 continue
255 continue
256
256
257 count += 1
257 count += 1
258 hn = hex(n)
258 hn = hex(n)
259
259
260 yield self.t('searchentry',
260 yield self.t('searchentry',
261 parity=count & 1,
261 parity=count & 1,
262 author=changes[1],
262 author=changes[1],
263 parent=self.siblings(cl.parents(n), cl.rev),
263 parent=self.siblings(cl.parents(n), cl.rev),
264 child=self.siblings(cl.children(n), cl.rev),
264 child=self.siblings(cl.children(n), cl.rev),
265 changelogtag=self.showtag("changelogtag",n),
265 changelogtag=self.showtag("changelogtag",n),
266 manifest=hex(changes[0]),
266 manifest=hex(changes[0]),
267 desc=changes[4],
267 desc=changes[4],
268 date=changes[2],
268 date=changes[2],
269 files=self.listfilediffs(changes[3], n),
269 files=self.listfilediffs(changes[3], n),
270 rev=i,
270 rev=i,
271 node=hn)
271 node=hn)
272
272
273 if count >= self.maxchanges:
273 if count >= self.maxchanges:
274 break
274 break
275
275
276 cl = self.repo.changelog
276 cl = self.repo.changelog
277 mf = cl.read(cl.tip())[0]
277 mf = cl.read(cl.tip())[0]
278
278
279 yield self.t('search',
279 yield self.t('search',
280 query=query,
280 query=query,
281 manifest=hex(mf),
281 manifest=hex(mf),
282 entries=changelist)
282 entries=changelist)
283
283
284 def changeset(self, nodeid):
284 def changeset(self, nodeid):
285 cl = self.repo.changelog
285 cl = self.repo.changelog
286 n = self.repo.lookup(nodeid)
286 n = self.repo.lookup(nodeid)
287 nodeid = hex(n)
287 nodeid = hex(n)
288 changes = cl.read(n)
288 changes = cl.read(n)
289 p1 = cl.parents(n)[0]
289 p1 = cl.parents(n)[0]
290
290
291 files = []
291 files = []
292 mf = self.repo.manifest.read(changes[0])
292 mf = self.repo.manifest.read(changes[0])
293 for f in changes[3]:
293 for f in changes[3]:
294 files.append(self.t("filenodelink",
294 files.append(self.t("filenodelink",
295 filenode=hex(mf.get(f, nullid)), file=f))
295 filenode=hex(mf.get(f, nullid)), file=f))
296
296
297 def diff(**map):
297 def diff(**map):
298 yield self.diff(p1, n, None)
298 yield self.diff(p1, n, None)
299
299
300 yield self.t('changeset',
300 yield self.t('changeset',
301 diff=diff,
301 diff=diff,
302 rev=cl.rev(n),
302 rev=cl.rev(n),
303 node=nodeid,
303 node=nodeid,
304 parent=self.siblings(cl.parents(n), cl.rev),
304 parent=self.siblings(cl.parents(n), cl.rev),
305 child=self.siblings(cl.children(n), cl.rev),
305 child=self.siblings(cl.children(n), cl.rev),
306 changesettag=self.showtag("changesettag",n),
306 changesettag=self.showtag("changesettag",n),
307 manifest=hex(changes[0]),
307 manifest=hex(changes[0]),
308 author=changes[1],
308 author=changes[1],
309 desc=changes[4],
309 desc=changes[4],
310 date=changes[2],
310 date=changes[2],
311 files=files,
311 files=files,
312 archives=self.archivelist(nodeid))
312 archives=self.archivelist(nodeid))
313
313
314 def filelog(self, f, filenode):
314 def filelog(self, f, filenode):
315 cl = self.repo.changelog
315 cl = self.repo.changelog
316 fl = self.repo.file(f)
316 fl = self.repo.file(f)
317 filenode = hex(fl.lookup(filenode))
317 filenode = hex(fl.lookup(filenode))
318 count = fl.count()
318 count = fl.count()
319
319
320 def entries(**map):
320 def entries(**map):
321 l = []
321 l = []
322 parity = (count - 1) & 1
322 parity = (count - 1) & 1
323
323
324 for i in range(count):
324 for i in range(count):
325 n = fl.node(i)
325 n = fl.node(i)
326 lr = fl.linkrev(n)
326 lr = fl.linkrev(n)
327 cn = cl.node(lr)
327 cn = cl.node(lr)
328 cs = cl.read(cl.node(lr))
328 cs = cl.read(cl.node(lr))
329
329
330 l.insert(0, {"parity": parity,
330 l.insert(0, {"parity": parity,
331 "filenode": hex(n),
331 "filenode": hex(n),
332 "filerev": i,
332 "filerev": i,
333 "file": f,
333 "file": f,
334 "node": hex(cn),
334 "node": hex(cn),
335 "author": cs[1],
335 "author": cs[1],
336 "date": cs[2],
336 "date": cs[2],
337 "rename": self.renamelink(fl, n),
337 "rename": self.renamelink(fl, n),
338 "parent": self.siblings(fl.parents(n),
338 "parent": self.siblings(fl.parents(n),
339 fl.rev, file=f),
339 fl.rev, file=f),
340 "child": self.siblings(fl.children(n),
340 "child": self.siblings(fl.children(n),
341 fl.rev, file=f),
341 fl.rev, file=f),
342 "desc": cs[4]})
342 "desc": cs[4]})
343 parity = 1 - parity
343 parity = 1 - parity
344
344
345 for e in l:
345 for e in l:
346 yield e
346 yield e
347
347
348 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
348 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
349
349
350 def filerevision(self, f, node):
350 def filerevision(self, f, node):
351 fl = self.repo.file(f)
351 fl = self.repo.file(f)
352 n = fl.lookup(node)
352 n = fl.lookup(node)
353 node = hex(n)
353 node = hex(n)
354 text = fl.read(n)
354 text = fl.read(n)
355 changerev = fl.linkrev(n)
355 changerev = fl.linkrev(n)
356 cl = self.repo.changelog
356 cl = self.repo.changelog
357 cn = cl.node(changerev)
357 cn = cl.node(changerev)
358 cs = cl.read(cn)
358 cs = cl.read(cn)
359 mfn = cs[0]
359 mfn = cs[0]
360
360
361 mt = mimetypes.guess_type(f)[0]
361 mt = mimetypes.guess_type(f)[0]
362 rawtext = text
362 rawtext = text
363 if util.binary(text):
363 if util.binary(text):
364 mt = mt or 'application/octet-stream'
364 mt = mt or 'application/octet-stream'
365 text = "(binary:%s)" % mt
365 text = "(binary:%s)" % mt
366 mt = mt or 'text/plain'
366 mt = mt or 'text/plain'
367
367
368 def lines():
368 def lines():
369 for l, t in enumerate(text.splitlines(1)):
369 for l, t in enumerate(text.splitlines(1)):
370 yield {"line": t,
370 yield {"line": t,
371 "linenumber": "% 6d" % (l + 1),
371 "linenumber": "% 6d" % (l + 1),
372 "parity": l & 1}
372 "parity": l & 1}
373
373
374 yield self.t("filerevision",
374 yield self.t("filerevision",
375 file=f,
375 file=f,
376 filenode=node,
376 filenode=node,
377 path=_up(f),
377 path=_up(f),
378 text=lines(),
378 text=lines(),
379 raw=rawtext,
379 raw=rawtext,
380 mimetype=mt,
380 mimetype=mt,
381 rev=changerev,
381 rev=changerev,
382 node=hex(cn),
382 node=hex(cn),
383 manifest=hex(mfn),
383 manifest=hex(mfn),
384 author=cs[1],
384 author=cs[1],
385 date=cs[2],
385 date=cs[2],
386 parent=self.siblings(fl.parents(n), fl.rev, file=f),
386 parent=self.siblings(fl.parents(n), fl.rev, file=f),
387 child=self.siblings(fl.children(n), fl.rev, file=f),
387 child=self.siblings(fl.children(n), fl.rev, file=f),
388 rename=self.renamelink(fl, n),
388 rename=self.renamelink(fl, n),
389 permissions=self.repo.manifest.readflags(mfn)[f])
389 permissions=self.repo.manifest.readflags(mfn)[f])
390
390
391 def fileannotate(self, f, node):
391 def fileannotate(self, f, node):
392 bcache = {}
392 bcache = {}
393 ncache = {}
393 ncache = {}
394 fl = self.repo.file(f)
394 fl = self.repo.file(f)
395 n = fl.lookup(node)
395 n = fl.lookup(node)
396 node = hex(n)
396 node = hex(n)
397 changerev = fl.linkrev(n)
397 changerev = fl.linkrev(n)
398
398
399 cl = self.repo.changelog
399 cl = self.repo.changelog
400 cn = cl.node(changerev)
400 cn = cl.node(changerev)
401 cs = cl.read(cn)
401 cs = cl.read(cn)
402 mfn = cs[0]
402 mfn = cs[0]
403
403
404 def annotate(**map):
404 def annotate(**map):
405 parity = 1
405 parity = 1
406 last = None
406 last = None
407 for r, l in fl.annotate(n):
407 for r, l in fl.annotate(n):
408 try:
408 try:
409 cnode = ncache[r]
409 cnode = ncache[r]
410 except KeyError:
410 except KeyError:
411 cnode = ncache[r] = self.repo.changelog.node(r)
411 cnode = ncache[r] = self.repo.changelog.node(r)
412
412
413 try:
413 try:
414 name = bcache[r]
414 name = bcache[r]
415 except KeyError:
415 except KeyError:
416 cl = self.repo.changelog.read(cnode)
416 cl = self.repo.changelog.read(cnode)
417 bcache[r] = name = self.repo.ui.shortuser(cl[1])
417 bcache[r] = name = self.repo.ui.shortuser(cl[1])
418
418
419 if last != cnode:
419 if last != cnode:
420 parity = 1 - parity
420 parity = 1 - parity
421 last = cnode
421 last = cnode
422
422
423 yield {"parity": parity,
423 yield {"parity": parity,
424 "node": hex(cnode),
424 "node": hex(cnode),
425 "rev": r,
425 "rev": r,
426 "author": name,
426 "author": name,
427 "file": f,
427 "file": f,
428 "line": l}
428 "line": l}
429
429
430 yield self.t("fileannotate",
430 yield self.t("fileannotate",
431 file=f,
431 file=f,
432 filenode=node,
432 filenode=node,
433 annotate=annotate,
433 annotate=annotate,
434 path=_up(f),
434 path=_up(f),
435 rev=changerev,
435 rev=changerev,
436 node=hex(cn),
436 node=hex(cn),
437 manifest=hex(mfn),
437 manifest=hex(mfn),
438 author=cs[1],
438 author=cs[1],
439 date=cs[2],
439 date=cs[2],
440 rename=self.renamelink(fl, n),
440 rename=self.renamelink(fl, n),
441 parent=self.siblings(fl.parents(n), fl.rev, file=f),
441 parent=self.siblings(fl.parents(n), fl.rev, file=f),
442 child=self.siblings(fl.children(n), fl.rev, file=f),
442 child=self.siblings(fl.children(n), fl.rev, file=f),
443 permissions=self.repo.manifest.readflags(mfn)[f])
443 permissions=self.repo.manifest.readflags(mfn)[f])
444
444
445 def manifest(self, mnode, path):
445 def manifest(self, mnode, path):
446 man = self.repo.manifest
446 man = self.repo.manifest
447 mn = man.lookup(mnode)
447 mn = man.lookup(mnode)
448 mnode = hex(mn)
448 mnode = hex(mn)
449 mf = man.read(mn)
449 mf = man.read(mn)
450 rev = man.rev(mn)
450 rev = man.rev(mn)
451 changerev = man.linkrev(mn)
451 changerev = man.linkrev(mn)
452 node = self.repo.changelog.node(changerev)
452 node = self.repo.changelog.node(changerev)
453 mff = man.readflags(mn)
453 mff = man.readflags(mn)
454
454
455 files = {}
455 files = {}
456
456
457 p = path[1:]
457 p = path[1:]
458 if p and p[-1] != "/":
458 if p and p[-1] != "/":
459 p += "/"
459 p += "/"
460 l = len(p)
460 l = len(p)
461
461
462 for f,n in mf.items():
462 for f,n in mf.items():
463 if f[:l] != p:
463 if f[:l] != p:
464 continue
464 continue
465 remain = f[l:]
465 remain = f[l:]
466 if "/" in remain:
466 if "/" in remain:
467 short = remain[:remain.find("/") + 1] # bleah
467 short = remain[:remain.find("/") + 1] # bleah
468 files[short] = (f, None)
468 files[short] = (f, None)
469 else:
469 else:
470 short = os.path.basename(remain)
470 short = os.path.basename(remain)
471 files[short] = (f, n)
471 files[short] = (f, n)
472
472
473 def filelist(**map):
473 def filelist(**map):
474 parity = 0
474 parity = 0
475 fl = files.keys()
475 fl = files.keys()
476 fl.sort()
476 fl.sort()
477 for f in fl:
477 for f in fl:
478 full, fnode = files[f]
478 full, fnode = files[f]
479 if not fnode:
479 if not fnode:
480 continue
480 continue
481
481
482 yield {"file": full,
482 yield {"file": full,
483 "manifest": mnode,
483 "manifest": mnode,
484 "filenode": hex(fnode),
484 "filenode": hex(fnode),
485 "parity": parity,
485 "parity": parity,
486 "basename": f,
486 "basename": f,
487 "permissions": mff[full]}
487 "permissions": mff[full]}
488 parity = 1 - parity
488 parity = 1 - parity
489
489
490 def dirlist(**map):
490 def dirlist(**map):
491 parity = 0
491 parity = 0
492 fl = files.keys()
492 fl = files.keys()
493 fl.sort()
493 fl.sort()
494 for f in fl:
494 for f in fl:
495 full, fnode = files[f]
495 full, fnode = files[f]
496 if fnode:
496 if fnode:
497 continue
497 continue
498
498
499 yield {"parity": parity,
499 yield {"parity": parity,
500 "path": os.path.join(path, f),
500 "path": os.path.join(path, f),
501 "manifest": mnode,
501 "manifest": mnode,
502 "basename": f[:-1]}
502 "basename": f[:-1]}
503 parity = 1 - parity
503 parity = 1 - parity
504
504
505 yield self.t("manifest",
505 yield self.t("manifest",
506 manifest=mnode,
506 manifest=mnode,
507 rev=rev,
507 rev=rev,
508 node=hex(node),
508 node=hex(node),
509 path=path,
509 path=path,
510 up=_up(path),
510 up=_up(path),
511 fentries=filelist,
511 fentries=filelist,
512 dentries=dirlist,
512 dentries=dirlist,
513 archives=self.archivelist(hex(node)))
513 archives=self.archivelist(hex(node)))
514
514
515 def tags(self):
515 def tags(self):
516 cl = self.repo.changelog
516 cl = self.repo.changelog
517 mf = cl.read(cl.tip())[0]
517 mf = cl.read(cl.tip())[0]
518
518
519 i = self.repo.tagslist()
519 i = self.repo.tagslist()
520 i.reverse()
520 i.reverse()
521
521
522 def entries(notip=False, **map):
522 def entries(notip=False, **map):
523 parity = 0
523 parity = 0
524 for k,n in i:
524 for k,n in i:
525 if notip and k == "tip": continue
525 if notip and k == "tip": continue
526 yield {"parity": parity,
526 yield {"parity": parity,
527 "tag": k,
527 "tag": k,
528 "tagmanifest": hex(cl.read(n)[0]),
528 "tagmanifest": hex(cl.read(n)[0]),
529 "date": cl.read(n)[2],
529 "date": cl.read(n)[2],
530 "node": hex(n)}
530 "node": hex(n)}
531 parity = 1 - parity
531 parity = 1 - parity
532
532
533 yield self.t("tags",
533 yield self.t("tags",
534 manifest=hex(mf),
534 manifest=hex(mf),
535 entries=lambda **x: entries(False, **x),
535 entries=lambda **x: entries(False, **x),
536 entriesnotip=lambda **x: entries(True, **x))
536 entriesnotip=lambda **x: entries(True, **x))
537
537
538 def summary(self):
538 def summary(self):
539 cl = self.repo.changelog
539 cl = self.repo.changelog
540 mf = cl.read(cl.tip())[0]
540 mf = cl.read(cl.tip())[0]
541
541
542 i = self.repo.tagslist()
542 i = self.repo.tagslist()
543 i.reverse()
543 i.reverse()
544
544
545 def tagentries(**map):
545 def tagentries(**map):
546 parity = 0
546 parity = 0
547 count = 0
547 count = 0
548 for k,n in i:
548 for k,n in i:
549 if k == "tip": # skip tip
549 if k == "tip": # skip tip
550 continue;
550 continue;
551
551
552 count += 1
552 count += 1
553 if count > 10: # limit to 10 tags
553 if count > 10: # limit to 10 tags
554 break;
554 break;
555
555
556 c = cl.read(n)
556 c = cl.read(n)
557 m = c[0]
557 m = c[0]
558 t = c[2]
558 t = c[2]
559
559
560 yield self.t("tagentry",
560 yield self.t("tagentry",
561 parity = parity,
561 parity = parity,
562 tag = k,
562 tag = k,
563 node = hex(n),
563 node = hex(n),
564 date = t,
564 date = t,
565 tagmanifest = hex(m))
565 tagmanifest = hex(m))
566 parity = 1 - parity
566 parity = 1 - parity
567
567
568 def changelist(**map):
568 def changelist(**map):
569 parity = 0
569 parity = 0
570 cl = self.repo.changelog
570 cl = self.repo.changelog
571 l = [] # build a list in forward order for efficiency
571 l = [] # build a list in forward order for efficiency
572 for i in range(start, end):
572 for i in range(start, end):
573 n = cl.node(i)
573 n = cl.node(i)
574 changes = cl.read(n)
574 changes = cl.read(n)
575 hn = hex(n)
575 hn = hex(n)
576 t = changes[2]
576 t = changes[2]
577
577
578 l.insert(0, self.t(
578 l.insert(0, self.t(
579 'shortlogentry',
579 'shortlogentry',
580 parity = parity,
580 parity = parity,
581 author = changes[1],
581 author = changes[1],
582 manifest = hex(changes[0]),
582 manifest = hex(changes[0]),
583 desc = changes[4],
583 desc = changes[4],
584 date = t,
584 date = t,
585 rev = i,
585 rev = i,
586 node = hn))
586 node = hn))
587 parity = 1 - parity
587 parity = 1 - parity
588
588
589 yield l
589 yield l
590
590
591 cl = self.repo.changelog
591 cl = self.repo.changelog
592 mf = cl.read(cl.tip())[0]
592 mf = cl.read(cl.tip())[0]
593 count = cl.count()
593 count = cl.count()
594 start = max(0, count - self.maxchanges)
594 start = max(0, count - self.maxchanges)
595 end = min(count, start + self.maxchanges)
595 end = min(count, start + self.maxchanges)
596
596
597 yield self.t("summary",
597 yield self.t("summary",
598 desc = self.repo.ui.config("web", "description", "unknown"),
598 desc = self.repo.ui.config("web", "description", "unknown"),
599 owner = (self.repo.ui.config("ui", "username") or # preferred
599 owner = (self.repo.ui.config("ui", "username") or # preferred
600 self.repo.ui.config("web", "contact") or # deprecated
600 self.repo.ui.config("web", "contact") or # deprecated
601 self.repo.ui.config("web", "author", "unknown")), # also
601 self.repo.ui.config("web", "author", "unknown")), # also
602 lastchange = (0, 0), # FIXME
602 lastchange = (0, 0), # FIXME
603 manifest = hex(mf),
603 manifest = hex(mf),
604 tags = tagentries,
604 tags = tagentries,
605 shortlog = changelist)
605 shortlog = changelist)
606
606
607 def filediff(self, file, changeset):
607 def filediff(self, file, changeset):
608 cl = self.repo.changelog
608 cl = self.repo.changelog
609 n = self.repo.lookup(changeset)
609 n = self.repo.lookup(changeset)
610 changeset = hex(n)
610 changeset = hex(n)
611 p1 = cl.parents(n)[0]
611 p1 = cl.parents(n)[0]
612 cs = cl.read(n)
612 cs = cl.read(n)
613 mf = self.repo.manifest.read(cs[0])
613 mf = self.repo.manifest.read(cs[0])
614
614
615 def diff(**map):
615 def diff(**map):
616 yield self.diff(p1, n, [file])
616 yield self.diff(p1, n, [file])
617
617
618 yield self.t("filediff",
618 yield self.t("filediff",
619 file=file,
619 file=file,
620 filenode=hex(mf.get(file, nullid)),
620 filenode=hex(mf.get(file, nullid)),
621 node=changeset,
621 node=changeset,
622 rev=self.repo.changelog.rev(n),
622 rev=self.repo.changelog.rev(n),
623 parent=self.siblings(cl.parents(n), cl.rev),
623 parent=self.siblings(cl.parents(n), cl.rev),
624 child=self.siblings(cl.children(n), cl.rev),
624 child=self.siblings(cl.children(n), cl.rev),
625 diff=diff)
625 diff=diff)
626
626
627 archive_specs = {
627 archive_specs = {
628 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
628 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
629 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
629 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
630 'zip': ('application/zip', 'zip', '.zip', None),
630 'zip': ('application/zip', 'zip', '.zip', None),
631 }
631 }
632
632
633 def archive(self, req, cnode, type_):
633 def archive(self, req, cnode, type_):
634 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
634 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
635 name = "%s-%s" % (reponame, short(cnode))
635 name = "%s-%s" % (reponame, short(cnode))
636 mimetype, artype, extension, encoding = self.archive_specs[type_]
636 mimetype, artype, extension, encoding = self.archive_specs[type_]
637 headers = [('Content-type', mimetype),
637 headers = [('Content-type', mimetype),
638 ('Content-disposition', 'attachment; filename=%s%s' %
638 ('Content-disposition', 'attachment; filename=%s%s' %
639 (name, extension))]
639 (name, extension))]
640 if encoding:
640 if encoding:
641 headers.append(('Content-encoding', encoding))
641 headers.append(('Content-encoding', encoding))
642 req.header(headers)
642 req.header(headers)
643 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
643 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
644
644
645 # add tags to things
645 # add tags to things
646 # tags -> list of changesets corresponding to tags
646 # tags -> list of changesets corresponding to tags
647 # find tag, changeset, file
647 # find tag, changeset, file
648
648
649 def cleanpath(self, path):
649 def cleanpath(self, path):
650 p = util.normpath(path)
650 p = util.normpath(path)
651 if p[:2] == "..":
651 if p[:2] == "..":
652 raise Exception("suspicious path")
652 raise Exception("suspicious path")
653 return p
653 return p
654
654
655 def run(self, req=hgrequest()):
655 def run(self, req=hgrequest()):
656 def header(**map):
656 def header(**map):
657 yield self.t("header", **map)
657 yield self.t("header", **map)
658
658
659 def footer(**map):
659 def footer(**map):
660 yield self.t("footer",
660 yield self.t("footer",
661 motd=self.repo.ui.config("web", "motd", ""),
661 motd=self.repo.ui.config("web", "motd", ""),
662 **map)
662 **map)
663
663
664 def expand_form(form):
664 def expand_form(form):
665 shortcuts = {
665 shortcuts = {
666 'cl': [('cmd', ['changelog']), ('rev', None)],
666 'cl': [('cmd', ['changelog']), ('rev', None)],
667 'cs': [('cmd', ['changeset']), ('node', None)],
667 'cs': [('cmd', ['changeset']), ('node', None)],
668 'f': [('cmd', ['file']), ('filenode', None)],
668 'f': [('cmd', ['file']), ('filenode', None)],
669 'fl': [('cmd', ['filelog']), ('filenode', None)],
669 'fl': [('cmd', ['filelog']), ('filenode', None)],
670 'fd': [('cmd', ['filediff']), ('node', None)],
670 'fd': [('cmd', ['filediff']), ('node', None)],
671 'fa': [('cmd', ['annotate']), ('filenode', None)],
671 'fa': [('cmd', ['annotate']), ('filenode', None)],
672 'mf': [('cmd', ['manifest']), ('manifest', None)],
672 'mf': [('cmd', ['manifest']), ('manifest', None)],
673 'ca': [('cmd', ['archive']), ('node', None)],
673 'ca': [('cmd', ['archive']), ('node', None)],
674 'tags': [('cmd', ['tags'])],
674 'tags': [('cmd', ['tags'])],
675 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
675 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
676 'static': [('cmd', ['static']), ('file', None)]
676 'static': [('cmd', ['static']), ('file', None)]
677 }
677 }
678
678
679 for k in shortcuts.iterkeys():
679 for k in shortcuts.iterkeys():
680 if form.has_key(k):
680 if form.has_key(k):
681 for name, value in shortcuts[k]:
681 for name, value in shortcuts[k]:
682 if value is None:
682 if value is None:
683 value = form[k]
683 value = form[k]
684 form[name] = value
684 form[name] = value
685 del form[k]
685 del form[k]
686
686
687 self.refresh()
687 self.refresh()
688
688
689 expand_form(req.form)
689 expand_form(req.form)
690
690
691 m = os.path.join(self.templatepath, "map")
691 m = os.path.join(self.templatepath, "map")
692 style = self.repo.ui.config("web", "style", "")
692 style = self.repo.ui.config("web", "style", "")
693 if req.form.has_key('style'):
693 if req.form.has_key('style'):
694 style = req.form['style'][0]
694 style = req.form['style'][0]
695 if style:
695 if style:
696 b = os.path.basename("map-" + style)
696 b = os.path.basename("map-" + style)
697 p = os.path.join(self.templatepath, b)
697 p = os.path.join(self.templatepath, b)
698 if os.path.isfile(p):
698 if os.path.isfile(p):
699 m = p
699 m = p
700
700
701 port = req.env["SERVER_PORT"]
701 port = req.env["SERVER_PORT"]
702 port = port != "80" and (":" + port) or ""
702 port = port != "80" and (":" + port) or ""
703 uri = req.env["REQUEST_URI"]
703 uri = req.env["REQUEST_URI"]
704 if "?" in uri:
704 if "?" in uri:
705 uri = uri.split("?")[0]
705 uri = uri.split("?")[0]
706 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
706 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
707 if not self.reponame:
707 if not self.reponame:
708 self.reponame = (self.repo.ui.config("web", "name")
708 self.reponame = (self.repo.ui.config("web", "name")
709 or uri.strip('/') or self.repo.root)
709 or uri.strip('/') or self.repo.root)
710
710
711 self.t = templater.templater(m, templater.common_filters,
711 self.t = templater.templater(m, templater.common_filters,
712 defaults={"url": url,
712 defaults={"url": url,
713 "repo": self.reponame,
713 "repo": self.reponame,
714 "header": header,
714 "header": header,
715 "footer": footer,
715 "footer": footer,
716 })
716 })
717
717
718 if not req.form.has_key('cmd'):
718 if not req.form.has_key('cmd'):
719 req.form['cmd'] = [self.t.cache['default'],]
719 req.form['cmd'] = [self.t.cache['default'],]
720
720
721 cmd = req.form['cmd'][0]
721 cmd = req.form['cmd'][0]
722
722
723 method = getattr(self, 'do_' + cmd, None)
723 method = getattr(self, 'do_' + cmd, None)
724 if method:
724 if method:
725 method(req)
725 method(req)
726 else:
726 else:
727 req.write(self.t("error"))
727 req.write(self.t("error"))
728 req.done()
728 req.done()
729
729
730 def do_changelog(self, req):
730 def do_changelog(self, req):
731 hi = self.repo.changelog.count() - 1
731 hi = self.repo.changelog.count() - 1
732 if req.form.has_key('rev'):
732 if req.form.has_key('rev'):
733 hi = req.form['rev'][0]
733 hi = req.form['rev'][0]
734 try:
734 try:
735 hi = self.repo.changelog.rev(self.repo.lookup(hi))
735 hi = self.repo.changelog.rev(self.repo.lookup(hi))
736 except hg.RepoError:
736 except hg.RepoError:
737 req.write(self.search(hi)) # XXX redirect to 404 page?
737 req.write(self.search(hi)) # XXX redirect to 404 page?
738 return
738 return
739
739
740 req.write(self.changelog(hi))
740 req.write(self.changelog(hi))
741
741
742 def do_changeset(self, req):
742 def do_changeset(self, req):
743 req.write(self.changeset(req.form['node'][0]))
743 req.write(self.changeset(req.form['node'][0]))
744
744
745 def do_manifest(self, req):
745 def do_manifest(self, req):
746 req.write(self.manifest(req.form['manifest'][0],
746 req.write(self.manifest(req.form['manifest'][0],
747 self.cleanpath(req.form['path'][0])))
747 self.cleanpath(req.form['path'][0])))
748
748
749 def do_tags(self, req):
749 def do_tags(self, req):
750 req.write(self.tags())
750 req.write(self.tags())
751
751
752 def do_summary(self, req):
752 def do_summary(self, req):
753 req.write(self.summary())
753 req.write(self.summary())
754
754
755 def do_filediff(self, req):
755 def do_filediff(self, req):
756 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
756 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
757 req.form['node'][0]))
757 req.form['node'][0]))
758
758
759 def do_file(self, req):
759 def do_file(self, req):
760 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
760 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
761 req.form['filenode'][0]))
761 req.form['filenode'][0]))
762
762
763 def do_annotate(self, req):
763 def do_annotate(self, req):
764 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
764 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
765 req.form['filenode'][0]))
765 req.form['filenode'][0]))
766
766
767 def do_filelog(self, req):
767 def do_filelog(self, req):
768 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
768 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
769 req.form['filenode'][0]))
769 req.form['filenode'][0]))
770
770
771 def do_heads(self, req):
771 def do_heads(self, req):
772 resp = " ".join(map(hex, self.repo.heads())) + "\n"
772 resp = " ".join(map(hex, self.repo.heads())) + "\n"
773 req.httphdr("application/mercurial-0.1", length=len(resp))
773 req.httphdr("application/mercurial-0.1", length=len(resp))
774 req.write(resp)
774 req.write(resp)
775
775
776 def do_branches(self, req):
776 def do_branches(self, req):
777 nodes = []
777 nodes = []
778 if req.form.has_key('nodes'):
778 if req.form.has_key('nodes'):
779 nodes = map(bin, req.form['nodes'][0].split(" "))
779 nodes = map(bin, req.form['nodes'][0].split(" "))
780 resp = cStringIO.StringIO()
780 resp = cStringIO.StringIO()
781 for b in self.repo.branches(nodes):
781 for b in self.repo.branches(nodes):
782 resp.write(" ".join(map(hex, b)) + "\n")
782 resp.write(" ".join(map(hex, b)) + "\n")
783 resp = resp.getvalue()
783 resp = resp.getvalue()
784 req.httphdr("application/mercurial-0.1", length=len(resp))
784 req.httphdr("application/mercurial-0.1", length=len(resp))
785 req.write(resp)
785 req.write(resp)
786
786
787 def do_between(self, req):
787 def do_between(self, req):
788 nodes = []
788 nodes = []
789 if req.form.has_key('pairs'):
789 if req.form.has_key('pairs'):
790 pairs = [map(bin, p.split("-"))
790 pairs = [map(bin, p.split("-"))
791 for p in req.form['pairs'][0].split(" ")]
791 for p in req.form['pairs'][0].split(" ")]
792 resp = cStringIO.StringIO()
792 resp = cStringIO.StringIO()
793 for b in self.repo.between(pairs):
793 for b in self.repo.between(pairs):
794 resp.write(" ".join(map(hex, b)) + "\n")
794 resp.write(" ".join(map(hex, b)) + "\n")
795 resp = resp.getvalue()
795 resp = resp.getvalue()
796 req.httphdr("application/mercurial-0.1", length=len(resp))
796 req.httphdr("application/mercurial-0.1", length=len(resp))
797 req.write(resp)
797 req.write(resp)
798
798
799 def do_changegroup(self, req):
799 def do_changegroup(self, req):
800 req.httphdr("application/mercurial-0.1")
800 req.httphdr("application/mercurial-0.1")
801 nodes = []
801 nodes = []
802 if not self.allowpull:
802 if not self.allowpull:
803 return
803 return
804
804
805 if req.form.has_key('roots'):
805 if req.form.has_key('roots'):
806 nodes = map(bin, req.form['roots'][0].split(" "))
806 nodes = map(bin, req.form['roots'][0].split(" "))
807
807
808 z = zlib.compressobj()
808 z = zlib.compressobj()
809 f = self.repo.changegroup(nodes, 'serve')
809 f = self.repo.changegroup(nodes, 'serve')
810 while 1:
810 while 1:
811 chunk = f.read(4096)
811 chunk = f.read(4096)
812 if not chunk:
812 if not chunk:
813 break
813 break
814 req.write(z.compress(chunk))
814 req.write(z.compress(chunk))
815
815
816 req.write(z.flush())
816 req.write(z.flush())
817
817
818 def do_archive(self, req):
818 def do_archive(self, req):
819 changeset = self.repo.lookup(req.form['node'][0])
819 changeset = self.repo.lookup(req.form['node'][0])
820 type_ = req.form['type'][0]
820 type_ = req.form['type'][0]
821 allowed = self.repo.ui.config("web", "allow_archive", "").split()
821 allowed = self.repo.ui.config("web", "allow_archive", "").split()
822 if (type_ in self.archives and (type_ in allowed or
822 if (type_ in self.archives and (type_ in allowed or
823 self.repo.ui.configbool("web", "allow" + type_, False))):
823 self.repo.ui.configbool("web", "allow" + type_, False))):
824 self.archive(req, changeset, type_)
824 self.archive(req, changeset, type_)
825 return
825 return
826
826
827 req.write(self.t("error"))
827 req.write(self.t("error"))
828
828
829 def do_static(self, req):
829 def do_static(self, req):
830 fname = req.form['file'][0]
830 fname = req.form['file'][0]
831 static = self.repo.ui.config("web", "static",
831 static = self.repo.ui.config("web", "static",
832 os.path.join(self.templatepath,
832 os.path.join(self.templatepath,
833 "static"))
833 "static"))
834 req.write(staticfile(static, fname)
834 req.write(staticfile(static, fname)
835 or self.t("error", error="%r not found" % fname))
835 or self.t("error", error="%r not found" % fname))
836
836
837 def do_capabilities(self, req):
837 def do_capabilities(self, req):
838 resp = 'unbundle'
838 resp = 'unbundle'
839 req.httphdr("application/mercurial-0.1", length=len(resp))
839 req.httphdr("application/mercurial-0.1", length=len(resp))
840 req.write(resp)
840 req.write(resp)
841
841
842 def check_perm(self, req, op, default):
843 '''check permission for operation based on user auth.
844 return true if op allowed, else false.
845 default is policy to use if no config given.'''
846
847 user = req.env.get('REMOTE_USER')
848
849 deny = self.repo.ui.config('web', 'deny_' + op, '')
850 deny = deny.replace(',', ' ').split()
851
852 if deny and (not user or deny == ['*'] or user in deny):
853 return False
854
855 allow = self.repo.ui.config('web', 'allow_' + op, '')
856 allow = allow.replace(',', ' ').split()
857
858 return (allow and (allow == ['*'] or user in allow)) or default
859
842 def do_unbundle(self, req):
860 def do_unbundle(self, req):
861 def bail(response, headers={}):
862 length = int(req.env['CONTENT_LENGTH'])
863 for s in util.filechunkiter(req, limit=length):
864 # drain incoming bundle, else client will not see
865 # response when run outside cgi script
866 pass
867 req.httphdr("application/mercurial-0.1", headers=headers)
868 req.write('0\n')
869 req.write(response)
870
871 # require ssl by default, auth info cannot be sniffed and
872 # replayed
873 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
874 if ssl_req and not req.env.get('HTTPS'):
875 bail(_('ssl required\n'))
876 return
877
878 # do not allow push unless explicitly allowed
879 if not self.check_perm(req, 'push', False):
880 bail(_('push not authorized\n'),
881 headers={'status': '401 Unauthorized'})
882 return
883
884 req.httphdr("application/mercurial-0.1")
885
843 their_heads = req.form['heads'][0].split(' ')
886 their_heads = req.form['heads'][0].split(' ')
844
887
845 def check_heads():
888 def check_heads():
846 heads = map(hex, self.repo.heads())
889 heads = map(hex, self.repo.heads())
847 return their_heads == [hex('force')] or their_heads == heads
890 return their_heads == [hex('force')] or their_heads == heads
848
891
849 req.httphdr("application/mercurial-0.1")
850
851 # fail early if possible
892 # fail early if possible
852 if not check_heads():
893 if not check_heads():
853 req.write('0\n')
894 bail(_('unsynced changes\n'))
854 req.write(_('unsynced changes\n'))
855 return
895 return
856
896
857 # do not lock repo until all changegroup data is
897 # do not lock repo until all changegroup data is
858 # streamed. save to temporary file.
898 # streamed. save to temporary file.
859
899
860 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
900 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
861 fp = os.fdopen(fd, 'wb+')
901 fp = os.fdopen(fd, 'wb+')
862 try:
902 try:
863 length = int(req.env['CONTENT_LENGTH'])
903 length = int(req.env['CONTENT_LENGTH'])
864 for s in util.filechunkiter(req, limit=length):
904 for s in util.filechunkiter(req, limit=length):
865 fp.write(s)
905 fp.write(s)
866
906
867 lock = self.repo.lock()
907 lock = self.repo.lock()
868 try:
908 try:
869 if not check_heads():
909 if not check_heads():
870 req.write('0\n')
910 req.write('0\n')
871 req.write(_('unsynced changes\n'))
911 req.write(_('unsynced changes\n'))
872 return
912 return
873
913
874 fp.seek(0)
914 fp.seek(0)
875
915
876 # send addchangegroup output to client
916 # send addchangegroup output to client
877
917
878 old_stdout = sys.stdout
918 old_stdout = sys.stdout
879 sys.stdout = cStringIO.StringIO()
919 sys.stdout = cStringIO.StringIO()
880
920
881 try:
921 try:
882 ret = self.repo.addchangegroup(fp, 'serve')
922 ret = self.repo.addchangegroup(fp, 'serve')
883 req.write('%d\n' % ret)
923 req.write('%d\n' % ret)
884 req.write(sys.stdout.getvalue())
924 req.write(sys.stdout.getvalue())
885 finally:
925 finally:
886 sys.stdout = old_stdout
926 sys.stdout = old_stdout
887 finally:
927 finally:
888 lock.release()
928 lock.release()
889 finally:
929 finally:
890 fp.close()
930 fp.close()
891 os.unlink(tempname)
931 os.unlink(tempname)
@@ -1,62 +1,62 b''
1 # hgweb/request.py - An http request from either CGI or the standalone server.
1 # hgweb/request.py - An http request from either CGI or the standalone server.
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 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 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 from mercurial.demandload import demandload
9 from mercurial.demandload import demandload
10 demandload(globals(), "socket sys cgi os errno")
10 demandload(globals(), "socket sys cgi os errno")
11 from mercurial.i18n import gettext as _
11 from mercurial.i18n import gettext as _
12
12
13 class hgrequest(object):
13 class hgrequest(object):
14 def __init__(self, inp=None, out=None, env=None):
14 def __init__(self, inp=None, out=None, env=None):
15 self.inp = inp or sys.stdin
15 self.inp = inp or sys.stdin
16 self.out = out or sys.stdout
16 self.out = out or sys.stdout
17 self.env = env or os.environ
17 self.env = env or os.environ
18 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
18 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
19 self.will_close = True
19 self.will_close = True
20
20
21 def read(self, count=-1):
21 def read(self, count=-1):
22 return self.inp.read(count)
22 return self.inp.read(count)
23
23
24 def write(self, *things):
24 def write(self, *things):
25 for thing in things:
25 for thing in things:
26 if hasattr(thing, "__iter__"):
26 if hasattr(thing, "__iter__"):
27 for part in thing:
27 for part in thing:
28 self.write(part)
28 self.write(part)
29 else:
29 else:
30 try:
30 try:
31 self.out.write(str(thing))
31 self.out.write(str(thing))
32 except socket.error, inst:
32 except socket.error, inst:
33 if inst[0] != errno.ECONNRESET:
33 if inst[0] != errno.ECONNRESET:
34 raise
34 raise
35
35
36 def done(self):
36 def done(self):
37 if self.will_close:
37 if self.will_close:
38 self.inp.close()
38 self.inp.close()
39 self.out.close()
39 self.out.close()
40 else:
40 else:
41 self.out.flush()
41 self.out.flush()
42
42
43 def header(self, headers=[('Content-type','text/html')]):
43 def header(self, headers=[('Content-type','text/html')]):
44 for header in headers:
44 for header in headers:
45 self.out.write("%s: %s\r\n" % header)
45 self.out.write("%s: %s\r\n" % header)
46 self.out.write("\r\n")
46 self.out.write("\r\n")
47
47
48 def httphdr(self, type, filename=None, length=0):
48 def httphdr(self, type, filename=None, length=0, headers={}):
49
49 headers = headers.items()
50 headers = [('Content-type', type)]
50 headers.append(('Content-type', type))
51 if filename:
51 if filename:
52 headers.append(('Content-disposition', 'attachment; filename=%s' %
52 headers.append(('Content-disposition', 'attachment; filename=%s' %
53 filename))
53 filename))
54 # we do not yet support http 1.1 chunked transfer, so we have
54 # we do not yet support http 1.1 chunked transfer, so we have
55 # to force connection to close if content-length not known
55 # to force connection to close if content-length not known
56 if length:
56 if length:
57 headers.append(('Content-length', str(length)))
57 headers.append(('Content-length', str(length)))
58 self.will_close = False
58 self.will_close = False
59 else:
59 else:
60 headers.append(('Connection', 'close'))
60 headers.append(('Connection', 'close'))
61 self.will_close = True
61 self.will_close = True
62 self.header(headers)
62 self.header(headers)
General Comments 0
You need to be logged in to leave comments. Login now