comparison hgext/fix.py @ 40532:93bab80993f4

fix: add a config to abort when a fixer tool fails This allows users to stop and address tool failures before proceeding, instead of the default behavior of continuing to apply any tools that didn't fail. For example, a code formatting tool could fail if you have syntax errors, and you might want your repo to stay in its current state while you fix the syntax error before re-running 'hg fix'. It's conceivable that this would even be necessary for the correctness of some fixer tools across a chain of revisions. Differential Revision: https://phab.mercurial-scm.org/D5200
author Danny Hooper <hooper@google.com>
date Wed, 31 Oct 2018 13:11:51 -0700
parents 8ebb05f747e5
children 2ecf5c24d0cd
comparison
equal deleted inserted replaced
40531:e6c8a0fd3db4 40532:93bab80993f4
17 clang-format:linerange=--lines={first}:{last} 17 clang-format:linerange=--lines={first}:{last}
18 clang-format:fileset=set:**.cpp or **.hpp 18 clang-format:fileset=set:**.cpp or **.hpp
19 19
20 The :command suboption forms the first part of the shell command that will be 20 The :command suboption forms the first part of the shell command that will be
21 used to fix a file. The content of the file is passed on standard input, and the 21 used to fix a file. The content of the file is passed on standard input, and the
22 fixed file content is expected on standard output. If there is any output on 22 fixed file content is expected on standard output. Any output on standard error
23 standard error, the file will not be affected. Some values may be substituted 23 will be displayed as a warning. If the exit status is not zero, the file will
24 into the command:: 24 not be affected. A placeholder warning is displayed if there is a non-zero exit
25 status but no standard error output. Some values may be substituted into the
26 command::
25 27
26 {rootpath} The path of the file being fixed, relative to the repo root 28 {rootpath} The path of the file being fixed, relative to the repo root
27 {basename} The name of the file being fixed, without the directory path 29 {basename} The name of the file being fixed, without the directory path
28 30
29 If the :linerange suboption is set, the tool will only be run if there are 31 If the :linerange suboption is set, the tool will only be run if there are
40 42
41 There is also a configurable limit for the maximum size of file that will be 43 There is also a configurable limit for the maximum size of file that will be
42 processed by :hg:`fix`:: 44 processed by :hg:`fix`::
43 45
44 [fix] 46 [fix]
45 maxfilesize=2MB 47 maxfilesize = 2MB
48
49 Normally, execution of configured tools will continue after a failure (indicated
50 by a non-zero exit status). It can also be configured to abort after the first
51 such failure, so that no files will be affected if any tool fails. This abort
52 will also cause :hg:`fix` to exit with a non-zero status::
53
54 [fix]
55 failure = abort
46 56
47 """ 57 """
48 58
49 from __future__ import absolute_import 59 from __future__ import absolute_import
50 60
97 107
98 # A good default size allows most source code files to be fixed, but avoids 108 # A good default size allows most source code files to be fixed, but avoids
99 # letting fixer tools choke on huge inputs, which could be surprising to the 109 # letting fixer tools choke on huge inputs, which could be surprising to the
100 # user. 110 # user.
101 configitem('fix', 'maxfilesize', default='2MB') 111 configitem('fix', 'maxfilesize', default='2MB')
112
113 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
114 # This helps users do shell scripts that stop when a fixer tool signals a
115 # problem.
116 configitem('fix', 'failure', default='continue')
117
118 def checktoolfailureaction(ui, message, hint=None):
119 """Abort with 'message' if fix.failure=abort"""
120 action = ui.config('fix', 'failure')
121 if action not in ('continue', 'abort'):
122 raise error.Abort(_('unknown fix.failure action: %s') % (action,),
123 hint=_('use "continue" or "abort"'))
124 if action == 'abort':
125 raise error.Abort(message, hint=hint)
102 126
103 allopt = ('', 'all', False, _('fix all non-public non-obsolete revisions')) 127 allopt = ('', 'all', False, _('fix all non-public non-obsolete revisions'))
104 baseopt = ('', 'base', [], _('revisions to diff against (overrides automatic ' 128 baseopt = ('', 'base', [], _('revisions to diff against (overrides automatic '
105 'selection, and applies to every revision being ' 129 'selection, and applies to every revision being '
106 'fixed)'), _('REV')) 130 'fixed)'), _('REV'))
463 newerdata, stderr = proc.communicate(newdata) 487 newerdata, stderr = proc.communicate(newdata)
464 if stderr: 488 if stderr:
465 showstderr(ui, fixctx.rev(), fixername, stderr) 489 showstderr(ui, fixctx.rev(), fixername, stderr)
466 if proc.returncode == 0: 490 if proc.returncode == 0:
467 newdata = newerdata 491 newdata = newerdata
468 elif not stderr: 492 else:
469 showstderr(ui, fixctx.rev(), fixername, 493 if not stderr:
470 _('exited with status %d\n') % (proc.returncode,)) 494 message = _('exited with status %d\n') % (proc.returncode,)
495 showstderr(ui, fixctx.rev(), fixername, message)
496 checktoolfailureaction(
497 ui, _('no fixes will be applied'),
498 hint=_('use --config fix.failure=continue to apply any '
499 'successful fixes anyway'))
471 return newdata 500 return newdata
472 501
473 def showstderr(ui, rev, fixername, stderr): 502 def showstderr(ui, rev, fixername, stderr):
474 """Writes the lines of the stderr string as warnings on the ui 503 """Writes the lines of the stderr string as warnings on the ui
475 504