##// END OF EJS Templates
hgweb: Configurable zebra stripes...
Frank Kingswood -
r2666:ebf033bc default
parent child Browse files
Show More
@@ -1,464 +1,467 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 local_hostname;;
312 local_hostname;;
313 Optional. It's the hostname that the sender can use to identify itself
313 Optional. It's the hostname that the sender can use to identify itself
314 to the MTA.
314 to the MTA.
315
315
316 paths::
316 paths::
317 Assigns symbolic names to repositories. The left side is the
317 Assigns symbolic names to repositories. The left side is the
318 symbolic name, and the right gives the directory or URL that is the
318 symbolic name, and the right gives the directory or URL that is the
319 location of the repository. Default paths can be declared by
319 location of the repository. Default paths can be declared by
320 setting the following entries.
320 setting the following entries.
321 default;;
321 default;;
322 Directory or URL to use when pulling if no source is specified.
322 Directory or URL to use when pulling if no source is specified.
323 Default is set to repository from which the current repository
323 Default is set to repository from which the current repository
324 was cloned.
324 was cloned.
325 default-push;;
325 default-push;;
326 Optional. Directory or URL to use when pushing if no destination
326 Optional. Directory or URL to use when pushing if no destination
327 is specified.
327 is specified.
328
328
329 server::
329 server::
330 Controls generic server settings.
330 Controls generic server settings.
331 uncompressed;;
331 uncompressed;;
332 Whether to allow clients to clone a repo using the uncompressed
332 Whether to allow clients to clone a repo using the uncompressed
333 streaming protocol. This transfers about 40% more data than a
333 streaming protocol. This transfers about 40% more data than a
334 regular clone, but uses less memory and CPU on both server and
334 regular clone, but uses less memory and CPU on both server and
335 client. Over a LAN (100Mbps or better) or a very fast WAN, an
335 client. Over a LAN (100Mbps or better) or a very fast WAN, an
336 uncompressed streaming clone is a lot faster (~10x) than a regular
336 uncompressed streaming clone is a lot faster (~10x) than a regular
337 clone. Over most WAN connections (anything slower than about
337 clone. Over most WAN connections (anything slower than about
338 6Mbps), uncompressed streaming is slower, because of the extra
338 6Mbps), uncompressed streaming is slower, because of the extra
339 data transfer overhead. Default is False.
339 data transfer overhead. Default is False.
340
340
341 ui::
341 ui::
342 User interface controls.
342 User interface controls.
343 debug;;
343 debug;;
344 Print debugging information. True or False. Default is False.
344 Print debugging information. True or False. Default is False.
345 editor;;
345 editor;;
346 The editor to use during a commit. Default is $EDITOR or "vi".
346 The editor to use during a commit. Default is $EDITOR or "vi".
347 ignore;;
347 ignore;;
348 A file to read per-user ignore patterns from. This file should be in
348 A file to read per-user ignore patterns from. This file should be in
349 the same format as a repository-wide .hgignore file. This option
349 the same format as a repository-wide .hgignore file. This option
350 supports hook syntax, so if you want to specify multiple ignore
350 supports hook syntax, so if you want to specify multiple ignore
351 files, you can do so by setting something like
351 files, you can do so by setting something like
352 "ignore.other = ~/.hgignore2". For details of the ignore file
352 "ignore.other = ~/.hgignore2". For details of the ignore file
353 format, see the hgignore(5) man page.
353 format, see the hgignore(5) man page.
354 interactive;;
354 interactive;;
355 Allow to prompt the user. True or False. Default is True.
355 Allow to prompt the user. True or False. Default is True.
356 logtemplate;;
356 logtemplate;;
357 Template string for commands that print changesets.
357 Template string for commands that print changesets.
358 style;;
358 style;;
359 Name of style to use for command output.
359 Name of style to use for command output.
360 merge;;
360 merge;;
361 The conflict resolution program to use during a manual merge.
361 The conflict resolution program to use during a manual merge.
362 Default is "hgmerge".
362 Default is "hgmerge".
363 quiet;;
363 quiet;;
364 Reduce the amount of output printed. True or False. Default is False.
364 Reduce the amount of output printed. True or False. Default is False.
365 remotecmd;;
365 remotecmd;;
366 remote command to use for clone/push/pull operations. Default is 'hg'.
366 remote command to use for clone/push/pull operations. Default is 'hg'.
367 ssh;;
367 ssh;;
368 command to use for SSH connections. Default is 'ssh'.
368 command to use for SSH connections. Default is 'ssh'.
369 timeout;;
369 timeout;;
370 The timeout used when a lock is held (in seconds), a negative value
370 The timeout used when a lock is held (in seconds), a negative value
371 means no timeout. Default is 600.
371 means no timeout. Default is 600.
372 username;;
372 username;;
373 The committer of a changeset created when running "commit".
373 The committer of a changeset created when running "commit".
374 Typically a person's name and email address, e.g. "Fred Widget
374 Typically a person's name and email address, e.g. "Fred Widget
375 <fred@example.com>". Default is $EMAIL or username@hostname, unless
375 <fred@example.com>". Default is $EMAIL or username@hostname, unless
376 username is set to an empty string, which enforces specifying the
376 username is set to an empty string, which enforces specifying the
377 username manually.
377 username manually.
378 verbose;;
378 verbose;;
379 Increase the amount of output printed. True or False. Default is False.
379 Increase the amount of output printed. True or False. Default is False.
380
380
381
381
382 web::
382 web::
383 Web interface configuration.
383 Web interface configuration.
384 accesslog;;
384 accesslog;;
385 Where to output the access log. Default is stdout.
385 Where to output the access log. Default is stdout.
386 address;;
386 address;;
387 Interface address to bind to. Default is all.
387 Interface address to bind to. Default is all.
388 allow_archive;;
388 allow_archive;;
389 List of archive format (bz2, gz, zip) allowed for downloading.
389 List of archive format (bz2, gz, zip) allowed for downloading.
390 Default is empty.
390 Default is empty.
391 allowbz2;;
391 allowbz2;;
392 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
392 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
393 Default is false.
393 Default is false.
394 allowgz;;
394 allowgz;;
395 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
395 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
396 Default is false.
396 Default is false.
397 allowpull;;
397 allowpull;;
398 Whether to allow pulling from the repository. Default is true.
398 Whether to allow pulling from the repository. Default is true.
399 allow_push;;
399 allow_push;;
400 Whether to allow pushing to the repository. If empty or not set,
400 Whether to allow pushing to the repository. If empty or not set,
401 push is not allowed. If the special value "*", any remote user
401 push is not allowed. If the special value "*", any remote user
402 can push, including unauthenticated users. Otherwise, the remote
402 can push, including unauthenticated users. Otherwise, the remote
403 user must have been authenticated, and the authenticated user name
403 user must have been authenticated, and the authenticated user name
404 must be present in this list (separated by whitespace or ",").
404 must be present in this list (separated by whitespace or ",").
405 The contents of the allow_push list are examined after the
405 The contents of the allow_push list are examined after the
406 deny_push list.
406 deny_push list.
407 allowzip;;
407 allowzip;;
408 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
408 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
409 Default is false. This feature creates temporary files.
409 Default is false. This feature creates temporary files.
410 baseurl;;
410 baseurl;;
411 Base URL to use when publishing URLs in other locations, so
411 Base URL to use when publishing URLs in other locations, so
412 third-party tools like email notification hooks can construct URLs.
412 third-party tools like email notification hooks can construct URLs.
413 Example: "http://hgserver/repos/"
413 Example: "http://hgserver/repos/"
414 contact;;
414 contact;;
415 Name or email address of the person in charge of the repository.
415 Name or email address of the person in charge of the repository.
416 Default is "unknown".
416 Default is "unknown".
417 deny_push;;
417 deny_push;;
418 Whether to deny pushing to the repository. If empty or not set,
418 Whether to deny pushing to the repository. If empty or not set,
419 push is not denied. If the special value "*", all remote users
419 push is not denied. If the special value "*", all remote users
420 are denied push. Otherwise, unauthenticated users are all denied,
420 are denied push. Otherwise, unauthenticated users are all denied,
421 and any authenticated user name present in this list (separated by
421 and any authenticated user name present in this list (separated by
422 whitespace or ",") is also denied. The contents of the deny_push
422 whitespace or ",") is also denied. The contents of the deny_push
423 list are examined before the allow_push list.
423 list are examined before the allow_push list.
424 description;;
424 description;;
425 Textual description of the repository's purpose or contents.
425 Textual description of the repository's purpose or contents.
426 Default is "unknown".
426 Default is "unknown".
427 errorlog;;
427 errorlog;;
428 Where to output the error log. Default is stderr.
428 Where to output the error log. Default is stderr.
429 ipv6;;
429 ipv6;;
430 Whether to use IPv6. Default is false.
430 Whether to use IPv6. Default is false.
431 name;;
431 name;;
432 Repository name to use in the web interface. Default is current
432 Repository name to use in the web interface. Default is current
433 working directory.
433 working directory.
434 maxchanges;;
434 maxchanges;;
435 Maximum number of changes to list on the changelog. Default is 10.
435 Maximum number of changes to list on the changelog. Default is 10.
436 maxfiles;;
436 maxfiles;;
437 Maximum number of files to list per changeset. Default is 10.
437 Maximum number of files to list per changeset. Default is 10.
438 port;;
438 port;;
439 Port to listen on. Default is 8000.
439 Port to listen on. Default is 8000.
440 push_ssl;;
440 push_ssl;;
441 Whether to require that inbound pushes be transported over SSL to
441 Whether to require that inbound pushes be transported over SSL to
442 prevent password sniffing. Default is true.
442 prevent password sniffing. Default is true.
443 stripes;;
444 How many lines a "zebra stripe" should span in multiline output.
445 Default is 1; set to 0 to disable.
443 style;;
446 style;;
444 Which template map style to use.
447 Which template map style to use.
445 templates;;
448 templates;;
446 Where to find the HTML templates. Default is install path.
449 Where to find the HTML templates. Default is install path.
447
450
448
451
449 AUTHOR
452 AUTHOR
450 ------
453 ------
451 Bryan O'Sullivan <bos@serpentine.com>.
454 Bryan O'Sullivan <bos@serpentine.com>.
452
455
453 Mercurial was written by Matt Mackall <mpm@selenic.com>.
456 Mercurial was written by Matt Mackall <mpm@selenic.com>.
454
457
455 SEE ALSO
458 SEE ALSO
456 --------
459 --------
457 hg(1), hgignore(5)
460 hg(1), hgignore(5)
458
461
459 COPYING
462 COPYING
460 -------
463 -------
461 This manual page is copyright 2005 Bryan O'Sullivan.
464 This manual page is copyright 2005 Bryan O'Sullivan.
462 Mercurial is copyright 2005, 2006 Matt Mackall.
465 Mercurial is copyright 2005, 2006 Matt Mackall.
463 Free use of this software is granted under the terms of the GNU General
466 Free use of this software is granted under the terms of the GNU General
464 Public License (GPL).
467 Public License (GPL).
@@ -1,960 +1,969 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 mimetools cStringIO sys tempfile")
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone")
15 demandload(globals(), "mercurial:templater")
15 demandload(globals(), "mercurial:templater")
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.stripecount = 1
40 self.templatepath = self.repo.ui.config("web", "templates",
41 self.templatepath = self.repo.ui.config("web", "templates",
41 templater.templatepath())
42 templater.templatepath())
42
43
43 def refresh(self):
44 def refresh(self):
44 mtime = get_mtime(self.repo.root)
45 mtime = get_mtime(self.repo.root)
45 if mtime != self.mtime:
46 if mtime != self.mtime:
46 self.mtime = mtime
47 self.mtime = mtime
47 self.repo = hg.repository(self.repo.ui, self.repo.root)
48 self.repo = hg.repository(self.repo.ui, self.repo.root)
48 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
49 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
50 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
49 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
51 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
50 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
52 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
51
53
52 def archivelist(self, nodeid):
54 def archivelist(self, nodeid):
53 allowed = self.repo.ui.configlist("web", "allow_archive")
55 allowed = self.repo.ui.configlist("web", "allow_archive")
54 for i in self.archives:
56 for i in self.archives:
55 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
57 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
56 yield {"type" : i, "node" : nodeid, "url": ""}
58 yield {"type" : i, "node" : nodeid, "url": ""}
57
59
58 def listfiles(self, files, mf):
60 def listfiles(self, files, mf):
59 for f in files[:self.maxfiles]:
61 for f in files[:self.maxfiles]:
60 yield self.t("filenodelink", node=hex(mf[f]), file=f)
62 yield self.t("filenodelink", node=hex(mf[f]), file=f)
61 if len(files) > self.maxfiles:
63 if len(files) > self.maxfiles:
62 yield self.t("fileellipses")
64 yield self.t("fileellipses")
63
65
64 def listfilediffs(self, files, changeset):
66 def listfilediffs(self, files, changeset):
65 for f in files[:self.maxfiles]:
67 for f in files[:self.maxfiles]:
66 yield self.t("filedifflink", node=hex(changeset), file=f)
68 yield self.t("filedifflink", node=hex(changeset), file=f)
67 if len(files) > self.maxfiles:
69 if len(files) > self.maxfiles:
68 yield self.t("fileellipses")
70 yield self.t("fileellipses")
69
71
70 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
72 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
71 if not rev:
73 if not rev:
72 rev = lambda x: ""
74 rev = lambda x: ""
73 siblings = [s for s in siblings if s != nullid]
75 siblings = [s for s in siblings if s != nullid]
74 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
76 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
75 return
77 return
76 for s in siblings:
78 for s in siblings:
77 yield dict(node=hex(s), rev=rev(s), **args)
79 yield dict(node=hex(s), rev=rev(s), **args)
78
80
79 def renamelink(self, fl, node):
81 def renamelink(self, fl, node):
80 r = fl.renamed(node)
82 r = fl.renamed(node)
81 if r:
83 if r:
82 return [dict(file=r[0], node=hex(r[1]))]
84 return [dict(file=r[0], node=hex(r[1]))]
83 return []
85 return []
84
86
85 def showtag(self, t1, node=nullid, **args):
87 def showtag(self, t1, node=nullid, **args):
86 for t in self.repo.nodetags(node):
88 for t in self.repo.nodetags(node):
87 yield self.t(t1, tag=t, **args)
89 yield self.t(t1, tag=t, **args)
88
90
89 def diff(self, node1, node2, files):
91 def diff(self, node1, node2, files):
90 def filterfiles(filters, files):
92 def filterfiles(filters, files):
91 l = [x for x in files if x in filters]
93 l = [x for x in files if x in filters]
92
94
93 for t in filters:
95 for t in filters:
94 if t and t[-1] != os.sep:
96 if t and t[-1] != os.sep:
95 t += os.sep
97 t += os.sep
96 l += [x for x in files if x.startswith(t)]
98 l += [x for x in files if x.startswith(t)]
97 return l
99 return l
98
100
99 parity = [0]
101 parity = [0]
100 def diffblock(diff, f, fn):
102 def diffblock(diff, f, fn):
101 yield self.t("diffblock",
103 yield self.t("diffblock",
102 lines=prettyprintlines(diff),
104 lines=prettyprintlines(diff),
103 parity=parity[0],
105 parity=parity[0],
104 file=f,
106 file=f,
105 filenode=hex(fn or nullid))
107 filenode=hex(fn or nullid))
106 parity[0] = 1 - parity[0]
108 parity[0] = 1 - parity[0]
107
109
108 def prettyprintlines(diff):
110 def prettyprintlines(diff):
109 for l in diff.splitlines(1):
111 for l in diff.splitlines(1):
110 if l.startswith('+'):
112 if l.startswith('+'):
111 yield self.t("difflineplus", line=l)
113 yield self.t("difflineplus", line=l)
112 elif l.startswith('-'):
114 elif l.startswith('-'):
113 yield self.t("difflineminus", line=l)
115 yield self.t("difflineminus", line=l)
114 elif l.startswith('@'):
116 elif l.startswith('@'):
115 yield self.t("difflineat", line=l)
117 yield self.t("difflineat", line=l)
116 else:
118 else:
117 yield self.t("diffline", line=l)
119 yield self.t("diffline", line=l)
118
120
119 r = self.repo
121 r = self.repo
120 cl = r.changelog
122 cl = r.changelog
121 mf = r.manifest
123 mf = r.manifest
122 change1 = cl.read(node1)
124 change1 = cl.read(node1)
123 change2 = cl.read(node2)
125 change2 = cl.read(node2)
124 mmap1 = mf.read(change1[0])
126 mmap1 = mf.read(change1[0])
125 mmap2 = mf.read(change2[0])
127 mmap2 = mf.read(change2[0])
126 date1 = util.datestr(change1[2])
128 date1 = util.datestr(change1[2])
127 date2 = util.datestr(change2[2])
129 date2 = util.datestr(change2[2])
128
130
129 modified, added, removed, deleted, unknown = r.changes(node1, node2)
131 modified, added, removed, deleted, unknown = r.changes(node1, node2)
130 if files:
132 if files:
131 modified, added, removed = map(lambda x: filterfiles(files, x),
133 modified, added, removed = map(lambda x: filterfiles(files, x),
132 (modified, added, removed))
134 (modified, added, removed))
133
135
134 diffopts = self.repo.ui.diffopts()
136 diffopts = self.repo.ui.diffopts()
135 showfunc = diffopts['showfunc']
137 showfunc = diffopts['showfunc']
136 ignorews = diffopts['ignorews']
138 ignorews = diffopts['ignorews']
137 ignorewsamount = diffopts['ignorewsamount']
139 ignorewsamount = diffopts['ignorewsamount']
138 ignoreblanklines = diffopts['ignoreblanklines']
140 ignoreblanklines = diffopts['ignoreblanklines']
139 for f in modified:
141 for f in modified:
140 to = r.file(f).read(mmap1[f])
142 to = r.file(f).read(mmap1[f])
141 tn = r.file(f).read(mmap2[f])
143 tn = r.file(f).read(mmap2[f])
142 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
144 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
143 showfunc=showfunc, ignorews=ignorews,
145 showfunc=showfunc, ignorews=ignorews,
144 ignorewsamount=ignorewsamount,
146 ignorewsamount=ignorewsamount,
145 ignoreblanklines=ignoreblanklines), f, tn)
147 ignoreblanklines=ignoreblanklines), f, tn)
146 for f in added:
148 for f in added:
147 to = None
149 to = None
148 tn = r.file(f).read(mmap2[f])
150 tn = r.file(f).read(mmap2[f])
149 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
151 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
150 showfunc=showfunc, ignorews=ignorews,
152 showfunc=showfunc, ignorews=ignorews,
151 ignorewsamount=ignorewsamount,
153 ignorewsamount=ignorewsamount,
152 ignoreblanklines=ignoreblanklines), f, tn)
154 ignoreblanklines=ignoreblanklines), f, tn)
153 for f in removed:
155 for f in removed:
154 to = r.file(f).read(mmap1[f])
156 to = r.file(f).read(mmap1[f])
155 tn = None
157 tn = None
156 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
158 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
157 showfunc=showfunc, ignorews=ignorews,
159 showfunc=showfunc, ignorews=ignorews,
158 ignorewsamount=ignorewsamount,
160 ignorewsamount=ignorewsamount,
159 ignoreblanklines=ignoreblanklines), f, tn)
161 ignoreblanklines=ignoreblanklines), f, tn)
160
162
161 def changelog(self, pos):
163 def changelog(self, pos):
162 def changenav(**map):
164 def changenav(**map):
163 def seq(factor, maxchanges=None):
165 def seq(factor, maxchanges=None):
164 if maxchanges:
166 if maxchanges:
165 yield maxchanges
167 yield maxchanges
166 if maxchanges >= 20 and maxchanges <= 40:
168 if maxchanges >= 20 and maxchanges <= 40:
167 yield 50
169 yield 50
168 else:
170 else:
169 yield 1 * factor
171 yield 1 * factor
170 yield 3 * factor
172 yield 3 * factor
171 for f in seq(factor * 10):
173 for f in seq(factor * 10):
172 yield f
174 yield f
173
175
174 l = []
176 l = []
175 last = 0
177 last = 0
176 for f in seq(1, self.maxchanges):
178 for f in seq(1, self.maxchanges):
177 if f < self.maxchanges or f <= last:
179 if f < self.maxchanges or f <= last:
178 continue
180 continue
179 if f > count:
181 if f > count:
180 break
182 break
181 last = f
183 last = f
182 r = "%d" % f
184 r = "%d" % f
183 if pos + f < count:
185 if pos + f < count:
184 l.append(("+" + r, pos + f))
186 l.append(("+" + r, pos + f))
185 if pos - f >= 0:
187 if pos - f >= 0:
186 l.insert(0, ("-" + r, pos - f))
188 l.insert(0, ("-" + r, pos - f))
187
189
188 yield {"rev": 0, "label": "(0)"}
190 yield {"rev": 0, "label": "(0)"}
189
191
190 for label, rev in l:
192 for label, rev in l:
191 yield {"label": label, "rev": rev}
193 yield {"label": label, "rev": rev}
192
194
193 yield {"label": "tip", "rev": "tip"}
195 yield {"label": "tip", "rev": "tip"}
194
196
195 def changelist(**map):
197 def changelist(**map):
196 parity = (start - end) & 1
198 parity = (start - end) & 1
197 cl = self.repo.changelog
199 cl = self.repo.changelog
198 l = [] # build a list in forward order for efficiency
200 l = [] # build a list in forward order for efficiency
199 for i in range(start, end):
201 for i in range(start, end):
200 n = cl.node(i)
202 n = cl.node(i)
201 changes = cl.read(n)
203 changes = cl.read(n)
202 hn = hex(n)
204 hn = hex(n)
203
205
204 l.insert(0, {"parity": parity,
206 l.insert(0, {"parity": parity,
205 "author": changes[1],
207 "author": changes[1],
206 "parent": self.siblings(cl.parents(n), cl.rev,
208 "parent": self.siblings(cl.parents(n), cl.rev,
207 cl.rev(n) - 1),
209 cl.rev(n) - 1),
208 "child": self.siblings(cl.children(n), cl.rev,
210 "child": self.siblings(cl.children(n), cl.rev,
209 cl.rev(n) + 1),
211 cl.rev(n) + 1),
210 "changelogtag": self.showtag("changelogtag",n),
212 "changelogtag": self.showtag("changelogtag",n),
211 "manifest": hex(changes[0]),
213 "manifest": hex(changes[0]),
212 "desc": changes[4],
214 "desc": changes[4],
213 "date": changes[2],
215 "date": changes[2],
214 "files": self.listfilediffs(changes[3], n),
216 "files": self.listfilediffs(changes[3], n),
215 "rev": i,
217 "rev": i,
216 "node": hn})
218 "node": hn})
217 parity = 1 - parity
219 parity = 1 - parity
218
220
219 for e in l:
221 for e in l:
220 yield e
222 yield e
221
223
222 cl = self.repo.changelog
224 cl = self.repo.changelog
223 mf = cl.read(cl.tip())[0]
225 mf = cl.read(cl.tip())[0]
224 count = cl.count()
226 count = cl.count()
225 start = max(0, pos - self.maxchanges + 1)
227 start = max(0, pos - self.maxchanges + 1)
226 end = min(count, start + self.maxchanges)
228 end = min(count, start + self.maxchanges)
227 pos = end - 1
229 pos = end - 1
228
230
229 yield self.t('changelog',
231 yield self.t('changelog',
230 changenav=changenav,
232 changenav=changenav,
231 manifest=hex(mf),
233 manifest=hex(mf),
232 rev=pos, changesets=count, entries=changelist,
234 rev=pos, changesets=count, entries=changelist,
233 archives=self.archivelist("tip"))
235 archives=self.archivelist("tip"))
234
236
235 def search(self, query):
237 def search(self, query):
236
238
237 def changelist(**map):
239 def changelist(**map):
238 cl = self.repo.changelog
240 cl = self.repo.changelog
239 count = 0
241 count = 0
240 qw = query.lower().split()
242 qw = query.lower().split()
241
243
242 def revgen():
244 def revgen():
243 for i in range(cl.count() - 1, 0, -100):
245 for i in range(cl.count() - 1, 0, -100):
244 l = []
246 l = []
245 for j in range(max(0, i - 100), i):
247 for j in range(max(0, i - 100), i):
246 n = cl.node(j)
248 n = cl.node(j)
247 changes = cl.read(n)
249 changes = cl.read(n)
248 l.append((n, j, changes))
250 l.append((n, j, changes))
249 l.reverse()
251 l.reverse()
250 for e in l:
252 for e in l:
251 yield e
253 yield e
252
254
253 for n, i, changes in revgen():
255 for n, i, changes in revgen():
254 miss = 0
256 miss = 0
255 for q in qw:
257 for q in qw:
256 if not (q in changes[1].lower() or
258 if not (q in changes[1].lower() or
257 q in changes[4].lower() or
259 q in changes[4].lower() or
258 q in " ".join(changes[3][:20]).lower()):
260 q in " ".join(changes[3][:20]).lower()):
259 miss = 1
261 miss = 1
260 break
262 break
261 if miss:
263 if miss:
262 continue
264 continue
263
265
264 count += 1
266 count += 1
265 hn = hex(n)
267 hn = hex(n)
266
268
267 yield self.t('searchentry',
269 yield self.t('searchentry',
268 parity=count & 1,
270 parity=self.stripes(count),
269 author=changes[1],
271 author=changes[1],
270 parent=self.siblings(cl.parents(n), cl.rev),
272 parent=self.siblings(cl.parents(n), cl.rev),
271 child=self.siblings(cl.children(n), cl.rev),
273 child=self.siblings(cl.children(n), cl.rev),
272 changelogtag=self.showtag("changelogtag",n),
274 changelogtag=self.showtag("changelogtag",n),
273 manifest=hex(changes[0]),
275 manifest=hex(changes[0]),
274 desc=changes[4],
276 desc=changes[4],
275 date=changes[2],
277 date=changes[2],
276 files=self.listfilediffs(changes[3], n),
278 files=self.listfilediffs(changes[3], n),
277 rev=i,
279 rev=i,
278 node=hn)
280 node=hn)
279
281
280 if count >= self.maxchanges:
282 if count >= self.maxchanges:
281 break
283 break
282
284
283 cl = self.repo.changelog
285 cl = self.repo.changelog
284 mf = cl.read(cl.tip())[0]
286 mf = cl.read(cl.tip())[0]
285
287
286 yield self.t('search',
288 yield self.t('search',
287 query=query,
289 query=query,
288 manifest=hex(mf),
290 manifest=hex(mf),
289 entries=changelist)
291 entries=changelist)
290
292
291 def changeset(self, nodeid):
293 def changeset(self, nodeid):
292 cl = self.repo.changelog
294 cl = self.repo.changelog
293 n = self.repo.lookup(nodeid)
295 n = self.repo.lookup(nodeid)
294 nodeid = hex(n)
296 nodeid = hex(n)
295 changes = cl.read(n)
297 changes = cl.read(n)
296 p1 = cl.parents(n)[0]
298 p1 = cl.parents(n)[0]
297
299
298 files = []
300 files = []
299 mf = self.repo.manifest.read(changes[0])
301 mf = self.repo.manifest.read(changes[0])
300 for f in changes[3]:
302 for f in changes[3]:
301 files.append(self.t("filenodelink",
303 files.append(self.t("filenodelink",
302 filenode=hex(mf.get(f, nullid)), file=f))
304 filenode=hex(mf.get(f, nullid)), file=f))
303
305
304 def diff(**map):
306 def diff(**map):
305 yield self.diff(p1, n, None)
307 yield self.diff(p1, n, None)
306
308
307 yield self.t('changeset',
309 yield self.t('changeset',
308 diff=diff,
310 diff=diff,
309 rev=cl.rev(n),
311 rev=cl.rev(n),
310 node=nodeid,
312 node=nodeid,
311 parent=self.siblings(cl.parents(n), cl.rev),
313 parent=self.siblings(cl.parents(n), cl.rev),
312 child=self.siblings(cl.children(n), cl.rev),
314 child=self.siblings(cl.children(n), cl.rev),
313 changesettag=self.showtag("changesettag",n),
315 changesettag=self.showtag("changesettag",n),
314 manifest=hex(changes[0]),
316 manifest=hex(changes[0]),
315 author=changes[1],
317 author=changes[1],
316 desc=changes[4],
318 desc=changes[4],
317 date=changes[2],
319 date=changes[2],
318 files=files,
320 files=files,
319 archives=self.archivelist(nodeid))
321 archives=self.archivelist(nodeid))
320
322
321 def filelog(self, f, filenode):
323 def filelog(self, f, filenode):
322 cl = self.repo.changelog
324 cl = self.repo.changelog
323 fl = self.repo.file(f)
325 fl = self.repo.file(f)
324 filenode = hex(fl.lookup(filenode))
326 filenode = hex(fl.lookup(filenode))
325 count = fl.count()
327 count = fl.count()
326
328
327 def entries(**map):
329 def entries(**map):
328 l = []
330 l = []
329 parity = (count - 1) & 1
331 parity = (count - 1) & 1
330
332
331 for i in range(count):
333 for i in range(count):
332 n = fl.node(i)
334 n = fl.node(i)
333 lr = fl.linkrev(n)
335 lr = fl.linkrev(n)
334 cn = cl.node(lr)
336 cn = cl.node(lr)
335 cs = cl.read(cl.node(lr))
337 cs = cl.read(cl.node(lr))
336
338
337 l.insert(0, {"parity": parity,
339 l.insert(0, {"parity": parity,
338 "filenode": hex(n),
340 "filenode": hex(n),
339 "filerev": i,
341 "filerev": i,
340 "file": f,
342 "file": f,
341 "node": hex(cn),
343 "node": hex(cn),
342 "author": cs[1],
344 "author": cs[1],
343 "date": cs[2],
345 "date": cs[2],
344 "rename": self.renamelink(fl, n),
346 "rename": self.renamelink(fl, n),
345 "parent": self.siblings(fl.parents(n),
347 "parent": self.siblings(fl.parents(n),
346 fl.rev, file=f),
348 fl.rev, file=f),
347 "child": self.siblings(fl.children(n),
349 "child": self.siblings(fl.children(n),
348 fl.rev, file=f),
350 fl.rev, file=f),
349 "desc": cs[4]})
351 "desc": cs[4]})
350 parity = 1 - parity
352 parity = 1 - parity
351
353
352 for e in l:
354 for e in l:
353 yield e
355 yield e
354
356
355 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
357 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
356
358
357 def filerevision(self, f, node):
359 def filerevision(self, f, node):
358 fl = self.repo.file(f)
360 fl = self.repo.file(f)
359 n = fl.lookup(node)
361 n = fl.lookup(node)
360 node = hex(n)
362 node = hex(n)
361 text = fl.read(n)
363 text = fl.read(n)
362 changerev = fl.linkrev(n)
364 changerev = fl.linkrev(n)
363 cl = self.repo.changelog
365 cl = self.repo.changelog
364 cn = cl.node(changerev)
366 cn = cl.node(changerev)
365 cs = cl.read(cn)
367 cs = cl.read(cn)
366 mfn = cs[0]
368 mfn = cs[0]
367
369
368 mt = mimetypes.guess_type(f)[0]
370 mt = mimetypes.guess_type(f)[0]
369 rawtext = text
371 rawtext = text
370 if util.binary(text):
372 if util.binary(text):
371 mt = mt or 'application/octet-stream'
373 mt = mt or 'application/octet-stream'
372 text = "(binary:%s)" % mt
374 text = "(binary:%s)" % mt
373 mt = mt or 'text/plain'
375 mt = mt or 'text/plain'
374
376
375 def lines():
377 def lines():
376 for l, t in enumerate(text.splitlines(1)):
378 for l, t in enumerate(text.splitlines(1)):
377 yield {"line": t,
379 yield {"line": t,
378 "linenumber": "% 6d" % (l + 1),
380 "linenumber": "% 6d" % (l + 1),
379 "parity": l & 1}
381 "parity": self.stripes(l)}
380
382
381 yield self.t("filerevision",
383 yield self.t("filerevision",
382 file=f,
384 file=f,
383 filenode=node,
385 filenode=node,
384 path=_up(f),
386 path=_up(f),
385 text=lines(),
387 text=lines(),
386 raw=rawtext,
388 raw=rawtext,
387 mimetype=mt,
389 mimetype=mt,
388 rev=changerev,
390 rev=changerev,
389 node=hex(cn),
391 node=hex(cn),
390 manifest=hex(mfn),
392 manifest=hex(mfn),
391 author=cs[1],
393 author=cs[1],
392 date=cs[2],
394 date=cs[2],
393 parent=self.siblings(fl.parents(n), fl.rev, file=f),
395 parent=self.siblings(fl.parents(n), fl.rev, file=f),
394 child=self.siblings(fl.children(n), fl.rev, file=f),
396 child=self.siblings(fl.children(n), fl.rev, file=f),
395 rename=self.renamelink(fl, n),
397 rename=self.renamelink(fl, n),
396 permissions=self.repo.manifest.readflags(mfn)[f])
398 permissions=self.repo.manifest.readflags(mfn)[f])
397
399
398 def fileannotate(self, f, node):
400 def fileannotate(self, f, node):
399 bcache = {}
401 bcache = {}
400 ncache = {}
402 ncache = {}
401 fl = self.repo.file(f)
403 fl = self.repo.file(f)
402 n = fl.lookup(node)
404 n = fl.lookup(node)
403 node = hex(n)
405 node = hex(n)
404 changerev = fl.linkrev(n)
406 changerev = fl.linkrev(n)
405
407
406 cl = self.repo.changelog
408 cl = self.repo.changelog
407 cn = cl.node(changerev)
409 cn = cl.node(changerev)
408 cs = cl.read(cn)
410 cs = cl.read(cn)
409 mfn = cs[0]
411 mfn = cs[0]
410
412
411 def annotate(**map):
413 def annotate(**map):
412 parity = 1
414 parity = 0
413 last = None
415 last = None
414 for r, l in fl.annotate(n):
416 for r, l in fl.annotate(n):
415 try:
417 try:
416 cnode = ncache[r]
418 cnode = ncache[r]
417 except KeyError:
419 except KeyError:
418 cnode = ncache[r] = self.repo.changelog.node(r)
420 cnode = ncache[r] = self.repo.changelog.node(r)
419
421
420 try:
422 try:
421 name = bcache[r]
423 name = bcache[r]
422 except KeyError:
424 except KeyError:
423 cl = self.repo.changelog.read(cnode)
425 cl = self.repo.changelog.read(cnode)
424 bcache[r] = name = self.repo.ui.shortuser(cl[1])
426 bcache[r] = name = self.repo.ui.shortuser(cl[1])
425
427
426 if last != cnode:
428 if last != cnode:
427 parity = 1 - parity
429 parity = 1 - parity
428 last = cnode
430 last = cnode
429
431
430 yield {"parity": parity,
432 yield {"parity": parity,
431 "node": hex(cnode),
433 "node": hex(cnode),
432 "rev": r,
434 "rev": r,
433 "author": name,
435 "author": name,
434 "file": f,
436 "file": f,
435 "line": l}
437 "line": l}
436
438
437 yield self.t("fileannotate",
439 yield self.t("fileannotate",
438 file=f,
440 file=f,
439 filenode=node,
441 filenode=node,
440 annotate=annotate,
442 annotate=annotate,
441 path=_up(f),
443 path=_up(f),
442 rev=changerev,
444 rev=changerev,
443 node=hex(cn),
445 node=hex(cn),
444 manifest=hex(mfn),
446 manifest=hex(mfn),
445 author=cs[1],
447 author=cs[1],
446 date=cs[2],
448 date=cs[2],
447 rename=self.renamelink(fl, n),
449 rename=self.renamelink(fl, n),
448 parent=self.siblings(fl.parents(n), fl.rev, file=f),
450 parent=self.siblings(fl.parents(n), fl.rev, file=f),
449 child=self.siblings(fl.children(n), fl.rev, file=f),
451 child=self.siblings(fl.children(n), fl.rev, file=f),
450 permissions=self.repo.manifest.readflags(mfn)[f])
452 permissions=self.repo.manifest.readflags(mfn)[f])
451
453
452 def manifest(self, mnode, path):
454 def manifest(self, mnode, path):
453 man = self.repo.manifest
455 man = self.repo.manifest
454 mn = man.lookup(mnode)
456 mn = man.lookup(mnode)
455 mnode = hex(mn)
457 mnode = hex(mn)
456 mf = man.read(mn)
458 mf = man.read(mn)
457 rev = man.rev(mn)
459 rev = man.rev(mn)
458 changerev = man.linkrev(mn)
460 changerev = man.linkrev(mn)
459 node = self.repo.changelog.node(changerev)
461 node = self.repo.changelog.node(changerev)
460 mff = man.readflags(mn)
462 mff = man.readflags(mn)
461
463
462 files = {}
464 files = {}
463
465
464 p = path[1:]
466 p = path[1:]
465 if p and p[-1] != "/":
467 if p and p[-1] != "/":
466 p += "/"
468 p += "/"
467 l = len(p)
469 l = len(p)
468
470
469 for f,n in mf.items():
471 for f,n in mf.items():
470 if f[:l] != p:
472 if f[:l] != p:
471 continue
473 continue
472 remain = f[l:]
474 remain = f[l:]
473 if "/" in remain:
475 if "/" in remain:
474 short = remain[:remain.index("/") + 1] # bleah
476 short = remain[:remain.index("/") + 1] # bleah
475 files[short] = (f, None)
477 files[short] = (f, None)
476 else:
478 else:
477 short = os.path.basename(remain)
479 short = os.path.basename(remain)
478 files[short] = (f, n)
480 files[short] = (f, n)
479
481
480 def filelist(**map):
482 def filelist(**map):
481 parity = 0
483 parity = 0
482 fl = files.keys()
484 fl = files.keys()
483 fl.sort()
485 fl.sort()
484 for f in fl:
486 for f in fl:
485 full, fnode = files[f]
487 full, fnode = files[f]
486 if not fnode:
488 if not fnode:
487 continue
489 continue
488
490
489 yield {"file": full,
491 yield {"file": full,
490 "manifest": mnode,
492 "manifest": mnode,
491 "filenode": hex(fnode),
493 "filenode": hex(fnode),
492 "parity": parity,
494 "parity": self.stripes(parity),
493 "basename": f,
495 "basename": f,
494 "permissions": mff[full]}
496 "permissions": mff[full]}
495 parity = 1 - parity
497 parity += 1
496
498
497 def dirlist(**map):
499 def dirlist(**map):
498 parity = 0
500 parity = 0
499 fl = files.keys()
501 fl = files.keys()
500 fl.sort()
502 fl.sort()
501 for f in fl:
503 for f in fl:
502 full, fnode = files[f]
504 full, fnode = files[f]
503 if fnode:
505 if fnode:
504 continue
506 continue
505
507
506 yield {"parity": parity,
508 yield {"parity": self.stripes(parity),
507 "path": os.path.join(path, f),
509 "path": os.path.join(path, f),
508 "manifest": mnode,
510 "manifest": mnode,
509 "basename": f[:-1]}
511 "basename": f[:-1]}
510 parity = 1 - parity
512 parity += 1
511
513
512 yield self.t("manifest",
514 yield self.t("manifest",
513 manifest=mnode,
515 manifest=mnode,
514 rev=rev,
516 rev=rev,
515 node=hex(node),
517 node=hex(node),
516 path=path,
518 path=path,
517 up=_up(path),
519 up=_up(path),
518 fentries=filelist,
520 fentries=filelist,
519 dentries=dirlist,
521 dentries=dirlist,
520 archives=self.archivelist(hex(node)))
522 archives=self.archivelist(hex(node)))
521
523
522 def tags(self):
524 def tags(self):
523 cl = self.repo.changelog
525 cl = self.repo.changelog
524 mf = cl.read(cl.tip())[0]
526 mf = cl.read(cl.tip())[0]
525
527
526 i = self.repo.tagslist()
528 i = self.repo.tagslist()
527 i.reverse()
529 i.reverse()
528
530
529 def entries(notip=False, **map):
531 def entries(notip=False, **map):
530 parity = 0
532 parity = 0
531 for k,n in i:
533 for k,n in i:
532 if notip and k == "tip": continue
534 if notip and k == "tip": continue
533 yield {"parity": parity,
535 yield {"parity": self.stripes(parity),
534 "tag": k,
536 "tag": k,
535 "tagmanifest": hex(cl.read(n)[0]),
537 "tagmanifest": hex(cl.read(n)[0]),
536 "date": cl.read(n)[2],
538 "date": cl.read(n)[2],
537 "node": hex(n)}
539 "node": hex(n)}
538 parity = 1 - parity
540 parity += 1
539
541
540 yield self.t("tags",
542 yield self.t("tags",
541 manifest=hex(mf),
543 manifest=hex(mf),
542 entries=lambda **x: entries(False, **x),
544 entries=lambda **x: entries(False, **x),
543 entriesnotip=lambda **x: entries(True, **x))
545 entriesnotip=lambda **x: entries(True, **x))
544
546
545 def summary(self):
547 def summary(self):
546 cl = self.repo.changelog
548 cl = self.repo.changelog
547 mf = cl.read(cl.tip())[0]
549 mf = cl.read(cl.tip())[0]
548
550
549 i = self.repo.tagslist()
551 i = self.repo.tagslist()
550 i.reverse()
552 i.reverse()
551
553
552 def tagentries(**map):
554 def tagentries(**map):
553 parity = 0
555 parity = 0
554 count = 0
556 count = 0
555 for k,n in i:
557 for k,n in i:
556 if k == "tip": # skip tip
558 if k == "tip": # skip tip
557 continue;
559 continue;
558
560
559 count += 1
561 count += 1
560 if count > 10: # limit to 10 tags
562 if count > 10: # limit to 10 tags
561 break;
563 break;
562
564
563 c = cl.read(n)
565 c = cl.read(n)
564 m = c[0]
566 m = c[0]
565 t = c[2]
567 t = c[2]
566
568
567 yield self.t("tagentry",
569 yield self.t("tagentry",
568 parity = parity,
570 parity = self.stripes(parity),
569 tag = k,
571 tag = k,
570 node = hex(n),
572 node = hex(n),
571 date = t,
573 date = t,
572 tagmanifest = hex(m))
574 tagmanifest = hex(m))
573 parity = 1 - parity
575 parity += 1
574
576
575 def changelist(**map):
577 def changelist(**map):
576 parity = 0
578 parity = 0
577 cl = self.repo.changelog
579 cl = self.repo.changelog
578 l = [] # build a list in forward order for efficiency
580 l = [] # build a list in forward order for efficiency
579 for i in range(start, end):
581 for i in range(start, end):
580 n = cl.node(i)
582 n = cl.node(i)
581 changes = cl.read(n)
583 changes = cl.read(n)
582 hn = hex(n)
584 hn = hex(n)
583 t = changes[2]
585 t = changes[2]
584
586
585 l.insert(0, self.t(
587 l.insert(0, self.t(
586 'shortlogentry',
588 'shortlogentry',
587 parity = parity,
589 parity = parity,
588 author = changes[1],
590 author = changes[1],
589 manifest = hex(changes[0]),
591 manifest = hex(changes[0]),
590 desc = changes[4],
592 desc = changes[4],
591 date = t,
593 date = t,
592 rev = i,
594 rev = i,
593 node = hn))
595 node = hn))
594 parity = 1 - parity
596 parity = 1 - parity
595
597
596 yield l
598 yield l
597
599
598 cl = self.repo.changelog
600 cl = self.repo.changelog
599 mf = cl.read(cl.tip())[0]
601 mf = cl.read(cl.tip())[0]
600 count = cl.count()
602 count = cl.count()
601 start = max(0, count - self.maxchanges)
603 start = max(0, count - self.maxchanges)
602 end = min(count, start + self.maxchanges)
604 end = min(count, start + self.maxchanges)
603
605
604 yield self.t("summary",
606 yield self.t("summary",
605 desc = self.repo.ui.config("web", "description", "unknown"),
607 desc = self.repo.ui.config("web", "description", "unknown"),
606 owner = (self.repo.ui.config("ui", "username") or # preferred
608 owner = (self.repo.ui.config("ui", "username") or # preferred
607 self.repo.ui.config("web", "contact") or # deprecated
609 self.repo.ui.config("web", "contact") or # deprecated
608 self.repo.ui.config("web", "author", "unknown")), # also
610 self.repo.ui.config("web", "author", "unknown")), # also
609 lastchange = (0, 0), # FIXME
611 lastchange = (0, 0), # FIXME
610 manifest = hex(mf),
612 manifest = hex(mf),
611 tags = tagentries,
613 tags = tagentries,
612 shortlog = changelist)
614 shortlog = changelist)
613
615
614 def filediff(self, file, changeset):
616 def filediff(self, file, changeset):
615 cl = self.repo.changelog
617 cl = self.repo.changelog
616 n = self.repo.lookup(changeset)
618 n = self.repo.lookup(changeset)
617 changeset = hex(n)
619 changeset = hex(n)
618 p1 = cl.parents(n)[0]
620 p1 = cl.parents(n)[0]
619 cs = cl.read(n)
621 cs = cl.read(n)
620 mf = self.repo.manifest.read(cs[0])
622 mf = self.repo.manifest.read(cs[0])
621
623
622 def diff(**map):
624 def diff(**map):
623 yield self.diff(p1, n, [file])
625 yield self.diff(p1, n, [file])
624
626
625 yield self.t("filediff",
627 yield self.t("filediff",
626 file=file,
628 file=file,
627 filenode=hex(mf.get(file, nullid)),
629 filenode=hex(mf.get(file, nullid)),
628 node=changeset,
630 node=changeset,
629 rev=self.repo.changelog.rev(n),
631 rev=self.repo.changelog.rev(n),
630 parent=self.siblings(cl.parents(n), cl.rev),
632 parent=self.siblings(cl.parents(n), cl.rev),
631 child=self.siblings(cl.children(n), cl.rev),
633 child=self.siblings(cl.children(n), cl.rev),
632 diff=diff)
634 diff=diff)
633
635
634 archive_specs = {
636 archive_specs = {
635 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
637 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
636 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
638 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
637 'zip': ('application/zip', 'zip', '.zip', None),
639 'zip': ('application/zip', 'zip', '.zip', None),
638 }
640 }
639
641
640 def archive(self, req, cnode, type_):
642 def archive(self, req, cnode, type_):
641 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
643 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
642 name = "%s-%s" % (reponame, short(cnode))
644 name = "%s-%s" % (reponame, short(cnode))
643 mimetype, artype, extension, encoding = self.archive_specs[type_]
645 mimetype, artype, extension, encoding = self.archive_specs[type_]
644 headers = [('Content-type', mimetype),
646 headers = [('Content-type', mimetype),
645 ('Content-disposition', 'attachment; filename=%s%s' %
647 ('Content-disposition', 'attachment; filename=%s%s' %
646 (name, extension))]
648 (name, extension))]
647 if encoding:
649 if encoding:
648 headers.append(('Content-encoding', encoding))
650 headers.append(('Content-encoding', encoding))
649 req.header(headers)
651 req.header(headers)
650 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
652 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
651
653
652 # add tags to things
654 # add tags to things
653 # tags -> list of changesets corresponding to tags
655 # tags -> list of changesets corresponding to tags
654 # find tag, changeset, file
656 # find tag, changeset, file
655
657
656 def cleanpath(self, path):
658 def cleanpath(self, path):
657 p = util.normpath(path)
659 p = util.normpath(path)
658 if p[:2] == "..":
660 if p[:2] == "..":
659 raise Exception("suspicious path")
661 raise Exception("suspicious path")
660 return p
662 return p
661
663
662 def run(self):
664 def run(self):
663 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
665 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
664 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
666 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
665 import mercurial.hgweb.wsgicgi as wsgicgi
667 import mercurial.hgweb.wsgicgi as wsgicgi
666 from request import wsgiapplication
668 from request import wsgiapplication
667 def make_web_app():
669 def make_web_app():
668 return self
670 return self
669 wsgicgi.launch(wsgiapplication(make_web_app))
671 wsgicgi.launch(wsgiapplication(make_web_app))
670
672
671 def run_wsgi(self, req):
673 def run_wsgi(self, req):
672 def header(**map):
674 def header(**map):
673 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
675 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
674 msg = mimetools.Message(header_file, 0)
676 msg = mimetools.Message(header_file, 0)
675 req.header(msg.items())
677 req.header(msg.items())
676 yield header_file.read()
678 yield header_file.read()
677
679
678 def rawfileheader(**map):
680 def rawfileheader(**map):
679 req.header([('Content-type', map['mimetype']),
681 req.header([('Content-type', map['mimetype']),
680 ('Content-disposition', 'filename=%s' % map['file']),
682 ('Content-disposition', 'filename=%s' % map['file']),
681 ('Content-length', str(len(map['raw'])))])
683 ('Content-length', str(len(map['raw'])))])
682 yield ''
684 yield ''
683
685
684 def footer(**map):
686 def footer(**map):
685 yield self.t("footer",
687 yield self.t("footer",
686 motd=self.repo.ui.config("web", "motd", ""),
688 motd=self.repo.ui.config("web", "motd", ""),
687 **map)
689 **map)
688
690
689 def expand_form(form):
691 def expand_form(form):
690 shortcuts = {
692 shortcuts = {
691 'cl': [('cmd', ['changelog']), ('rev', None)],
693 'cl': [('cmd', ['changelog']), ('rev', None)],
692 'cs': [('cmd', ['changeset']), ('node', None)],
694 'cs': [('cmd', ['changeset']), ('node', None)],
693 'f': [('cmd', ['file']), ('filenode', None)],
695 'f': [('cmd', ['file']), ('filenode', None)],
694 'fl': [('cmd', ['filelog']), ('filenode', None)],
696 'fl': [('cmd', ['filelog']), ('filenode', None)],
695 'fd': [('cmd', ['filediff']), ('node', None)],
697 'fd': [('cmd', ['filediff']), ('node', None)],
696 'fa': [('cmd', ['annotate']), ('filenode', None)],
698 'fa': [('cmd', ['annotate']), ('filenode', None)],
697 'mf': [('cmd', ['manifest']), ('manifest', None)],
699 'mf': [('cmd', ['manifest']), ('manifest', None)],
698 'ca': [('cmd', ['archive']), ('node', None)],
700 'ca': [('cmd', ['archive']), ('node', None)],
699 'tags': [('cmd', ['tags'])],
701 'tags': [('cmd', ['tags'])],
700 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
702 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
701 'static': [('cmd', ['static']), ('file', None)]
703 'static': [('cmd', ['static']), ('file', None)]
702 }
704 }
703
705
704 for k in shortcuts.iterkeys():
706 for k in shortcuts.iterkeys():
705 if form.has_key(k):
707 if form.has_key(k):
706 for name, value in shortcuts[k]:
708 for name, value in shortcuts[k]:
707 if value is None:
709 if value is None:
708 value = form[k]
710 value = form[k]
709 form[name] = value
711 form[name] = value
710 del form[k]
712 del form[k]
711
713
712 self.refresh()
714 self.refresh()
713
715
714 expand_form(req.form)
716 expand_form(req.form)
715
717
716 m = os.path.join(self.templatepath, "map")
718 m = os.path.join(self.templatepath, "map")
717 style = self.repo.ui.config("web", "style", "")
719 style = self.repo.ui.config("web", "style", "")
718 if req.form.has_key('style'):
720 if req.form.has_key('style'):
719 style = req.form['style'][0]
721 style = req.form['style'][0]
720 if style:
722 if style:
721 b = os.path.basename("map-" + style)
723 b = os.path.basename("map-" + style)
722 p = os.path.join(self.templatepath, b)
724 p = os.path.join(self.templatepath, b)
723 if os.path.isfile(p):
725 if os.path.isfile(p):
724 m = p
726 m = p
725
727
726 port = req.env["SERVER_PORT"]
728 port = req.env["SERVER_PORT"]
727 port = port != "80" and (":" + port) or ""
729 port = port != "80" and (":" + port) or ""
728 uri = req.env["REQUEST_URI"]
730 uri = req.env["REQUEST_URI"]
729 if "?" in uri:
731 if "?" in uri:
730 uri = uri.split("?")[0]
732 uri = uri.split("?")[0]
731 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
733 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
732 if not self.reponame:
734 if not self.reponame:
733 self.reponame = (self.repo.ui.config("web", "name")
735 self.reponame = (self.repo.ui.config("web", "name")
734 or uri.strip('/') or self.repo.root)
736 or uri.strip('/') or self.repo.root)
735
737
736 self.t = templater.templater(m, templater.common_filters,
738 self.t = templater.templater(m, templater.common_filters,
737 defaults={"url": url,
739 defaults={"url": url,
738 "repo": self.reponame,
740 "repo": self.reponame,
739 "header": header,
741 "header": header,
740 "footer": footer,
742 "footer": footer,
741 "rawfileheader": rawfileheader,
743 "rawfileheader": rawfileheader,
742 })
744 })
743
745
744 if not req.form.has_key('cmd'):
746 if not req.form.has_key('cmd'):
745 req.form['cmd'] = [self.t.cache['default'],]
747 req.form['cmd'] = [self.t.cache['default'],]
746
748
747 cmd = req.form['cmd'][0]
749 cmd = req.form['cmd'][0]
748
750
749 method = getattr(self, 'do_' + cmd, None)
751 method = getattr(self, 'do_' + cmd, None)
750 if method:
752 if method:
751 method(req)
753 method(req)
752 else:
754 else:
753 req.write(self.t("error"))
755 req.write(self.t("error"))
754
756
757 def stripes(self, parity):
758 "make horizontal stripes for easier reading"
759 if self.stripecount:
760 return (1 + parity / self.stripecount) & 1
761 else:
762 return 0
763
755 def do_changelog(self, req):
764 def do_changelog(self, req):
756 hi = self.repo.changelog.count() - 1
765 hi = self.repo.changelog.count() - 1
757 if req.form.has_key('rev'):
766 if req.form.has_key('rev'):
758 hi = req.form['rev'][0]
767 hi = req.form['rev'][0]
759 try:
768 try:
760 hi = self.repo.changelog.rev(self.repo.lookup(hi))
769 hi = self.repo.changelog.rev(self.repo.lookup(hi))
761 except hg.RepoError:
770 except hg.RepoError:
762 req.write(self.search(hi)) # XXX redirect to 404 page?
771 req.write(self.search(hi)) # XXX redirect to 404 page?
763 return
772 return
764
773
765 req.write(self.changelog(hi))
774 req.write(self.changelog(hi))
766
775
767 def do_changeset(self, req):
776 def do_changeset(self, req):
768 req.write(self.changeset(req.form['node'][0]))
777 req.write(self.changeset(req.form['node'][0]))
769
778
770 def do_manifest(self, req):
779 def do_manifest(self, req):
771 req.write(self.manifest(req.form['manifest'][0],
780 req.write(self.manifest(req.form['manifest'][0],
772 self.cleanpath(req.form['path'][0])))
781 self.cleanpath(req.form['path'][0])))
773
782
774 def do_tags(self, req):
783 def do_tags(self, req):
775 req.write(self.tags())
784 req.write(self.tags())
776
785
777 def do_summary(self, req):
786 def do_summary(self, req):
778 req.write(self.summary())
787 req.write(self.summary())
779
788
780 def do_filediff(self, req):
789 def do_filediff(self, req):
781 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
790 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
782 req.form['node'][0]))
791 req.form['node'][0]))
783
792
784 def do_file(self, req):
793 def do_file(self, req):
785 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
794 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
786 req.form['filenode'][0]))
795 req.form['filenode'][0]))
787
796
788 def do_annotate(self, req):
797 def do_annotate(self, req):
789 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
798 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
790 req.form['filenode'][0]))
799 req.form['filenode'][0]))
791
800
792 def do_filelog(self, req):
801 def do_filelog(self, req):
793 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
802 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
794 req.form['filenode'][0]))
803 req.form['filenode'][0]))
795
804
796 def do_heads(self, req):
805 def do_heads(self, req):
797 resp = " ".join(map(hex, self.repo.heads())) + "\n"
806 resp = " ".join(map(hex, self.repo.heads())) + "\n"
798 req.httphdr("application/mercurial-0.1", length=len(resp))
807 req.httphdr("application/mercurial-0.1", length=len(resp))
799 req.write(resp)
808 req.write(resp)
800
809
801 def do_branches(self, req):
810 def do_branches(self, req):
802 nodes = []
811 nodes = []
803 if req.form.has_key('nodes'):
812 if req.form.has_key('nodes'):
804 nodes = map(bin, req.form['nodes'][0].split(" "))
813 nodes = map(bin, req.form['nodes'][0].split(" "))
805 resp = cStringIO.StringIO()
814 resp = cStringIO.StringIO()
806 for b in self.repo.branches(nodes):
815 for b in self.repo.branches(nodes):
807 resp.write(" ".join(map(hex, b)) + "\n")
816 resp.write(" ".join(map(hex, b)) + "\n")
808 resp = resp.getvalue()
817 resp = resp.getvalue()
809 req.httphdr("application/mercurial-0.1", length=len(resp))
818 req.httphdr("application/mercurial-0.1", length=len(resp))
810 req.write(resp)
819 req.write(resp)
811
820
812 def do_between(self, req):
821 def do_between(self, req):
813 nodes = []
822 nodes = []
814 if req.form.has_key('pairs'):
823 if req.form.has_key('pairs'):
815 pairs = [map(bin, p.split("-"))
824 pairs = [map(bin, p.split("-"))
816 for p in req.form['pairs'][0].split(" ")]
825 for p in req.form['pairs'][0].split(" ")]
817 resp = cStringIO.StringIO()
826 resp = cStringIO.StringIO()
818 for b in self.repo.between(pairs):
827 for b in self.repo.between(pairs):
819 resp.write(" ".join(map(hex, b)) + "\n")
828 resp.write(" ".join(map(hex, b)) + "\n")
820 resp = resp.getvalue()
829 resp = resp.getvalue()
821 req.httphdr("application/mercurial-0.1", length=len(resp))
830 req.httphdr("application/mercurial-0.1", length=len(resp))
822 req.write(resp)
831 req.write(resp)
823
832
824 def do_changegroup(self, req):
833 def do_changegroup(self, req):
825 req.httphdr("application/mercurial-0.1")
834 req.httphdr("application/mercurial-0.1")
826 nodes = []
835 nodes = []
827 if not self.allowpull:
836 if not self.allowpull:
828 return
837 return
829
838
830 if req.form.has_key('roots'):
839 if req.form.has_key('roots'):
831 nodes = map(bin, req.form['roots'][0].split(" "))
840 nodes = map(bin, req.form['roots'][0].split(" "))
832
841
833 z = zlib.compressobj()
842 z = zlib.compressobj()
834 f = self.repo.changegroup(nodes, 'serve')
843 f = self.repo.changegroup(nodes, 'serve')
835 while 1:
844 while 1:
836 chunk = f.read(4096)
845 chunk = f.read(4096)
837 if not chunk:
846 if not chunk:
838 break
847 break
839 req.write(z.compress(chunk))
848 req.write(z.compress(chunk))
840
849
841 req.write(z.flush())
850 req.write(z.flush())
842
851
843 def do_archive(self, req):
852 def do_archive(self, req):
844 changeset = self.repo.lookup(req.form['node'][0])
853 changeset = self.repo.lookup(req.form['node'][0])
845 type_ = req.form['type'][0]
854 type_ = req.form['type'][0]
846 allowed = self.repo.ui.configlist("web", "allow_archive")
855 allowed = self.repo.ui.configlist("web", "allow_archive")
847 if (type_ in self.archives and (type_ in allowed or
856 if (type_ in self.archives and (type_ in allowed or
848 self.repo.ui.configbool("web", "allow" + type_, False))):
857 self.repo.ui.configbool("web", "allow" + type_, False))):
849 self.archive(req, changeset, type_)
858 self.archive(req, changeset, type_)
850 return
859 return
851
860
852 req.write(self.t("error"))
861 req.write(self.t("error"))
853
862
854 def do_static(self, req):
863 def do_static(self, req):
855 fname = req.form['file'][0]
864 fname = req.form['file'][0]
856 static = self.repo.ui.config("web", "static",
865 static = self.repo.ui.config("web", "static",
857 os.path.join(self.templatepath,
866 os.path.join(self.templatepath,
858 "static"))
867 "static"))
859 req.write(staticfile(static, fname, req)
868 req.write(staticfile(static, fname, req)
860 or self.t("error", error="%r not found" % fname))
869 or self.t("error", error="%r not found" % fname))
861
870
862 def do_capabilities(self, req):
871 def do_capabilities(self, req):
863 caps = ['unbundle']
872 caps = ['unbundle']
864 if self.repo.ui.configbool('server', 'uncompressed'):
873 if self.repo.ui.configbool('server', 'uncompressed'):
865 caps.append('stream=%d' % self.repo.revlogversion)
874 caps.append('stream=%d' % self.repo.revlogversion)
866 resp = ' '.join(caps)
875 resp = ' '.join(caps)
867 req.httphdr("application/mercurial-0.1", length=len(resp))
876 req.httphdr("application/mercurial-0.1", length=len(resp))
868 req.write(resp)
877 req.write(resp)
869
878
870 def check_perm(self, req, op, default):
879 def check_perm(self, req, op, default):
871 '''check permission for operation based on user auth.
880 '''check permission for operation based on user auth.
872 return true if op allowed, else false.
881 return true if op allowed, else false.
873 default is policy to use if no config given.'''
882 default is policy to use if no config given.'''
874
883
875 user = req.env.get('REMOTE_USER')
884 user = req.env.get('REMOTE_USER')
876
885
877 deny = self.repo.ui.configlist('web', 'deny_' + op)
886 deny = self.repo.ui.configlist('web', 'deny_' + op)
878 if deny and (not user or deny == ['*'] or user in deny):
887 if deny and (not user or deny == ['*'] or user in deny):
879 return False
888 return False
880
889
881 allow = self.repo.ui.configlist('web', 'allow_' + op)
890 allow = self.repo.ui.configlist('web', 'allow_' + op)
882 return (allow and (allow == ['*'] or user in allow)) or default
891 return (allow and (allow == ['*'] or user in allow)) or default
883
892
884 def do_unbundle(self, req):
893 def do_unbundle(self, req):
885 def bail(response, headers={}):
894 def bail(response, headers={}):
886 length = int(req.env['CONTENT_LENGTH'])
895 length = int(req.env['CONTENT_LENGTH'])
887 for s in util.filechunkiter(req, limit=length):
896 for s in util.filechunkiter(req, limit=length):
888 # drain incoming bundle, else client will not see
897 # drain incoming bundle, else client will not see
889 # response when run outside cgi script
898 # response when run outside cgi script
890 pass
899 pass
891 req.httphdr("application/mercurial-0.1", headers=headers)
900 req.httphdr("application/mercurial-0.1", headers=headers)
892 req.write('0\n')
901 req.write('0\n')
893 req.write(response)
902 req.write(response)
894
903
895 # require ssl by default, auth info cannot be sniffed and
904 # require ssl by default, auth info cannot be sniffed and
896 # replayed
905 # replayed
897 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
906 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
898 if ssl_req and not req.env.get('HTTPS'):
907 if ssl_req and not req.env.get('HTTPS'):
899 bail(_('ssl required\n'))
908 bail(_('ssl required\n'))
900 return
909 return
901
910
902 # do not allow push unless explicitly allowed
911 # do not allow push unless explicitly allowed
903 if not self.check_perm(req, 'push', False):
912 if not self.check_perm(req, 'push', False):
904 bail(_('push not authorized\n'),
913 bail(_('push not authorized\n'),
905 headers={'status': '401 Unauthorized'})
914 headers={'status': '401 Unauthorized'})
906 return
915 return
907
916
908 req.httphdr("application/mercurial-0.1")
917 req.httphdr("application/mercurial-0.1")
909
918
910 their_heads = req.form['heads'][0].split(' ')
919 their_heads = req.form['heads'][0].split(' ')
911
920
912 def check_heads():
921 def check_heads():
913 heads = map(hex, self.repo.heads())
922 heads = map(hex, self.repo.heads())
914 return their_heads == [hex('force')] or their_heads == heads
923 return their_heads == [hex('force')] or their_heads == heads
915
924
916 # fail early if possible
925 # fail early if possible
917 if not check_heads():
926 if not check_heads():
918 bail(_('unsynced changes\n'))
927 bail(_('unsynced changes\n'))
919 return
928 return
920
929
921 # do not lock repo until all changegroup data is
930 # do not lock repo until all changegroup data is
922 # streamed. save to temporary file.
931 # streamed. save to temporary file.
923
932
924 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
933 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
925 fp = os.fdopen(fd, 'wb+')
934 fp = os.fdopen(fd, 'wb+')
926 try:
935 try:
927 length = int(req.env['CONTENT_LENGTH'])
936 length = int(req.env['CONTENT_LENGTH'])
928 for s in util.filechunkiter(req, limit=length):
937 for s in util.filechunkiter(req, limit=length):
929 fp.write(s)
938 fp.write(s)
930
939
931 lock = self.repo.lock()
940 lock = self.repo.lock()
932 try:
941 try:
933 if not check_heads():
942 if not check_heads():
934 req.write('0\n')
943 req.write('0\n')
935 req.write(_('unsynced changes\n'))
944 req.write(_('unsynced changes\n'))
936 return
945 return
937
946
938 fp.seek(0)
947 fp.seek(0)
939
948
940 # send addchangegroup output to client
949 # send addchangegroup output to client
941
950
942 old_stdout = sys.stdout
951 old_stdout = sys.stdout
943 sys.stdout = cStringIO.StringIO()
952 sys.stdout = cStringIO.StringIO()
944
953
945 try:
954 try:
946 ret = self.repo.addchangegroup(fp, 'serve')
955 ret = self.repo.addchangegroup(fp, 'serve')
947 finally:
956 finally:
948 val = sys.stdout.getvalue()
957 val = sys.stdout.getvalue()
949 sys.stdout = old_stdout
958 sys.stdout = old_stdout
950 req.write('%d\n' % ret)
959 req.write('%d\n' % ret)
951 req.write(val)
960 req.write(val)
952 finally:
961 finally:
953 lock.release()
962 lock.release()
954 finally:
963 finally:
955 fp.close()
964 fp.close()
956 os.unlink(tempname)
965 os.unlink(tempname)
957
966
958 def do_stream_out(self, req):
967 def do_stream_out(self, req):
959 req.httphdr("application/mercurial-0.1")
968 req.httphdr("application/mercurial-0.1")
960 streamclone.stream_out(self.repo, req)
969 streamclone.stream_out(self.repo, req)
General Comments 0
You need to be logged in to leave comments. Login now