##// END OF EJS Templates
move mail sending code into core, so extensions can share it....
Vadim Gelfer -
r2200:9f43b6e2 default
parent child Browse files
Show More
@@ -1,344 +1,368 b''
1 HGRC(5)
1 HGRC(5)
2 =======
2 =======
3 Bryan O'Sullivan <bos@serpentine.com>
3 Bryan O'Sullivan <bos@serpentine.com>
4
4
5 NAME
5 NAME
6 ----
6 ----
7 hgrc - configuration files for Mercurial
7 hgrc - configuration files for Mercurial
8
8
9 SYNOPSIS
9 SYNOPSIS
10 --------
10 --------
11
11
12 The Mercurial system uses a set of configuration files to control
12 The Mercurial system uses a set of configuration files to control
13 aspects of its behaviour.
13 aspects of its behaviour.
14
14
15 FILES
15 FILES
16 -----
16 -----
17
17
18 Mercurial reads configuration data from several files, if they exist.
18 Mercurial reads configuration data from several files, if they exist.
19 The names of these files depend on the system on which Mercurial is
19 The names of these files depend on the system on which Mercurial is
20 installed.
20 installed.
21
21
22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
23 (Unix) <install-root>/etc/mercurial/hgrc::
23 (Unix) <install-root>/etc/mercurial/hgrc::
24 Per-installation configuration files, searched for in the
24 Per-installation configuration files, searched for in the
25 directory where Mercurial is installed. For example, if installed
25 directory where Mercurial is installed. For example, if installed
26 in /shared/tools, Mercurial will look in
26 in /shared/tools, Mercurial will look in
27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
28 all Mercurial commands executed by any user in any directory.
28 all Mercurial commands executed by any user in any directory.
29
29
30 (Unix) /etc/mercurial/hgrc.d/*.rc::
30 (Unix) /etc/mercurial/hgrc.d/*.rc::
31 (Unix) /etc/mercurial/hgrc::
31 (Unix) /etc/mercurial/hgrc::
32 (Windows) C:\Mercurial\Mercurial.ini::
32 (Windows) C:\Mercurial\Mercurial.ini::
33 Per-system configuration files, for the system on which Mercurial
33 Per-system configuration files, for the system on which Mercurial
34 is running. Options in these files apply to all Mercurial
34 is running. Options in these files apply to all Mercurial
35 commands executed by any user in any directory. Options in these
35 commands executed by any user in any directory. Options in these
36 files override per-installation options.
36 files override per-installation options.
37
37
38 (Unix) $HOME/.hgrc::
38 (Unix) $HOME/.hgrc::
39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini
39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini
40 Per-user configuration file, for the user running Mercurial.
40 Per-user configuration file, for the user running Mercurial.
41 Options in this file apply to all Mercurial commands executed by
41 Options in this file apply to all Mercurial commands executed by
42 any user in any directory. Options in this file override
42 any user in any directory. Options in this file override
43 per-installation and per-system options.
43 per-installation and per-system options.
44
44
45 (Unix, Windows) <repo>/.hg/hgrc::
45 (Unix, Windows) <repo>/.hg/hgrc::
46 Per-repository configuration options that only apply in a
46 Per-repository configuration options that only apply in a
47 particular repository. This file is not version-controlled, and
47 particular repository. This file is not version-controlled, and
48 will not get transferred during a "clone" operation. Options in
48 will not get transferred during a "clone" operation. Options in
49 this file override options in all other configuration files.
49 this file override options in all other configuration files.
50
50
51 SYNTAX
51 SYNTAX
52 ------
52 ------
53
53
54 A configuration file consists of sections, led by a "[section]" header
54 A configuration file consists of sections, led by a "[section]" header
55 and followed by "name: value" entries; "name=value" is also accepted.
55 and followed by "name: value" entries; "name=value" is also accepted.
56
56
57 [spam]
57 [spam]
58 eggs=ham
58 eggs=ham
59 green=
59 green=
60 eggs
60 eggs
61
61
62 Each line contains one entry. If the lines that follow are indented,
62 Each line contains one entry. If the lines that follow are indented,
63 they are treated as continuations of that entry.
63 they are treated as continuations of that entry.
64
64
65 Leading whitespace is removed from values. Empty lines are skipped.
65 Leading whitespace is removed from values. Empty lines are skipped.
66
66
67 The optional values can contain format strings which refer to other
67 The optional values can contain format strings which refer to other
68 values in the same section, or values in a special DEFAULT section.
68 values in the same section, or values in a special DEFAULT section.
69
69
70 Lines beginning with "#" or ";" are ignored and may be used to provide
70 Lines beginning with "#" or ";" are ignored and may be used to provide
71 comments.
71 comments.
72
72
73 SECTIONS
73 SECTIONS
74 --------
74 --------
75
75
76 This section describes the different sections that may appear in a
76 This section describes the different sections that may appear in a
77 Mercurial "hgrc" file, the purpose of each section, its possible
77 Mercurial "hgrc" file, the purpose of each section, its possible
78 keys, and their possible values.
78 keys, and their possible values.
79
79
80 decode/encode::
80 decode/encode::
81 Filters for transforming files on checkout/checkin. This would
81 Filters for transforming files on checkout/checkin. This would
82 typically be used for newline processing or other
82 typically be used for newline processing or other
83 localization/canonicalization of files.
83 localization/canonicalization of files.
84
84
85 Filters consist of a filter pattern followed by a filter command.
85 Filters consist of a filter pattern followed by a filter command.
86 Filter patterns are globs by default, rooted at the repository
86 Filter patterns are globs by default, rooted at the repository
87 root. For example, to match any file ending in ".txt" in the root
87 root. For example, to match any file ending in ".txt" in the root
88 directory only, use the pattern "*.txt". To match any file ending
88 directory only, use the pattern "*.txt". To match any file ending
89 in ".c" anywhere in the repository, use the pattern "**.c".
89 in ".c" anywhere in the repository, use the pattern "**.c".
90
90
91 The filter command can start with a specifier, either "pipe:" or
91 The filter command can start with a specifier, either "pipe:" or
92 "tempfile:". If no specifier is given, "pipe:" is used by default.
92 "tempfile:". If no specifier is given, "pipe:" is used by default.
93
93
94 A "pipe:" command must accept data on stdin and return the
94 A "pipe:" command must accept data on stdin and return the
95 transformed data on stdout.
95 transformed data on stdout.
96
96
97 Pipe example:
97 Pipe example:
98
98
99 [encode]
99 [encode]
100 # uncompress gzip files on checkin to improve delta compression
100 # uncompress gzip files on checkin to improve delta compression
101 # note: not necessarily a good idea, just an example
101 # note: not necessarily a good idea, just an example
102 *.gz = pipe: gunzip
102 *.gz = pipe: gunzip
103
103
104 [decode]
104 [decode]
105 # recompress gzip files when writing them to the working dir (we
105 # recompress gzip files when writing them to the working dir (we
106 # can safely omit "pipe:", because it's the default)
106 # can safely omit "pipe:", because it's the default)
107 *.gz = gzip
107 *.gz = gzip
108
108
109 A "tempfile:" command is a template. The string INFILE is replaced
109 A "tempfile:" command is a template. The string INFILE is replaced
110 with the name of a temporary file that contains the data to be
110 with the name of a temporary file that contains the data to be
111 filtered by the command. The string OUTFILE is replaced with the
111 filtered by the command. The string OUTFILE is replaced with the
112 name of an empty temporary file, where the filtered data must be
112 name of an empty temporary file, where the filtered data must be
113 written by the command.
113 written by the command.
114
114
115 NOTE: the tempfile mechanism is recommended for Windows systems,
115 NOTE: the tempfile mechanism is recommended for Windows systems,
116 where the standard shell I/O redirection operators often have
116 where the standard shell I/O redirection operators often have
117 strange effects. In particular, if you are doing line ending
117 strange effects. In particular, if you are doing line ending
118 conversion on Windows using the popular dos2unix and unix2dos
118 conversion on Windows using the popular dos2unix and unix2dos
119 programs, you *must* use the tempfile mechanism, as using pipes will
119 programs, you *must* use the tempfile mechanism, as using pipes will
120 corrupt the contents of your files.
120 corrupt the contents of your files.
121
121
122 Tempfile example:
122 Tempfile example:
123
123
124 [encode]
124 [encode]
125 # convert files to unix line ending conventions on checkin
125 # convert files to unix line ending conventions on checkin
126 **.txt = tempfile: dos2unix -n INFILE OUTFILE
126 **.txt = tempfile: dos2unix -n INFILE OUTFILE
127
127
128 [decode]
128 [decode]
129 # convert files to windows line ending conventions when writing
129 # convert files to windows line ending conventions when writing
130 # them to the working dir
130 # them to the working dir
131 **.txt = tempfile: unix2dos -n INFILE OUTFILE
131 **.txt = tempfile: unix2dos -n INFILE OUTFILE
132
132
133 email::
134 Settings for extensions that send email messages.
135 from;;
136 Optional. Email address to use in "From" header and SMTP envelope
137 of outgoing messages.
138
133 hooks::
139 hooks::
134 Commands or Python functions that get automatically executed by
140 Commands or Python functions that get automatically executed by
135 various actions such as starting or finishing a commit. Multiple
141 various actions such as starting or finishing a commit. Multiple
136 hooks can be run for the same action by appending a suffix to the
142 hooks can be run for the same action by appending a suffix to the
137 action. Overriding a site-wide hook can be done by changing its
143 action. Overriding a site-wide hook can be done by changing its
138 value or setting it to an empty string.
144 value or setting it to an empty string.
139
145
140 Example .hg/hgrc:
146 Example .hg/hgrc:
141
147
142 [hooks]
148 [hooks]
143 # do not use the site-wide hook
149 # do not use the site-wide hook
144 incoming =
150 incoming =
145 incoming.email = /my/email/hook
151 incoming.email = /my/email/hook
146 incoming.autobuild = /my/build/hook
152 incoming.autobuild = /my/build/hook
147
153
148 Most hooks are run with environment variables set that give added
154 Most hooks are run with environment variables set that give added
149 useful information. For each hook below, the environment variables
155 useful information. For each hook below, the environment variables
150 it is passed are listed with names of the form "$HG_foo".
156 it is passed are listed with names of the form "$HG_foo".
151
157
152 changegroup;;
158 changegroup;;
153 Run after a changegroup has been added via push, pull or
159 Run after a changegroup has been added via push, pull or
154 unbundle. ID of the first new changeset is in $HG_NODE.
160 unbundle. ID of the first new changeset is in $HG_NODE.
155 commit;;
161 commit;;
156 Run after a changeset has been created in the local repository.
162 Run after a changeset has been created in the local repository.
157 ID of the newly created changeset is in $HG_NODE. Parent
163 ID of the newly created changeset is in $HG_NODE. Parent
158 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
164 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
159 incoming;;
165 incoming;;
160 Run after a changeset has been pulled, pushed, or unbundled into
166 Run after a changeset has been pulled, pushed, or unbundled into
161 the local repository. The ID of the newly arrived changeset is in
167 the local repository. The ID of the newly arrived changeset is in
162 $HG_NODE.
168 $HG_NODE.
163 outgoing;;
169 outgoing;;
164 Run after sending changes from local repository to another. ID of
170 Run after sending changes from local repository to another. ID of
165 first changeset sent is in $HG_NODE. Source of operation is in
171 first changeset sent is in $HG_NODE. Source of operation is in
166 $HG_SOURCE; see "preoutgoing" hook for description.
172 $HG_SOURCE; see "preoutgoing" hook for description.
167 prechangegroup;;
173 prechangegroup;;
168 Run before a changegroup is added via push, pull or unbundle.
174 Run before a changegroup is added via push, pull or unbundle.
169 Exit status 0 allows the changegroup to proceed. Non-zero status
175 Exit status 0 allows the changegroup to proceed. Non-zero status
170 will cause the push, pull or unbundle to fail.
176 will cause the push, pull or unbundle to fail.
171 precommit;;
177 precommit;;
172 Run before starting a local commit. Exit status 0 allows the
178 Run before starting a local commit. Exit status 0 allows the
173 commit to proceed. Non-zero status will cause the commit to fail.
179 commit to proceed. Non-zero status will cause the commit to fail.
174 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
180 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
175 preoutgoing;;
181 preoutgoing;;
176 Run before computing changes to send from the local repository to
182 Run before computing changes to send from the local repository to
177 another. Non-zero status will cause failure. This lets you
183 another. Non-zero status will cause failure. This lets you
178 prevent pull over http or ssh. Also prevents against local pull,
184 prevent pull over http or ssh. Also prevents against local pull,
179 push (outbound) or bundle commands, but not effective, since you
185 push (outbound) or bundle commands, but not effective, since you
180 can just copy files instead then. Source of operation is in
186 can just copy files instead then. Source of operation is in
181 $HG_SOURCE. If "serve", operation is happening on behalf of
187 $HG_SOURCE. If "serve", operation is happening on behalf of
182 remote ssh or http repository. If "push", "pull" or "bundle",
188 remote ssh or http repository. If "push", "pull" or "bundle",
183 operation is happening on behalf of repository on same system.
189 operation is happening on behalf of repository on same system.
184 pretag;;
190 pretag;;
185 Run before creating a tag. Exit status 0 allows the tag to be
191 Run before creating a tag. Exit status 0 allows the tag to be
186 created. Non-zero status will cause the tag to fail. ID of
192 created. Non-zero status will cause the tag to fail. ID of
187 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
193 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
188 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
194 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
189 pretxnchangegroup;;
195 pretxnchangegroup;;
190 Run after a changegroup has been added via push, pull or unbundle,
196 Run after a changegroup has been added via push, pull or unbundle,
191 but before the transaction has been committed. Changegroup is
197 but before the transaction has been committed. Changegroup is
192 visible to hook program. This lets you validate incoming changes
198 visible to hook program. This lets you validate incoming changes
193 before accepting them. Passed the ID of the first new changeset
199 before accepting them. Passed the ID of the first new changeset
194 in $HG_NODE. Exit status 0 allows the transaction to commit.
200 in $HG_NODE. Exit status 0 allows the transaction to commit.
195 Non-zero status will cause the transaction to be rolled back and
201 Non-zero status will cause the transaction to be rolled back and
196 the push, pull or unbundle will fail.
202 the push, pull or unbundle will fail.
197 pretxncommit;;
203 pretxncommit;;
198 Run after a changeset has been created but the transaction not yet
204 Run after a changeset has been created but the transaction not yet
199 committed. Changeset is visible to hook program. This lets you
205 committed. Changeset is visible to hook program. This lets you
200 validate commit message and changes. Exit status 0 allows the
206 validate commit message and changes. Exit status 0 allows the
201 commit to proceed. Non-zero status will cause the transaction to
207 commit to proceed. Non-zero status will cause the transaction to
202 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
208 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
203 IDs are in $HG_PARENT1 and $HG_PARENT2.
209 IDs are in $HG_PARENT1 and $HG_PARENT2.
204 tag;;
210 tag;;
205 Run after a tag is created. ID of tagged changeset is in
211 Run after a tag is created. ID of tagged changeset is in
206 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
212 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
207 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
213 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
208
214
209 In earlier releases, the names of hook environment variables did not
215 In earlier releases, the names of hook environment variables did not
210 have a "HG_" prefix. These unprefixed names are still provided in
216 have a "HG_" prefix. These unprefixed names are still provided in
211 the environment for backwards compatibility, but their use is
217 the environment for backwards compatibility, but their use is
212 deprecated, and they will be removed in a future release.
218 deprecated, and they will be removed in a future release.
213
219
214 The syntax for Python hooks is as follows:
220 The syntax for Python hooks is as follows:
215
221
216 hookname = python:modulename.submodule.callable
222 hookname = python:modulename.submodule.callable
217
223
218 Python hooks are run within the Mercurial process. Each hook is
224 Python hooks are run within the Mercurial process. Each hook is
219 called with at least three keyword arguments: a ui object (keyword
225 called with at least three keyword arguments: a ui object (keyword
220 "ui"), a repository object (keyword "repo"), and a "hooktype"
226 "ui"), a repository object (keyword "repo"), and a "hooktype"
221 keyword that tells what kind of hook is used. Arguments listed as
227 keyword that tells what kind of hook is used. Arguments listed as
222 environment variables above are passed as keyword arguments, with no
228 environment variables above are passed as keyword arguments, with no
223 "HG_" prefix, and names in lower case.
229 "HG_" prefix, and names in lower case.
224
230
225 A Python hook must return a "true" value to succeed. Returning a
231 A Python hook must return a "true" value to succeed. Returning a
226 "false" value or raising an exception is treated as failure of the
232 "false" value or raising an exception is treated as failure of the
227 hook.
233 hook.
228
234
229 http_proxy::
235 http_proxy::
230 Used to access web-based Mercurial repositories through a HTTP
236 Used to access web-based Mercurial repositories through a HTTP
231 proxy.
237 proxy.
232 host;;
238 host;;
233 Host name and (optional) port of the proxy server, for example
239 Host name and (optional) port of the proxy server, for example
234 "myproxy:8000".
240 "myproxy:8000".
235 no;;
241 no;;
236 Optional. Comma-separated list of host names that should bypass
242 Optional. Comma-separated list of host names that should bypass
237 the proxy.
243 the proxy.
238 passwd;;
244 passwd;;
239 Optional. Password to authenticate with at the proxy server.
245 Optional. Password to authenticate with at the proxy server.
240 user;;
246 user;;
241 Optional. User name to authenticate with at the proxy server.
247 Optional. User name to authenticate with at the proxy server.
242
248
249 smtp::
250 Configuration for extensions that need to send email messages.
251 host;;
252 Optional. Host name of mail server. Default: "mail".
253 port;;
254 Optional. Port to connect to on mail server. Default: 25.
255 tls;;
256 Optional. Whether to connect to mail server using TLS. True or
257 False. Default: False.
258 username;;
259 Optional. User name to authenticate to SMTP server with.
260 If username is specified, password must also be specified.
261 Default: none.
262 password;;
263 Optional. Password to authenticate to SMTP server with.
264 If username is specified, password must also be specified.
265 Default: none.
266
243 paths::
267 paths::
244 Assigns symbolic names to repositories. The left side is the
268 Assigns symbolic names to repositories. The left side is the
245 symbolic name, and the right gives the directory or URL that is the
269 symbolic name, and the right gives the directory or URL that is the
246 location of the repository.
270 location of the repository.
247
271
248 ui::
272 ui::
249 User interface controls.
273 User interface controls.
250 debug;;
274 debug;;
251 Print debugging information. True or False. Default is False.
275 Print debugging information. True or False. Default is False.
252 editor;;
276 editor;;
253 The editor to use during a commit. Default is $EDITOR or "vi".
277 The editor to use during a commit. Default is $EDITOR or "vi".
254 ignore;;
278 ignore;;
255 A file to read per-user ignore patterns from. This file should be in
279 A file to read per-user ignore patterns from. This file should be in
256 the same format as a repository-wide .hgignore file. This option
280 the same format as a repository-wide .hgignore file. This option
257 supports hook syntax, so if you want to specify multiple ignore
281 supports hook syntax, so if you want to specify multiple ignore
258 files, you can do so by setting something like
282 files, you can do so by setting something like
259 "ignore.other = ~/.hgignore2".
283 "ignore.other = ~/.hgignore2".
260 interactive;;
284 interactive;;
261 Allow to prompt the user. True or False. Default is True.
285 Allow to prompt the user. True or False. Default is True.
262 logtemplate;;
286 logtemplate;;
263 Template string for commands that print changesets.
287 Template string for commands that print changesets.
264 style;;
288 style;;
265 Name of style to use for command output.
289 Name of style to use for command output.
266 merge;;
290 merge;;
267 The conflict resolution program to use during a manual merge.
291 The conflict resolution program to use during a manual merge.
268 Default is "hgmerge".
292 Default is "hgmerge".
269 quiet;;
293 quiet;;
270 Reduce the amount of output printed. True or False. Default is False.
294 Reduce the amount of output printed. True or False. Default is False.
271 remotecmd;;
295 remotecmd;;
272 remote command to use for clone/push/pull operations. Default is 'hg'.
296 remote command to use for clone/push/pull operations. Default is 'hg'.
273 ssh;;
297 ssh;;
274 command to use for SSH connections. Default is 'ssh'.
298 command to use for SSH connections. Default is 'ssh'.
275 timeout;;
299 timeout;;
276 The timeout used when a lock is held (in seconds), a negative value
300 The timeout used when a lock is held (in seconds), a negative value
277 means no timeout. Default is 600.
301 means no timeout. Default is 600.
278 username;;
302 username;;
279 The committer of a changeset created when running "commit".
303 The committer of a changeset created when running "commit".
280 Typically a person's name and email address, e.g. "Fred Widget
304 Typically a person's name and email address, e.g. "Fred Widget
281 <fred@example.com>". Default is $EMAIL or username@hostname, unless
305 <fred@example.com>". Default is $EMAIL or username@hostname, unless
282 username is set to an empty string, which enforces specifying the
306 username is set to an empty string, which enforces specifying the
283 username manually.
307 username manually.
284 verbose;;
308 verbose;;
285 Increase the amount of output printed. True or False. Default is False.
309 Increase the amount of output printed. True or False. Default is False.
286
310
287
311
288 web::
312 web::
289 Web interface configuration.
313 Web interface configuration.
290 accesslog;;
314 accesslog;;
291 Where to output the access log. Default is stdout.
315 Where to output the access log. Default is stdout.
292 address;;
316 address;;
293 Interface address to bind to. Default is all.
317 Interface address to bind to. Default is all.
294 allowbz2;;
318 allowbz2;;
295 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
319 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
296 allowgz;;
320 allowgz;;
297 Whether to allow .tar.gz downloading of repo revisions. Default is false.
321 Whether to allow .tar.gz downloading of repo revisions. Default is false.
298 allowpull;;
322 allowpull;;
299 Whether to allow pulling from the repository. Default is true.
323 Whether to allow pulling from the repository. Default is true.
300 allowzip;;
324 allowzip;;
301 Whether to allow .zip downloading of repo revisions. Default is false.
325 Whether to allow .zip downloading of repo revisions. Default is false.
302 This feature creates temporary files.
326 This feature creates temporary files.
303 baseurl;;
327 baseurl;;
304 Base URL to use when publishing URLs in other locations, so
328 Base URL to use when publishing URLs in other locations, so
305 third-party tools like email notification hooks can construct URLs.
329 third-party tools like email notification hooks can construct URLs.
306 Example: "http://hgserver/repos/"
330 Example: "http://hgserver/repos/"
307 description;;
331 description;;
308 Textual description of the repository's purpose or contents.
332 Textual description of the repository's purpose or contents.
309 Default is "unknown".
333 Default is "unknown".
310 errorlog;;
334 errorlog;;
311 Where to output the error log. Default is stderr.
335 Where to output the error log. Default is stderr.
312 ipv6;;
336 ipv6;;
313 Whether to use IPv6. Default is false.
337 Whether to use IPv6. Default is false.
314 name;;
338 name;;
315 Repository name to use in the web interface. Default is current
339 Repository name to use in the web interface. Default is current
316 working directory.
340 working directory.
317 maxchanges;;
341 maxchanges;;
318 Maximum number of changes to list on the changelog. Default is 10.
342 Maximum number of changes to list on the changelog. Default is 10.
319 maxfiles;;
343 maxfiles;;
320 Maximum number of files to list per changeset. Default is 10.
344 Maximum number of files to list per changeset. Default is 10.
321 port;;
345 port;;
322 Port to listen on. Default is 8000.
346 Port to listen on. Default is 8000.
323 style;;
347 style;;
324 Which template map style to use.
348 Which template map style to use.
325 templates;;
349 templates;;
326 Where to find the HTML templates. Default is install path.
350 Where to find the HTML templates. Default is install path.
327
351
328
352
329 AUTHOR
353 AUTHOR
330 ------
354 ------
331 Bryan O'Sullivan <bos@serpentine.com>.
355 Bryan O'Sullivan <bos@serpentine.com>.
332
356
333 Mercurial was written by Matt Mackall <mpm@selenic.com>.
357 Mercurial was written by Matt Mackall <mpm@selenic.com>.
334
358
335 SEE ALSO
359 SEE ALSO
336 --------
360 --------
337 hg(1)
361 hg(1)
338
362
339 COPYING
363 COPYING
340 -------
364 -------
341 This manual page is copyright 2005 Bryan O'Sullivan.
365 This manual page is copyright 2005 Bryan O'Sullivan.
342 Mercurial is copyright 2005 Matt Mackall.
366 Mercurial is copyright 2005 Matt Mackall.
343 Free use of this software is granted under the terms of the GNU General
367 Free use of this software is granted under the terms of the GNU General
344 Public License (GPL).
368 Public License (GPL).
@@ -1,292 +1,272 b''
1 # Command for sending a collection of Mercurial changesets as a series
1 # Command for sending a collection of Mercurial changesets as a series
2 # of patch emails.
2 # of patch emails.
3 #
3 #
4 # The series is started off with a "[PATCH 0 of N]" introduction,
4 # The series is started off with a "[PATCH 0 of N]" introduction,
5 # which describes the series as a whole.
5 # which describes the series as a whole.
6 #
6 #
7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
8 # the first line of the changeset description as the subject text.
8 # the first line of the changeset description as the subject text.
9 # The message contains two or three body parts:
9 # The message contains two or three body parts:
10 #
10 #
11 # The remainder of the changeset description.
11 # The remainder of the changeset description.
12 #
12 #
13 # [Optional] If the diffstat program is installed, the result of
13 # [Optional] If the diffstat program is installed, the result of
14 # running diffstat on the patch.
14 # running diffstat on the patch.
15 #
15 #
16 # The patch itself, as generated by "hg export".
16 # The patch itself, as generated by "hg export".
17 #
17 #
18 # Each message refers to all of its predecessors using the In-Reply-To
18 # Each message refers to all of its predecessors using the In-Reply-To
19 # and References headers, so they will show up as a sequence in
19 # and References headers, so they will show up as a sequence in
20 # threaded mail and news readers, and in mail archives.
20 # threaded mail and news readers, and in mail archives.
21 #
21 #
22 # For each changeset, you will be prompted with a diffstat summary and
22 # For each changeset, you will be prompted with a diffstat summary and
23 # the changeset summary, so you can be sure you are sending the right
23 # the changeset summary, so you can be sure you are sending the right
24 # changes.
24 # changes.
25 #
25 #
26 # It is best to run this script with the "-n" (test only) flag before
26 # It is best to run this script with the "-n" (test only) flag before
27 # firing it up "for real", in which case it will use your pager to
27 # firing it up "for real", in which case it will use your pager to
28 # display each of the messages that it would send.
28 # display each of the messages that it would send.
29 #
29 #
30 # The "-m" (mbox) option will create an mbox file instead of sending
30 # The "-m" (mbox) option will create an mbox file instead of sending
31 # the messages directly. This can be reviewed e.g. with "mutt -R -f mbox",
31 # the messages directly. This can be reviewed e.g. with "mutt -R -f mbox",
32 # and finally sent with "formail -s sendmail -bm -t < mbox".
32 # and finally sent with "formail -s sendmail -bm -t < mbox".
33 #
33 #
34 # To configure a default mail host, add a section like this to your
35 # hgrc file:
36 #
37 # [smtp]
38 # host = my_mail_host
39 # port = 1025
40 # tls = yes # or omit if not needed
41 # username = user # if SMTP authentication required
42 # password = password # if SMTP authentication required - PLAINTEXT
43 #
44 # To configure other defaults, add a section like this to your hgrc
34 # To configure other defaults, add a section like this to your hgrc
45 # file:
35 # file:
46 #
36 #
47 # [email]
37 # [email]
48 # from = My Name <my@email>
38 # from = My Name <my@email>
49 # to = recipient1, recipient2, ...
39 # to = recipient1, recipient2, ...
50 # cc = cc1, cc2, ...
40 # cc = cc1, cc2, ...
51
41
52 from mercurial.demandload import *
42 from mercurial.demandload import *
53 demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
43 demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
54 mercurial:commands,hg,ui
44 mercurial:commands,hg,ui
55 os errno popen2 smtplib socket sys tempfile time''')
45 os errno popen2 socket sys tempfile time''')
56 from mercurial.i18n import gettext as _
46 from mercurial.i18n import gettext as _
57
47
58 try:
48 try:
59 # readline gives raw_input editing capabilities, but is not
49 # readline gives raw_input editing capabilities, but is not
60 # present on windows
50 # present on windows
61 import readline
51 import readline
62 except ImportError: pass
52 except ImportError: pass
63
53
64 def diffstat(patch):
54 def diffstat(patch):
65 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
55 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
66 try:
56 try:
67 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
57 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
68 try:
58 try:
69 for line in patch: print >> p.tochild, line
59 for line in patch: print >> p.tochild, line
70 p.tochild.close()
60 p.tochild.close()
71 if p.wait(): return
61 if p.wait(): return
72 fp = os.fdopen(fd, 'r')
62 fp = os.fdopen(fd, 'r')
73 stat = []
63 stat = []
74 for line in fp: stat.append(line.lstrip())
64 for line in fp: stat.append(line.lstrip())
75 last = stat.pop()
65 last = stat.pop()
76 stat.insert(0, last)
66 stat.insert(0, last)
77 stat = ''.join(stat)
67 stat = ''.join(stat)
78 if stat.startswith('0 files'): raise ValueError
68 if stat.startswith('0 files'): raise ValueError
79 return stat
69 return stat
80 except: raise
70 except: raise
81 finally:
71 finally:
82 try: os.unlink(name)
72 try: os.unlink(name)
83 except: pass
73 except: pass
84
74
85 def patchbomb(ui, repo, *revs, **opts):
75 def patchbomb(ui, repo, *revs, **opts):
86 '''send changesets as a series of patch emails
76 '''send changesets as a series of patch emails
87
77
88 The series starts with a "[PATCH 0 of N]" introduction, which
78 The series starts with a "[PATCH 0 of N]" introduction, which
89 describes the series as a whole.
79 describes the series as a whole.
90
80
91 Each patch email has a Subject line of "[PATCH M of N] ...", using
81 Each patch email has a Subject line of "[PATCH M of N] ...", using
92 the first line of the changeset description as the subject text.
82 the first line of the changeset description as the subject text.
93 The message contains two or three body parts. First, the rest of
83 The message contains two or three body parts. First, the rest of
94 the changeset description. Next, (optionally) if the diffstat
84 the changeset description. Next, (optionally) if the diffstat
95 program is installed, the result of running diffstat on the patch.
85 program is installed, the result of running diffstat on the patch.
96 Finally, the patch itself, as generated by "hg export".'''
86 Finally, the patch itself, as generated by "hg export".'''
97 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
87 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
98 if default: prompt += ' [%s]' % default
88 if default: prompt += ' [%s]' % default
99 prompt += rest
89 prompt += rest
100 while True:
90 while True:
101 r = raw_input(prompt)
91 r = raw_input(prompt)
102 if r: return r
92 if r: return r
103 if default is not None: return default
93 if default is not None: return default
104 if empty_ok: return r
94 if empty_ok: return r
105 ui.warn(_('Please enter a valid value.\n'))
95 ui.warn(_('Please enter a valid value.\n'))
106
96
107 def confirm(s):
97 def confirm(s):
108 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
98 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
109 raise ValueError
99 raise ValueError
110
100
111 def cdiffstat(summary, patch):
101 def cdiffstat(summary, patch):
112 s = diffstat(patch)
102 s = diffstat(patch)
113 if s:
103 if s:
114 if summary:
104 if summary:
115 ui.write(summary, '\n')
105 ui.write(summary, '\n')
116 ui.write(s, '\n')
106 ui.write(s, '\n')
117 confirm(_('Does the diffstat above look okay'))
107 confirm(_('Does the diffstat above look okay'))
118 return s
108 return s
119
109
120 def makepatch(patch, idx, total):
110 def makepatch(patch, idx, total):
121 desc = []
111 desc = []
122 node = None
112 node = None
123 body = ''
113 body = ''
124 for line in patch:
114 for line in patch:
125 if line.startswith('#'):
115 if line.startswith('#'):
126 if line.startswith('# Node ID'): node = line.split()[-1]
116 if line.startswith('# Node ID'): node = line.split()[-1]
127 continue
117 continue
128 if line.startswith('diff -r'): break
118 if line.startswith('diff -r'): break
129 desc.append(line)
119 desc.append(line)
130 if not node: raise ValueError
120 if not node: raise ValueError
131
121
132 #body = ('\n'.join(desc[1:]).strip() or
122 #body = ('\n'.join(desc[1:]).strip() or
133 # 'Patch subject is complete summary.')
123 # 'Patch subject is complete summary.')
134 #body += '\n\n\n'
124 #body += '\n\n\n'
135
125
136 if opts['plain']:
126 if opts['plain']:
137 while patch and patch[0].startswith('# '): patch.pop(0)
127 while patch and patch[0].startswith('# '): patch.pop(0)
138 if patch: patch.pop(0)
128 if patch: patch.pop(0)
139 while patch and not patch[0].strip(): patch.pop(0)
129 while patch and not patch[0].strip(): patch.pop(0)
140 if opts['diffstat']:
130 if opts['diffstat']:
141 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
131 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
142 body += '\n'.join(patch)
132 body += '\n'.join(patch)
143 msg = email.MIMEText.MIMEText(body)
133 msg = email.MIMEText.MIMEText(body)
144 if total == 1:
134 if total == 1:
145 subj = '[PATCH] ' + desc[0].strip()
135 subj = '[PATCH] ' + desc[0].strip()
146 else:
136 else:
147 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
137 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
148 if subj.endswith('.'): subj = subj[:-1]
138 if subj.endswith('.'): subj = subj[:-1]
149 msg['Subject'] = subj
139 msg['Subject'] = subj
150 msg['X-Mercurial-Node'] = node
140 msg['X-Mercurial-Node'] = node
151 return msg
141 return msg
152
142
153 start_time = int(time.time())
143 start_time = int(time.time())
154
144
155 def genmsgid(id):
145 def genmsgid(id):
156 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
146 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
157
147
158 patches = []
148 patches = []
159
149
160 class exportee:
150 class exportee:
161 def __init__(self, container):
151 def __init__(self, container):
162 self.lines = []
152 self.lines = []
163 self.container = container
153 self.container = container
164 self.name = 'email'
154 self.name = 'email'
165
155
166 def write(self, data):
156 def write(self, data):
167 self.lines.append(data)
157 self.lines.append(data)
168
158
169 def close(self):
159 def close(self):
170 self.container.append(''.join(self.lines).split('\n'))
160 self.container.append(''.join(self.lines).split('\n'))
171 self.lines = []
161 self.lines = []
172
162
173 commands.export(ui, repo, *revs, **{'output': exportee(patches),
163 commands.export(ui, repo, *revs, **{'output': exportee(patches),
174 'switch_parent': False,
164 'switch_parent': False,
175 'text': None})
165 'text': None})
176
166
177 jumbo = []
167 jumbo = []
178 msgs = []
168 msgs = []
179
169
180 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
170 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
181
171
182 for p, i in zip(patches, range(len(patches))):
172 for p, i in zip(patches, range(len(patches))):
183 jumbo.extend(p)
173 jumbo.extend(p)
184 msgs.append(makepatch(p, i + 1, len(patches)))
174 msgs.append(makepatch(p, i + 1, len(patches)))
185
175
186 sender = (opts['from'] or ui.config('email', 'from') or
176 sender = (opts['from'] or ui.config('email', 'from') or
187 ui.config('patchbomb', 'from') or
177 ui.config('patchbomb', 'from') or
188 prompt('From', ui.username()))
178 prompt('From', ui.username()))
189
179
190 def getaddrs(opt, prpt, default = None):
180 def getaddrs(opt, prpt, default = None):
191 addrs = opts[opt] or (ui.config('email', opt) or
181 addrs = opts[opt] or (ui.config('email', opt) or
192 ui.config('patchbomb', opt) or
182 ui.config('patchbomb', opt) or
193 prompt(prpt, default = default)).split(',')
183 prompt(prpt, default = default)).split(',')
194 return [a.strip() for a in addrs if a.strip()]
184 return [a.strip() for a in addrs if a.strip()]
195 to = getaddrs('to', 'To')
185 to = getaddrs('to', 'To')
196 cc = getaddrs('cc', 'Cc', '')
186 cc = getaddrs('cc', 'Cc', '')
197
187
198 if len(patches) > 1:
188 if len(patches) > 1:
199 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
189 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
200
190
201 msg = email.MIMEMultipart.MIMEMultipart()
191 msg = email.MIMEMultipart.MIMEMultipart()
202 msg['Subject'] = '[PATCH 0 of %d] %s' % (
192 msg['Subject'] = '[PATCH 0 of %d] %s' % (
203 len(patches),
193 len(patches),
204 opts['subject'] or
194 opts['subject'] or
205 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
195 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
206
196
207 ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
197 ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
208
198
209 body = []
199 body = []
210
200
211 while True:
201 while True:
212 try: l = raw_input()
202 try: l = raw_input()
213 except EOFError: break
203 except EOFError: break
214 if l == '.': break
204 if l == '.': break
215 body.append(l)
205 body.append(l)
216
206
217 msg.attach(email.MIMEText.MIMEText('\n'.join(body) + '\n'))
207 msg.attach(email.MIMEText.MIMEText('\n'.join(body) + '\n'))
218
208
219 if opts['diffstat']:
209 if opts['diffstat']:
220 d = cdiffstat(_('Final summary:\n'), jumbo)
210 d = cdiffstat(_('Final summary:\n'), jumbo)
221 if d: msg.attach(email.MIMEText.MIMEText(d))
211 if d: msg.attach(email.MIMEText.MIMEText(d))
222
212
223 msgs.insert(0, msg)
213 msgs.insert(0, msg)
224
214
225 ui.write('\n')
215 ui.write('\n')
226
216
227 if not opts['test'] and not opts['mbox']:
217 if not opts['test'] and not opts['mbox']:
228 s = smtplib.SMTP()
218 mail = ui.sendmail()
229 s.connect(host = ui.config('smtp', 'host', 'mail'),
230 port = int(ui.config('smtp', 'port', 25)))
231 if ui.configbool('smtp', 'tls'):
232 s.ehlo()
233 s.starttls()
234 s.ehlo()
235 username = ui.config('smtp', 'username')
236 password = ui.config('smtp', 'password')
237 if username and password:
238 s.login(username, password)
239 parent = None
219 parent = None
240 tz = time.strftime('%z')
220 tz = time.strftime('%z')
241 sender_addr = email.Utils.parseaddr(sender)[1]
221 sender_addr = email.Utils.parseaddr(sender)[1]
242 for m in msgs:
222 for m in msgs:
243 try:
223 try:
244 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
224 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
245 except TypeError:
225 except TypeError:
246 m['Message-Id'] = genmsgid('patchbomb')
226 m['Message-Id'] = genmsgid('patchbomb')
247 if parent:
227 if parent:
248 m['In-Reply-To'] = parent
228 m['In-Reply-To'] = parent
249 else:
229 else:
250 parent = m['Message-Id']
230 parent = m['Message-Id']
251 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz
231 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz
252 start_time += 1
232 start_time += 1
253 m['From'] = sender
233 m['From'] = sender
254 m['To'] = ', '.join(to)
234 m['To'] = ', '.join(to)
255 if cc: m['Cc'] = ', '.join(cc)
235 if cc: m['Cc'] = ', '.join(cc)
256 if opts['test']:
236 if opts['test']:
257 ui.status('Displaying ', m['Subject'], ' ...\n')
237 ui.status('Displaying ', m['Subject'], ' ...\n')
258 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
238 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
259 try:
239 try:
260 fp.write(m.as_string(0))
240 fp.write(m.as_string(0))
261 fp.write('\n')
241 fp.write('\n')
262 except IOError, inst:
242 except IOError, inst:
263 if inst.errno != errno.EPIPE:
243 if inst.errno != errno.EPIPE:
264 raise
244 raise
265 fp.close()
245 fp.close()
266 elif opts['mbox']:
246 elif opts['mbox']:
267 ui.status('Writing ', m['Subject'], ' ...\n')
247 ui.status('Writing ', m['Subject'], ' ...\n')
268 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
248 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
269 date = time.asctime(time.localtime(start_time))
249 date = time.asctime(time.localtime(start_time))
270 fp.write('From %s %s\n' % (sender_addr, date))
250 fp.write('From %s %s\n' % (sender_addr, date))
271 fp.write(m.as_string(0))
251 fp.write(m.as_string(0))
272 fp.write('\n\n')
252 fp.write('\n\n')
273 fp.close()
253 fp.close()
274 else:
254 else:
275 ui.status('Sending ', m['Subject'], ' ...\n')
255 ui.status('Sending ', m['Subject'], ' ...\n')
276 s.sendmail(sender, to + cc, m.as_string(0))
256 mail.sendmail(sender, to + cc, m.as_string(0))
277 if not opts['test'] and not opts['mbox']:
257 if not opts['test'] and not opts['mbox']:
278 s.close()
258 mail.close()
279
259
280 cmdtable = {
260 cmdtable = {
281 'email':
261 'email':
282 (patchbomb,
262 (patchbomb,
283 [('c', 'cc', [], 'email addresses of copy recipients'),
263 [('c', 'cc', [], 'email addresses of copy recipients'),
284 ('d', 'diffstat', None, 'add diffstat output to messages'),
264 ('d', 'diffstat', None, 'add diffstat output to messages'),
285 ('f', 'from', '', 'email address of sender'),
265 ('f', 'from', '', 'email address of sender'),
286 ('', 'plain', None, 'omit hg patch header'),
266 ('', 'plain', None, 'omit hg patch header'),
287 ('n', 'test', None, 'print messages that would be sent'),
267 ('n', 'test', None, 'print messages that would be sent'),
288 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
268 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
289 ('s', 'subject', '', 'subject of introductory message'),
269 ('s', 'subject', '', 'subject of introductory message'),
290 ('t', 'to', [], 'email addresses of recipients')],
270 ('t', 'to', [], 'email addresses of recipients')],
291 "hg email [OPTION]... [REV]...")
271 "hg email [OPTION]... [REV]...")
292 }
272 }
@@ -1,266 +1,280 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits 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 import ConfigParser
8 import ConfigParser
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "errno os re socket sys tempfile util")
11 demandload(globals(), "errno os re smtplib socket sys tempfile util")
12
12
13 class ui(object):
13 class ui(object):
14 def __init__(self, verbose=False, debug=False, quiet=False,
14 def __init__(self, verbose=False, debug=False, quiet=False,
15 interactive=True, traceback=False, parentui=None):
15 interactive=True, traceback=False, parentui=None):
16 self.overlay = {}
16 self.overlay = {}
17 if parentui is None:
17 if parentui is None:
18 # this is the parent of all ui children
18 # this is the parent of all ui children
19 self.parentui = None
19 self.parentui = None
20 self.cdata = ConfigParser.SafeConfigParser()
20 self.cdata = ConfigParser.SafeConfigParser()
21 self.readconfig(util.rcpath())
21 self.readconfig(util.rcpath())
22
22
23 self.quiet = self.configbool("ui", "quiet")
23 self.quiet = self.configbool("ui", "quiet")
24 self.verbose = self.configbool("ui", "verbose")
24 self.verbose = self.configbool("ui", "verbose")
25 self.debugflag = self.configbool("ui", "debug")
25 self.debugflag = self.configbool("ui", "debug")
26 self.interactive = self.configbool("ui", "interactive", True)
26 self.interactive = self.configbool("ui", "interactive", True)
27 self.traceback = traceback
27 self.traceback = traceback
28
28
29 self.updateopts(verbose, debug, quiet, interactive)
29 self.updateopts(verbose, debug, quiet, interactive)
30 self.diffcache = None
30 self.diffcache = None
31 self.header = []
31 self.header = []
32 self.prev_header = []
32 self.prev_header = []
33 self.revlogopts = self.configrevlog()
33 self.revlogopts = self.configrevlog()
34 else:
34 else:
35 # parentui may point to an ui object which is already a child
35 # parentui may point to an ui object which is already a child
36 self.parentui = parentui.parentui or parentui
36 self.parentui = parentui.parentui or parentui
37 parent_cdata = self.parentui.cdata
37 parent_cdata = self.parentui.cdata
38 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
38 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
39 # make interpolation work
39 # make interpolation work
40 for section in parent_cdata.sections():
40 for section in parent_cdata.sections():
41 self.cdata.add_section(section)
41 self.cdata.add_section(section)
42 for name, value in parent_cdata.items(section, raw=True):
42 for name, value in parent_cdata.items(section, raw=True):
43 self.cdata.set(section, name, value)
43 self.cdata.set(section, name, value)
44
44
45 def __getattr__(self, key):
45 def __getattr__(self, key):
46 return getattr(self.parentui, key)
46 return getattr(self.parentui, key)
47
47
48 def updateopts(self, verbose=False, debug=False, quiet=False,
48 def updateopts(self, verbose=False, debug=False, quiet=False,
49 interactive=True, traceback=False):
49 interactive=True, traceback=False):
50 self.quiet = (self.quiet or quiet) and not verbose and not debug
50 self.quiet = (self.quiet or quiet) and not verbose and not debug
51 self.verbose = (self.verbose or verbose) or debug
51 self.verbose = (self.verbose or verbose) or debug
52 self.debugflag = (self.debugflag or debug)
52 self.debugflag = (self.debugflag or debug)
53 self.interactive = (self.interactive and interactive)
53 self.interactive = (self.interactive and interactive)
54 self.traceback = self.traceback or traceback
54 self.traceback = self.traceback or traceback
55
55
56 def readconfig(self, fn, root=None):
56 def readconfig(self, fn, root=None):
57 if isinstance(fn, basestring):
57 if isinstance(fn, basestring):
58 fn = [fn]
58 fn = [fn]
59 for f in fn:
59 for f in fn:
60 try:
60 try:
61 self.cdata.read(f)
61 self.cdata.read(f)
62 except ConfigParser.ParsingError, inst:
62 except ConfigParser.ParsingError, inst:
63 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
63 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
64 # translate paths relative to root (or home) into absolute paths
64 # translate paths relative to root (or home) into absolute paths
65 if root is None:
65 if root is None:
66 root = os.path.expanduser('~')
66 root = os.path.expanduser('~')
67 for name, path in self.configitems("paths"):
67 for name, path in self.configitems("paths"):
68 if path and path.find("://") == -1 and not os.path.isabs(path):
68 if path and path.find("://") == -1 and not os.path.isabs(path):
69 self.cdata.set("paths", name, os.path.join(root, path))
69 self.cdata.set("paths", name, os.path.join(root, path))
70
70
71 def setconfig(self, section, name, val):
71 def setconfig(self, section, name, val):
72 self.overlay[(section, name)] = val
72 self.overlay[(section, name)] = val
73
73
74 def config(self, section, name, default=None):
74 def config(self, section, name, default=None):
75 if self.overlay.has_key((section, name)):
75 if self.overlay.has_key((section, name)):
76 return self.overlay[(section, name)]
76 return self.overlay[(section, name)]
77 if self.cdata.has_option(section, name):
77 if self.cdata.has_option(section, name):
78 try:
78 try:
79 return self.cdata.get(section, name)
79 return self.cdata.get(section, name)
80 except ConfigParser.InterpolationError, inst:
80 except ConfigParser.InterpolationError, inst:
81 raise util.Abort(_("Error in configuration:\n%s") % inst)
81 raise util.Abort(_("Error in configuration:\n%s") % inst)
82 if self.parentui is None:
82 if self.parentui is None:
83 return default
83 return default
84 else:
84 else:
85 return self.parentui.config(section, name, default)
85 return self.parentui.config(section, name, default)
86
86
87 def configbool(self, section, name, default=False):
87 def configbool(self, section, name, default=False):
88 if self.overlay.has_key((section, name)):
88 if self.overlay.has_key((section, name)):
89 return self.overlay[(section, name)]
89 return self.overlay[(section, name)]
90 if self.cdata.has_option(section, name):
90 if self.cdata.has_option(section, name):
91 try:
91 try:
92 return self.cdata.getboolean(section, name)
92 return self.cdata.getboolean(section, name)
93 except ConfigParser.InterpolationError, inst:
93 except ConfigParser.InterpolationError, inst:
94 raise util.Abort(_("Error in configuration:\n%s") % inst)
94 raise util.Abort(_("Error in configuration:\n%s") % inst)
95 if self.parentui is None:
95 if self.parentui is None:
96 return default
96 return default
97 else:
97 else:
98 return self.parentui.configbool(section, name, default)
98 return self.parentui.configbool(section, name, default)
99
99
100 def configitems(self, section):
100 def configitems(self, section):
101 items = {}
101 items = {}
102 if self.parentui is not None:
102 if self.parentui is not None:
103 items = dict(self.parentui.configitems(section))
103 items = dict(self.parentui.configitems(section))
104 if self.cdata.has_section(section):
104 if self.cdata.has_section(section):
105 try:
105 try:
106 items.update(dict(self.cdata.items(section)))
106 items.update(dict(self.cdata.items(section)))
107 except ConfigParser.InterpolationError, inst:
107 except ConfigParser.InterpolationError, inst:
108 raise util.Abort(_("Error in configuration:\n%s") % inst)
108 raise util.Abort(_("Error in configuration:\n%s") % inst)
109 x = items.items()
109 x = items.items()
110 x.sort()
110 x.sort()
111 return x
111 return x
112
112
113 def walkconfig(self, seen=None):
113 def walkconfig(self, seen=None):
114 if seen is None:
114 if seen is None:
115 seen = {}
115 seen = {}
116 for (section, name), value in self.overlay.iteritems():
116 for (section, name), value in self.overlay.iteritems():
117 yield section, name, value
117 yield section, name, value
118 seen[section, name] = 1
118 seen[section, name] = 1
119 for section in self.cdata.sections():
119 for section in self.cdata.sections():
120 for name, value in self.cdata.items(section):
120 for name, value in self.cdata.items(section):
121 if (section, name) in seen: continue
121 if (section, name) in seen: continue
122 yield section, name, value.replace('\n', '\\n')
122 yield section, name, value.replace('\n', '\\n')
123 seen[section, name] = 1
123 seen[section, name] = 1
124 if self.parentui is not None:
124 if self.parentui is not None:
125 for parent in self.parentui.walkconfig(seen):
125 for parent in self.parentui.walkconfig(seen):
126 yield parent
126 yield parent
127
127
128 def extensions(self):
128 def extensions(self):
129 return self.configitems("extensions")
129 return self.configitems("extensions")
130
130
131 def hgignorefiles(self):
131 def hgignorefiles(self):
132 result = []
132 result = []
133 cfgitems = self.configitems("ui")
133 cfgitems = self.configitems("ui")
134 for key, value in cfgitems:
134 for key, value in cfgitems:
135 if key == 'ignore' or key.startswith('ignore.'):
135 if key == 'ignore' or key.startswith('ignore.'):
136 path = os.path.expanduser(value)
136 path = os.path.expanduser(value)
137 result.append(path)
137 result.append(path)
138 return result
138 return result
139
139
140 def configrevlog(self):
140 def configrevlog(self):
141 ret = {}
141 ret = {}
142 for x in self.configitems("revlog"):
142 for x in self.configitems("revlog"):
143 k = x[0].lower()
143 k = x[0].lower()
144 ret[k] = x[1]
144 ret[k] = x[1]
145 return ret
145 return ret
146 def diffopts(self):
146 def diffopts(self):
147 if self.diffcache:
147 if self.diffcache:
148 return self.diffcache
148 return self.diffcache
149 ret = { 'showfunc' : True, 'ignorews' : False}
149 ret = { 'showfunc' : True, 'ignorews' : False}
150 for x in self.configitems("diff"):
150 for x in self.configitems("diff"):
151 k = x[0].lower()
151 k = x[0].lower()
152 v = x[1]
152 v = x[1]
153 if v:
153 if v:
154 v = v.lower()
154 v = v.lower()
155 if v == 'true':
155 if v == 'true':
156 value = True
156 value = True
157 else:
157 else:
158 value = False
158 value = False
159 ret[k] = value
159 ret[k] = value
160 self.diffcache = ret
160 self.diffcache = ret
161 return ret
161 return ret
162
162
163 def username(self):
163 def username(self):
164 """Return default username to be used in commits.
164 """Return default username to be used in commits.
165
165
166 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
166 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
167 and stop searching if one of these is set.
167 and stop searching if one of these is set.
168 Abort if found username is an empty string to force specifying
168 Abort if found username is an empty string to force specifying
169 the commit user elsewhere, e.g. with line option or repo hgrc.
169 the commit user elsewhere, e.g. with line option or repo hgrc.
170 If not found, use $LOGNAME or $USERNAME +"@full.hostname".
170 If not found, use $LOGNAME or $USERNAME +"@full.hostname".
171 """
171 """
172 user = os.environ.get("HGUSER")
172 user = os.environ.get("HGUSER")
173 if user is None:
173 if user is None:
174 user = self.config("ui", "username")
174 user = self.config("ui", "username")
175 if user is None:
175 if user is None:
176 user = os.environ.get("EMAIL")
176 user = os.environ.get("EMAIL")
177 if user is None:
177 if user is None:
178 user = os.environ.get("LOGNAME") or os.environ.get("USERNAME")
178 user = os.environ.get("LOGNAME") or os.environ.get("USERNAME")
179 if user:
179 if user:
180 user = "%s@%s" % (user, socket.getfqdn())
180 user = "%s@%s" % (user, socket.getfqdn())
181 if not user:
181 if not user:
182 raise util.Abort(_("Please specify a username."))
182 raise util.Abort(_("Please specify a username."))
183 return user
183 return user
184
184
185 def shortuser(self, user):
185 def shortuser(self, user):
186 """Return a short representation of a user name or email address."""
186 """Return a short representation of a user name or email address."""
187 if not self.verbose: user = util.shortuser(user)
187 if not self.verbose: user = util.shortuser(user)
188 return user
188 return user
189
189
190 def expandpath(self, loc):
190 def expandpath(self, loc):
191 """Return repository location relative to cwd or from [paths]"""
191 """Return repository location relative to cwd or from [paths]"""
192 if loc.find("://") != -1 or os.path.exists(loc):
192 if loc.find("://") != -1 or os.path.exists(loc):
193 return loc
193 return loc
194
194
195 return self.config("paths", loc, loc)
195 return self.config("paths", loc, loc)
196
196
197 def write(self, *args):
197 def write(self, *args):
198 if self.header:
198 if self.header:
199 if self.header != self.prev_header:
199 if self.header != self.prev_header:
200 self.prev_header = self.header
200 self.prev_header = self.header
201 self.write(*self.header)
201 self.write(*self.header)
202 self.header = []
202 self.header = []
203 for a in args:
203 for a in args:
204 sys.stdout.write(str(a))
204 sys.stdout.write(str(a))
205
205
206 def write_header(self, *args):
206 def write_header(self, *args):
207 for a in args:
207 for a in args:
208 self.header.append(str(a))
208 self.header.append(str(a))
209
209
210 def write_err(self, *args):
210 def write_err(self, *args):
211 try:
211 try:
212 if not sys.stdout.closed: sys.stdout.flush()
212 if not sys.stdout.closed: sys.stdout.flush()
213 for a in args:
213 for a in args:
214 sys.stderr.write(str(a))
214 sys.stderr.write(str(a))
215 except IOError, inst:
215 except IOError, inst:
216 if inst.errno != errno.EPIPE:
216 if inst.errno != errno.EPIPE:
217 raise
217 raise
218
218
219 def flush(self):
219 def flush(self):
220 try: sys.stdout.flush()
220 try: sys.stdout.flush()
221 except: pass
221 except: pass
222 try: sys.stderr.flush()
222 try: sys.stderr.flush()
223 except: pass
223 except: pass
224
224
225 def readline(self):
225 def readline(self):
226 return sys.stdin.readline()[:-1]
226 return sys.stdin.readline()[:-1]
227 def prompt(self, msg, pat, default="y"):
227 def prompt(self, msg, pat, default="y"):
228 if not self.interactive: return default
228 if not self.interactive: return default
229 while 1:
229 while 1:
230 self.write(msg, " ")
230 self.write(msg, " ")
231 r = self.readline()
231 r = self.readline()
232 if re.match(pat, r):
232 if re.match(pat, r):
233 return r
233 return r
234 else:
234 else:
235 self.write(_("unrecognized response\n"))
235 self.write(_("unrecognized response\n"))
236 def status(self, *msg):
236 def status(self, *msg):
237 if not self.quiet: self.write(*msg)
237 if not self.quiet: self.write(*msg)
238 def warn(self, *msg):
238 def warn(self, *msg):
239 self.write_err(*msg)
239 self.write_err(*msg)
240 def note(self, *msg):
240 def note(self, *msg):
241 if self.verbose: self.write(*msg)
241 if self.verbose: self.write(*msg)
242 def debug(self, *msg):
242 def debug(self, *msg):
243 if self.debugflag: self.write(*msg)
243 if self.debugflag: self.write(*msg)
244 def edit(self, text, user):
244 def edit(self, text, user):
245 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt")
245 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt")
246 try:
246 try:
247 f = os.fdopen(fd, "w")
247 f = os.fdopen(fd, "w")
248 f.write(text)
248 f.write(text)
249 f.close()
249 f.close()
250
250
251 editor = (os.environ.get("HGEDITOR") or
251 editor = (os.environ.get("HGEDITOR") or
252 self.config("ui", "editor") or
252 self.config("ui", "editor") or
253 os.environ.get("EDITOR", "vi"))
253 os.environ.get("EDITOR", "vi"))
254
254
255 util.system("%s \"%s\"" % (editor, name),
255 util.system("%s \"%s\"" % (editor, name),
256 environ={'HGUSER': user},
256 environ={'HGUSER': user},
257 onerr=util.Abort, errprefix=_("edit failed"))
257 onerr=util.Abort, errprefix=_("edit failed"))
258
258
259 f = open(name)
259 f = open(name)
260 t = f.read()
260 t = f.read()
261 f.close()
261 f.close()
262 t = re.sub("(?m)^HG:.*\n", "", t)
262 t = re.sub("(?m)^HG:.*\n", "", t)
263 finally:
263 finally:
264 os.unlink(name)
264 os.unlink(name)
265
265
266 return t
266 return t
267
268 def sendmail(self):
269 s = smtplib.SMTP()
270 s.connect(host = self.config('smtp', 'host', 'mail'),
271 port = int(self.config('smtp', 'port', 25)))
272 if self.configbool('smtp', 'tls'):
273 s.ehlo()
274 s.starttls()
275 s.ehlo()
276 username = self.config('smtp', 'username')
277 password = self.config('smtp', 'password')
278 if username and password:
279 s.login(username, password)
280 return s
General Comments 0
You need to be logged in to leave comments. Login now