##// END OF EJS Templates
dispatch: add generic pre- and post-command hooks
Matt Mackall -
r4630:e6d105a5 default
parent child Browse files
Show More
@@ -1,556 +1,566
1 1 HGRC(5)
2 2 =======
3 3 Bryan O'Sullivan <bos@serpentine.com>
4 4
5 5 NAME
6 6 ----
7 7 hgrc - configuration files for Mercurial
8 8
9 9 SYNOPSIS
10 10 --------
11 11
12 12 The Mercurial system uses a set of configuration files to control
13 13 aspects of its behaviour.
14 14
15 15 FILES
16 16 -----
17 17
18 18 Mercurial reads configuration data from several files, if they exist.
19 19 The names of these files depend on the system on which Mercurial is
20 20 installed.
21 21
22 22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
23 23 (Unix) <install-root>/etc/mercurial/hgrc::
24 24 Per-installation configuration files, searched for in the
25 25 directory where Mercurial is installed. For example, if installed
26 26 in /shared/tools, Mercurial will look in
27 27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
28 28 all Mercurial commands executed by any user in any directory.
29 29
30 30 (Unix) /etc/mercurial/hgrc.d/*.rc::
31 31 (Unix) /etc/mercurial/hgrc::
32 32 (Windows) C:\Mercurial\Mercurial.ini::
33 33 Per-system configuration files, for the system on which Mercurial
34 34 is running. Options in these files apply to all Mercurial
35 35 commands executed by any user in any directory. Options in these
36 36 files override per-installation options.
37 37
38 38 (Unix) $HOME/.hgrc::
39 39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
40 40 (Windows) $HOME\Mercurial.ini::
41 41 Per-user configuration file, for the user running Mercurial.
42 42 Options in this file apply to all Mercurial commands executed by
43 43 any user in any directory. Options in this file override
44 44 per-installation and per-system options.
45 45 On Windows system, one of these is chosen exclusively according
46 46 to definition of HOME environment variable.
47 47
48 48 (Unix, Windows) <repo>/.hg/hgrc::
49 49 Per-repository configuration options that only apply in a
50 50 particular repository. This file is not version-controlled, and
51 51 will not get transferred during a "clone" operation. Options in
52 52 this file override options in all other configuration files.
53 53 On Unix, most of this file will be ignored if it doesn't belong
54 54 to a trusted user or to a trusted group. See the documentation
55 55 for the trusted section below for more details.
56 56
57 57 SYNTAX
58 58 ------
59 59
60 60 A configuration file consists of sections, led by a "[section]" header
61 61 and followed by "name: value" entries; "name=value" is also accepted.
62 62
63 63 [spam]
64 64 eggs=ham
65 65 green=
66 66 eggs
67 67
68 68 Each line contains one entry. If the lines that follow are indented,
69 69 they are treated as continuations of that entry.
70 70
71 71 Leading whitespace is removed from values. Empty lines are skipped.
72 72
73 73 The optional values can contain format strings which refer to other
74 74 values in the same section, or values in a special DEFAULT section.
75 75
76 76 Lines beginning with "#" or ";" are ignored and may be used to provide
77 77 comments.
78 78
79 79 SECTIONS
80 80 --------
81 81
82 82 This section describes the different sections that may appear in a
83 83 Mercurial "hgrc" file, the purpose of each section, its possible
84 84 keys, and their possible values.
85 85
86 86 decode/encode::
87 87 Filters for transforming files on checkout/checkin. This would
88 88 typically be used for newline processing or other
89 89 localization/canonicalization of files.
90 90
91 91 Filters consist of a filter pattern followed by a filter command.
92 92 Filter patterns are globs by default, rooted at the repository
93 93 root. For example, to match any file ending in ".txt" in the root
94 94 directory only, use the pattern "*.txt". To match any file ending
95 95 in ".c" anywhere in the repository, use the pattern "**.c".
96 96
97 97 The filter command can start with a specifier, either "pipe:" or
98 98 "tempfile:". If no specifier is given, "pipe:" is used by default.
99 99
100 100 A "pipe:" command must accept data on stdin and return the
101 101 transformed data on stdout.
102 102
103 103 Pipe example:
104 104
105 105 [encode]
106 106 # uncompress gzip files on checkin to improve delta compression
107 107 # note: not necessarily a good idea, just an example
108 108 *.gz = pipe: gunzip
109 109
110 110 [decode]
111 111 # recompress gzip files when writing them to the working dir (we
112 112 # can safely omit "pipe:", because it's the default)
113 113 *.gz = gzip
114 114
115 115 A "tempfile:" command is a template. The string INFILE is replaced
116 116 with the name of a temporary file that contains the data to be
117 117 filtered by the command. The string OUTFILE is replaced with the
118 118 name of an empty temporary file, where the filtered data must be
119 119 written by the command.
120 120
121 121 NOTE: the tempfile mechanism is recommended for Windows systems,
122 122 where the standard shell I/O redirection operators often have
123 123 strange effects. In particular, if you are doing line ending
124 124 conversion on Windows using the popular dos2unix and unix2dos
125 125 programs, you *must* use the tempfile mechanism, as using pipes will
126 126 corrupt the contents of your files.
127 127
128 128 Tempfile example:
129 129
130 130 [encode]
131 131 # convert files to unix line ending conventions on checkin
132 132 **.txt = tempfile: dos2unix -n INFILE OUTFILE
133 133
134 134 [decode]
135 135 # convert files to windows line ending conventions when writing
136 136 # them to the working dir
137 137 **.txt = tempfile: unix2dos -n INFILE OUTFILE
138 138
139 139 defaults::
140 140 Use the [defaults] section to define command defaults, i.e. the
141 141 default options/arguments to pass to the specified commands.
142 142
143 143 The following example makes 'hg log' run in verbose mode, and
144 144 'hg status' show only the modified files, by default.
145 145
146 146 [defaults]
147 147 log = -v
148 148 status = -m
149 149
150 150 The actual commands, instead of their aliases, must be used when
151 151 defining command defaults. The command defaults will also be
152 152 applied to the aliases of the commands defined.
153 153
154 154 diff::
155 155 Settings used when displaying diffs. They are all boolean and
156 156 defaults to False.
157 157 git;;
158 158 Use git extended diff format.
159 159 nodates;;
160 160 Don't include dates in diff headers.
161 161 showfunc;;
162 162 Show which function each change is in.
163 163 ignorews;;
164 164 Ignore white space when comparing lines.
165 165 ignorewsamount;;
166 166 Ignore changes in the amount of white space.
167 167 ignoreblanklines;;
168 168 Ignore changes whose lines are all blank.
169 169
170 170 email::
171 171 Settings for extensions that send email messages.
172 172 from;;
173 173 Optional. Email address to use in "From" header and SMTP envelope
174 174 of outgoing messages.
175 175 to;;
176 176 Optional. Comma-separated list of recipients' email addresses.
177 177 cc;;
178 178 Optional. Comma-separated list of carbon copy recipients'
179 179 email addresses.
180 180 bcc;;
181 181 Optional. Comma-separated list of blind carbon copy
182 182 recipients' email addresses. Cannot be set interactively.
183 183 method;;
184 184 Optional. Method to use to send email messages. If value is
185 185 "smtp" (default), use SMTP (see section "[smtp]" for
186 186 configuration). Otherwise, use as name of program to run that
187 187 acts like sendmail (takes "-f" option for sender, list of
188 188 recipients on command line, message on stdin). Normally, setting
189 189 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
190 190 sendmail to send messages.
191 191
192 192 Email example:
193 193
194 194 [email]
195 195 from = Joseph User <joe.user@example.com>
196 196 method = /usr/sbin/sendmail
197 197
198 198 extensions::
199 199 Mercurial has an extension mechanism for adding new features. To
200 200 enable an extension, create an entry for it in this section.
201 201
202 202 If you know that the extension is already in Python's search path,
203 203 you can give the name of the module, followed by "=", with nothing
204 204 after the "=".
205 205
206 206 Otherwise, give a name that you choose, followed by "=", followed by
207 207 the path to the ".py" file (including the file name extension) that
208 208 defines the extension.
209 209
210 210 Example for ~/.hgrc:
211 211
212 212 [extensions]
213 213 # (the mq extension will get loaded from mercurial's path)
214 214 hgext.mq =
215 215 # (this extension will get loaded from the file specified)
216 216 myfeature = ~/.hgext/myfeature.py
217 217
218 218 format::
219 219
220 220 usestore;;
221 221 Enable or disable the "store" repository format which improves
222 222 compatibility with systems that fold case or otherwise mangle
223 223 filenames. Enabled by default. Disabling this option will allow
224 224 you to store longer filenames in some situations at the expense of
225 225 compatibility.
226 226
227 227 hooks::
228 228 Commands or Python functions that get automatically executed by
229 229 various actions such as starting or finishing a commit. Multiple
230 230 hooks can be run for the same action by appending a suffix to the
231 231 action. Overriding a site-wide hook can be done by changing its
232 232 value or setting it to an empty string.
233 233
234 234 Example .hg/hgrc:
235 235
236 236 [hooks]
237 237 # do not use the site-wide hook
238 238 incoming =
239 239 incoming.email = /my/email/hook
240 240 incoming.autobuild = /my/build/hook
241 241
242 242 Most hooks are run with environment variables set that give added
243 243 useful information. For each hook below, the environment variables
244 244 it is passed are listed with names of the form "$HG_foo".
245 245
246 246 changegroup;;
247 247 Run after a changegroup has been added via push, pull or
248 248 unbundle. ID of the first new changeset is in $HG_NODE. URL from
249 249 which changes came is in $HG_URL.
250 250 commit;;
251 251 Run after a changeset has been created in the local repository.
252 252 ID of the newly created changeset is in $HG_NODE. Parent
253 253 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
254 254 incoming;;
255 255 Run after a changeset has been pulled, pushed, or unbundled into
256 256 the local repository. The ID of the newly arrived changeset is in
257 257 $HG_NODE. URL that was source of changes came is in $HG_URL.
258 258 outgoing;;
259 259 Run after sending changes from local repository to another. ID of
260 260 first changeset sent is in $HG_NODE. Source of operation is in
261 261 $HG_SOURCE; see "preoutgoing" hook for description.
262 262 prechangegroup;;
263 263 Run before a changegroup is added via push, pull or unbundle.
264 264 Exit status 0 allows the changegroup to proceed. Non-zero status
265 265 will cause the push, pull or unbundle to fail. URL from which
266 266 changes will come is in $HG_URL.
267 267 precommit;;
268 268 Run before starting a local commit. Exit status 0 allows the
269 269 commit to proceed. Non-zero status will cause the commit to fail.
270 270 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
271 271 preoutgoing;;
272 272 Run before computing changes to send from the local repository to
273 273 another. Non-zero status will cause failure. This lets you
274 274 prevent pull over http or ssh. Also prevents against local pull,
275 275 push (outbound) or bundle commands, but not effective, since you
276 276 can just copy files instead then. Source of operation is in
277 277 $HG_SOURCE. If "serve", operation is happening on behalf of
278 278 remote ssh or http repository. If "push", "pull" or "bundle",
279 279 operation is happening on behalf of repository on same system.
280 280 pretag;;
281 281 Run before creating a tag. Exit status 0 allows the tag to be
282 282 created. Non-zero status will cause the tag to fail. ID of
283 283 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
284 284 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
285 285 pretxnchangegroup;;
286 286 Run after a changegroup has been added via push, pull or unbundle,
287 287 but before the transaction has been committed. Changegroup is
288 288 visible to hook program. This lets you validate incoming changes
289 289 before accepting them. Passed the ID of the first new changeset
290 290 in $HG_NODE. Exit status 0 allows the transaction to commit.
291 291 Non-zero status will cause the transaction to be rolled back and
292 292 the push, pull or unbundle will fail. URL that was source of
293 293 changes is in $HG_URL.
294 294 pretxncommit;;
295 295 Run after a changeset has been created but the transaction not yet
296 296 committed. Changeset is visible to hook program. This lets you
297 297 validate commit message and changes. Exit status 0 allows the
298 298 commit to proceed. Non-zero status will cause the transaction to
299 299 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
300 300 IDs are in $HG_PARENT1 and $HG_PARENT2.
301 301 preupdate;;
302 302 Run before updating the working directory. Exit status 0 allows
303 303 the update to proceed. Non-zero status will prevent the update.
304 304 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
305 305 of second new parent is in $HG_PARENT2.
306 306 tag;;
307 307 Run after a tag is created. ID of tagged changeset is in
308 308 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
309 309 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
310 310 update;;
311 311 Run after updating the working directory. Changeset ID of first
312 312 new parent is in $HG_PARENT1. If merge, ID of second new parent
313 313 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
314 314 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
315 pre-<command>;;
316 Run before executing the associated command. The contents of the
317 command line are passed as $HG_ARGS. If the hook returns failure,
318 the command doesn't execute and Mercurial returns the failure code.
319 post-<command>;;
320 Run after successful invocations of the associated command. The
321 contents of the command line are passed as $HG_ARGS and the result
322 code in $HG_RESULT. Hook failure is ignored.
315 323
316 Note: In earlier releases, the names of hook environment variables
317 did not have a "HG_" prefix. The old unprefixed names are no longer
318 provided in the environment.
324 Note: it is generally better to use standard hooks rather than the
325 generic pre- and post- command hooks as they are guaranteed to be
326 called in the appropriate contexts for influencing transactions.
327 Also, hooks like "commit" will be called in all contexts that
328 generate a commit (eg. tag) and not just the commit command.
319 329
320 330 The syntax for Python hooks is as follows:
321 331
322 332 hookname = python:modulename.submodule.callable
323 333
324 334 Python hooks are run within the Mercurial process. Each hook is
325 335 called with at least three keyword arguments: a ui object (keyword
326 336 "ui"), a repository object (keyword "repo"), and a "hooktype"
327 337 keyword that tells what kind of hook is used. Arguments listed as
328 338 environment variables above are passed as keyword arguments, with no
329 339 "HG_" prefix, and names in lower case.
330 340
331 341 If a Python hook returns a "true" value or raises an exception, this
332 342 is treated as failure of the hook.
333 343
334 344 http_proxy::
335 345 Used to access web-based Mercurial repositories through a HTTP
336 346 proxy.
337 347 host;;
338 348 Host name and (optional) port of the proxy server, for example
339 349 "myproxy:8000".
340 350 no;;
341 351 Optional. Comma-separated list of host names that should bypass
342 352 the proxy.
343 353 passwd;;
344 354 Optional. Password to authenticate with at the proxy server.
345 355 user;;
346 356 Optional. User name to authenticate with at the proxy server.
347 357
348 358 smtp::
349 359 Configuration for extensions that need to send email messages.
350 360 host;;
351 361 Host name of mail server, e.g. "mail.example.com".
352 362 port;;
353 363 Optional. Port to connect to on mail server. Default: 25.
354 364 tls;;
355 365 Optional. Whether to connect to mail server using TLS. True or
356 366 False. Default: False.
357 367 username;;
358 368 Optional. User name to authenticate to SMTP server with.
359 369 If username is specified, password must also be specified.
360 370 Default: none.
361 371 password;;
362 372 Optional. Password to authenticate to SMTP server with.
363 373 If username is specified, password must also be specified.
364 374 Default: none.
365 375 local_hostname;;
366 376 Optional. It's the hostname that the sender can use to identify itself
367 377 to the MTA.
368 378
369 379 paths::
370 380 Assigns symbolic names to repositories. The left side is the
371 381 symbolic name, and the right gives the directory or URL that is the
372 382 location of the repository. Default paths can be declared by
373 383 setting the following entries.
374 384 default;;
375 385 Directory or URL to use when pulling if no source is specified.
376 386 Default is set to repository from which the current repository
377 387 was cloned.
378 388 default-push;;
379 389 Optional. Directory or URL to use when pushing if no destination
380 390 is specified.
381 391
382 392 server::
383 393 Controls generic server settings.
384 394 uncompressed;;
385 395 Whether to allow clients to clone a repo using the uncompressed
386 396 streaming protocol. This transfers about 40% more data than a
387 397 regular clone, but uses less memory and CPU on both server and
388 398 client. Over a LAN (100Mbps or better) or a very fast WAN, an
389 399 uncompressed streaming clone is a lot faster (~10x) than a regular
390 400 clone. Over most WAN connections (anything slower than about
391 401 6Mbps), uncompressed streaming is slower, because of the extra
392 402 data transfer overhead. Default is False.
393 403
394 404 trusted::
395 405 For security reasons, Mercurial will not use the settings in
396 406 the .hg/hgrc file from a repository if it doesn't belong to a
397 407 trusted user or to a trusted group. The main exception is the
398 408 web interface, which automatically uses some safe settings, since
399 409 it's common to serve repositories from different users.
400 410
401 411 This section specifies what users and groups are trusted. The
402 412 current user is always trusted. To trust everybody, list a user
403 413 or a group with name "*".
404 414
405 415 users;;
406 416 Comma-separated list of trusted users.
407 417 groups;;
408 418 Comma-separated list of trusted groups.
409 419
410 420 ui::
411 421 User interface controls.
412 422 debug;;
413 423 Print debugging information. True or False. Default is False.
414 424 editor;;
415 425 The editor to use during a commit. Default is $EDITOR or "vi".
416 426 fallbackencoding;;
417 427 Encoding to try if it's not possible to decode the changelog using
418 428 UTF-8. Default is ISO-8859-1.
419 429 ignore;;
420 430 A file to read per-user ignore patterns from. This file should be in
421 431 the same format as a repository-wide .hgignore file. This option
422 432 supports hook syntax, so if you want to specify multiple ignore
423 433 files, you can do so by setting something like
424 434 "ignore.other = ~/.hgignore2". For details of the ignore file
425 435 format, see the hgignore(5) man page.
426 436 interactive;;
427 437 Allow to prompt the user. True or False. Default is True.
428 438 logtemplate;;
429 439 Template string for commands that print changesets.
430 440 style;;
431 441 Name of style to use for command output.
432 442 merge;;
433 443 The conflict resolution program to use during a manual merge.
434 444 Default is "hgmerge".
435 445 patch;;
436 446 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
437 447 unset.
438 448 quiet;;
439 449 Reduce the amount of output printed. True or False. Default is False.
440 450 remotecmd;;
441 451 remote command to use for clone/push/pull operations. Default is 'hg'.
442 452 slash;;
443 453 Display paths using a slash ("/") as the path separator. This only
444 454 makes a difference on systems where the default path separator is not
445 455 the slash character (e.g. Windows uses the backslash character ("\")).
446 456 Default is False.
447 457 ssh;;
448 458 command to use for SSH connections. Default is 'ssh'.
449 459 strict;;
450 460 Require exact command names, instead of allowing unambiguous
451 461 abbreviations. True or False. Default is False.
452 462 timeout;;
453 463 The timeout used when a lock is held (in seconds), a negative value
454 464 means no timeout. Default is 600.
455 465 username;;
456 466 The committer of a changeset created when running "commit".
457 467 Typically a person's name and email address, e.g. "Fred Widget
458 468 <fred@example.com>". Default is $EMAIL or username@hostname.
459 469 If the username in hgrc is empty, it has to be specified manually or
460 470 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
461 471 in the system hgrc).
462 472 verbose;;
463 473 Increase the amount of output printed. True or False. Default is False.
464 474
465 475
466 476 web::
467 477 Web interface configuration.
468 478 accesslog;;
469 479 Where to output the access log. Default is stdout.
470 480 address;;
471 481 Interface address to bind to. Default is all.
472 482 allow_archive;;
473 483 List of archive format (bz2, gz, zip) allowed for downloading.
474 484 Default is empty.
475 485 allowbz2;;
476 486 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
477 487 Default is false.
478 488 allowgz;;
479 489 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
480 490 Default is false.
481 491 allowpull;;
482 492 Whether to allow pulling from the repository. Default is true.
483 493 allow_push;;
484 494 Whether to allow pushing to the repository. If empty or not set,
485 495 push is not allowed. If the special value "*", any remote user
486 496 can push, including unauthenticated users. Otherwise, the remote
487 497 user must have been authenticated, and the authenticated user name
488 498 must be present in this list (separated by whitespace or ",").
489 499 The contents of the allow_push list are examined after the
490 500 deny_push list.
491 501 allowzip;;
492 502 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
493 503 Default is false. This feature creates temporary files.
494 504 baseurl;;
495 505 Base URL to use when publishing URLs in other locations, so
496 506 third-party tools like email notification hooks can construct URLs.
497 507 Example: "http://hgserver/repos/"
498 508 contact;;
499 509 Name or email address of the person in charge of the repository.
500 510 Default is "unknown".
501 511 deny_push;;
502 512 Whether to deny pushing to the repository. If empty or not set,
503 513 push is not denied. If the special value "*", all remote users
504 514 are denied push. Otherwise, unauthenticated users are all denied,
505 515 and any authenticated user name present in this list (separated by
506 516 whitespace or ",") is also denied. The contents of the deny_push
507 517 list are examined before the allow_push list.
508 518 description;;
509 519 Textual description of the repository's purpose or contents.
510 520 Default is "unknown".
511 521 errorlog;;
512 522 Where to output the error log. Default is stderr.
513 523 ipv6;;
514 524 Whether to use IPv6. Default is false.
515 525 name;;
516 526 Repository name to use in the web interface. Default is current
517 527 working directory.
518 528 maxchanges;;
519 529 Maximum number of changes to list on the changelog. Default is 10.
520 530 maxfiles;;
521 531 Maximum number of files to list per changeset. Default is 10.
522 532 port;;
523 533 Port to listen on. Default is 8000.
524 534 push_ssl;;
525 535 Whether to require that inbound pushes be transported over SSL to
526 536 prevent password sniffing. Default is true.
527 537 staticurl;;
528 538 Base URL to use for static files. If unset, static files (e.g.
529 539 the hgicon.png favicon) will be served by the CGI script itself.
530 540 Use this setting to serve them directly with the HTTP server.
531 541 Example: "http://hgserver/static/"
532 542 stripes;;
533 543 How many lines a "zebra stripe" should span in multiline output.
534 544 Default is 1; set to 0 to disable.
535 545 style;;
536 546 Which template map style to use.
537 547 templates;;
538 548 Where to find the HTML templates. Default is install path.
539 549
540 550
541 551 AUTHOR
542 552 ------
543 553 Bryan O'Sullivan <bos@serpentine.com>.
544 554
545 555 Mercurial was written by Matt Mackall <mpm@selenic.com>.
546 556
547 557 SEE ALSO
548 558 --------
549 559 hg(1), hgignore(5)
550 560
551 561 COPYING
552 562 -------
553 563 This manual page is copyright 2005 Bryan O'Sullivan.
554 564 Mercurial is copyright 2005, 2006 Matt Mackall.
555 565 Free use of this software is granted under the terms of the GNU General
556 566 Public License (GPL).
@@ -1,1208 +1,1217
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex
11 11 import mdiff, bdiff, util, templater, patch, commands, hg, lock, time
12 import fancyopts, revlog, version, extensions
12 import fancyopts, revlog, version, extensions, hook
13 13
14 14 revrangesep = ':'
15 15
16 16 class UnknownCommand(Exception):
17 17 """Exception raised if command is not in the command table."""
18 18 class AmbiguousCommand(Exception):
19 19 """Exception raised if command shortcut matches more than one command."""
20 20 class ParseError(Exception):
21 21 """Exception raised on errors in parsing the command line."""
22 22
23 23 def runcatch(ui, args):
24 24 def catchterm(*args):
25 25 raise util.SignalInterrupt
26 26
27 27 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
28 28 num = getattr(signal, name, None)
29 29 if num: signal.signal(num, catchterm)
30 30
31 31 try:
32 32 try:
33 33 # enter the debugger before command execution
34 34 if '--debugger' in args:
35 35 pdb.set_trace()
36 36 try:
37 37 return dispatch(ui, args)
38 38 finally:
39 39 ui.flush()
40 40 except:
41 41 # enter the debugger when we hit an exception
42 42 if '--debugger' in args:
43 43 pdb.post_mortem(sys.exc_info()[2])
44 44 ui.print_exc()
45 45 raise
46 46
47 47 except ParseError, inst:
48 48 if inst.args[0]:
49 49 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
50 50 commands.help_(ui, inst.args[0])
51 51 else:
52 52 ui.warn(_("hg: %s\n") % inst.args[1])
53 53 commands.help_(ui, 'shortlist')
54 54 except AmbiguousCommand, inst:
55 55 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
56 56 (inst.args[0], " ".join(inst.args[1])))
57 57 except UnknownCommand, inst:
58 58 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
59 59 commands.help_(ui, 'shortlist')
60 60 except hg.RepoError, inst:
61 61 ui.warn(_("abort: %s!\n") % inst)
62 62 except lock.LockHeld, inst:
63 63 if inst.errno == errno.ETIMEDOUT:
64 64 reason = _('timed out waiting for lock held by %s') % inst.locker
65 65 else:
66 66 reason = _('lock held by %s') % inst.locker
67 67 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
68 68 except lock.LockUnavailable, inst:
69 69 ui.warn(_("abort: could not lock %s: %s\n") %
70 70 (inst.desc or inst.filename, inst.strerror))
71 71 except revlog.RevlogError, inst:
72 72 ui.warn(_("abort: %s!\n") % inst)
73 73 except util.SignalInterrupt:
74 74 ui.warn(_("killed!\n"))
75 75 except KeyboardInterrupt:
76 76 try:
77 77 ui.warn(_("interrupted!\n"))
78 78 except IOError, inst:
79 79 if inst.errno == errno.EPIPE:
80 80 if ui.debugflag:
81 81 ui.warn(_("\nbroken pipe\n"))
82 82 else:
83 83 raise
84 84 except socket.error, inst:
85 85 ui.warn(_("abort: %s\n") % inst[1])
86 86 except IOError, inst:
87 87 if hasattr(inst, "code"):
88 88 ui.warn(_("abort: %s\n") % inst)
89 89 elif hasattr(inst, "reason"):
90 90 try: # usually it is in the form (errno, strerror)
91 91 reason = inst.reason.args[1]
92 92 except: # it might be anything, for example a string
93 93 reason = inst.reason
94 94 ui.warn(_("abort: error: %s\n") % reason)
95 95 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
96 96 if ui.debugflag:
97 97 ui.warn(_("broken pipe\n"))
98 98 elif getattr(inst, "strerror", None):
99 99 if getattr(inst, "filename", None):
100 100 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
101 101 else:
102 102 ui.warn(_("abort: %s\n") % inst.strerror)
103 103 else:
104 104 raise
105 105 except OSError, inst:
106 106 if getattr(inst, "filename", None):
107 107 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
108 108 else:
109 109 ui.warn(_("abort: %s\n") % inst.strerror)
110 110 except util.UnexpectedOutput, inst:
111 111 ui.warn(_("abort: %s") % inst[0])
112 112 if not isinstance(inst[1], basestring):
113 113 ui.warn(" %r\n" % (inst[1],))
114 114 elif not inst[1]:
115 115 ui.warn(_(" empty string\n"))
116 116 else:
117 117 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
118 118 except util.Abort, inst:
119 119 ui.warn(_("abort: %s\n") % inst)
120 120 except SystemExit, inst:
121 121 # Commands shouldn't sys.exit directly, but give a return code.
122 122 # Just in case catch this and and pass exit code to caller.
123 123 return inst.code
124 124 except:
125 125 ui.warn(_("** unknown exception encountered, details follow\n"))
126 126 ui.warn(_("** report bug details to "
127 127 "http://www.selenic.com/mercurial/bts\n"))
128 128 ui.warn(_("** or mercurial@selenic.com\n"))
129 129 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
130 130 % version.get_version())
131 131 raise
132 132
133 133 return -1
134 134
135 135 def findpossible(ui, cmd):
136 136 """
137 137 Return cmd -> (aliases, command table entry)
138 138 for each matching command.
139 139 Return debug commands (or their aliases) only if no normal command matches.
140 140 """
141 141 choice = {}
142 142 debugchoice = {}
143 143 for e in commands.table.keys():
144 144 aliases = e.lstrip("^").split("|")
145 145 found = None
146 146 if cmd in aliases:
147 147 found = cmd
148 148 elif not ui.config("ui", "strict"):
149 149 for a in aliases:
150 150 if a.startswith(cmd):
151 151 found = a
152 152 break
153 153 if found is not None:
154 154 if aliases[0].startswith("debug") or found.startswith("debug"):
155 155 debugchoice[found] = (aliases, commands.table[e])
156 156 else:
157 157 choice[found] = (aliases, commands.table[e])
158 158
159 159 if not choice and debugchoice:
160 160 choice = debugchoice
161 161
162 162 return choice
163 163
164 164 def findcmd(ui, cmd):
165 165 """Return (aliases, command table entry) for command string."""
166 166 choice = findpossible(ui, cmd)
167 167
168 168 if choice.has_key(cmd):
169 169 return choice[cmd]
170 170
171 171 if len(choice) > 1:
172 172 clist = choice.keys()
173 173 clist.sort()
174 174 raise AmbiguousCommand(cmd, clist)
175 175
176 176 if choice:
177 177 return choice.values()[0]
178 178
179 179 raise UnknownCommand(cmd)
180 180
181 181 def findrepo():
182 182 p = os.getcwd()
183 183 while not os.path.isdir(os.path.join(p, ".hg")):
184 184 oldp, p = p, os.path.dirname(p)
185 185 if p == oldp:
186 186 return None
187 187
188 188 return p
189 189
190 190 def parse(ui, args):
191 191 options = {}
192 192 cmdoptions = {}
193 193
194 194 try:
195 195 args = fancyopts.fancyopts(args, commands.globalopts, options)
196 196 except fancyopts.getopt.GetoptError, inst:
197 197 raise ParseError(None, inst)
198 198
199 199 if args:
200 200 cmd, args = args[0], args[1:]
201 201 aliases, i = findcmd(ui, cmd)
202 202 cmd = aliases[0]
203 203 defaults = ui.config("defaults", cmd)
204 204 if defaults:
205 205 args = shlex.split(defaults) + args
206 206 c = list(i[1])
207 207 else:
208 208 cmd = None
209 209 c = []
210 210
211 211 # combine global options into local
212 212 for o in commands.globalopts:
213 213 c.append((o[0], o[1], options[o[1]], o[3]))
214 214
215 215 try:
216 216 args = fancyopts.fancyopts(args, c, cmdoptions)
217 217 except fancyopts.getopt.GetoptError, inst:
218 218 raise ParseError(cmd, inst)
219 219
220 220 # separate global options back out
221 221 for o in commands.globalopts:
222 222 n = o[1]
223 223 options[n] = cmdoptions[n]
224 224 del cmdoptions[n]
225 225
226 226 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
227 227
228 228 def parseconfig(config):
229 229 """parse the --config options from the command line"""
230 230 parsed = []
231 231 for cfg in config:
232 232 try:
233 233 name, value = cfg.split('=', 1)
234 234 section, name = name.split('.', 1)
235 235 if not section or not name:
236 236 raise IndexError
237 237 parsed.append((section, name, value))
238 238 except (IndexError, ValueError):
239 239 raise util.Abort(_('malformed --config option: %s') % cfg)
240 240 return parsed
241 241
242 242 def earlygetopt(aliases, args):
243 243 if "--" in args:
244 244 args = args[:args.index("--")]
245 245 for opt in aliases:
246 246 if opt in args:
247 247 return args[args.index(opt) + 1]
248 248 return None
249 249
250 250 def dispatch(ui, args):
251 251 # check for cwd first
252 252 cwd = earlygetopt(['--cwd'], args)
253 253 if cwd:
254 254 os.chdir(cwd)
255 255
256 256 # read the local repository .hgrc into a local ui object
257 257 # this will trigger its extensions to load
258 258 path = earlygetopt(["-R", "--repository", "--repo"], args)
259 259 if not path:
260 260 path = findrepo() or ""
261 261 lui = ui
262 262 if path:
263 263 try:
264 264 lui = commands.ui.ui(parentui=ui)
265 265 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
266 266 except IOError:
267 267 pass
268 268
269 269 extensions.loadall(lui)
270 270 # check for fallback encoding
271 271 fallback = lui.config('ui', 'fallbackencoding')
272 272 if fallback:
273 273 util._fallbackencoding = fallback
274 274
275 fullargs = args
275 276 cmd, func, args, options, cmdoptions = parse(ui, args)
276 277
277 278 if options["encoding"]:
278 279 util._encoding = options["encoding"]
279 280 if options["encodingmode"]:
280 281 util._encodingmode = options["encodingmode"]
281 282 if options["time"]:
282 283 def get_times():
283 284 t = os.times()
284 285 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
285 286 t = (t[0], t[1], t[2], t[3], time.clock())
286 287 return t
287 288 s = get_times()
288 289 def print_time():
289 290 t = get_times()
290 291 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
291 292 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
292 293 atexit.register(print_time)
293 294
294 295 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
295 296 not options["noninteractive"], options["traceback"],
296 297 parseconfig(options["config"]))
297 298
298 299 if options['help']:
299 300 return commands.help_(ui, cmd, options['version'])
300 301 elif options['version']:
301 302 return commands.version_(ui)
302 303 elif not cmd:
303 304 return commands.help_(ui, 'shortlist')
304 305
306 repo = None
305 307 if cmd not in commands.norepo.split():
306 repo = None
307 308 try:
308 309 repo = hg.repository(ui, path=path)
309 310 ui = repo.ui
310 311 if not repo.local():
311 312 raise util.Abort(_("repository '%s' is not local") % path)
312 313 except hg.RepoError:
313 314 if cmd not in commands.optionalrepo.split():
314 315 raise
315 316 d = lambda: func(ui, repo, *args, **cmdoptions)
316 317 else:
317 318 d = lambda: func(ui, *args, **cmdoptions)
318 319
319 return runcommand(ui, options, cmd, d)
320 # run pre-hook, and abort if it fails
321 ret = hook.hook(ui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
322 if ret:
323 return ret
324 ret = runcommand(ui, options, cmd, d)
325 # run post-hook, passing command result
326 hook.hook(ui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
327 result = ret)
328 return ret
320 329
321 330 def runcommand(ui, options, cmd, cmdfunc):
322 331 def checkargs():
323 332 try:
324 333 return cmdfunc()
325 334 except TypeError, inst:
326 335 # was this an argument error?
327 336 tb = traceback.extract_tb(sys.exc_info()[2])
328 337 if len(tb) != 2: # no
329 338 raise
330 339 raise ParseError(cmd, _("invalid arguments"))
331 340
332 341 if options['profile']:
333 342 import hotshot, hotshot.stats
334 343 prof = hotshot.Profile("hg.prof")
335 344 try:
336 345 try:
337 346 return prof.runcall(checkargs)
338 347 except:
339 348 try:
340 349 ui.warn(_('exception raised - generating '
341 350 'profile anyway\n'))
342 351 except:
343 352 pass
344 353 raise
345 354 finally:
346 355 prof.close()
347 356 stats = hotshot.stats.load("hg.prof")
348 357 stats.strip_dirs()
349 358 stats.sort_stats('time', 'calls')
350 359 stats.print_stats(40)
351 360 elif options['lsprof']:
352 361 try:
353 362 from mercurial import lsprof
354 363 except ImportError:
355 364 raise util.Abort(_(
356 365 'lsprof not available - install from '
357 366 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
358 367 p = lsprof.Profiler()
359 368 p.enable(subcalls=True)
360 369 try:
361 370 return checkargs()
362 371 finally:
363 372 p.disable()
364 373 stats = lsprof.Stats(p.getstats())
365 374 stats.sort()
366 375 stats.pprint(top=10, file=sys.stderr, climit=5)
367 376 else:
368 377 return checkargs()
369 378
370 379 def bail_if_changed(repo):
371 380 modified, added, removed, deleted = repo.status()[:4]
372 381 if modified or added or removed or deleted:
373 382 raise util.Abort(_("outstanding uncommitted changes"))
374 383
375 384 def logmessage(opts):
376 385 """ get the log message according to -m and -l option """
377 386 message = opts['message']
378 387 logfile = opts['logfile']
379 388
380 389 if message and logfile:
381 390 raise util.Abort(_('options --message and --logfile are mutually '
382 391 'exclusive'))
383 392 if not message and logfile:
384 393 try:
385 394 if logfile == '-':
386 395 message = sys.stdin.read()
387 396 else:
388 397 message = open(logfile).read()
389 398 except IOError, inst:
390 399 raise util.Abort(_("can't read commit message '%s': %s") %
391 400 (logfile, inst.strerror))
392 401 return message
393 402
394 403 def setremoteconfig(ui, opts):
395 404 "copy remote options to ui tree"
396 405 if opts.get('ssh'):
397 406 ui.setconfig("ui", "ssh", opts['ssh'])
398 407 if opts.get('remotecmd'):
399 408 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
400 409
401 410 def parseurl(url, revs):
402 411 '''parse url#branch, returning url, branch + revs'''
403 412
404 413 if '#' not in url:
405 414 return url, (revs or None)
406 415
407 416 url, rev = url.split('#', 1)
408 417 return url, revs + [rev]
409 418
410 419 def revpair(repo, revs):
411 420 '''return pair of nodes, given list of revisions. second item can
412 421 be None, meaning use working dir.'''
413 422
414 423 def revfix(repo, val, defval):
415 424 if not val and val != 0 and defval is not None:
416 425 val = defval
417 426 return repo.lookup(val)
418 427
419 428 if not revs:
420 429 return repo.dirstate.parents()[0], None
421 430 end = None
422 431 if len(revs) == 1:
423 432 if revrangesep in revs[0]:
424 433 start, end = revs[0].split(revrangesep, 1)
425 434 start = revfix(repo, start, 0)
426 435 end = revfix(repo, end, repo.changelog.count() - 1)
427 436 else:
428 437 start = revfix(repo, revs[0], None)
429 438 elif len(revs) == 2:
430 439 if revrangesep in revs[0] or revrangesep in revs[1]:
431 440 raise util.Abort(_('too many revisions specified'))
432 441 start = revfix(repo, revs[0], None)
433 442 end = revfix(repo, revs[1], None)
434 443 else:
435 444 raise util.Abort(_('too many revisions specified'))
436 445 return start, end
437 446
438 447 def revrange(repo, revs):
439 448 """Yield revision as strings from a list of revision specifications."""
440 449
441 450 def revfix(repo, val, defval):
442 451 if not val and val != 0 and defval is not None:
443 452 return defval
444 453 return repo.changelog.rev(repo.lookup(val))
445 454
446 455 seen, l = {}, []
447 456 for spec in revs:
448 457 if revrangesep in spec:
449 458 start, end = spec.split(revrangesep, 1)
450 459 start = revfix(repo, start, 0)
451 460 end = revfix(repo, end, repo.changelog.count() - 1)
452 461 step = start > end and -1 or 1
453 462 for rev in xrange(start, end+step, step):
454 463 if rev in seen:
455 464 continue
456 465 seen[rev] = 1
457 466 l.append(rev)
458 467 else:
459 468 rev = revfix(repo, spec, None)
460 469 if rev in seen:
461 470 continue
462 471 seen[rev] = 1
463 472 l.append(rev)
464 473
465 474 return l
466 475
467 476 def make_filename(repo, pat, node,
468 477 total=None, seqno=None, revwidth=None, pathname=None):
469 478 node_expander = {
470 479 'H': lambda: hex(node),
471 480 'R': lambda: str(repo.changelog.rev(node)),
472 481 'h': lambda: short(node),
473 482 }
474 483 expander = {
475 484 '%': lambda: '%',
476 485 'b': lambda: os.path.basename(repo.root),
477 486 }
478 487
479 488 try:
480 489 if node:
481 490 expander.update(node_expander)
482 491 if node and revwidth is not None:
483 492 expander['r'] = (lambda:
484 493 str(repo.changelog.rev(node)).zfill(revwidth))
485 494 if total is not None:
486 495 expander['N'] = lambda: str(total)
487 496 if seqno is not None:
488 497 expander['n'] = lambda: str(seqno)
489 498 if total is not None and seqno is not None:
490 499 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
491 500 if pathname is not None:
492 501 expander['s'] = lambda: os.path.basename(pathname)
493 502 expander['d'] = lambda: os.path.dirname(pathname) or '.'
494 503 expander['p'] = lambda: pathname
495 504
496 505 newname = []
497 506 patlen = len(pat)
498 507 i = 0
499 508 while i < patlen:
500 509 c = pat[i]
501 510 if c == '%':
502 511 i += 1
503 512 c = pat[i]
504 513 c = expander[c]()
505 514 newname.append(c)
506 515 i += 1
507 516 return ''.join(newname)
508 517 except KeyError, inst:
509 518 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
510 519 inst.args[0])
511 520
512 521 def make_file(repo, pat, node=None,
513 522 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
514 523 if not pat or pat == '-':
515 524 return 'w' in mode and sys.stdout or sys.stdin
516 525 if hasattr(pat, 'write') and 'w' in mode:
517 526 return pat
518 527 if hasattr(pat, 'read') and 'r' in mode:
519 528 return pat
520 529 return open(make_filename(repo, pat, node, total, seqno, revwidth,
521 530 pathname),
522 531 mode)
523 532
524 533 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
525 534 cwd = repo.getcwd()
526 535 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
527 536 opts.get('exclude'), globbed=globbed,
528 537 default=default)
529 538
530 539 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
531 540 default=None):
532 541 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
533 542 default=default)
534 543 exact = dict.fromkeys(files)
535 544 cwd = repo.getcwd()
536 545 for src, fn in repo.walk(node=node, files=files, match=matchfn,
537 546 badmatch=badmatch):
538 547 yield src, fn, repo.pathto(fn, cwd), fn in exact
539 548
540 549 def findrenames(repo, added=None, removed=None, threshold=0.5):
541 550 '''find renamed files -- yields (before, after, score) tuples'''
542 551 if added is None or removed is None:
543 552 added, removed = repo.status()[1:3]
544 553 ctx = repo.changectx()
545 554 for a in added:
546 555 aa = repo.wread(a)
547 556 bestname, bestscore = None, threshold
548 557 for r in removed:
549 558 rr = ctx.filectx(r).data()
550 559
551 560 # bdiff.blocks() returns blocks of matching lines
552 561 # count the number of bytes in each
553 562 equal = 0
554 563 alines = mdiff.splitnewlines(aa)
555 564 matches = bdiff.blocks(aa, rr)
556 565 for x1,x2,y1,y2 in matches:
557 566 for line in alines[x1:x2]:
558 567 equal += len(line)
559 568
560 569 lengths = len(aa) + len(rr)
561 570 if lengths:
562 571 myscore = equal*2.0 / lengths
563 572 if myscore >= bestscore:
564 573 bestname, bestscore = r, myscore
565 574 if bestname:
566 575 yield bestname, a, bestscore
567 576
568 577 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
569 578 similarity=None):
570 579 if dry_run is None:
571 580 dry_run = opts.get('dry_run')
572 581 if similarity is None:
573 582 similarity = float(opts.get('similarity') or 0)
574 583 add, remove = [], []
575 584 mapping = {}
576 585 for src, abs, rel, exact in walk(repo, pats, opts):
577 586 target = repo.wjoin(abs)
578 587 if src == 'f' and repo.dirstate.state(abs) == '?':
579 588 add.append(abs)
580 589 mapping[abs] = rel, exact
581 590 if repo.ui.verbose or not exact:
582 591 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
583 592 if repo.dirstate.state(abs) != 'r' and not util.lexists(target):
584 593 remove.append(abs)
585 594 mapping[abs] = rel, exact
586 595 if repo.ui.verbose or not exact:
587 596 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
588 597 if not dry_run:
589 598 repo.add(add, wlock=wlock)
590 599 repo.remove(remove, wlock=wlock)
591 600 if similarity > 0:
592 601 for old, new, score in findrenames(repo, add, remove, similarity):
593 602 oldrel, oldexact = mapping[old]
594 603 newrel, newexact = mapping[new]
595 604 if repo.ui.verbose or not oldexact or not newexact:
596 605 repo.ui.status(_('recording removal of %s as rename to %s '
597 606 '(%d%% similar)\n') %
598 607 (oldrel, newrel, score * 100))
599 608 if not dry_run:
600 609 repo.copy(old, new, wlock=wlock)
601 610
602 611 def service(opts, parentfn=None, initfn=None, runfn=None):
603 612 '''Run a command as a service.'''
604 613
605 614 if opts['daemon'] and not opts['daemon_pipefds']:
606 615 rfd, wfd = os.pipe()
607 616 args = sys.argv[:]
608 617 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
609 618 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
610 619 args[0], args)
611 620 os.close(wfd)
612 621 os.read(rfd, 1)
613 622 if parentfn:
614 623 return parentfn(pid)
615 624 else:
616 625 os._exit(0)
617 626
618 627 if initfn:
619 628 initfn()
620 629
621 630 if opts['pid_file']:
622 631 fp = open(opts['pid_file'], 'w')
623 632 fp.write(str(os.getpid()) + '\n')
624 633 fp.close()
625 634
626 635 if opts['daemon_pipefds']:
627 636 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
628 637 os.close(rfd)
629 638 try:
630 639 os.setsid()
631 640 except AttributeError:
632 641 pass
633 642 os.write(wfd, 'y')
634 643 os.close(wfd)
635 644 sys.stdout.flush()
636 645 sys.stderr.flush()
637 646 fd = os.open(util.nulldev, os.O_RDWR)
638 647 if fd != 0: os.dup2(fd, 0)
639 648 if fd != 1: os.dup2(fd, 1)
640 649 if fd != 2: os.dup2(fd, 2)
641 650 if fd not in (0, 1, 2): os.close(fd)
642 651
643 652 if runfn:
644 653 return runfn()
645 654
646 655 class changeset_printer(object):
647 656 '''show changeset information when templating not requested.'''
648 657
649 658 def __init__(self, ui, repo, patch, buffered):
650 659 self.ui = ui
651 660 self.repo = repo
652 661 self.buffered = buffered
653 662 self.patch = patch
654 663 self.header = {}
655 664 self.hunk = {}
656 665 self.lastheader = None
657 666
658 667 def flush(self, rev):
659 668 if rev in self.header:
660 669 h = self.header[rev]
661 670 if h != self.lastheader:
662 671 self.lastheader = h
663 672 self.ui.write(h)
664 673 del self.header[rev]
665 674 if rev in self.hunk:
666 675 self.ui.write(self.hunk[rev])
667 676 del self.hunk[rev]
668 677 return 1
669 678 return 0
670 679
671 680 def show(self, rev=0, changenode=None, copies=(), **props):
672 681 if self.buffered:
673 682 self.ui.pushbuffer()
674 683 self._show(rev, changenode, copies, props)
675 684 self.hunk[rev] = self.ui.popbuffer()
676 685 else:
677 686 self._show(rev, changenode, copies, props)
678 687
679 688 def _show(self, rev, changenode, copies, props):
680 689 '''show a single changeset or file revision'''
681 690 log = self.repo.changelog
682 691 if changenode is None:
683 692 changenode = log.node(rev)
684 693 elif not rev:
685 694 rev = log.rev(changenode)
686 695
687 696 if self.ui.quiet:
688 697 self.ui.write("%d:%s\n" % (rev, short(changenode)))
689 698 return
690 699
691 700 changes = log.read(changenode)
692 701 date = util.datestr(changes[2])
693 702 extra = changes[5]
694 703 branch = extra.get("branch")
695 704
696 705 hexfunc = self.ui.debugflag and hex or short
697 706
698 707 parents = log.parentrevs(rev)
699 708 if not self.ui.debugflag:
700 709 if parents[1] == nullrev:
701 710 if parents[0] >= rev - 1:
702 711 parents = []
703 712 else:
704 713 parents = [parents[0]]
705 714 parents = [(p, hexfunc(log.node(p))) for p in parents]
706 715
707 716 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
708 717
709 718 # don't show the default branch name
710 719 if branch != 'default':
711 720 branch = util.tolocal(branch)
712 721 self.ui.write(_("branch: %s\n") % branch)
713 722 for tag in self.repo.nodetags(changenode):
714 723 self.ui.write(_("tag: %s\n") % tag)
715 724 for parent in parents:
716 725 self.ui.write(_("parent: %d:%s\n") % parent)
717 726
718 727 if self.ui.debugflag:
719 728 self.ui.write(_("manifest: %d:%s\n") %
720 729 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
721 730 self.ui.write(_("user: %s\n") % changes[1])
722 731 self.ui.write(_("date: %s\n") % date)
723 732
724 733 if self.ui.debugflag:
725 734 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
726 735 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
727 736 files):
728 737 if value:
729 738 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
730 739 elif changes[3] and self.ui.verbose:
731 740 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
732 741 if copies and self.ui.verbose:
733 742 copies = ['%s (%s)' % c for c in copies]
734 743 self.ui.write(_("copies: %s\n") % ' '.join(copies))
735 744
736 745 if extra and self.ui.debugflag:
737 746 extraitems = extra.items()
738 747 extraitems.sort()
739 748 for key, value in extraitems:
740 749 self.ui.write(_("extra: %s=%s\n")
741 750 % (key, value.encode('string_escape')))
742 751
743 752 description = changes[4].strip()
744 753 if description:
745 754 if self.ui.verbose:
746 755 self.ui.write(_("description:\n"))
747 756 self.ui.write(description)
748 757 self.ui.write("\n\n")
749 758 else:
750 759 self.ui.write(_("summary: %s\n") %
751 760 description.splitlines()[0])
752 761 self.ui.write("\n")
753 762
754 763 self.showpatch(changenode)
755 764
756 765 def showpatch(self, node):
757 766 if self.patch:
758 767 prev = self.repo.changelog.parents(node)[0]
759 768 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
760 769 self.ui.write("\n")
761 770
762 771 class changeset_templater(changeset_printer):
763 772 '''format changeset information.'''
764 773
765 774 def __init__(self, ui, repo, patch, mapfile, buffered):
766 775 changeset_printer.__init__(self, ui, repo, patch, buffered)
767 776 filters = templater.common_filters.copy()
768 777 filters['formatnode'] = (ui.debugflag and (lambda x: x)
769 778 or (lambda x: x[:12]))
770 779 self.t = templater.templater(mapfile, filters,
771 780 cache={
772 781 'parent': '{rev}:{node|formatnode} ',
773 782 'manifest': '{rev}:{node|formatnode}',
774 783 'filecopy': '{name} ({source})'})
775 784
776 785 def use_template(self, t):
777 786 '''set template string to use'''
778 787 self.t.cache['changeset'] = t
779 788
780 789 def _show(self, rev, changenode, copies, props):
781 790 '''show a single changeset or file revision'''
782 791 log = self.repo.changelog
783 792 if changenode is None:
784 793 changenode = log.node(rev)
785 794 elif not rev:
786 795 rev = log.rev(changenode)
787 796
788 797 changes = log.read(changenode)
789 798
790 799 def showlist(name, values, plural=None, **args):
791 800 '''expand set of values.
792 801 name is name of key in template map.
793 802 values is list of strings or dicts.
794 803 plural is plural of name, if not simply name + 's'.
795 804
796 805 expansion works like this, given name 'foo'.
797 806
798 807 if values is empty, expand 'no_foos'.
799 808
800 809 if 'foo' not in template map, return values as a string,
801 810 joined by space.
802 811
803 812 expand 'start_foos'.
804 813
805 814 for each value, expand 'foo'. if 'last_foo' in template
806 815 map, expand it instead of 'foo' for last key.
807 816
808 817 expand 'end_foos'.
809 818 '''
810 819 if plural: names = plural
811 820 else: names = name + 's'
812 821 if not values:
813 822 noname = 'no_' + names
814 823 if noname in self.t:
815 824 yield self.t(noname, **args)
816 825 return
817 826 if name not in self.t:
818 827 if isinstance(values[0], str):
819 828 yield ' '.join(values)
820 829 else:
821 830 for v in values:
822 831 yield dict(v, **args)
823 832 return
824 833 startname = 'start_' + names
825 834 if startname in self.t:
826 835 yield self.t(startname, **args)
827 836 vargs = args.copy()
828 837 def one(v, tag=name):
829 838 try:
830 839 vargs.update(v)
831 840 except (AttributeError, ValueError):
832 841 try:
833 842 for a, b in v:
834 843 vargs[a] = b
835 844 except ValueError:
836 845 vargs[name] = v
837 846 return self.t(tag, **vargs)
838 847 lastname = 'last_' + name
839 848 if lastname in self.t:
840 849 last = values.pop()
841 850 else:
842 851 last = None
843 852 for v in values:
844 853 yield one(v)
845 854 if last is not None:
846 855 yield one(last, tag=lastname)
847 856 endname = 'end_' + names
848 857 if endname in self.t:
849 858 yield self.t(endname, **args)
850 859
851 860 def showbranches(**args):
852 861 branch = changes[5].get("branch")
853 862 if branch != 'default':
854 863 branch = util.tolocal(branch)
855 864 return showlist('branch', [branch], plural='branches', **args)
856 865
857 866 def showparents(**args):
858 867 parents = [[('rev', log.rev(p)), ('node', hex(p))]
859 868 for p in log.parents(changenode)
860 869 if self.ui.debugflag or p != nullid]
861 870 if (not self.ui.debugflag and len(parents) == 1 and
862 871 parents[0][0][1] == rev - 1):
863 872 return
864 873 return showlist('parent', parents, **args)
865 874
866 875 def showtags(**args):
867 876 return showlist('tag', self.repo.nodetags(changenode), **args)
868 877
869 878 def showextras(**args):
870 879 extras = changes[5].items()
871 880 extras.sort()
872 881 for key, value in extras:
873 882 args = args.copy()
874 883 args.update(dict(key=key, value=value))
875 884 yield self.t('extra', **args)
876 885
877 886 def showcopies(**args):
878 887 c = [{'name': x[0], 'source': x[1]} for x in copies]
879 888 return showlist('file_copy', c, plural='file_copies', **args)
880 889
881 890 if self.ui.debugflag:
882 891 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
883 892 def showfiles(**args):
884 893 return showlist('file', files[0], **args)
885 894 def showadds(**args):
886 895 return showlist('file_add', files[1], **args)
887 896 def showdels(**args):
888 897 return showlist('file_del', files[2], **args)
889 898 def showmanifest(**args):
890 899 args = args.copy()
891 900 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
892 901 node=hex(changes[0])))
893 902 return self.t('manifest', **args)
894 903 else:
895 904 def showfiles(**args):
896 905 return showlist('file', changes[3], **args)
897 906 showadds = ''
898 907 showdels = ''
899 908 showmanifest = ''
900 909
901 910 defprops = {
902 911 'author': changes[1],
903 912 'branches': showbranches,
904 913 'date': changes[2],
905 914 'desc': changes[4],
906 915 'file_adds': showadds,
907 916 'file_dels': showdels,
908 917 'files': showfiles,
909 918 'file_copies': showcopies,
910 919 'manifest': showmanifest,
911 920 'node': hex(changenode),
912 921 'parents': showparents,
913 922 'rev': rev,
914 923 'tags': showtags,
915 924 'extras': showextras,
916 925 }
917 926 props = props.copy()
918 927 props.update(defprops)
919 928
920 929 try:
921 930 if self.ui.debugflag and 'header_debug' in self.t:
922 931 key = 'header_debug'
923 932 elif self.ui.quiet and 'header_quiet' in self.t:
924 933 key = 'header_quiet'
925 934 elif self.ui.verbose and 'header_verbose' in self.t:
926 935 key = 'header_verbose'
927 936 elif 'header' in self.t:
928 937 key = 'header'
929 938 else:
930 939 key = ''
931 940 if key:
932 941 h = templater.stringify(self.t(key, **props))
933 942 if self.buffered:
934 943 self.header[rev] = h
935 944 else:
936 945 self.ui.write(h)
937 946 if self.ui.debugflag and 'changeset_debug' in self.t:
938 947 key = 'changeset_debug'
939 948 elif self.ui.quiet and 'changeset_quiet' in self.t:
940 949 key = 'changeset_quiet'
941 950 elif self.ui.verbose and 'changeset_verbose' in self.t:
942 951 key = 'changeset_verbose'
943 952 else:
944 953 key = 'changeset'
945 954 self.ui.write(templater.stringify(self.t(key, **props)))
946 955 self.showpatch(changenode)
947 956 except KeyError, inst:
948 957 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
949 958 inst.args[0]))
950 959 except SyntaxError, inst:
951 960 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
952 961
953 962 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
954 963 """show one changeset using template or regular display.
955 964
956 965 Display format will be the first non-empty hit of:
957 966 1. option 'template'
958 967 2. option 'style'
959 968 3. [ui] setting 'logtemplate'
960 969 4. [ui] setting 'style'
961 970 If all of these values are either the unset or the empty string,
962 971 regular display via changeset_printer() is done.
963 972 """
964 973 # options
965 974 patch = False
966 975 if opts.get('patch'):
967 976 patch = matchfn or util.always
968 977
969 978 tmpl = opts.get('template')
970 979 mapfile = None
971 980 if tmpl:
972 981 tmpl = templater.parsestring(tmpl, quoted=False)
973 982 else:
974 983 mapfile = opts.get('style')
975 984 # ui settings
976 985 if not mapfile:
977 986 tmpl = ui.config('ui', 'logtemplate')
978 987 if tmpl:
979 988 tmpl = templater.parsestring(tmpl)
980 989 else:
981 990 mapfile = ui.config('ui', 'style')
982 991
983 992 if tmpl or mapfile:
984 993 if mapfile:
985 994 if not os.path.split(mapfile)[0]:
986 995 mapname = (templater.templatepath('map-cmdline.' + mapfile)
987 996 or templater.templatepath(mapfile))
988 997 if mapname: mapfile = mapname
989 998 try:
990 999 t = changeset_templater(ui, repo, patch, mapfile, buffered)
991 1000 except SyntaxError, inst:
992 1001 raise util.Abort(inst.args[0])
993 1002 if tmpl: t.use_template(tmpl)
994 1003 return t
995 1004 return changeset_printer(ui, repo, patch, buffered)
996 1005
997 1006 def finddate(ui, repo, date):
998 1007 """Find the tipmost changeset that matches the given date spec"""
999 1008 df = util.matchdate(date + " to " + date)
1000 1009 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1001 1010 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
1002 1011 results = {}
1003 1012 for st, rev, fns in changeiter:
1004 1013 if st == 'add':
1005 1014 d = get(rev)[2]
1006 1015 if df(d[0]):
1007 1016 results[rev] = d
1008 1017 elif st == 'iter':
1009 1018 if rev in results:
1010 1019 ui.status("Found revision %s from %s\n" %
1011 1020 (rev, util.datestr(results[rev])))
1012 1021 return str(rev)
1013 1022
1014 1023 raise util.Abort(_("revision matching date not found"))
1015 1024
1016 1025 def walkchangerevs(ui, repo, pats, change, opts):
1017 1026 '''Iterate over files and the revs they changed in.
1018 1027
1019 1028 Callers most commonly need to iterate backwards over the history
1020 1029 it is interested in. Doing so has awful (quadratic-looking)
1021 1030 performance, so we use iterators in a "windowed" way.
1022 1031
1023 1032 We walk a window of revisions in the desired order. Within the
1024 1033 window, we first walk forwards to gather data, then in the desired
1025 1034 order (usually backwards) to display it.
1026 1035
1027 1036 This function returns an (iterator, matchfn) tuple. The iterator
1028 1037 yields 3-tuples. They will be of one of the following forms:
1029 1038
1030 1039 "window", incrementing, lastrev: stepping through a window,
1031 1040 positive if walking forwards through revs, last rev in the
1032 1041 sequence iterated over - use to reset state for the current window
1033 1042
1034 1043 "add", rev, fns: out-of-order traversal of the given file names
1035 1044 fns, which changed during revision rev - use to gather data for
1036 1045 possible display
1037 1046
1038 1047 "iter", rev, None: in-order traversal of the revs earlier iterated
1039 1048 over with "add" - use to display data'''
1040 1049
1041 1050 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1042 1051 if start < end:
1043 1052 while start < end:
1044 1053 yield start, min(windowsize, end-start)
1045 1054 start += windowsize
1046 1055 if windowsize < sizelimit:
1047 1056 windowsize *= 2
1048 1057 else:
1049 1058 while start > end:
1050 1059 yield start, min(windowsize, start-end-1)
1051 1060 start -= windowsize
1052 1061 if windowsize < sizelimit:
1053 1062 windowsize *= 2
1054 1063
1055 1064 files, matchfn, anypats = matchpats(repo, pats, opts)
1056 1065 follow = opts.get('follow') or opts.get('follow_first')
1057 1066
1058 1067 if repo.changelog.count() == 0:
1059 1068 return [], matchfn
1060 1069
1061 1070 if follow:
1062 1071 defrange = '%s:0' % repo.changectx().rev()
1063 1072 else:
1064 1073 defrange = 'tip:0'
1065 1074 revs = revrange(repo, opts['rev'] or [defrange])
1066 1075 wanted = {}
1067 1076 slowpath = anypats or opts.get('removed')
1068 1077 fncache = {}
1069 1078
1070 1079 if not slowpath and not files:
1071 1080 # No files, no patterns. Display all revs.
1072 1081 wanted = dict.fromkeys(revs)
1073 1082 copies = []
1074 1083 if not slowpath:
1075 1084 # Only files, no patterns. Check the history of each file.
1076 1085 def filerevgen(filelog, node):
1077 1086 cl_count = repo.changelog.count()
1078 1087 if node is None:
1079 1088 last = filelog.count() - 1
1080 1089 else:
1081 1090 last = filelog.rev(node)
1082 1091 for i, window in increasing_windows(last, nullrev):
1083 1092 revs = []
1084 1093 for j in xrange(i - window, i + 1):
1085 1094 n = filelog.node(j)
1086 1095 revs.append((filelog.linkrev(n),
1087 1096 follow and filelog.renamed(n)))
1088 1097 revs.reverse()
1089 1098 for rev in revs:
1090 1099 # only yield rev for which we have the changelog, it can
1091 1100 # happen while doing "hg log" during a pull or commit
1092 1101 if rev[0] < cl_count:
1093 1102 yield rev
1094 1103 def iterfiles():
1095 1104 for filename in files:
1096 1105 yield filename, None
1097 1106 for filename_node in copies:
1098 1107 yield filename_node
1099 1108 minrev, maxrev = min(revs), max(revs)
1100 1109 for file_, node in iterfiles():
1101 1110 filelog = repo.file(file_)
1102 1111 # A zero count may be a directory or deleted file, so
1103 1112 # try to find matching entries on the slow path.
1104 1113 if filelog.count() == 0:
1105 1114 slowpath = True
1106 1115 break
1107 1116 for rev, copied in filerevgen(filelog, node):
1108 1117 if rev <= maxrev:
1109 1118 if rev < minrev:
1110 1119 break
1111 1120 fncache.setdefault(rev, [])
1112 1121 fncache[rev].append(file_)
1113 1122 wanted[rev] = 1
1114 1123 if follow and copied:
1115 1124 copies.append(copied)
1116 1125 if slowpath:
1117 1126 if follow:
1118 1127 raise util.Abort(_('can only follow copies/renames for explicit '
1119 1128 'file names'))
1120 1129
1121 1130 # The slow path checks files modified in every changeset.
1122 1131 def changerevgen():
1123 1132 for i, window in increasing_windows(repo.changelog.count()-1,
1124 1133 nullrev):
1125 1134 for j in xrange(i - window, i + 1):
1126 1135 yield j, change(j)[3]
1127 1136
1128 1137 for rev, changefiles in changerevgen():
1129 1138 matches = filter(matchfn, changefiles)
1130 1139 if matches:
1131 1140 fncache[rev] = matches
1132 1141 wanted[rev] = 1
1133 1142
1134 1143 class followfilter:
1135 1144 def __init__(self, onlyfirst=False):
1136 1145 self.startrev = nullrev
1137 1146 self.roots = []
1138 1147 self.onlyfirst = onlyfirst
1139 1148
1140 1149 def match(self, rev):
1141 1150 def realparents(rev):
1142 1151 if self.onlyfirst:
1143 1152 return repo.changelog.parentrevs(rev)[0:1]
1144 1153 else:
1145 1154 return filter(lambda x: x != nullrev,
1146 1155 repo.changelog.parentrevs(rev))
1147 1156
1148 1157 if self.startrev == nullrev:
1149 1158 self.startrev = rev
1150 1159 return True
1151 1160
1152 1161 if rev > self.startrev:
1153 1162 # forward: all descendants
1154 1163 if not self.roots:
1155 1164 self.roots.append(self.startrev)
1156 1165 for parent in realparents(rev):
1157 1166 if parent in self.roots:
1158 1167 self.roots.append(rev)
1159 1168 return True
1160 1169 else:
1161 1170 # backwards: all parents
1162 1171 if not self.roots:
1163 1172 self.roots.extend(realparents(self.startrev))
1164 1173 if rev in self.roots:
1165 1174 self.roots.remove(rev)
1166 1175 self.roots.extend(realparents(rev))
1167 1176 return True
1168 1177
1169 1178 return False
1170 1179
1171 1180 # it might be worthwhile to do this in the iterator if the rev range
1172 1181 # is descending and the prune args are all within that range
1173 1182 for rev in opts.get('prune', ()):
1174 1183 rev = repo.changelog.rev(repo.lookup(rev))
1175 1184 ff = followfilter()
1176 1185 stop = min(revs[0], revs[-1])
1177 1186 for x in xrange(rev, stop-1, -1):
1178 1187 if ff.match(x) and x in wanted:
1179 1188 del wanted[x]
1180 1189
1181 1190 def iterate():
1182 1191 if follow and not files:
1183 1192 ff = followfilter(onlyfirst=opts.get('follow_first'))
1184 1193 def want(rev):
1185 1194 if ff.match(rev) and rev in wanted:
1186 1195 return True
1187 1196 return False
1188 1197 else:
1189 1198 def want(rev):
1190 1199 return rev in wanted
1191 1200
1192 1201 for i, window in increasing_windows(0, len(revs)):
1193 1202 yield 'window', revs[0] < revs[-1], revs[-1]
1194 1203 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1195 1204 srevs = list(nrevs)
1196 1205 srevs.sort()
1197 1206 for rev in srevs:
1198 1207 fns = fncache.get(rev)
1199 1208 if not fns:
1200 1209 def fns_generator():
1201 1210 for f in change(rev)[3]:
1202 1211 if matchfn(f):
1203 1212 yield f
1204 1213 fns = fns_generator()
1205 1214 yield 'add', rev, fns
1206 1215 for rev in nrevs:
1207 1216 yield 'iter', rev, None
1208 1217 return iterate(), matchfn
@@ -1,213 +1,220
1 1 #!/bin/sh
2 2
3 3 cp "$TESTDIR"/printenv.py .
4 4
5 5 # commit hooks can see env vars
6 6 hg init a
7 7 cd a
8 8 echo "[hooks]" > .hg/hgrc
9 9 echo 'commit = unset HG_LOCAL HG_TAG; python ../printenv.py commit' >> .hg/hgrc
10 10 echo 'commit.b = unset HG_LOCAL HG_TAG; python ../printenv.py commit.b' >> .hg/hgrc
11 11 echo 'precommit = unset HG_LOCAL HG_NODE HG_TAG; python ../printenv.py precommit' >> .hg/hgrc
12 12 echo 'pretxncommit = unset HG_LOCAL HG_TAG; python ../printenv.py pretxncommit' >> .hg/hgrc
13 13 echo 'pretxncommit.tip = hg -q tip' >> .hg/hgrc
14 echo 'pre-identify = false' >> .hg/hgrc
15 echo 'pre-cat = echo "meow $HG_ARGS"' >> .hg/hgrc
16 echo 'post-cat = echo "purr $HG_RESULT"' >> .hg/hgrc
14 17 echo a > a
15 18 hg add a
16 19 hg commit -m a -d "1000000 0"
17 20
18 21 hg clone . ../b
19 22 cd ../b
20 23
21 24 # changegroup hooks can see env vars
22 25 echo '[hooks]' > .hg/hgrc
23 26 echo 'prechangegroup = python ../printenv.py prechangegroup' >> .hg/hgrc
24 27 echo 'changegroup = python ../printenv.py changegroup' >> .hg/hgrc
25 28 echo 'incoming = python ../printenv.py incoming' >> .hg/hgrc
26 29
27 30 # pretxncommit and commit hooks can see both parents of merge
28 31 cd ../a
29 32 echo b >> a
30 33 hg commit -m a1 -d "1 0"
31 34 hg update -C 0
32 35 echo b > b
33 36 hg add b
34 37 hg commit -m b -d '1 0'
35 38 hg merge 1
36 39 hg commit -m merge -d '2 0'
37 40
41 # test generic hooks
42 hg id
43 hg cat b
44
38 45 cd ../b
39 46 hg pull ../a
40 47
41 48 # tag hooks can see env vars
42 49 cd ../a
43 50 echo 'pretag = python ../printenv.py pretag' >> .hg/hgrc
44 51 echo 'tag = unset HG_PARENT1 HG_PARENT2; python ../printenv.py tag' >> .hg/hgrc
45 52 hg tag -d '3 0' a
46 53 hg tag -l la
47 54
48 55 # pretag hook can forbid tagging
49 56 echo 'pretag.forbid = python ../printenv.py pretag.forbid 1' >> .hg/hgrc
50 57 hg tag -d '4 0' fa
51 58 hg tag -l fla
52 59
53 60 # pretxncommit hook can see changeset, can roll back txn, changeset
54 61 # no more there after
55 62 echo 'pretxncommit.forbid0 = hg tip -q' >> .hg/hgrc
56 63 echo 'pretxncommit.forbid1 = python ../printenv.py pretxncommit.forbid 1' >> .hg/hgrc
57 64 echo z > z
58 65 hg add z
59 66 hg -q tip
60 67 hg commit -m 'fail' -d '4 0'
61 68 hg -q tip
62 69
63 70 # precommit hook can prevent commit
64 71 echo 'precommit.forbid = python ../printenv.py precommit.forbid 1' >> .hg/hgrc
65 72 hg commit -m 'fail' -d '4 0'
66 73 hg -q tip
67 74
68 75 # preupdate hook can prevent update
69 76 echo 'preupdate = python ../printenv.py preupdate' >> .hg/hgrc
70 77 hg update 1
71 78
72 79 # update hook
73 80 echo 'update = python ../printenv.py update' >> .hg/hgrc
74 81 hg update
75 82
76 83 # prechangegroup hook can prevent incoming changes
77 84 cd ../b
78 85 hg -q tip
79 86 echo '[hooks]' > .hg/hgrc
80 87 echo 'prechangegroup.forbid = python ../printenv.py prechangegroup.forbid 1' >> .hg/hgrc
81 88 hg pull ../a
82 89
83 90 # pretxnchangegroup hook can see incoming changes, can roll back txn,
84 91 # incoming changes no longer there after
85 92 echo '[hooks]' > .hg/hgrc
86 93 echo 'pretxnchangegroup.forbid0 = hg tip -q' >> .hg/hgrc
87 94 echo 'pretxnchangegroup.forbid1 = python ../printenv.py pretxnchangegroup.forbid 1' >> .hg/hgrc
88 95 hg pull ../a
89 96 hg -q tip
90 97
91 98 # outgoing hooks can see env vars
92 99 rm .hg/hgrc
93 100 echo '[hooks]' > ../a/.hg/hgrc
94 101 echo 'preoutgoing = python ../printenv.py preoutgoing' >> ../a/.hg/hgrc
95 102 echo 'outgoing = python ../printenv.py outgoing' >> ../a/.hg/hgrc
96 103 hg pull ../a
97 104 hg rollback
98 105
99 106 # preoutgoing hook can prevent outgoing changes
100 107 echo 'preoutgoing.forbid = python ../printenv.py preoutgoing.forbid 1' >> ../a/.hg/hgrc
101 108 hg pull ../a
102 109
103 110 cat > hooktests.py <<EOF
104 111 from mercurial import util
105 112
106 113 uncallable = 0
107 114
108 115 def printargs(args):
109 116 args.pop('ui', None)
110 117 args.pop('repo', None)
111 118 a = list(args.items())
112 119 a.sort()
113 120 print 'hook args:'
114 121 for k, v in a:
115 122 print ' ', k, v
116 123
117 124 def passhook(**args):
118 125 printargs(args)
119 126
120 127 def failhook(**args):
121 128 printargs(args)
122 129 return True
123 130
124 131 class LocalException(Exception):
125 132 pass
126 133
127 134 def raisehook(**args):
128 135 raise LocalException('exception from hook')
129 136
130 137 def aborthook(**args):
131 138 raise util.Abort('raise abort from hook')
132 139
133 140 def brokenhook(**args):
134 141 return 1 + {}
135 142
136 143 class container:
137 144 unreachable = 1
138 145 EOF
139 146
140 147 echo '# test python hooks'
141 148 PYTHONPATH="`pwd`:$PYTHONPATH"
142 149 export PYTHONPATH
143 150
144 151 echo '[hooks]' > ../a/.hg/hgrc
145 152 echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
146 153 hg pull ../a 2>&1 | grep 'raised an exception'
147 154
148 155 echo '[hooks]' > ../a/.hg/hgrc
149 156 echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
150 157 hg pull ../a 2>&1 | grep 'raised an exception'
151 158
152 159 echo '[hooks]' > ../a/.hg/hgrc
153 160 echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
154 161 hg pull ../a
155 162
156 163 echo '[hooks]' > ../a/.hg/hgrc
157 164 echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
158 165 hg pull ../a
159 166
160 167 echo '[hooks]' > ../a/.hg/hgrc
161 168 echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
162 169 hg pull ../a
163 170
164 171 echo '[hooks]' > ../a/.hg/hgrc
165 172 echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
166 173 hg pull ../a
167 174
168 175 echo '[hooks]' > ../a/.hg/hgrc
169 176 echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
170 177 hg pull ../a
171 178
172 179 echo '[hooks]' > ../a/.hg/hgrc
173 180 echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
174 181 hg pull ../a
175 182
176 183 echo '[hooks]' > ../a/.hg/hgrc
177 184 echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
178 185 hg pull ../a
179 186
180 187 echo '[hooks]' > ../a/.hg/hgrc
181 188 echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
182 189 hg pull ../a
183 190
184 191 echo '# make sure --traceback works'
185 192 echo '[hooks]' > .hg/hgrc
186 193 echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
187 194
188 195 echo a >> a
189 196 hg --traceback commit -A -m a 2>&1 | grep '^Traceback'
190 197
191 198 cd ..
192 199 hg init c
193 200 cd c
194 201
195 202 cat > hookext.py <<EOF
196 203 def autohook(**args):
197 204 print "Automatically installed hook"
198 205
199 206 def reposetup(ui, repo):
200 207 repo.ui.setconfig("hooks", "commit.auto", autohook)
201 208 EOF
202 209 echo '[extensions]' >> .hg/hgrc
203 210 echo 'hookext = hookext.py' >> .hg/hgrc
204 211
205 212 touch foo
206 213 hg add foo
207 214 hg ci -m 'add foo'
208 215 echo >> foo
209 216 hg ci --debug -m 'change foo' | sed -e 's/ at .*>/>/'
210 217
211 218 hg showconfig hooks | sed -e 's/ at .*>/>/'
212 219
213 220 exit 0
@@ -1,146 +1,150
1 1 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000 HG_PARENT2=
2 2 pretxncommit hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 HG_PARENT2=
3 3 0:29b62aeb769f
4 4 commit hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 HG_PARENT2=
5 5 commit.b hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 HG_PARENT2=
6 6 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
7 7 precommit hook: HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2=
8 8 pretxncommit hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2=
9 9 1:b702efe96888
10 10 commit hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2=
11 11 commit.b hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2=
12 12 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
13 13 precommit hook: HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2=
14 14 pretxncommit hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2=
15 15 2:1324a5531bac
16 16 commit hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2=
17 17 commit.b hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2=
18 18 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
19 19 (branch merge, don't forget to commit)
20 20 precommit hook: HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2
21 21 pretxncommit hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2
22 22 3:4c52fb2e4022
23 23 commit hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2
24 24 commit.b hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2
25 warning: pre-identify hook exited with status 1
26 meow cat b
27 purr 0
28 b
25 29 prechangegroup hook: HG_SOURCE=pull HG_URL=file:
26 30 changegroup hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_SOURCE=pull HG_URL=file:
27 31 incoming hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_SOURCE=pull HG_URL=file:
28 32 incoming hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_SOURCE=pull HG_URL=file:
29 33 incoming hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_SOURCE=pull HG_URL=file:
30 34 pulling from ../a
31 35 searching for changes
32 36 adding changesets
33 37 adding manifests
34 38 adding file changes
35 39 added 3 changesets with 2 changes to 2 files
36 40 (run 'hg update' to get a working copy)
37 41 pretag hook: HG_LOCAL=0 HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_TAG=a
38 42 precommit hook: HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT2=
39 43 pretxncommit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT2=
40 44 4:8ea2ef7ad3e8
41 45 commit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT2=
42 46 commit.b hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT2=
43 47 tag hook: HG_LOCAL=0 HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_TAG=a
44 48 pretag hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=la
45 49 tag hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=la
46 50 pretag hook: HG_LOCAL=0 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fa
47 51 pretag.forbid hook: HG_LOCAL=0 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fa
48 52 abort: pretag.forbid hook exited with status 1
49 53 pretag hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fla
50 54 pretag.forbid hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fla
51 55 abort: pretag.forbid hook exited with status 1
52 56 4:8ea2ef7ad3e8
53 57 precommit hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2=
54 58 pretxncommit hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2=
55 59 5:fad284daf8c0
56 60 pretxncommit.forbid hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2=
57 61 abort: pretxncommit.forbid1 hook exited with status 1
58 62 transaction abort!
59 63 rollback completed
60 64 4:8ea2ef7ad3e8
61 65 precommit hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2=
62 66 precommit.forbid hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2=
63 67 abort: precommit.forbid hook exited with status 1
64 68 4:8ea2ef7ad3e8
65 69 preupdate hook: HG_PARENT1=b702efe96888 HG_PARENT2=
66 70 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
67 71 preupdate hook: HG_PARENT1=8ea2ef7ad3e8 HG_PARENT2=
68 72 update hook: HG_ERROR=0 HG_PARENT1=8ea2ef7ad3e8 HG_PARENT2=
69 73 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 74 3:4c52fb2e4022
71 75 prechangegroup.forbid hook: HG_SOURCE=pull HG_URL=file:
72 76 pulling from ../a
73 77 searching for changes
74 78 abort: prechangegroup.forbid hook exited with status 1
75 79 4:8ea2ef7ad3e8
76 80 pretxnchangegroup.forbid hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_SOURCE=pull HG_URL=file:
77 81 pulling from ../a
78 82 searching for changes
79 83 adding changesets
80 84 adding manifests
81 85 adding file changes
82 86 added 1 changesets with 1 changes to 1 files
83 87 abort: pretxnchangegroup.forbid1 hook exited with status 1
84 88 transaction abort!
85 89 rollback completed
86 90 3:4c52fb2e4022
87 91 preoutgoing hook: HG_SOURCE=pull
88 92 outgoing hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_SOURCE=pull
89 93 pulling from ../a
90 94 searching for changes
91 95 adding changesets
92 96 adding manifests
93 97 adding file changes
94 98 added 1 changesets with 1 changes to 1 files
95 99 (run 'hg update' to get a working copy)
96 100 rolling back last transaction
97 101 preoutgoing hook: HG_SOURCE=pull
98 102 preoutgoing.forbid hook: HG_SOURCE=pull
99 103 pulling from ../a
100 104 searching for changes
101 105 abort: preoutgoing.forbid hook exited with status 1
102 106 # test python hooks
103 107 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
104 108 error: preoutgoing.raise hook raised an exception: exception from hook
105 109 pulling from ../a
106 110 searching for changes
107 111 error: preoutgoing.abort hook failed: raise abort from hook
108 112 abort: raise abort from hook
109 113 pulling from ../a
110 114 searching for changes
111 115 hook args:
112 116 hooktype preoutgoing
113 117 source pull
114 118 abort: preoutgoing.fail hook failed
115 119 pulling from ../a
116 120 searching for changes
117 121 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
118 122 pulling from ../a
119 123 searching for changes
120 124 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
121 125 pulling from ../a
122 126 searching for changes
123 127 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
124 128 pulling from ../a
125 129 searching for changes
126 130 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
127 131 pulling from ../a
128 132 searching for changes
129 133 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
130 134 pulling from ../a
131 135 searching for changes
132 136 hook args:
133 137 hooktype preoutgoing
134 138 source pull
135 139 adding changesets
136 140 adding manifests
137 141 adding file changes
138 142 added 1 changesets with 1 changes to 1 files
139 143 (run 'hg update' to get a working copy)
140 144 # make sure --traceback works
141 145 Traceback (most recent call last):
142 146 Automatically installed hook
143 147 foo
144 148 calling hook commit.auto: <function autohook>
145 149 Automatically installed hook
146 150 hooks.commit.auto=<function autohook>
General Comments 0
You need to be logged in to leave comments. Login now