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