##// END OF EJS Templates
define standard name for base url to use when printing hgweb urls....
Vadim Gelfer -
r2197:5de8b44f default
parent child Browse files
Show More
@@ -1,340 +1,344 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 hooks::
133 hooks::
134 Commands or Python functions that get automatically executed by
134 Commands or Python functions that get automatically executed by
135 various actions such as starting or finishing a commit. Multiple
135 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
136 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
137 action. Overriding a site-wide hook can be done by changing its
138 value or setting it to an empty string.
138 value or setting it to an empty string.
139
139
140 Example .hg/hgrc:
140 Example .hg/hgrc:
141
141
142 [hooks]
142 [hooks]
143 # do not use the site-wide hook
143 # do not use the site-wide hook
144 incoming =
144 incoming =
145 incoming.email = /my/email/hook
145 incoming.email = /my/email/hook
146 incoming.autobuild = /my/build/hook
146 incoming.autobuild = /my/build/hook
147
147
148 Most hooks are run with environment variables set that give added
148 Most hooks are run with environment variables set that give added
149 useful information. For each hook below, the environment variables
149 useful information. For each hook below, the environment variables
150 it is passed are listed with names of the form "$HG_foo".
150 it is passed are listed with names of the form "$HG_foo".
151
151
152 changegroup;;
152 changegroup;;
153 Run after a changegroup has been added via push, pull or
153 Run after a changegroup has been added via push, pull or
154 unbundle. ID of the first new changeset is in $HG_NODE.
154 unbundle. ID of the first new changeset is in $HG_NODE.
155 commit;;
155 commit;;
156 Run after a changeset has been created in the local repository.
156 Run after a changeset has been created in the local repository.
157 ID of the newly created changeset is in $HG_NODE. Parent
157 ID of the newly created changeset is in $HG_NODE. Parent
158 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
158 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
159 incoming;;
159 incoming;;
160 Run after a changeset has been pulled, pushed, or unbundled into
160 Run after a changeset has been pulled, pushed, or unbundled into
161 the local repository. The ID of the newly arrived changeset is in
161 the local repository. The ID of the newly arrived changeset is in
162 $HG_NODE.
162 $HG_NODE.
163 outgoing;;
163 outgoing;;
164 Run after sending changes from local repository to another. ID of
164 Run after sending changes from local repository to another. ID of
165 first changeset sent is in $HG_NODE. Source of operation is in
165 first changeset sent is in $HG_NODE. Source of operation is in
166 $HG_SOURCE; see "preoutgoing" hook for description.
166 $HG_SOURCE; see "preoutgoing" hook for description.
167 prechangegroup;;
167 prechangegroup;;
168 Run before a changegroup is added via push, pull or unbundle.
168 Run before a changegroup is added via push, pull or unbundle.
169 Exit status 0 allows the changegroup to proceed. Non-zero status
169 Exit status 0 allows the changegroup to proceed. Non-zero status
170 will cause the push, pull or unbundle to fail.
170 will cause the push, pull or unbundle to fail.
171 precommit;;
171 precommit;;
172 Run before starting a local commit. Exit status 0 allows the
172 Run before starting a local commit. Exit status 0 allows the
173 commit to proceed. Non-zero status will cause the commit to fail.
173 commit to proceed. Non-zero status will cause the commit to fail.
174 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
174 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
175 preoutgoing;;
175 preoutgoing;;
176 Run before computing changes to send from the local repository to
176 Run before computing changes to send from the local repository to
177 another. Non-zero status will cause failure. This lets you
177 another. Non-zero status will cause failure. This lets you
178 prevent pull over http or ssh. Also prevents against local pull,
178 prevent pull over http or ssh. Also prevents against local pull,
179 push (outbound) or bundle commands, but not effective, since you
179 push (outbound) or bundle commands, but not effective, since you
180 can just copy files instead then. Source of operation is in
180 can just copy files instead then. Source of operation is in
181 $HG_SOURCE. If "serve", operation is happening on behalf of
181 $HG_SOURCE. If "serve", operation is happening on behalf of
182 remote ssh or http repository. If "push", "pull" or "bundle",
182 remote ssh or http repository. If "push", "pull" or "bundle",
183 operation is happening on behalf of repository on same system.
183 operation is happening on behalf of repository on same system.
184 pretag;;
184 pretag;;
185 Run before creating a tag. Exit status 0 allows the tag to be
185 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
186 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
187 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.
188 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
189 pretxnchangegroup;;
189 pretxnchangegroup;;
190 Run after a changegroup has been added via push, pull or unbundle,
190 Run after a changegroup has been added via push, pull or unbundle,
191 but before the transaction has been committed. Changegroup is
191 but before the transaction has been committed. Changegroup is
192 visible to hook program. This lets you validate incoming changes
192 visible to hook program. This lets you validate incoming changes
193 before accepting them. Passed the ID of the first new changeset
193 before accepting them. Passed the ID of the first new changeset
194 in $HG_NODE. Exit status 0 allows the transaction to commit.
194 in $HG_NODE. Exit status 0 allows the transaction to commit.
195 Non-zero status will cause the transaction to be rolled back and
195 Non-zero status will cause the transaction to be rolled back and
196 the push, pull or unbundle will fail.
196 the push, pull or unbundle will fail.
197 pretxncommit;;
197 pretxncommit;;
198 Run after a changeset has been created but the transaction not yet
198 Run after a changeset has been created but the transaction not yet
199 committed. Changeset is visible to hook program. This lets you
199 committed. Changeset is visible to hook program. This lets you
200 validate commit message and changes. Exit status 0 allows the
200 validate commit message and changes. Exit status 0 allows the
201 commit to proceed. Non-zero status will cause the transaction to
201 commit to proceed. Non-zero status will cause the transaction to
202 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
202 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
203 IDs are in $HG_PARENT1 and $HG_PARENT2.
203 IDs are in $HG_PARENT1 and $HG_PARENT2.
204 tag;;
204 tag;;
205 Run after a tag is created. ID of tagged changeset is in
205 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
206 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
207 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
207 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
208
208
209 In earlier releases, the names of hook environment variables did not
209 In earlier releases, the names of hook environment variables did not
210 have a "HG_" prefix. These unprefixed names are still provided in
210 have a "HG_" prefix. These unprefixed names are still provided in
211 the environment for backwards compatibility, but their use is
211 the environment for backwards compatibility, but their use is
212 deprecated, and they will be removed in a future release.
212 deprecated, and they will be removed in a future release.
213
213
214 The syntax for Python hooks is as follows:
214 The syntax for Python hooks is as follows:
215
215
216 hookname = python:modulename.submodule.callable
216 hookname = python:modulename.submodule.callable
217
217
218 Python hooks are run within the Mercurial process. Each hook is
218 Python hooks are run within the Mercurial process. Each hook is
219 called with at least three keyword arguments: a ui object (keyword
219 called with at least three keyword arguments: a ui object (keyword
220 "ui"), a repository object (keyword "repo"), and a "hooktype"
220 "ui"), a repository object (keyword "repo"), and a "hooktype"
221 keyword that tells what kind of hook is used. Arguments listed as
221 keyword that tells what kind of hook is used. Arguments listed as
222 environment variables above are passed as keyword arguments, with no
222 environment variables above are passed as keyword arguments, with no
223 "HG_" prefix, and names in lower case.
223 "HG_" prefix, and names in lower case.
224
224
225 A Python hook must return a "true" value to succeed. Returning a
225 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
226 "false" value or raising an exception is treated as failure of the
227 hook.
227 hook.
228
228
229 http_proxy::
229 http_proxy::
230 Used to access web-based Mercurial repositories through a HTTP
230 Used to access web-based Mercurial repositories through a HTTP
231 proxy.
231 proxy.
232 host;;
232 host;;
233 Host name and (optional) port of the proxy server, for example
233 Host name and (optional) port of the proxy server, for example
234 "myproxy:8000".
234 "myproxy:8000".
235 no;;
235 no;;
236 Optional. Comma-separated list of host names that should bypass
236 Optional. Comma-separated list of host names that should bypass
237 the proxy.
237 the proxy.
238 passwd;;
238 passwd;;
239 Optional. Password to authenticate with at the proxy server.
239 Optional. Password to authenticate with at the proxy server.
240 user;;
240 user;;
241 Optional. User name to authenticate with at the proxy server.
241 Optional. User name to authenticate with at the proxy server.
242
242
243 paths::
243 paths::
244 Assigns symbolic names to repositories. The left side is the
244 Assigns symbolic names to repositories. The left side is the
245 symbolic name, and the right gives the directory or URL that is the
245 symbolic name, and the right gives the directory or URL that is the
246 location of the repository.
246 location of the repository.
247
247
248 ui::
248 ui::
249 User interface controls.
249 User interface controls.
250 debug;;
250 debug;;
251 Print debugging information. True or False. Default is False.
251 Print debugging information. True or False. Default is False.
252 editor;;
252 editor;;
253 The editor to use during a commit. Default is $EDITOR or "vi".
253 The editor to use during a commit. Default is $EDITOR or "vi".
254 ignore;;
254 ignore;;
255 A file to read per-user ignore patterns from. This file should be in
255 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
256 the same format as a repository-wide .hgignore file. This option
257 supports hook syntax, so if you want to specify multiple ignore
257 supports hook syntax, so if you want to specify multiple ignore
258 files, you can do so by setting something like
258 files, you can do so by setting something like
259 "ignore.other = ~/.hgignore2".
259 "ignore.other = ~/.hgignore2".
260 interactive;;
260 interactive;;
261 Allow to prompt the user. True or False. Default is True.
261 Allow to prompt the user. True or False. Default is True.
262 logtemplate;;
262 logtemplate;;
263 Template string for commands that print changesets.
263 Template string for commands that print changesets.
264 style;;
264 style;;
265 Name of style to use for command output.
265 Name of style to use for command output.
266 merge;;
266 merge;;
267 The conflict resolution program to use during a manual merge.
267 The conflict resolution program to use during a manual merge.
268 Default is "hgmerge".
268 Default is "hgmerge".
269 quiet;;
269 quiet;;
270 Reduce the amount of output printed. True or False. Default is False.
270 Reduce the amount of output printed. True or False. Default is False.
271 remotecmd;;
271 remotecmd;;
272 remote command to use for clone/push/pull operations. Default is 'hg'.
272 remote command to use for clone/push/pull operations. Default is 'hg'.
273 ssh;;
273 ssh;;
274 command to use for SSH connections. Default is 'ssh'.
274 command to use for SSH connections. Default is 'ssh'.
275 timeout;;
275 timeout;;
276 The timeout used when a lock is held (in seconds), a negative value
276 The timeout used when a lock is held (in seconds), a negative value
277 means no timeout. Default is 600.
277 means no timeout. Default is 600.
278 username;;
278 username;;
279 The committer of a changeset created when running "commit".
279 The committer of a changeset created when running "commit".
280 Typically a person's name and email address, e.g. "Fred Widget
280 Typically a person's name and email address, e.g. "Fred Widget
281 <fred@example.com>". Default is $EMAIL or username@hostname, unless
281 <fred@example.com>". Default is $EMAIL or username@hostname, unless
282 username is set to an empty string, which enforces specifying the
282 username is set to an empty string, which enforces specifying the
283 username manually.
283 username manually.
284 verbose;;
284 verbose;;
285 Increase the amount of output printed. True or False. Default is False.
285 Increase the amount of output printed. True or False. Default is False.
286
286
287
287
288 web::
288 web::
289 Web interface configuration.
289 Web interface configuration.
290 accesslog;;
290 accesslog;;
291 Where to output the access log. Default is stdout.
291 Where to output the access log. Default is stdout.
292 address;;
292 address;;
293 Interface address to bind to. Default is all.
293 Interface address to bind to. Default is all.
294 allowbz2;;
294 allowbz2;;
295 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
295 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
296 allowgz;;
296 allowgz;;
297 Whether to allow .tar.gz downloading of repo revisions. Default is false.
297 Whether to allow .tar.gz downloading of repo revisions. Default is false.
298 allowpull;;
298 allowpull;;
299 Whether to allow pulling from the repository. Default is true.
299 Whether to allow pulling from the repository. Default is true.
300 allowzip;;
300 allowzip;;
301 Whether to allow .zip downloading of repo revisions. Default is false.
301 Whether to allow .zip downloading of repo revisions. Default is false.
302 This feature creates temporary files.
302 This feature creates temporary files.
303 baseurl;;
304 Base URL to use when publishing URLs in other locations, so
305 third-party tools like email notification hooks can construct URLs.
306 Example: "http://hgserver/repos/"
303 description;;
307 description;;
304 Textual description of the repository's purpose or contents.
308 Textual description of the repository's purpose or contents.
305 Default is "unknown".
309 Default is "unknown".
306 errorlog;;
310 errorlog;;
307 Where to output the error log. Default is stderr.
311 Where to output the error log. Default is stderr.
308 ipv6;;
312 ipv6;;
309 Whether to use IPv6. Default is false.
313 Whether to use IPv6. Default is false.
310 name;;
314 name;;
311 Repository name to use in the web interface. Default is current
315 Repository name to use in the web interface. Default is current
312 working directory.
316 working directory.
313 maxchanges;;
317 maxchanges;;
314 Maximum number of changes to list on the changelog. Default is 10.
318 Maximum number of changes to list on the changelog. Default is 10.
315 maxfiles;;
319 maxfiles;;
316 Maximum number of files to list per changeset. Default is 10.
320 Maximum number of files to list per changeset. Default is 10.
317 port;;
321 port;;
318 Port to listen on. Default is 8000.
322 Port to listen on. Default is 8000.
319 style;;
323 style;;
320 Which template map style to use.
324 Which template map style to use.
321 templates;;
325 templates;;
322 Where to find the HTML templates. Default is install path.
326 Where to find the HTML templates. Default is install path.
323
327
324
328
325 AUTHOR
329 AUTHOR
326 ------
330 ------
327 Bryan O'Sullivan <bos@serpentine.com>.
331 Bryan O'Sullivan <bos@serpentine.com>.
328
332
329 Mercurial was written by Matt Mackall <mpm@selenic.com>.
333 Mercurial was written by Matt Mackall <mpm@selenic.com>.
330
334
331 SEE ALSO
335 SEE ALSO
332 --------
336 --------
333 hg(1)
337 hg(1)
334
338
335 COPYING
339 COPYING
336 -------
340 -------
337 This manual page is copyright 2005 Bryan O'Sullivan.
341 This manual page is copyright 2005 Bryan O'Sullivan.
338 Mercurial is copyright 2005 Matt Mackall.
342 Mercurial is copyright 2005 Matt Mackall.
339 Free use of this software is granted under the terms of the GNU General
343 Free use of this software is granted under the terms of the GNU General
340 Public License (GPL).
344 Public License (GPL).
@@ -1,293 +1,294 b''
1 # bugzilla.py - bugzilla integration for mercurial
1 # bugzilla.py - bugzilla integration 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 # hook extension to update comments of bugzilla bugs when changesets
8 # hook extension to update comments of bugzilla bugs when changesets
9 # that refer to bugs by id are seen. this hook does not change bug
9 # that refer to bugs by id are seen. this hook does not change bug
10 # status, only comments.
10 # status, only comments.
11 #
11 #
12 # to configure, add items to '[bugzilla]' section of hgrc.
12 # to configure, add items to '[bugzilla]' section of hgrc.
13 #
13 #
14 # to use, configure bugzilla extension and enable like this:
14 # to use, configure bugzilla extension and enable like this:
15 #
15 #
16 # [extensions]
16 # [extensions]
17 # hgext.bugzilla =
17 # hgext.bugzilla =
18 #
18 #
19 # [hooks]
19 # [hooks]
20 # # run bugzilla hook on every change pulled or pushed in here
20 # # run bugzilla hook on every change pulled or pushed in here
21 # incoming.bugzilla = python:hgext.bugzilla.hook
21 # incoming.bugzilla = python:hgext.bugzilla.hook
22 #
22 #
23 # config items:
23 # config items:
24 #
24 #
25 # REQUIRED:
25 # REQUIRED:
26 # host = bugzilla # mysql server where bugzilla database lives
26 # host = bugzilla # mysql server where bugzilla database lives
27 # password = ** # user's password
27 # password = ** # user's password
28 # version = 2.16 # version of bugzilla installed
28 # version = 2.16 # version of bugzilla installed
29 #
29 #
30 # OPTIONAL:
30 # OPTIONAL:
31 # bzuser = ... # bugzilla user id to record comments with
31 # bzuser = ... # bugzilla user id to record comments with
32 # db = bugs # database to connect to
32 # db = bugs # database to connect to
33 # hgweb = http:// # root of hg web site for browsing commits
34 # notify = ... # command to run to get bugzilla to send mail
33 # notify = ... # command to run to get bugzilla to send mail
35 # regexp = ... # regexp to match bug ids (must contain one "()" group)
34 # regexp = ... # regexp to match bug ids (must contain one "()" group)
36 # strip = 0 # number of slashes to strip for url paths
35 # strip = 0 # number of slashes to strip for url paths
37 # style = ... # style file to use when formatting comments
36 # style = ... # style file to use when formatting comments
38 # template = ... # template to use when formatting comments
37 # template = ... # template to use when formatting comments
39 # timeout = 5 # database connection timeout (seconds)
38 # timeout = 5 # database connection timeout (seconds)
40 # user = bugs # user to connect to database as
39 # user = bugs # user to connect to database as
40 # [web]
41 # baseurl = http://hgserver/... # root of hg web site for browsing commits
41
42
42 from mercurial.demandload import *
43 from mercurial.demandload import *
43 from mercurial.i18n import gettext as _
44 from mercurial.i18n import gettext as _
44 from mercurial.node import *
45 from mercurial.node import *
45 demandload(globals(), 'cStringIO mercurial:templater,util os re time')
46 demandload(globals(), 'cStringIO mercurial:templater,util os re time')
46
47
47 try:
48 try:
48 import MySQLdb
49 import MySQLdb
49 except ImportError:
50 except ImportError:
50 raise util.Abort(_('python mysql support not available'))
51 raise util.Abort(_('python mysql support not available'))
51
52
52 def buglist(ids):
53 def buglist(ids):
53 return '(' + ','.join(map(str, ids)) + ')'
54 return '(' + ','.join(map(str, ids)) + ')'
54
55
55 class bugzilla_2_16(object):
56 class bugzilla_2_16(object):
56 '''support for bugzilla version 2.16.'''
57 '''support for bugzilla version 2.16.'''
57
58
58 def __init__(self, ui):
59 def __init__(self, ui):
59 self.ui = ui
60 self.ui = ui
60 host = self.ui.config('bugzilla', 'host', 'localhost')
61 host = self.ui.config('bugzilla', 'host', 'localhost')
61 user = self.ui.config('bugzilla', 'user', 'bugs')
62 user = self.ui.config('bugzilla', 'user', 'bugs')
62 passwd = self.ui.config('bugzilla', 'password')
63 passwd = self.ui.config('bugzilla', 'password')
63 db = self.ui.config('bugzilla', 'db', 'bugs')
64 db = self.ui.config('bugzilla', 'db', 'bugs')
64 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
65 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
65 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
66 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
66 (host, db, user, '*' * len(passwd)))
67 (host, db, user, '*' * len(passwd)))
67 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
68 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
68 db=db, connect_timeout=timeout)
69 db=db, connect_timeout=timeout)
69 self.cursor = self.conn.cursor()
70 self.cursor = self.conn.cursor()
70 self.run('select fieldid from fielddefs where name = "longdesc"')
71 self.run('select fieldid from fielddefs where name = "longdesc"')
71 ids = self.cursor.fetchall()
72 ids = self.cursor.fetchall()
72 if len(ids) != 1:
73 if len(ids) != 1:
73 raise util.Abort(_('unknown database schema'))
74 raise util.Abort(_('unknown database schema'))
74 self.longdesc_id = ids[0][0]
75 self.longdesc_id = ids[0][0]
75 self.user_ids = {}
76 self.user_ids = {}
76
77
77 def run(self, *args, **kwargs):
78 def run(self, *args, **kwargs):
78 '''run a query.'''
79 '''run a query.'''
79 self.ui.note(_('query: %s %s\n') % (args, kwargs))
80 self.ui.note(_('query: %s %s\n') % (args, kwargs))
80 try:
81 try:
81 self.cursor.execute(*args, **kwargs)
82 self.cursor.execute(*args, **kwargs)
82 except MySQLdb.MySQLError, err:
83 except MySQLdb.MySQLError, err:
83 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
84 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
84 raise
85 raise
85
86
86 def filter_real_bug_ids(self, ids):
87 def filter_real_bug_ids(self, ids):
87 '''filter not-existing bug ids from list.'''
88 '''filter not-existing bug ids from list.'''
88 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
89 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
89 ids = [c[0] for c in self.cursor.fetchall()]
90 ids = [c[0] for c in self.cursor.fetchall()]
90 ids.sort()
91 ids.sort()
91 return ids
92 return ids
92
93
93 def filter_unknown_bug_ids(self, node, ids):
94 def filter_unknown_bug_ids(self, node, ids):
94 '''filter bug ids from list that already refer to this changeset.'''
95 '''filter bug ids from list that already refer to this changeset.'''
95
96
96 self.run('''select bug_id from longdescs where
97 self.run('''select bug_id from longdescs where
97 bug_id in %s and thetext like "%%%s%%"''' %
98 bug_id in %s and thetext like "%%%s%%"''' %
98 (buglist(ids), short(node)))
99 (buglist(ids), short(node)))
99 unknown = dict.fromkeys(ids)
100 unknown = dict.fromkeys(ids)
100 for (id,) in self.cursor.fetchall():
101 for (id,) in self.cursor.fetchall():
101 self.ui.status(_('bug %d already knows about changeset %s\n') %
102 self.ui.status(_('bug %d already knows about changeset %s\n') %
102 (id, short(node)))
103 (id, short(node)))
103 unknown.pop(id, None)
104 unknown.pop(id, None)
104 ids = unknown.keys()
105 ids = unknown.keys()
105 ids.sort()
106 ids.sort()
106 return ids
107 return ids
107
108
108 def notify(self, ids):
109 def notify(self, ids):
109 '''tell bugzilla to send mail.'''
110 '''tell bugzilla to send mail.'''
110
111
111 self.ui.status(_('telling bugzilla to send mail:\n'))
112 self.ui.status(_('telling bugzilla to send mail:\n'))
112 for id in ids:
113 for id in ids:
113 self.ui.status(_(' bug %s\n') % id)
114 self.ui.status(_(' bug %s\n') % id)
114 cmd = self.ui.config('bugzilla', 'notify',
115 cmd = self.ui.config('bugzilla', 'notify',
115 'cd /var/www/html/bugzilla && '
116 'cd /var/www/html/bugzilla && '
116 './processmail %s nobody@nowhere.com') % id
117 './processmail %s nobody@nowhere.com') % id
117 fp = os.popen('(%s) 2>&1' % cmd)
118 fp = os.popen('(%s) 2>&1' % cmd)
118 out = fp.read()
119 out = fp.read()
119 ret = fp.close()
120 ret = fp.close()
120 if ret:
121 if ret:
121 self.ui.warn(out)
122 self.ui.warn(out)
122 raise util.Abort(_('bugzilla notify command %s') %
123 raise util.Abort(_('bugzilla notify command %s') %
123 util.explain_exit(ret)[0])
124 util.explain_exit(ret)[0])
124 self.ui.status(_('done\n'))
125 self.ui.status(_('done\n'))
125
126
126 def get_user_id(self, user):
127 def get_user_id(self, user):
127 '''look up numeric bugzilla user id.'''
128 '''look up numeric bugzilla user id.'''
128 try:
129 try:
129 return self.user_ids[user]
130 return self.user_ids[user]
130 except KeyError:
131 except KeyError:
131 try:
132 try:
132 userid = int(user)
133 userid = int(user)
133 except ValueError:
134 except ValueError:
134 self.ui.note(_('looking up user %s\n') % user)
135 self.ui.note(_('looking up user %s\n') % user)
135 self.run('''select userid from profiles
136 self.run('''select userid from profiles
136 where login_name like %s''', user)
137 where login_name like %s''', user)
137 all = self.cursor.fetchall()
138 all = self.cursor.fetchall()
138 if len(all) != 1:
139 if len(all) != 1:
139 raise KeyError(user)
140 raise KeyError(user)
140 userid = int(all[0][0])
141 userid = int(all[0][0])
141 self.user_ids[user] = userid
142 self.user_ids[user] = userid
142 return userid
143 return userid
143
144
144 def add_comment(self, bugid, text, prefuser):
145 def add_comment(self, bugid, text, prefuser):
145 '''add comment to bug. try adding comment as committer of
146 '''add comment to bug. try adding comment as committer of
146 changeset, otherwise as default bugzilla user.'''
147 changeset, otherwise as default bugzilla user.'''
147 try:
148 try:
148 userid = self.get_user_id(prefuser)
149 userid = self.get_user_id(prefuser)
149 except KeyError:
150 except KeyError:
150 try:
151 try:
151 defaultuser = self.ui.config('bugzilla', 'bzuser')
152 defaultuser = self.ui.config('bugzilla', 'bzuser')
152 userid = self.get_user_id(defaultuser)
153 userid = self.get_user_id(defaultuser)
153 except KeyError:
154 except KeyError:
154 raise util.Abort(_('cannot find user id for %s or %s') %
155 raise util.Abort(_('cannot find user id for %s or %s') %
155 (prefuser, defaultuser))
156 (prefuser, defaultuser))
156 now = time.strftime('%Y-%m-%d %H:%M:%S')
157 now = time.strftime('%Y-%m-%d %H:%M:%S')
157 self.run('''insert into longdescs
158 self.run('''insert into longdescs
158 (bug_id, who, bug_when, thetext)
159 (bug_id, who, bug_when, thetext)
159 values (%s, %s, %s, %s)''',
160 values (%s, %s, %s, %s)''',
160 (bugid, userid, now, text))
161 (bugid, userid, now, text))
161 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
162 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
162 values (%s, %s, %s, %s)''',
163 values (%s, %s, %s, %s)''',
163 (bugid, userid, now, self.longdesc_id))
164 (bugid, userid, now, self.longdesc_id))
164
165
165 class bugzilla(object):
166 class bugzilla(object):
166 # supported versions of bugzilla. different versions have
167 # supported versions of bugzilla. different versions have
167 # different schemas.
168 # different schemas.
168 _versions = {
169 _versions = {
169 '2.16': bugzilla_2_16,
170 '2.16': bugzilla_2_16,
170 }
171 }
171
172
172 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
173 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
173 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
174 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
174
175
175 _bz = None
176 _bz = None
176
177
177 def __init__(self, ui, repo):
178 def __init__(self, ui, repo):
178 self.ui = ui
179 self.ui = ui
179 self.repo = repo
180 self.repo = repo
180
181
181 def bz(self):
182 def bz(self):
182 '''return object that knows how to talk to bugzilla version in
183 '''return object that knows how to talk to bugzilla version in
183 use.'''
184 use.'''
184
185
185 if bugzilla._bz is None:
186 if bugzilla._bz is None:
186 bzversion = self.ui.config('bugzilla', 'version')
187 bzversion = self.ui.config('bugzilla', 'version')
187 try:
188 try:
188 bzclass = bugzilla._versions[bzversion]
189 bzclass = bugzilla._versions[bzversion]
189 except KeyError:
190 except KeyError:
190 raise util.Abort(_('bugzilla version %s not supported') %
191 raise util.Abort(_('bugzilla version %s not supported') %
191 bzversion)
192 bzversion)
192 bugzilla._bz = bzclass(self.ui)
193 bugzilla._bz = bzclass(self.ui)
193 return bugzilla._bz
194 return bugzilla._bz
194
195
195 def __getattr__(self, key):
196 def __getattr__(self, key):
196 return getattr(self.bz(), key)
197 return getattr(self.bz(), key)
197
198
198 _bug_re = None
199 _bug_re = None
199 _split_re = None
200 _split_re = None
200
201
201 def find_bug_ids(self, node, desc):
202 def find_bug_ids(self, node, desc):
202 '''find valid bug ids that are referred to in changeset
203 '''find valid bug ids that are referred to in changeset
203 comments and that do not already have references to this
204 comments and that do not already have references to this
204 changeset.'''
205 changeset.'''
205
206
206 if bugzilla._bug_re is None:
207 if bugzilla._bug_re is None:
207 bugzilla._bug_re = re.compile(
208 bugzilla._bug_re = re.compile(
208 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
209 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
209 re.IGNORECASE)
210 re.IGNORECASE)
210 bugzilla._split_re = re.compile(r'\D+')
211 bugzilla._split_re = re.compile(r'\D+')
211 start = 0
212 start = 0
212 ids = {}
213 ids = {}
213 while True:
214 while True:
214 m = bugzilla._bug_re.search(desc, start)
215 m = bugzilla._bug_re.search(desc, start)
215 if not m:
216 if not m:
216 break
217 break
217 start = m.end()
218 start = m.end()
218 for id in bugzilla._split_re.split(m.group(1)):
219 for id in bugzilla._split_re.split(m.group(1)):
219 ids[int(id)] = 1
220 ids[int(id)] = 1
220 ids = ids.keys()
221 ids = ids.keys()
221 if ids:
222 if ids:
222 ids = self.filter_real_bug_ids(ids)
223 ids = self.filter_real_bug_ids(ids)
223 if ids:
224 if ids:
224 ids = self.filter_unknown_bug_ids(node, ids)
225 ids = self.filter_unknown_bug_ids(node, ids)
225 return ids
226 return ids
226
227
227 def update(self, bugid, node, changes):
228 def update(self, bugid, node, changes):
228 '''update bugzilla bug with reference to changeset.'''
229 '''update bugzilla bug with reference to changeset.'''
229
230
230 def webroot(root):
231 def webroot(root):
231 '''strip leading prefix of repo root and turn into
232 '''strip leading prefix of repo root and turn into
232 url-safe path.'''
233 url-safe path.'''
233 count = int(self.ui.config('bugzilla', 'strip', 0))
234 count = int(self.ui.config('bugzilla', 'strip', 0))
234 root = util.pconvert(root)
235 root = util.pconvert(root)
235 while count > 0:
236 while count > 0:
236 c = root.find('/')
237 c = root.find('/')
237 if c == -1:
238 if c == -1:
238 break
239 break
239 root = root[c+1:]
240 root = root[c+1:]
240 count -= 1
241 count -= 1
241 return root
242 return root
242
243
243 class stringio(object):
244 class stringio(object):
244 '''wrap cStringIO.'''
245 '''wrap cStringIO.'''
245 def __init__(self):
246 def __init__(self):
246 self.fp = cStringIO.StringIO()
247 self.fp = cStringIO.StringIO()
247
248
248 def write(self, *args):
249 def write(self, *args):
249 for a in args:
250 for a in args:
250 self.fp.write(a)
251 self.fp.write(a)
251
252
252 write_header = write
253 write_header = write
253
254
254 def getvalue(self):
255 def getvalue(self):
255 return self.fp.getvalue()
256 return self.fp.getvalue()
256
257
257 mapfile = self.ui.config('bugzilla', 'style')
258 mapfile = self.ui.config('bugzilla', 'style')
258 tmpl = self.ui.config('bugzilla', 'template')
259 tmpl = self.ui.config('bugzilla', 'template')
259 sio = stringio()
260 sio = stringio()
260 t = templater.changeset_templater(self.ui, self.repo, mapfile, sio)
261 t = templater.changeset_templater(self.ui, self.repo, mapfile, sio)
261 if not mapfile and not tmpl:
262 if not mapfile and not tmpl:
262 tmpl = _('changeset {node|short} in repo {root} refers '
263 tmpl = _('changeset {node|short} in repo {root} refers '
263 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
264 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
264 if tmpl:
265 if tmpl:
265 tmpl = templater.parsestring(tmpl, quoted=False)
266 tmpl = templater.parsestring(tmpl, quoted=False)
266 t.use_template(tmpl)
267 t.use_template(tmpl)
267 t.show(changenode=node, changes=changes,
268 t.show(changenode=node, changes=changes,
268 bug=str(bugid),
269 bug=str(bugid),
269 hgweb=self.ui.config('bugzilla', 'hgweb'),
270 hgweb=self.ui.config('web', 'baseurl'),
270 root=self.repo.root,
271 root=self.repo.root,
271 webroot=webroot(self.repo.root))
272 webroot=webroot(self.repo.root))
272 self.add_comment(bugid, sio.getvalue(), templater.email(changes[1]))
273 self.add_comment(bugid, sio.getvalue(), templater.email(changes[1]))
273
274
274 def hook(ui, repo, hooktype, node=None, **kwargs):
275 def hook(ui, repo, hooktype, node=None, **kwargs):
275 '''add comment to bugzilla for each changeset that refers to a
276 '''add comment to bugzilla for each changeset that refers to a
276 bugzilla bug id. only add a comment once per bug, so same change
277 bugzilla bug id. only add a comment once per bug, so same change
277 seen multiple times does not fill bug with duplicate data.'''
278 seen multiple times does not fill bug with duplicate data.'''
278 if node is None:
279 if node is None:
279 raise util.Abort(_('hook type %s does not pass a changeset id') %
280 raise util.Abort(_('hook type %s does not pass a changeset id') %
280 hooktype)
281 hooktype)
281 try:
282 try:
282 bz = bugzilla(ui, repo)
283 bz = bugzilla(ui, repo)
283 bin_node = bin(node)
284 bin_node = bin(node)
284 changes = repo.changelog.read(bin_node)
285 changes = repo.changelog.read(bin_node)
285 ids = bz.find_bug_ids(bin_node, changes[4])
286 ids = bz.find_bug_ids(bin_node, changes[4])
286 if ids:
287 if ids:
287 for id in ids:
288 for id in ids:
288 bz.update(id, bin_node, changes)
289 bz.update(id, bin_node, changes)
289 bz.notify(ids)
290 bz.notify(ids)
290 return True
291 return True
291 except MySQLdb.MySQLError, err:
292 except MySQLdb.MySQLError, err:
292 raise util.Abort(_('database error: %s') % err[1])
293 raise util.Abort(_('database error: %s') % err[1])
293
294
General Comments 0
You need to be logged in to leave comments. Login now