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