##// END OF EJS Templates
fix: add :enabled sub-config for fixer tools...
Danny Hooper -
r43328:808a57a0 default
parent child Browse files
Show More
@@ -1,777 +1,782 b''
1 # fix - rewrite file content in changesets and working copy
1 # fix - rewrite file content in changesets and working copy
2 #
2 #
3 # Copyright 2018 Google LLC.
3 # Copyright 2018 Google LLC.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """rewrite file content in changesets or working copy (EXPERIMENTAL)
7 """rewrite file content in changesets or working copy (EXPERIMENTAL)
8
8
9 Provides a command that runs configured tools on the contents of modified files,
9 Provides a command that runs configured tools on the contents of modified files,
10 writing back any fixes to the working copy or replacing changesets.
10 writing back any fixes to the working copy or replacing changesets.
11
11
12 Here is an example configuration that causes :hg:`fix` to apply automatic
12 Here is an example configuration that causes :hg:`fix` to apply automatic
13 formatting fixes to modified lines in C++ code::
13 formatting fixes to modified lines in C++ code::
14
14
15 [fix]
15 [fix]
16 clang-format:command=clang-format --assume-filename={rootpath}
16 clang-format:command=clang-format --assume-filename={rootpath}
17 clang-format:linerange=--lines={first}:{last}
17 clang-format:linerange=--lines={first}:{last}
18 clang-format:pattern=set:**.cpp or **.hpp
18 clang-format:pattern=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. Any output on standard error
22 fixed file content is expected on standard output. Any output on standard error
23 will be displayed as a warning. If the exit status is not zero, the file will
23 will be displayed as a warning. If the exit status is not zero, the file will
24 not be affected. A placeholder warning is displayed if there is a non-zero exit
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
25 status but no standard error output. Some values may be substituted into the
26 command::
26 command::
27
27
28 {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
29 {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
30
30
31 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
32 changed lines in a file. The value of this suboption is appended to the shell
32 changed lines in a file. The value of this suboption is appended to the shell
33 command once for every range of changed lines in the file. Some values may be
33 command once for every range of changed lines in the file. Some values may be
34 substituted into the command::
34 substituted into the command::
35
35
36 {first} The 1-based line number of the first line in the modified range
36 {first} The 1-based line number of the first line in the modified range
37 {last} The 1-based line number of the last line in the modified range
37 {last} The 1-based line number of the last line in the modified range
38
38
39 Deleted sections of a file will be ignored by :linerange, because there is no
39 Deleted sections of a file will be ignored by :linerange, because there is no
40 corresponding line range in the version being fixed.
40 corresponding line range in the version being fixed.
41
41
42 By default, tools that set :linerange will only be executed if there is at least
42 By default, tools that set :linerange will only be executed if there is at least
43 one changed line range. This is meant to prevent accidents like running a code
43 one changed line range. This is meant to prevent accidents like running a code
44 formatter in such a way that it unexpectedly reformats the whole file. If such a
44 formatter in such a way that it unexpectedly reformats the whole file. If such a
45 tool needs to operate on unchanged files, it should set the :skipclean suboption
45 tool needs to operate on unchanged files, it should set the :skipclean suboption
46 to false.
46 to false.
47
47
48 The :pattern suboption determines which files will be passed through each
48 The :pattern suboption determines which files will be passed through each
49 configured tool. See :hg:`help patterns` for possible values. If there are file
49 configured tool. See :hg:`help patterns` for possible values. If there are file
50 arguments to :hg:`fix`, the intersection of these patterns is used.
50 arguments to :hg:`fix`, the intersection of these patterns is used.
51
51
52 There is also a configurable limit for the maximum size of file that will be
52 There is also a configurable limit for the maximum size of file that will be
53 processed by :hg:`fix`::
53 processed by :hg:`fix`::
54
54
55 [fix]
55 [fix]
56 maxfilesize = 2MB
56 maxfilesize = 2MB
57
57
58 Normally, execution of configured tools will continue after a failure (indicated
58 Normally, execution of configured tools will continue after a failure (indicated
59 by a non-zero exit status). It can also be configured to abort after the first
59 by a non-zero exit status). It can also be configured to abort after the first
60 such failure, so that no files will be affected if any tool fails. This abort
60 such failure, so that no files will be affected if any tool fails. This abort
61 will also cause :hg:`fix` to exit with a non-zero status::
61 will also cause :hg:`fix` to exit with a non-zero status::
62
62
63 [fix]
63 [fix]
64 failure = abort
64 failure = abort
65
65
66 When multiple tools are configured to affect a file, they execute in an order
66 When multiple tools are configured to affect a file, they execute in an order
67 defined by the :priority suboption. The priority suboption has a default value
67 defined by the :priority suboption. The priority suboption has a default value
68 of zero for each tool. Tools are executed in order of descending priority. The
68 of zero for each tool. Tools are executed in order of descending priority. The
69 execution order of tools with equal priority is unspecified. For example, you
69 execution order of tools with equal priority is unspecified. For example, you
70 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
70 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
71 in a text file by ensuring that 'sort' runs before 'head'::
71 in a text file by ensuring that 'sort' runs before 'head'::
72
72
73 [fix]
73 [fix]
74 sort:command = sort -n
74 sort:command = sort -n
75 head:command = head -n 10
75 head:command = head -n 10
76 sort:pattern = numbers.txt
76 sort:pattern = numbers.txt
77 head:pattern = numbers.txt
77 head:pattern = numbers.txt
78 sort:priority = 2
78 sort:priority = 2
79 head:priority = 1
79 head:priority = 1
80
80
81 To account for changes made by each tool, the line numbers used for incremental
81 To account for changes made by each tool, the line numbers used for incremental
82 formatting are recomputed before executing the next tool. So, each tool may see
82 formatting are recomputed before executing the next tool. So, each tool may see
83 different values for the arguments added by the :linerange suboption.
83 different values for the arguments added by the :linerange suboption.
84
84
85 Each fixer tool is allowed to return some metadata in addition to the fixed file
85 Each fixer tool is allowed to return some metadata in addition to the fixed file
86 content. The metadata must be placed before the file content on stdout,
86 content. The metadata must be placed before the file content on stdout,
87 separated from the file content by a zero byte. The metadata is parsed as a JSON
87 separated from the file content by a zero byte. The metadata is parsed as a JSON
88 value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer tool
88 value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer tool
89 is expected to produce this metadata encoding if and only if the :metadata
89 is expected to produce this metadata encoding if and only if the :metadata
90 suboption is true::
90 suboption is true::
91
91
92 [fix]
92 [fix]
93 tool:command = tool --prepend-json-metadata
93 tool:command = tool --prepend-json-metadata
94 tool:metadata = true
94 tool:metadata = true
95
95
96 The metadata values are passed to hooks, which can be used to print summaries or
96 The metadata values are passed to hooks, which can be used to print summaries or
97 perform other post-fixing work. The supported hooks are::
97 perform other post-fixing work. The supported hooks are::
98
98
99 "postfixfile"
99 "postfixfile"
100 Run once for each file in each revision where any fixer tools made changes
100 Run once for each file in each revision where any fixer tools made changes
101 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
101 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
102 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
102 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
103 tools that affected the file. Fixer tools that didn't affect the file have a
103 tools that affected the file. Fixer tools that didn't affect the file have a
104 valueof None. Only fixer tools that executed are present in the metadata.
104 valueof None. Only fixer tools that executed are present in the metadata.
105
105
106 "postfix"
106 "postfix"
107 Run once after all files and revisions have been handled. Provides
107 Run once after all files and revisions have been handled. Provides
108 "$HG_REPLACEMENTS" with information about what revisions were created and
108 "$HG_REPLACEMENTS" with information about what revisions were created and
109 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
109 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
110 files in the working copy were updated. Provides a list "$HG_METADATA"
110 files in the working copy were updated. Provides a list "$HG_METADATA"
111 mapping fixer tool names to lists of metadata values returned from
111 mapping fixer tool names to lists of metadata values returned from
112 executions that modified a file. This aggregates the same metadata
112 executions that modified a file. This aggregates the same metadata
113 previously passed to the "postfixfile" hook.
113 previously passed to the "postfixfile" hook.
114
114
115 Fixer tools are run the in repository's root directory. This allows them to read
115 Fixer tools are run the in repository's root directory. This allows them to read
116 configuration files from the working copy, or even write to the working copy.
116 configuration files from the working copy, or even write to the working copy.
117 The working copy is not updated to match the revision being fixed. In fact,
117 The working copy is not updated to match the revision being fixed. In fact,
118 several revisions may be fixed in parallel. Writes to the working copy are not
118 several revisions may be fixed in parallel. Writes to the working copy are not
119 amended into the revision being fixed; fixer tools should always write fixed
119 amended into the revision being fixed; fixer tools should always write fixed
120 file content back to stdout as documented above.
120 file content back to stdout as documented above.
121 """
121 """
122
122
123 from __future__ import absolute_import
123 from __future__ import absolute_import
124
124
125 import collections
125 import collections
126 import itertools
126 import itertools
127 import json
127 import json
128 import os
128 import os
129 import re
129 import re
130 import subprocess
130 import subprocess
131
131
132 from mercurial.i18n import _
132 from mercurial.i18n import _
133 from mercurial.node import nullrev
133 from mercurial.node import nullrev
134 from mercurial.node import wdirrev
134 from mercurial.node import wdirrev
135
135
136 from mercurial.utils import (
136 from mercurial.utils import (
137 procutil,
137 procutil,
138 stringutil,
138 stringutil,
139 )
139 )
140
140
141 from mercurial import (
141 from mercurial import (
142 cmdutil,
142 cmdutil,
143 context,
143 context,
144 copies,
144 copies,
145 error,
145 error,
146 mdiff,
146 mdiff,
147 merge,
147 merge,
148 obsolete,
148 obsolete,
149 pycompat,
149 pycompat,
150 registrar,
150 registrar,
151 scmutil,
151 scmutil,
152 util,
152 util,
153 worker,
153 worker,
154 )
154 )
155
155
156 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
156 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
157 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
157 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
158 # be specifying the version(s) of Mercurial they are tested with, or
158 # be specifying the version(s) of Mercurial they are tested with, or
159 # leave the attribute unspecified.
159 # leave the attribute unspecified.
160 testedwith = 'ships-with-hg-core'
160 testedwith = 'ships-with-hg-core'
161
161
162 cmdtable = {}
162 cmdtable = {}
163 command = registrar.command(cmdtable)
163 command = registrar.command(cmdtable)
164
164
165 configtable = {}
165 configtable = {}
166 configitem = registrar.configitem(configtable)
166 configitem = registrar.configitem(configtable)
167
167
168 # Register the suboptions allowed for each configured fixer, and default values.
168 # Register the suboptions allowed for each configured fixer, and default values.
169 FIXER_ATTRS = {
169 FIXER_ATTRS = {
170 'command': None,
170 'command': None,
171 'linerange': None,
171 'linerange': None,
172 'pattern': None,
172 'pattern': None,
173 'priority': 0,
173 'priority': 0,
174 'metadata': 'false',
174 'metadata': 'false',
175 'skipclean': 'true',
175 'skipclean': 'true',
176 'enabled': 'true',
176 }
177 }
177
178
178 for key, default in FIXER_ATTRS.items():
179 for key, default in FIXER_ATTRS.items():
179 configitem('fix', '.*(:%s)?' % key, default=default, generic=True)
180 configitem('fix', '.*(:%s)?' % key, default=default, generic=True)
180
181
181 # A good default size allows most source code files to be fixed, but avoids
182 # A good default size allows most source code files to be fixed, but avoids
182 # letting fixer tools choke on huge inputs, which could be surprising to the
183 # letting fixer tools choke on huge inputs, which could be surprising to the
183 # user.
184 # user.
184 configitem('fix', 'maxfilesize', default='2MB')
185 configitem('fix', 'maxfilesize', default='2MB')
185
186
186 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
187 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
187 # This helps users do shell scripts that stop when a fixer tool signals a
188 # This helps users do shell scripts that stop when a fixer tool signals a
188 # problem.
189 # problem.
189 configitem('fix', 'failure', default='continue')
190 configitem('fix', 'failure', default='continue')
190
191
191 def checktoolfailureaction(ui, message, hint=None):
192 def checktoolfailureaction(ui, message, hint=None):
192 """Abort with 'message' if fix.failure=abort"""
193 """Abort with 'message' if fix.failure=abort"""
193 action = ui.config('fix', 'failure')
194 action = ui.config('fix', 'failure')
194 if action not in ('continue', 'abort'):
195 if action not in ('continue', 'abort'):
195 raise error.Abort(_('unknown fix.failure action: %s') % (action,),
196 raise error.Abort(_('unknown fix.failure action: %s') % (action,),
196 hint=_('use "continue" or "abort"'))
197 hint=_('use "continue" or "abort"'))
197 if action == 'abort':
198 if action == 'abort':
198 raise error.Abort(message, hint=hint)
199 raise error.Abort(message, hint=hint)
199
200
200 allopt = ('', 'all', False, _('fix all non-public non-obsolete revisions'))
201 allopt = ('', 'all', False, _('fix all non-public non-obsolete revisions'))
201 baseopt = ('', 'base', [], _('revisions to diff against (overrides automatic '
202 baseopt = ('', 'base', [], _('revisions to diff against (overrides automatic '
202 'selection, and applies to every revision being '
203 'selection, and applies to every revision being '
203 'fixed)'), _('REV'))
204 'fixed)'), _('REV'))
204 revopt = ('r', 'rev', [], _('revisions to fix'), _('REV'))
205 revopt = ('r', 'rev', [], _('revisions to fix'), _('REV'))
205 wdiropt = ('w', 'working-dir', False, _('fix the working directory'))
206 wdiropt = ('w', 'working-dir', False, _('fix the working directory'))
206 wholeopt = ('', 'whole', False, _('always fix every line of a file'))
207 wholeopt = ('', 'whole', False, _('always fix every line of a file'))
207 usage = _('[OPTION]... [FILE]...')
208 usage = _('[OPTION]... [FILE]...')
208
209
209 @command('fix', [allopt, baseopt, revopt, wdiropt, wholeopt], usage,
210 @command('fix', [allopt, baseopt, revopt, wdiropt, wholeopt], usage,
210 helpcategory=command.CATEGORY_FILE_CONTENTS)
211 helpcategory=command.CATEGORY_FILE_CONTENTS)
211 def fix(ui, repo, *pats, **opts):
212 def fix(ui, repo, *pats, **opts):
212 """rewrite file content in changesets or working directory
213 """rewrite file content in changesets or working directory
213
214
214 Runs any configured tools to fix the content of files. Only affects files
215 Runs any configured tools to fix the content of files. Only affects files
215 with changes, unless file arguments are provided. Only affects changed lines
216 with changes, unless file arguments are provided. Only affects changed lines
216 of files, unless the --whole flag is used. Some tools may always affect the
217 of files, unless the --whole flag is used. Some tools may always affect the
217 whole file regardless of --whole.
218 whole file regardless of --whole.
218
219
219 If revisions are specified with --rev, those revisions will be checked, and
220 If revisions are specified with --rev, those revisions will be checked, and
220 they may be replaced with new revisions that have fixed file content. It is
221 they may be replaced with new revisions that have fixed file content. It is
221 desirable to specify all descendants of each specified revision, so that the
222 desirable to specify all descendants of each specified revision, so that the
222 fixes propagate to the descendants. If all descendants are fixed at the same
223 fixes propagate to the descendants. If all descendants are fixed at the same
223 time, no merging, rebasing, or evolution will be required.
224 time, no merging, rebasing, or evolution will be required.
224
225
225 If --working-dir is used, files with uncommitted changes in the working copy
226 If --working-dir is used, files with uncommitted changes in the working copy
226 will be fixed. If the checked-out revision is also fixed, the working
227 will be fixed. If the checked-out revision is also fixed, the working
227 directory will update to the replacement revision.
228 directory will update to the replacement revision.
228
229
229 When determining what lines of each file to fix at each revision, the whole
230 When determining what lines of each file to fix at each revision, the whole
230 set of revisions being fixed is considered, so that fixes to earlier
231 set of revisions being fixed is considered, so that fixes to earlier
231 revisions are not forgotten in later ones. The --base flag can be used to
232 revisions are not forgotten in later ones. The --base flag can be used to
232 override this default behavior, though it is not usually desirable to do so.
233 override this default behavior, though it is not usually desirable to do so.
233 """
234 """
234 opts = pycompat.byteskwargs(opts)
235 opts = pycompat.byteskwargs(opts)
235 if opts['all']:
236 if opts['all']:
236 if opts['rev']:
237 if opts['rev']:
237 raise error.Abort(_('cannot specify both "--rev" and "--all"'))
238 raise error.Abort(_('cannot specify both "--rev" and "--all"'))
238 opts['rev'] = ['not public() and not obsolete()']
239 opts['rev'] = ['not public() and not obsolete()']
239 opts['working_dir'] = True
240 opts['working_dir'] = True
240 with repo.wlock(), repo.lock(), repo.transaction('fix'):
241 with repo.wlock(), repo.lock(), repo.transaction('fix'):
241 revstofix = getrevstofix(ui, repo, opts)
242 revstofix = getrevstofix(ui, repo, opts)
242 basectxs = getbasectxs(repo, opts, revstofix)
243 basectxs = getbasectxs(repo, opts, revstofix)
243 workqueue, numitems = getworkqueue(ui, repo, pats, opts, revstofix,
244 workqueue, numitems = getworkqueue(ui, repo, pats, opts, revstofix,
244 basectxs)
245 basectxs)
245 fixers = getfixers(ui)
246 fixers = getfixers(ui)
246
247
247 # There are no data dependencies between the workers fixing each file
248 # There are no data dependencies between the workers fixing each file
248 # revision, so we can use all available parallelism.
249 # revision, so we can use all available parallelism.
249 def getfixes(items):
250 def getfixes(items):
250 for rev, path in items:
251 for rev, path in items:
251 ctx = repo[rev]
252 ctx = repo[rev]
252 olddata = ctx[path].data()
253 olddata = ctx[path].data()
253 metadata, newdata = fixfile(ui, repo, opts, fixers, ctx, path,
254 metadata, newdata = fixfile(ui, repo, opts, fixers, ctx, path,
254 basectxs[rev])
255 basectxs[rev])
255 # Don't waste memory/time passing unchanged content back, but
256 # Don't waste memory/time passing unchanged content back, but
256 # produce one result per item either way.
257 # produce one result per item either way.
257 yield (rev, path, metadata,
258 yield (rev, path, metadata,
258 newdata if newdata != olddata else None)
259 newdata if newdata != olddata else None)
259 results = worker.worker(ui, 1.0, getfixes, tuple(), workqueue,
260 results = worker.worker(ui, 1.0, getfixes, tuple(), workqueue,
260 threadsafe=False)
261 threadsafe=False)
261
262
262 # We have to hold on to the data for each successor revision in memory
263 # We have to hold on to the data for each successor revision in memory
263 # until all its parents are committed. We ensure this by committing and
264 # until all its parents are committed. We ensure this by committing and
264 # freeing memory for the revisions in some topological order. This
265 # freeing memory for the revisions in some topological order. This
265 # leaves a little bit of memory efficiency on the table, but also makes
266 # leaves a little bit of memory efficiency on the table, but also makes
266 # the tests deterministic. It might also be considered a feature since
267 # the tests deterministic. It might also be considered a feature since
267 # it makes the results more easily reproducible.
268 # it makes the results more easily reproducible.
268 filedata = collections.defaultdict(dict)
269 filedata = collections.defaultdict(dict)
269 aggregatemetadata = collections.defaultdict(list)
270 aggregatemetadata = collections.defaultdict(list)
270 replacements = {}
271 replacements = {}
271 wdirwritten = False
272 wdirwritten = False
272 commitorder = sorted(revstofix, reverse=True)
273 commitorder = sorted(revstofix, reverse=True)
273 with ui.makeprogress(topic=_('fixing'), unit=_('files'),
274 with ui.makeprogress(topic=_('fixing'), unit=_('files'),
274 total=sum(numitems.values())) as progress:
275 total=sum(numitems.values())) as progress:
275 for rev, path, filerevmetadata, newdata in results:
276 for rev, path, filerevmetadata, newdata in results:
276 progress.increment(item=path)
277 progress.increment(item=path)
277 for fixername, fixermetadata in filerevmetadata.items():
278 for fixername, fixermetadata in filerevmetadata.items():
278 aggregatemetadata[fixername].append(fixermetadata)
279 aggregatemetadata[fixername].append(fixermetadata)
279 if newdata is not None:
280 if newdata is not None:
280 filedata[rev][path] = newdata
281 filedata[rev][path] = newdata
281 hookargs = {
282 hookargs = {
282 'rev': rev,
283 'rev': rev,
283 'path': path,
284 'path': path,
284 'metadata': filerevmetadata,
285 'metadata': filerevmetadata,
285 }
286 }
286 repo.hook('postfixfile', throw=False,
287 repo.hook('postfixfile', throw=False,
287 **pycompat.strkwargs(hookargs))
288 **pycompat.strkwargs(hookargs))
288 numitems[rev] -= 1
289 numitems[rev] -= 1
289 # Apply the fixes for this and any other revisions that are
290 # Apply the fixes for this and any other revisions that are
290 # ready and sitting at the front of the queue. Using a loop here
291 # ready and sitting at the front of the queue. Using a loop here
291 # prevents the queue from being blocked by the first revision to
292 # prevents the queue from being blocked by the first revision to
292 # be ready out of order.
293 # be ready out of order.
293 while commitorder and not numitems[commitorder[-1]]:
294 while commitorder and not numitems[commitorder[-1]]:
294 rev = commitorder.pop()
295 rev = commitorder.pop()
295 ctx = repo[rev]
296 ctx = repo[rev]
296 if rev == wdirrev:
297 if rev == wdirrev:
297 writeworkingdir(repo, ctx, filedata[rev], replacements)
298 writeworkingdir(repo, ctx, filedata[rev], replacements)
298 wdirwritten = bool(filedata[rev])
299 wdirwritten = bool(filedata[rev])
299 else:
300 else:
300 replacerev(ui, repo, ctx, filedata[rev], replacements)
301 replacerev(ui, repo, ctx, filedata[rev], replacements)
301 del filedata[rev]
302 del filedata[rev]
302
303
303 cleanup(repo, replacements, wdirwritten)
304 cleanup(repo, replacements, wdirwritten)
304 hookargs = {
305 hookargs = {
305 'replacements': replacements,
306 'replacements': replacements,
306 'wdirwritten': wdirwritten,
307 'wdirwritten': wdirwritten,
307 'metadata': aggregatemetadata,
308 'metadata': aggregatemetadata,
308 }
309 }
309 repo.hook('postfix', throw=True, **pycompat.strkwargs(hookargs))
310 repo.hook('postfix', throw=True, **pycompat.strkwargs(hookargs))
310
311
311 def cleanup(repo, replacements, wdirwritten):
312 def cleanup(repo, replacements, wdirwritten):
312 """Calls scmutil.cleanupnodes() with the given replacements.
313 """Calls scmutil.cleanupnodes() with the given replacements.
313
314
314 "replacements" is a dict from nodeid to nodeid, with one key and one value
315 "replacements" is a dict from nodeid to nodeid, with one key and one value
315 for every revision that was affected by fixing. This is slightly different
316 for every revision that was affected by fixing. This is slightly different
316 from cleanupnodes().
317 from cleanupnodes().
317
318
318 "wdirwritten" is a bool which tells whether the working copy was affected by
319 "wdirwritten" is a bool which tells whether the working copy was affected by
319 fixing, since it has no entry in "replacements".
320 fixing, since it has no entry in "replacements".
320
321
321 Useful as a hook point for extending "hg fix" with output summarizing the
322 Useful as a hook point for extending "hg fix" with output summarizing the
322 effects of the command, though we choose not to output anything here.
323 effects of the command, though we choose not to output anything here.
323 """
324 """
324 replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
325 replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
325 scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
326 scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
326
327
327 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
328 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
328 """"Constructs the list of files to be fixed at specific revisions
329 """"Constructs the list of files to be fixed at specific revisions
329
330
330 It is up to the caller how to consume the work items, and the only
331 It is up to the caller how to consume the work items, and the only
331 dependence between them is that replacement revisions must be committed in
332 dependence between them is that replacement revisions must be committed in
332 topological order. Each work item represents a file in the working copy or
333 topological order. Each work item represents a file in the working copy or
333 in some revision that should be fixed and written back to the working copy
334 in some revision that should be fixed and written back to the working copy
334 or into a replacement revision.
335 or into a replacement revision.
335
336
336 Work items for the same revision are grouped together, so that a worker
337 Work items for the same revision are grouped together, so that a worker
337 pool starting with the first N items in parallel is likely to finish the
338 pool starting with the first N items in parallel is likely to finish the
338 first revision's work before other revisions. This can allow us to write
339 first revision's work before other revisions. This can allow us to write
339 the result to disk and reduce memory footprint. At time of writing, the
340 the result to disk and reduce memory footprint. At time of writing, the
340 partition strategy in worker.py seems favorable to this. We also sort the
341 partition strategy in worker.py seems favorable to this. We also sort the
341 items by ascending revision number to match the order in which we commit
342 items by ascending revision number to match the order in which we commit
342 the fixes later.
343 the fixes later.
343 """
344 """
344 workqueue = []
345 workqueue = []
345 numitems = collections.defaultdict(int)
346 numitems = collections.defaultdict(int)
346 maxfilesize = ui.configbytes('fix', 'maxfilesize')
347 maxfilesize = ui.configbytes('fix', 'maxfilesize')
347 for rev in sorted(revstofix):
348 for rev in sorted(revstofix):
348 fixctx = repo[rev]
349 fixctx = repo[rev]
349 match = scmutil.match(fixctx, pats, opts)
350 match = scmutil.match(fixctx, pats, opts)
350 for path in sorted(pathstofix(
351 for path in sorted(pathstofix(
351 ui, repo, pats, opts, match, basectxs[rev], fixctx)):
352 ui, repo, pats, opts, match, basectxs[rev], fixctx)):
352 fctx = fixctx[path]
353 fctx = fixctx[path]
353 if fctx.islink():
354 if fctx.islink():
354 continue
355 continue
355 if fctx.size() > maxfilesize:
356 if fctx.size() > maxfilesize:
356 ui.warn(_('ignoring file larger than %s: %s\n') %
357 ui.warn(_('ignoring file larger than %s: %s\n') %
357 (util.bytecount(maxfilesize), path))
358 (util.bytecount(maxfilesize), path))
358 continue
359 continue
359 workqueue.append((rev, path))
360 workqueue.append((rev, path))
360 numitems[rev] += 1
361 numitems[rev] += 1
361 return workqueue, numitems
362 return workqueue, numitems
362
363
363 def getrevstofix(ui, repo, opts):
364 def getrevstofix(ui, repo, opts):
364 """Returns the set of revision numbers that should be fixed"""
365 """Returns the set of revision numbers that should be fixed"""
365 revs = set(scmutil.revrange(repo, opts['rev']))
366 revs = set(scmutil.revrange(repo, opts['rev']))
366 for rev in revs:
367 for rev in revs:
367 checkfixablectx(ui, repo, repo[rev])
368 checkfixablectx(ui, repo, repo[rev])
368 if revs:
369 if revs:
369 cmdutil.checkunfinished(repo)
370 cmdutil.checkunfinished(repo)
370 checknodescendants(repo, revs)
371 checknodescendants(repo, revs)
371 if opts.get('working_dir'):
372 if opts.get('working_dir'):
372 revs.add(wdirrev)
373 revs.add(wdirrev)
373 if list(merge.mergestate.read(repo).unresolved()):
374 if list(merge.mergestate.read(repo).unresolved()):
374 raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
375 raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
375 if not revs:
376 if not revs:
376 raise error.Abort(
377 raise error.Abort(
377 'no changesets specified', hint='use --rev or --working-dir')
378 'no changesets specified', hint='use --rev or --working-dir')
378 return revs
379 return revs
379
380
380 def checknodescendants(repo, revs):
381 def checknodescendants(repo, revs):
381 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
382 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
382 repo.revs('(%ld::) - (%ld)', revs, revs)):
383 repo.revs('(%ld::) - (%ld)', revs, revs)):
383 raise error.Abort(_('can only fix a changeset together '
384 raise error.Abort(_('can only fix a changeset together '
384 'with all its descendants'))
385 'with all its descendants'))
385
386
386 def checkfixablectx(ui, repo, ctx):
387 def checkfixablectx(ui, repo, ctx):
387 """Aborts if the revision shouldn't be replaced with a fixed one."""
388 """Aborts if the revision shouldn't be replaced with a fixed one."""
388 if not ctx.mutable():
389 if not ctx.mutable():
389 raise error.Abort('can\'t fix immutable changeset %s' %
390 raise error.Abort('can\'t fix immutable changeset %s' %
390 (scmutil.formatchangeid(ctx),))
391 (scmutil.formatchangeid(ctx),))
391 if ctx.obsolete():
392 if ctx.obsolete():
392 # It would be better to actually check if the revision has a successor.
393 # It would be better to actually check if the revision has a successor.
393 allowdivergence = ui.configbool('experimental',
394 allowdivergence = ui.configbool('experimental',
394 'evolution.allowdivergence')
395 'evolution.allowdivergence')
395 if not allowdivergence:
396 if not allowdivergence:
396 raise error.Abort('fixing obsolete revision could cause divergence')
397 raise error.Abort('fixing obsolete revision could cause divergence')
397
398
398 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
399 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
399 """Returns the set of files that should be fixed in a context
400 """Returns the set of files that should be fixed in a context
400
401
401 The result depends on the base contexts; we include any file that has
402 The result depends on the base contexts; we include any file that has
402 changed relative to any of the base contexts. Base contexts should be
403 changed relative to any of the base contexts. Base contexts should be
403 ancestors of the context being fixed.
404 ancestors of the context being fixed.
404 """
405 """
405 files = set()
406 files = set()
406 for basectx in basectxs:
407 for basectx in basectxs:
407 stat = basectx.status(fixctx, match=match, listclean=bool(pats),
408 stat = basectx.status(fixctx, match=match, listclean=bool(pats),
408 listunknown=bool(pats))
409 listunknown=bool(pats))
409 files.update(
410 files.update(
410 set(itertools.chain(stat.added, stat.modified, stat.clean,
411 set(itertools.chain(stat.added, stat.modified, stat.clean,
411 stat.unknown)))
412 stat.unknown)))
412 return files
413 return files
413
414
414 def lineranges(opts, path, basectxs, fixctx, content2):
415 def lineranges(opts, path, basectxs, fixctx, content2):
415 """Returns the set of line ranges that should be fixed in a file
416 """Returns the set of line ranges that should be fixed in a file
416
417
417 Of the form [(10, 20), (30, 40)].
418 Of the form [(10, 20), (30, 40)].
418
419
419 This depends on the given base contexts; we must consider lines that have
420 This depends on the given base contexts; we must consider lines that have
420 changed versus any of the base contexts, and whether the file has been
421 changed versus any of the base contexts, and whether the file has been
421 renamed versus any of them.
422 renamed versus any of them.
422
423
423 Another way to understand this is that we exclude line ranges that are
424 Another way to understand this is that we exclude line ranges that are
424 common to the file in all base contexts.
425 common to the file in all base contexts.
425 """
426 """
426 if opts.get('whole'):
427 if opts.get('whole'):
427 # Return a range containing all lines. Rely on the diff implementation's
428 # Return a range containing all lines. Rely on the diff implementation's
428 # idea of how many lines are in the file, instead of reimplementing it.
429 # idea of how many lines are in the file, instead of reimplementing it.
429 return difflineranges('', content2)
430 return difflineranges('', content2)
430
431
431 rangeslist = []
432 rangeslist = []
432 for basectx in basectxs:
433 for basectx in basectxs:
433 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
434 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
434 if basepath in basectx:
435 if basepath in basectx:
435 content1 = basectx[basepath].data()
436 content1 = basectx[basepath].data()
436 else:
437 else:
437 content1 = ''
438 content1 = ''
438 rangeslist.extend(difflineranges(content1, content2))
439 rangeslist.extend(difflineranges(content1, content2))
439 return unionranges(rangeslist)
440 return unionranges(rangeslist)
440
441
441 def unionranges(rangeslist):
442 def unionranges(rangeslist):
442 """Return the union of some closed intervals
443 """Return the union of some closed intervals
443
444
444 >>> unionranges([])
445 >>> unionranges([])
445 []
446 []
446 >>> unionranges([(1, 100)])
447 >>> unionranges([(1, 100)])
447 [(1, 100)]
448 [(1, 100)]
448 >>> unionranges([(1, 100), (1, 100)])
449 >>> unionranges([(1, 100), (1, 100)])
449 [(1, 100)]
450 [(1, 100)]
450 >>> unionranges([(1, 100), (2, 100)])
451 >>> unionranges([(1, 100), (2, 100)])
451 [(1, 100)]
452 [(1, 100)]
452 >>> unionranges([(1, 99), (1, 100)])
453 >>> unionranges([(1, 99), (1, 100)])
453 [(1, 100)]
454 [(1, 100)]
454 >>> unionranges([(1, 100), (40, 60)])
455 >>> unionranges([(1, 100), (40, 60)])
455 [(1, 100)]
456 [(1, 100)]
456 >>> unionranges([(1, 49), (50, 100)])
457 >>> unionranges([(1, 49), (50, 100)])
457 [(1, 100)]
458 [(1, 100)]
458 >>> unionranges([(1, 48), (50, 100)])
459 >>> unionranges([(1, 48), (50, 100)])
459 [(1, 48), (50, 100)]
460 [(1, 48), (50, 100)]
460 >>> unionranges([(1, 2), (3, 4), (5, 6)])
461 >>> unionranges([(1, 2), (3, 4), (5, 6)])
461 [(1, 6)]
462 [(1, 6)]
462 """
463 """
463 rangeslist = sorted(set(rangeslist))
464 rangeslist = sorted(set(rangeslist))
464 unioned = []
465 unioned = []
465 if rangeslist:
466 if rangeslist:
466 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
467 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
467 for a, b in rangeslist:
468 for a, b in rangeslist:
468 c, d = unioned[-1]
469 c, d = unioned[-1]
469 if a > d + 1:
470 if a > d + 1:
470 unioned.append((a, b))
471 unioned.append((a, b))
471 else:
472 else:
472 unioned[-1] = (c, max(b, d))
473 unioned[-1] = (c, max(b, d))
473 return unioned
474 return unioned
474
475
475 def difflineranges(content1, content2):
476 def difflineranges(content1, content2):
476 """Return list of line number ranges in content2 that differ from content1.
477 """Return list of line number ranges in content2 that differ from content1.
477
478
478 Line numbers are 1-based. The numbers are the first and last line contained
479 Line numbers are 1-based. The numbers are the first and last line contained
479 in the range. Single-line ranges have the same line number for the first and
480 in the range. Single-line ranges have the same line number for the first and
480 last line. Excludes any empty ranges that result from lines that are only
481 last line. Excludes any empty ranges that result from lines that are only
481 present in content1. Relies on mdiff's idea of where the line endings are in
482 present in content1. Relies on mdiff's idea of where the line endings are in
482 the string.
483 the string.
483
484
484 >>> from mercurial import pycompat
485 >>> from mercurial import pycompat
485 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
486 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
486 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
487 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
487 >>> difflineranges2(b'', b'')
488 >>> difflineranges2(b'', b'')
488 []
489 []
489 >>> difflineranges2(b'a', b'')
490 >>> difflineranges2(b'a', b'')
490 []
491 []
491 >>> difflineranges2(b'', b'A')
492 >>> difflineranges2(b'', b'A')
492 [(1, 1)]
493 [(1, 1)]
493 >>> difflineranges2(b'a', b'a')
494 >>> difflineranges2(b'a', b'a')
494 []
495 []
495 >>> difflineranges2(b'a', b'A')
496 >>> difflineranges2(b'a', b'A')
496 [(1, 1)]
497 [(1, 1)]
497 >>> difflineranges2(b'ab', b'')
498 >>> difflineranges2(b'ab', b'')
498 []
499 []
499 >>> difflineranges2(b'', b'AB')
500 >>> difflineranges2(b'', b'AB')
500 [(1, 2)]
501 [(1, 2)]
501 >>> difflineranges2(b'abc', b'ac')
502 >>> difflineranges2(b'abc', b'ac')
502 []
503 []
503 >>> difflineranges2(b'ab', b'aCb')
504 >>> difflineranges2(b'ab', b'aCb')
504 [(2, 2)]
505 [(2, 2)]
505 >>> difflineranges2(b'abc', b'aBc')
506 >>> difflineranges2(b'abc', b'aBc')
506 [(2, 2)]
507 [(2, 2)]
507 >>> difflineranges2(b'ab', b'AB')
508 >>> difflineranges2(b'ab', b'AB')
508 [(1, 2)]
509 [(1, 2)]
509 >>> difflineranges2(b'abcde', b'aBcDe')
510 >>> difflineranges2(b'abcde', b'aBcDe')
510 [(2, 2), (4, 4)]
511 [(2, 2), (4, 4)]
511 >>> difflineranges2(b'abcde', b'aBCDe')
512 >>> difflineranges2(b'abcde', b'aBCDe')
512 [(2, 4)]
513 [(2, 4)]
513 """
514 """
514 ranges = []
515 ranges = []
515 for lines, kind in mdiff.allblocks(content1, content2):
516 for lines, kind in mdiff.allblocks(content1, content2):
516 firstline, lastline = lines[2:4]
517 firstline, lastline = lines[2:4]
517 if kind == '!' and firstline != lastline:
518 if kind == '!' and firstline != lastline:
518 ranges.append((firstline + 1, lastline))
519 ranges.append((firstline + 1, lastline))
519 return ranges
520 return ranges
520
521
521 def getbasectxs(repo, opts, revstofix):
522 def getbasectxs(repo, opts, revstofix):
522 """Returns a map of the base contexts for each revision
523 """Returns a map of the base contexts for each revision
523
524
524 The base contexts determine which lines are considered modified when we
525 The base contexts determine which lines are considered modified when we
525 attempt to fix just the modified lines in a file. It also determines which
526 attempt to fix just the modified lines in a file. It also determines which
526 files we attempt to fix, so it is important to compute this even when
527 files we attempt to fix, so it is important to compute this even when
527 --whole is used.
528 --whole is used.
528 """
529 """
529 # The --base flag overrides the usual logic, and we give every revision
530 # The --base flag overrides the usual logic, and we give every revision
530 # exactly the set of baserevs that the user specified.
531 # exactly the set of baserevs that the user specified.
531 if opts.get('base'):
532 if opts.get('base'):
532 baserevs = set(scmutil.revrange(repo, opts.get('base')))
533 baserevs = set(scmutil.revrange(repo, opts.get('base')))
533 if not baserevs:
534 if not baserevs:
534 baserevs = {nullrev}
535 baserevs = {nullrev}
535 basectxs = {repo[rev] for rev in baserevs}
536 basectxs = {repo[rev] for rev in baserevs}
536 return {rev: basectxs for rev in revstofix}
537 return {rev: basectxs for rev in revstofix}
537
538
538 # Proceed in topological order so that we can easily determine each
539 # Proceed in topological order so that we can easily determine each
539 # revision's baserevs by looking at its parents and their baserevs.
540 # revision's baserevs by looking at its parents and their baserevs.
540 basectxs = collections.defaultdict(set)
541 basectxs = collections.defaultdict(set)
541 for rev in sorted(revstofix):
542 for rev in sorted(revstofix):
542 ctx = repo[rev]
543 ctx = repo[rev]
543 for pctx in ctx.parents():
544 for pctx in ctx.parents():
544 if pctx.rev() in basectxs:
545 if pctx.rev() in basectxs:
545 basectxs[rev].update(basectxs[pctx.rev()])
546 basectxs[rev].update(basectxs[pctx.rev()])
546 else:
547 else:
547 basectxs[rev].add(pctx)
548 basectxs[rev].add(pctx)
548 return basectxs
549 return basectxs
549
550
550 def fixfile(ui, repo, opts, fixers, fixctx, path, basectxs):
551 def fixfile(ui, repo, opts, fixers, fixctx, path, basectxs):
551 """Run any configured fixers that should affect the file in this context
552 """Run any configured fixers that should affect the file in this context
552
553
553 Returns the file content that results from applying the fixers in some order
554 Returns the file content that results from applying the fixers in some order
554 starting with the file's content in the fixctx. Fixers that support line
555 starting with the file's content in the fixctx. Fixers that support line
555 ranges will affect lines that have changed relative to any of the basectxs
556 ranges will affect lines that have changed relative to any of the basectxs
556 (i.e. they will only avoid lines that are common to all basectxs).
557 (i.e. they will only avoid lines that are common to all basectxs).
557
558
558 A fixer tool's stdout will become the file's new content if and only if it
559 A fixer tool's stdout will become the file's new content if and only if it
559 exits with code zero. The fixer tool's working directory is the repository's
560 exits with code zero. The fixer tool's working directory is the repository's
560 root.
561 root.
561 """
562 """
562 metadata = {}
563 metadata = {}
563 newdata = fixctx[path].data()
564 newdata = fixctx[path].data()
564 for fixername, fixer in fixers.iteritems():
565 for fixername, fixer in fixers.iteritems():
565 if fixer.affects(opts, fixctx, path):
566 if fixer.affects(opts, fixctx, path):
566 ranges = lineranges(opts, path, basectxs, fixctx, newdata)
567 ranges = lineranges(opts, path, basectxs, fixctx, newdata)
567 command = fixer.command(ui, path, ranges)
568 command = fixer.command(ui, path, ranges)
568 if command is None:
569 if command is None:
569 continue
570 continue
570 ui.debug('subprocess: %s\n' % (command,))
571 ui.debug('subprocess: %s\n' % (command,))
571 proc = subprocess.Popen(
572 proc = subprocess.Popen(
572 procutil.tonativestr(command),
573 procutil.tonativestr(command),
573 shell=True,
574 shell=True,
574 cwd=repo.root,
575 cwd=repo.root,
575 stdin=subprocess.PIPE,
576 stdin=subprocess.PIPE,
576 stdout=subprocess.PIPE,
577 stdout=subprocess.PIPE,
577 stderr=subprocess.PIPE)
578 stderr=subprocess.PIPE)
578 stdout, stderr = proc.communicate(newdata)
579 stdout, stderr = proc.communicate(newdata)
579 if stderr:
580 if stderr:
580 showstderr(ui, fixctx.rev(), fixername, stderr)
581 showstderr(ui, fixctx.rev(), fixername, stderr)
581 newerdata = stdout
582 newerdata = stdout
582 if fixer.shouldoutputmetadata():
583 if fixer.shouldoutputmetadata():
583 try:
584 try:
584 metadatajson, newerdata = stdout.split('\0', 1)
585 metadatajson, newerdata = stdout.split('\0', 1)
585 metadata[fixername] = json.loads(metadatajson)
586 metadata[fixername] = json.loads(metadatajson)
586 except ValueError:
587 except ValueError:
587 ui.warn(_('ignored invalid output from fixer tool: %s\n') %
588 ui.warn(_('ignored invalid output from fixer tool: %s\n') %
588 (fixername,))
589 (fixername,))
589 continue
590 continue
590 else:
591 else:
591 metadata[fixername] = None
592 metadata[fixername] = None
592 if proc.returncode == 0:
593 if proc.returncode == 0:
593 newdata = newerdata
594 newdata = newerdata
594 else:
595 else:
595 if not stderr:
596 if not stderr:
596 message = _('exited with status %d\n') % (proc.returncode,)
597 message = _('exited with status %d\n') % (proc.returncode,)
597 showstderr(ui, fixctx.rev(), fixername, message)
598 showstderr(ui, fixctx.rev(), fixername, message)
598 checktoolfailureaction(
599 checktoolfailureaction(
599 ui, _('no fixes will be applied'),
600 ui, _('no fixes will be applied'),
600 hint=_('use --config fix.failure=continue to apply any '
601 hint=_('use --config fix.failure=continue to apply any '
601 'successful fixes anyway'))
602 'successful fixes anyway'))
602 return metadata, newdata
603 return metadata, newdata
603
604
604 def showstderr(ui, rev, fixername, stderr):
605 def showstderr(ui, rev, fixername, stderr):
605 """Writes the lines of the stderr string as warnings on the ui
606 """Writes the lines of the stderr string as warnings on the ui
606
607
607 Uses the revision number and fixername to give more context to each line of
608 Uses the revision number and fixername to give more context to each line of
608 the error message. Doesn't include file names, since those take up a lot of
609 the error message. Doesn't include file names, since those take up a lot of
609 space and would tend to be included in the error message if they were
610 space and would tend to be included in the error message if they were
610 relevant.
611 relevant.
611 """
612 """
612 for line in re.split('[\r\n]+', stderr):
613 for line in re.split('[\r\n]+', stderr):
613 if line:
614 if line:
614 ui.warn(('['))
615 ui.warn(('['))
615 if rev is None:
616 if rev is None:
616 ui.warn(_('wdir'), label='evolve.rev')
617 ui.warn(_('wdir'), label='evolve.rev')
617 else:
618 else:
618 ui.warn((str(rev)), label='evolve.rev')
619 ui.warn((str(rev)), label='evolve.rev')
619 ui.warn(('] %s: %s\n') % (fixername, line))
620 ui.warn(('] %s: %s\n') % (fixername, line))
620
621
621 def writeworkingdir(repo, ctx, filedata, replacements):
622 def writeworkingdir(repo, ctx, filedata, replacements):
622 """Write new content to the working copy and check out the new p1 if any
623 """Write new content to the working copy and check out the new p1 if any
623
624
624 We check out a new revision if and only if we fixed something in both the
625 We check out a new revision if and only if we fixed something in both the
625 working directory and its parent revision. This avoids the need for a full
626 working directory and its parent revision. This avoids the need for a full
626 update/merge, and means that the working directory simply isn't affected
627 update/merge, and means that the working directory simply isn't affected
627 unless the --working-dir flag is given.
628 unless the --working-dir flag is given.
628
629
629 Directly updates the dirstate for the affected files.
630 Directly updates the dirstate for the affected files.
630 """
631 """
631 for path, data in filedata.iteritems():
632 for path, data in filedata.iteritems():
632 fctx = ctx[path]
633 fctx = ctx[path]
633 fctx.write(data, fctx.flags())
634 fctx.write(data, fctx.flags())
634 if repo.dirstate[path] == 'n':
635 if repo.dirstate[path] == 'n':
635 repo.dirstate.normallookup(path)
636 repo.dirstate.normallookup(path)
636
637
637 oldparentnodes = repo.dirstate.parents()
638 oldparentnodes = repo.dirstate.parents()
638 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
639 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
639 if newparentnodes != oldparentnodes:
640 if newparentnodes != oldparentnodes:
640 repo.setparents(*newparentnodes)
641 repo.setparents(*newparentnodes)
641
642
642 def replacerev(ui, repo, ctx, filedata, replacements):
643 def replacerev(ui, repo, ctx, filedata, replacements):
643 """Commit a new revision like the given one, but with file content changes
644 """Commit a new revision like the given one, but with file content changes
644
645
645 "ctx" is the original revision to be replaced by a modified one.
646 "ctx" is the original revision to be replaced by a modified one.
646
647
647 "filedata" is a dict that maps paths to their new file content. All other
648 "filedata" is a dict that maps paths to their new file content. All other
648 paths will be recreated from the original revision without changes.
649 paths will be recreated from the original revision without changes.
649 "filedata" may contain paths that didn't exist in the original revision;
650 "filedata" may contain paths that didn't exist in the original revision;
650 they will be added.
651 they will be added.
651
652
652 "replacements" is a dict that maps a single node to a single node, and it is
653 "replacements" is a dict that maps a single node to a single node, and it is
653 updated to indicate the original revision is replaced by the newly created
654 updated to indicate the original revision is replaced by the newly created
654 one. No entry is added if the replacement's node already exists.
655 one. No entry is added if the replacement's node already exists.
655
656
656 The new revision has the same parents as the old one, unless those parents
657 The new revision has the same parents as the old one, unless those parents
657 have already been replaced, in which case those replacements are the parents
658 have already been replaced, in which case those replacements are the parents
658 of this new revision. Thus, if revisions are replaced in topological order,
659 of this new revision. Thus, if revisions are replaced in topological order,
659 there is no need to rebase them into the original topology later.
660 there is no need to rebase them into the original topology later.
660 """
661 """
661
662
662 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
663 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
663 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
664 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
664 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
665 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
665 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
666 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
666
667
667 # We don't want to create a revision that has no changes from the original,
668 # We don't want to create a revision that has no changes from the original,
668 # but we should if the original revision's parent has been replaced.
669 # but we should if the original revision's parent has been replaced.
669 # Otherwise, we would produce an orphan that needs no actual human
670 # Otherwise, we would produce an orphan that needs no actual human
670 # intervention to evolve. We can't rely on commit() to avoid creating the
671 # intervention to evolve. We can't rely on commit() to avoid creating the
671 # un-needed revision because the extra field added below produces a new hash
672 # un-needed revision because the extra field added below produces a new hash
672 # regardless of file content changes.
673 # regardless of file content changes.
673 if (not filedata and
674 if (not filedata and
674 p1ctx.node() not in replacements and
675 p1ctx.node() not in replacements and
675 p2ctx.node() not in replacements):
676 p2ctx.node() not in replacements):
676 return
677 return
677
678
678 def filectxfn(repo, memctx, path):
679 def filectxfn(repo, memctx, path):
679 if path not in ctx:
680 if path not in ctx:
680 return None
681 return None
681 fctx = ctx[path]
682 fctx = ctx[path]
682 copysource = fctx.copysource()
683 copysource = fctx.copysource()
683 return context.memfilectx(
684 return context.memfilectx(
684 repo,
685 repo,
685 memctx,
686 memctx,
686 path=fctx.path(),
687 path=fctx.path(),
687 data=filedata.get(path, fctx.data()),
688 data=filedata.get(path, fctx.data()),
688 islink=fctx.islink(),
689 islink=fctx.islink(),
689 isexec=fctx.isexec(),
690 isexec=fctx.isexec(),
690 copysource=copysource)
691 copysource=copysource)
691
692
692 extra = ctx.extra().copy()
693 extra = ctx.extra().copy()
693 extra['fix_source'] = ctx.hex()
694 extra['fix_source'] = ctx.hex()
694
695
695 memctx = context.memctx(
696 memctx = context.memctx(
696 repo,
697 repo,
697 parents=(newp1node, newp2node),
698 parents=(newp1node, newp2node),
698 text=ctx.description(),
699 text=ctx.description(),
699 files=set(ctx.files()) | set(filedata.keys()),
700 files=set(ctx.files()) | set(filedata.keys()),
700 filectxfn=filectxfn,
701 filectxfn=filectxfn,
701 user=ctx.user(),
702 user=ctx.user(),
702 date=ctx.date(),
703 date=ctx.date(),
703 extra=extra,
704 extra=extra,
704 branch=ctx.branch(),
705 branch=ctx.branch(),
705 editor=None)
706 editor=None)
706 sucnode = memctx.commit()
707 sucnode = memctx.commit()
707 prenode = ctx.node()
708 prenode = ctx.node()
708 if prenode == sucnode:
709 if prenode == sucnode:
709 ui.debug('node %s already existed\n' % (ctx.hex()))
710 ui.debug('node %s already existed\n' % (ctx.hex()))
710 else:
711 else:
711 replacements[ctx.node()] = sucnode
712 replacements[ctx.node()] = sucnode
712
713
713 def getfixers(ui):
714 def getfixers(ui):
714 """Returns a map of configured fixer tools indexed by their names
715 """Returns a map of configured fixer tools indexed by their names
715
716
716 Each value is a Fixer object with methods that implement the behavior of the
717 Each value is a Fixer object with methods that implement the behavior of the
717 fixer's config suboptions. Does not validate the config values.
718 fixer's config suboptions. Does not validate the config values.
718 """
719 """
719 fixers = {}
720 fixers = {}
720 for name in fixernames(ui):
721 for name in fixernames(ui):
721 fixers[name] = Fixer()
722 fixers[name] = Fixer()
722 attrs = ui.configsuboptions('fix', name)[1]
723 attrs = ui.configsuboptions('fix', name)[1]
723 for key, default in FIXER_ATTRS.items():
724 for key, default in FIXER_ATTRS.items():
724 setattr(fixers[name], pycompat.sysstr('_' + key),
725 setattr(fixers[name], pycompat.sysstr('_' + key),
725 attrs.get(key, default))
726 attrs.get(key, default))
726 fixers[name]._priority = int(fixers[name]._priority)
727 fixers[name]._priority = int(fixers[name]._priority)
727 fixers[name]._metadata = stringutil.parsebool(fixers[name]._metadata)
728 fixers[name]._metadata = stringutil.parsebool(fixers[name]._metadata)
728 fixers[name]._skipclean = stringutil.parsebool(fixers[name]._skipclean)
729 fixers[name]._skipclean = stringutil.parsebool(fixers[name]._skipclean)
730 fixers[name]._enabled = stringutil.parsebool(fixers[name]._enabled)
729 # Don't use a fixer if it has no pattern configured. It would be
731 # Don't use a fixer if it has no pattern configured. It would be
730 # dangerous to let it affect all files. It would be pointless to let it
732 # dangerous to let it affect all files. It would be pointless to let it
731 # affect no files. There is no reasonable subset of files to use as the
733 # affect no files. There is no reasonable subset of files to use as the
732 # default.
734 # default.
733 if fixers[name]._pattern is None:
735 if fixers[name]._pattern is None:
734 ui.warn(
736 ui.warn(
735 _('fixer tool has no pattern configuration: %s\n') % (name,))
737 _('fixer tool has no pattern configuration: %s\n') % (name,))
736 del fixers[name]
738 del fixers[name]
739 elif not fixers[name]._enabled:
740 ui.debug('ignoring disabled fixer tool: %s\n' % (name,))
741 del fixers[name]
737 return collections.OrderedDict(
742 return collections.OrderedDict(
738 sorted(fixers.items(), key=lambda item: item[1]._priority,
743 sorted(fixers.items(), key=lambda item: item[1]._priority,
739 reverse=True))
744 reverse=True))
740
745
741 def fixernames(ui):
746 def fixernames(ui):
742 """Returns the names of [fix] config options that have suboptions"""
747 """Returns the names of [fix] config options that have suboptions"""
743 names = set()
748 names = set()
744 for k, v in ui.configitems('fix'):
749 for k, v in ui.configitems('fix'):
745 if ':' in k:
750 if ':' in k:
746 names.add(k.split(':', 1)[0])
751 names.add(k.split(':', 1)[0])
747 return names
752 return names
748
753
749 class Fixer(object):
754 class Fixer(object):
750 """Wraps the raw config values for a fixer with methods"""
755 """Wraps the raw config values for a fixer with methods"""
751
756
752 def affects(self, opts, fixctx, path):
757 def affects(self, opts, fixctx, path):
753 """Should this fixer run on the file at the given path and context?"""
758 """Should this fixer run on the file at the given path and context?"""
754 return (self._pattern is not None and
759 return (self._pattern is not None and
755 scmutil.match(fixctx, [self._pattern], opts)(path))
760 scmutil.match(fixctx, [self._pattern], opts)(path))
756
761
757 def shouldoutputmetadata(self):
762 def shouldoutputmetadata(self):
758 """Should the stdout of this fixer start with JSON and a null byte?"""
763 """Should the stdout of this fixer start with JSON and a null byte?"""
759 return self._metadata
764 return self._metadata
760
765
761 def command(self, ui, path, ranges):
766 def command(self, ui, path, ranges):
762 """A shell command to use to invoke this fixer on the given file/lines
767 """A shell command to use to invoke this fixer on the given file/lines
763
768
764 May return None if there is no appropriate command to run for the given
769 May return None if there is no appropriate command to run for the given
765 parameters.
770 parameters.
766 """
771 """
767 expand = cmdutil.rendercommandtemplate
772 expand = cmdutil.rendercommandtemplate
768 parts = [expand(ui, self._command,
773 parts = [expand(ui, self._command,
769 {'rootpath': path, 'basename': os.path.basename(path)})]
774 {'rootpath': path, 'basename': os.path.basename(path)})]
770 if self._linerange:
775 if self._linerange:
771 if self._skipclean and not ranges:
776 if self._skipclean and not ranges:
772 # No line ranges to fix, so don't run the fixer.
777 # No line ranges to fix, so don't run the fixer.
773 return None
778 return None
774 for first, last in ranges:
779 for first, last in ranges:
775 parts.append(expand(ui, self._linerange,
780 parts.append(expand(ui, self._linerange,
776 {'first': first, 'last': last}))
781 {'first': first, 'last': last}))
777 return ' '.join(parts)
782 return ' '.join(parts)
@@ -1,1405 +1,1421 b''
1 A script that implements uppercasing of specific lines in a file. This
1 A script that implements uppercasing of specific lines in a file. This
2 approximates the behavior of code formatters well enough for our tests.
2 approximates the behavior of code formatters well enough for our tests.
3
3
4 $ UPPERCASEPY="$TESTTMP/uppercase.py"
4 $ UPPERCASEPY="$TESTTMP/uppercase.py"
5 $ cat > $UPPERCASEPY <<EOF
5 $ cat > $UPPERCASEPY <<EOF
6 > import sys
6 > import sys
7 > from mercurial.utils.procutil import setbinary
7 > from mercurial.utils.procutil import setbinary
8 > setbinary(sys.stdin)
8 > setbinary(sys.stdin)
9 > setbinary(sys.stdout)
9 > setbinary(sys.stdout)
10 > lines = set()
10 > lines = set()
11 > for arg in sys.argv[1:]:
11 > for arg in sys.argv[1:]:
12 > if arg == 'all':
12 > if arg == 'all':
13 > sys.stdout.write(sys.stdin.read().upper())
13 > sys.stdout.write(sys.stdin.read().upper())
14 > sys.exit(0)
14 > sys.exit(0)
15 > else:
15 > else:
16 > first, last = arg.split('-')
16 > first, last = arg.split('-')
17 > lines.update(range(int(first), int(last) + 1))
17 > lines.update(range(int(first), int(last) + 1))
18 > for i, line in enumerate(sys.stdin.readlines()):
18 > for i, line in enumerate(sys.stdin.readlines()):
19 > if i + 1 in lines:
19 > if i + 1 in lines:
20 > sys.stdout.write(line.upper())
20 > sys.stdout.write(line.upper())
21 > else:
21 > else:
22 > sys.stdout.write(line)
22 > sys.stdout.write(line)
23 > EOF
23 > EOF
24 $ TESTLINES="foo\nbar\nbaz\nqux\n"
24 $ TESTLINES="foo\nbar\nbaz\nqux\n"
25 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY
25 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY
26 foo
26 foo
27 bar
27 bar
28 baz
28 baz
29 qux
29 qux
30 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY all
30 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY all
31 FOO
31 FOO
32 BAR
32 BAR
33 BAZ
33 BAZ
34 QUX
34 QUX
35 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-1
35 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-1
36 FOO
36 FOO
37 bar
37 bar
38 baz
38 baz
39 qux
39 qux
40 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-2
40 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-2
41 FOO
41 FOO
42 BAR
42 BAR
43 baz
43 baz
44 qux
44 qux
45 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-3
45 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-3
46 foo
46 foo
47 BAR
47 BAR
48 BAZ
48 BAZ
49 qux
49 qux
50 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-2 4-4
50 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-2 4-4
51 foo
51 foo
52 BAR
52 BAR
53 baz
53 baz
54 QUX
54 QUX
55
55
56 Set up the config with two simple fixers: one that fixes specific line ranges,
56 Set up the config with two simple fixers: one that fixes specific line ranges,
57 and one that always fixes the whole file. They both "fix" files by converting
57 and one that always fixes the whole file. They both "fix" files by converting
58 letters to uppercase. They use different file extensions, so each test case can
58 letters to uppercase. They use different file extensions, so each test case can
59 choose which behavior to use by naming files.
59 choose which behavior to use by naming files.
60
60
61 $ cat >> $HGRCPATH <<EOF
61 $ cat >> $HGRCPATH <<EOF
62 > [extensions]
62 > [extensions]
63 > fix =
63 > fix =
64 > [experimental]
64 > [experimental]
65 > evolution.createmarkers=True
65 > evolution.createmarkers=True
66 > evolution.allowunstable=True
66 > evolution.allowunstable=True
67 > [fix]
67 > [fix]
68 > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY all
68 > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY all
69 > uppercase-whole-file:pattern=set:**.whole
69 > uppercase-whole-file:pattern=set:**.whole
70 > uppercase-changed-lines:command="$PYTHON" $UPPERCASEPY
70 > uppercase-changed-lines:command="$PYTHON" $UPPERCASEPY
71 > uppercase-changed-lines:linerange={first}-{last}
71 > uppercase-changed-lines:linerange={first}-{last}
72 > uppercase-changed-lines:pattern=set:**.changed
72 > uppercase-changed-lines:pattern=set:**.changed
73 > EOF
73 > EOF
74
74
75 Help text for fix.
75 Help text for fix.
76
76
77 $ hg help fix
77 $ hg help fix
78 hg fix [OPTION]... [FILE]...
78 hg fix [OPTION]... [FILE]...
79
79
80 rewrite file content in changesets or working directory
80 rewrite file content in changesets or working directory
81
81
82 Runs any configured tools to fix the content of files. Only affects files
82 Runs any configured tools to fix the content of files. Only affects files
83 with changes, unless file arguments are provided. Only affects changed
83 with changes, unless file arguments are provided. Only affects changed
84 lines of files, unless the --whole flag is used. Some tools may always
84 lines of files, unless the --whole flag is used. Some tools may always
85 affect the whole file regardless of --whole.
85 affect the whole file regardless of --whole.
86
86
87 If revisions are specified with --rev, those revisions will be checked,
87 If revisions are specified with --rev, those revisions will be checked,
88 and they may be replaced with new revisions that have fixed file content.
88 and they may be replaced with new revisions that have fixed file content.
89 It is desirable to specify all descendants of each specified revision, so
89 It is desirable to specify all descendants of each specified revision, so
90 that the fixes propagate to the descendants. If all descendants are fixed
90 that the fixes propagate to the descendants. If all descendants are fixed
91 at the same time, no merging, rebasing, or evolution will be required.
91 at the same time, no merging, rebasing, or evolution will be required.
92
92
93 If --working-dir is used, files with uncommitted changes in the working
93 If --working-dir is used, files with uncommitted changes in the working
94 copy will be fixed. If the checked-out revision is also fixed, the working
94 copy will be fixed. If the checked-out revision is also fixed, the working
95 directory will update to the replacement revision.
95 directory will update to the replacement revision.
96
96
97 When determining what lines of each file to fix at each revision, the
97 When determining what lines of each file to fix at each revision, the
98 whole set of revisions being fixed is considered, so that fixes to earlier
98 whole set of revisions being fixed is considered, so that fixes to earlier
99 revisions are not forgotten in later ones. The --base flag can be used to
99 revisions are not forgotten in later ones. The --base flag can be used to
100 override this default behavior, though it is not usually desirable to do
100 override this default behavior, though it is not usually desirable to do
101 so.
101 so.
102
102
103 (use 'hg help -e fix' to show help for the fix extension)
103 (use 'hg help -e fix' to show help for the fix extension)
104
104
105 options ([+] can be repeated):
105 options ([+] can be repeated):
106
106
107 --all fix all non-public non-obsolete revisions
107 --all fix all non-public non-obsolete revisions
108 --base REV [+] revisions to diff against (overrides automatic selection,
108 --base REV [+] revisions to diff against (overrides automatic selection,
109 and applies to every revision being fixed)
109 and applies to every revision being fixed)
110 -r --rev REV [+] revisions to fix
110 -r --rev REV [+] revisions to fix
111 -w --working-dir fix the working directory
111 -w --working-dir fix the working directory
112 --whole always fix every line of a file
112 --whole always fix every line of a file
113
113
114 (some details hidden, use --verbose to show complete help)
114 (some details hidden, use --verbose to show complete help)
115
115
116 $ hg help -e fix
116 $ hg help -e fix
117 fix extension - rewrite file content in changesets or working copy
117 fix extension - rewrite file content in changesets or working copy
118 (EXPERIMENTAL)
118 (EXPERIMENTAL)
119
119
120 Provides a command that runs configured tools on the contents of modified
120 Provides a command that runs configured tools on the contents of modified
121 files, writing back any fixes to the working copy or replacing changesets.
121 files, writing back any fixes to the working copy or replacing changesets.
122
122
123 Here is an example configuration that causes 'hg fix' to apply automatic
123 Here is an example configuration that causes 'hg fix' to apply automatic
124 formatting fixes to modified lines in C++ code:
124 formatting fixes to modified lines in C++ code:
125
125
126 [fix]
126 [fix]
127 clang-format:command=clang-format --assume-filename={rootpath}
127 clang-format:command=clang-format --assume-filename={rootpath}
128 clang-format:linerange=--lines={first}:{last}
128 clang-format:linerange=--lines={first}:{last}
129 clang-format:pattern=set:**.cpp or **.hpp
129 clang-format:pattern=set:**.cpp or **.hpp
130
130
131 The :command suboption forms the first part of the shell command that will be
131 The :command suboption forms the first part of the shell command that will be
132 used to fix a file. The content of the file is passed on standard input, and
132 used to fix a file. The content of the file is passed on standard input, and
133 the fixed file content is expected on standard output. Any output on standard
133 the fixed file content is expected on standard output. Any output on standard
134 error will be displayed as a warning. If the exit status is not zero, the file
134 error will be displayed as a warning. If the exit status is not zero, the file
135 will not be affected. A placeholder warning is displayed if there is a non-
135 will not be affected. A placeholder warning is displayed if there is a non-
136 zero exit status but no standard error output. Some values may be substituted
136 zero exit status but no standard error output. Some values may be substituted
137 into the command:
137 into the command:
138
138
139 {rootpath} The path of the file being fixed, relative to the repo root
139 {rootpath} The path of the file being fixed, relative to the repo root
140 {basename} The name of the file being fixed, without the directory path
140 {basename} The name of the file being fixed, without the directory path
141
141
142 If the :linerange suboption is set, the tool will only be run if there are
142 If the :linerange suboption is set, the tool will only be run if there are
143 changed lines in a file. The value of this suboption is appended to the shell
143 changed lines in a file. The value of this suboption is appended to the shell
144 command once for every range of changed lines in the file. Some values may be
144 command once for every range of changed lines in the file. Some values may be
145 substituted into the command:
145 substituted into the command:
146
146
147 {first} The 1-based line number of the first line in the modified range
147 {first} The 1-based line number of the first line in the modified range
148 {last} The 1-based line number of the last line in the modified range
148 {last} The 1-based line number of the last line in the modified range
149
149
150 Deleted sections of a file will be ignored by :linerange, because there is no
150 Deleted sections of a file will be ignored by :linerange, because there is no
151 corresponding line range in the version being fixed.
151 corresponding line range in the version being fixed.
152
152
153 By default, tools that set :linerange will only be executed if there is at
153 By default, tools that set :linerange will only be executed if there is at
154 least one changed line range. This is meant to prevent accidents like running
154 least one changed line range. This is meant to prevent accidents like running
155 a code formatter in such a way that it unexpectedly reformats the whole file.
155 a code formatter in such a way that it unexpectedly reformats the whole file.
156 If such a tool needs to operate on unchanged files, it should set the
156 If such a tool needs to operate on unchanged files, it should set the
157 :skipclean suboption to false.
157 :skipclean suboption to false.
158
158
159 The :pattern suboption determines which files will be passed through each
159 The :pattern suboption determines which files will be passed through each
160 configured tool. See 'hg help patterns' for possible values. If there are file
160 configured tool. See 'hg help patterns' for possible values. If there are file
161 arguments to 'hg fix', the intersection of these patterns is used.
161 arguments to 'hg fix', the intersection of these patterns is used.
162
162
163 There is also a configurable limit for the maximum size of file that will be
163 There is also a configurable limit for the maximum size of file that will be
164 processed by 'hg fix':
164 processed by 'hg fix':
165
165
166 [fix]
166 [fix]
167 maxfilesize = 2MB
167 maxfilesize = 2MB
168
168
169 Normally, execution of configured tools will continue after a failure
169 Normally, execution of configured tools will continue after a failure
170 (indicated by a non-zero exit status). It can also be configured to abort
170 (indicated by a non-zero exit status). It can also be configured to abort
171 after the first such failure, so that no files will be affected if any tool
171 after the first such failure, so that no files will be affected if any tool
172 fails. This abort will also cause 'hg fix' to exit with a non-zero status:
172 fails. This abort will also cause 'hg fix' to exit with a non-zero status:
173
173
174 [fix]
174 [fix]
175 failure = abort
175 failure = abort
176
176
177 When multiple tools are configured to affect a file, they execute in an order
177 When multiple tools are configured to affect a file, they execute in an order
178 defined by the :priority suboption. The priority suboption has a default value
178 defined by the :priority suboption. The priority suboption has a default value
179 of zero for each tool. Tools are executed in order of descending priority. The
179 of zero for each tool. Tools are executed in order of descending priority. The
180 execution order of tools with equal priority is unspecified. For example, you
180 execution order of tools with equal priority is unspecified. For example, you
181 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
181 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
182 in a text file by ensuring that 'sort' runs before 'head':
182 in a text file by ensuring that 'sort' runs before 'head':
183
183
184 [fix]
184 [fix]
185 sort:command = sort -n
185 sort:command = sort -n
186 head:command = head -n 10
186 head:command = head -n 10
187 sort:pattern = numbers.txt
187 sort:pattern = numbers.txt
188 head:pattern = numbers.txt
188 head:pattern = numbers.txt
189 sort:priority = 2
189 sort:priority = 2
190 head:priority = 1
190 head:priority = 1
191
191
192 To account for changes made by each tool, the line numbers used for
192 To account for changes made by each tool, the line numbers used for
193 incremental formatting are recomputed before executing the next tool. So, each
193 incremental formatting are recomputed before executing the next tool. So, each
194 tool may see different values for the arguments added by the :linerange
194 tool may see different values for the arguments added by the :linerange
195 suboption.
195 suboption.
196
196
197 Each fixer tool is allowed to return some metadata in addition to the fixed
197 Each fixer tool is allowed to return some metadata in addition to the fixed
198 file content. The metadata must be placed before the file content on stdout,
198 file content. The metadata must be placed before the file content on stdout,
199 separated from the file content by a zero byte. The metadata is parsed as a
199 separated from the file content by a zero byte. The metadata is parsed as a
200 JSON value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer
200 JSON value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer
201 tool is expected to produce this metadata encoding if and only if the
201 tool is expected to produce this metadata encoding if and only if the
202 :metadata suboption is true:
202 :metadata suboption is true:
203
203
204 [fix]
204 [fix]
205 tool:command = tool --prepend-json-metadata
205 tool:command = tool --prepend-json-metadata
206 tool:metadata = true
206 tool:metadata = true
207
207
208 The metadata values are passed to hooks, which can be used to print summaries
208 The metadata values are passed to hooks, which can be used to print summaries
209 or perform other post-fixing work. The supported hooks are:
209 or perform other post-fixing work. The supported hooks are:
210
210
211 "postfixfile"
211 "postfixfile"
212 Run once for each file in each revision where any fixer tools made changes
212 Run once for each file in each revision where any fixer tools made changes
213 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
213 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
214 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
214 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
215 tools that affected the file. Fixer tools that didn't affect the file have a
215 tools that affected the file. Fixer tools that didn't affect the file have a
216 valueof None. Only fixer tools that executed are present in the metadata.
216 valueof None. Only fixer tools that executed are present in the metadata.
217
217
218 "postfix"
218 "postfix"
219 Run once after all files and revisions have been handled. Provides
219 Run once after all files and revisions have been handled. Provides
220 "$HG_REPLACEMENTS" with information about what revisions were created and
220 "$HG_REPLACEMENTS" with information about what revisions were created and
221 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
221 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
222 files in the working copy were updated. Provides a list "$HG_METADATA"
222 files in the working copy were updated. Provides a list "$HG_METADATA"
223 mapping fixer tool names to lists of metadata values returned from
223 mapping fixer tool names to lists of metadata values returned from
224 executions that modified a file. This aggregates the same metadata
224 executions that modified a file. This aggregates the same metadata
225 previously passed to the "postfixfile" hook.
225 previously passed to the "postfixfile" hook.
226
226
227 Fixer tools are run the in repository's root directory. This allows them to
227 Fixer tools are run the in repository's root directory. This allows them to
228 read configuration files from the working copy, or even write to the working
228 read configuration files from the working copy, or even write to the working
229 copy. The working copy is not updated to match the revision being fixed. In
229 copy. The working copy is not updated to match the revision being fixed. In
230 fact, several revisions may be fixed in parallel. Writes to the working copy
230 fact, several revisions may be fixed in parallel. Writes to the working copy
231 are not amended into the revision being fixed; fixer tools should always write
231 are not amended into the revision being fixed; fixer tools should always write
232 fixed file content back to stdout as documented above.
232 fixed file content back to stdout as documented above.
233
233
234 list of commands:
234 list of commands:
235
235
236 fix rewrite file content in changesets or working directory
236 fix rewrite file content in changesets or working directory
237
237
238 (use 'hg help -v -e fix' to show built-in aliases and global options)
238 (use 'hg help -v -e fix' to show built-in aliases and global options)
239
239
240 There is no default behavior in the absence of --rev and --working-dir.
240 There is no default behavior in the absence of --rev and --working-dir.
241
241
242 $ hg init badusage
242 $ hg init badusage
243 $ cd badusage
243 $ cd badusage
244
244
245 $ hg fix
245 $ hg fix
246 abort: no changesets specified
246 abort: no changesets specified
247 (use --rev or --working-dir)
247 (use --rev or --working-dir)
248 [255]
248 [255]
249 $ hg fix --whole
249 $ hg fix --whole
250 abort: no changesets specified
250 abort: no changesets specified
251 (use --rev or --working-dir)
251 (use --rev or --working-dir)
252 [255]
252 [255]
253 $ hg fix --base 0
253 $ hg fix --base 0
254 abort: no changesets specified
254 abort: no changesets specified
255 (use --rev or --working-dir)
255 (use --rev or --working-dir)
256 [255]
256 [255]
257
257
258 Fixing a public revision isn't allowed. It should abort early enough that
258 Fixing a public revision isn't allowed. It should abort early enough that
259 nothing happens, even to the working directory.
259 nothing happens, even to the working directory.
260
260
261 $ printf "hello\n" > hello.whole
261 $ printf "hello\n" > hello.whole
262 $ hg commit -Aqm "hello"
262 $ hg commit -Aqm "hello"
263 $ hg phase -r 0 --public
263 $ hg phase -r 0 --public
264 $ hg fix -r 0
264 $ hg fix -r 0
265 abort: can't fix immutable changeset 0:6470986d2e7b
265 abort: can't fix immutable changeset 0:6470986d2e7b
266 [255]
266 [255]
267 $ hg fix -r 0 --working-dir
267 $ hg fix -r 0 --working-dir
268 abort: can't fix immutable changeset 0:6470986d2e7b
268 abort: can't fix immutable changeset 0:6470986d2e7b
269 [255]
269 [255]
270 $ hg cat -r tip hello.whole
270 $ hg cat -r tip hello.whole
271 hello
271 hello
272 $ cat hello.whole
272 $ cat hello.whole
273 hello
273 hello
274
274
275 $ cd ..
275 $ cd ..
276
276
277 Fixing a clean working directory should do nothing. Even the --whole flag
277 Fixing a clean working directory should do nothing. Even the --whole flag
278 shouldn't cause any clean files to be fixed. Specifying a clean file explicitly
278 shouldn't cause any clean files to be fixed. Specifying a clean file explicitly
279 should only fix it if the fixer always fixes the whole file. The combination of
279 should only fix it if the fixer always fixes the whole file. The combination of
280 an explicit filename and --whole should format the entire file regardless.
280 an explicit filename and --whole should format the entire file regardless.
281
281
282 $ hg init fixcleanwdir
282 $ hg init fixcleanwdir
283 $ cd fixcleanwdir
283 $ cd fixcleanwdir
284
284
285 $ printf "hello\n" > hello.changed
285 $ printf "hello\n" > hello.changed
286 $ printf "world\n" > hello.whole
286 $ printf "world\n" > hello.whole
287 $ hg commit -Aqm "foo"
287 $ hg commit -Aqm "foo"
288 $ hg fix --working-dir
288 $ hg fix --working-dir
289 $ hg diff
289 $ hg diff
290 $ hg fix --working-dir --whole
290 $ hg fix --working-dir --whole
291 $ hg diff
291 $ hg diff
292 $ hg fix --working-dir *
292 $ hg fix --working-dir *
293 $ cat *
293 $ cat *
294 hello
294 hello
295 WORLD
295 WORLD
296 $ hg revert --all --no-backup
296 $ hg revert --all --no-backup
297 reverting hello.whole
297 reverting hello.whole
298 $ hg fix --working-dir * --whole
298 $ hg fix --working-dir * --whole
299 $ cat *
299 $ cat *
300 HELLO
300 HELLO
301 WORLD
301 WORLD
302
302
303 The same ideas apply to fixing a revision, so we create a revision that doesn't
303 The same ideas apply to fixing a revision, so we create a revision that doesn't
304 modify either of the files in question and try fixing it. This also tests that
304 modify either of the files in question and try fixing it. This also tests that
305 we ignore a file that doesn't match any configured fixer.
305 we ignore a file that doesn't match any configured fixer.
306
306
307 $ hg revert --all --no-backup
307 $ hg revert --all --no-backup
308 reverting hello.changed
308 reverting hello.changed
309 reverting hello.whole
309 reverting hello.whole
310 $ printf "unimportant\n" > some.file
310 $ printf "unimportant\n" > some.file
311 $ hg commit -Aqm "some other file"
311 $ hg commit -Aqm "some other file"
312
312
313 $ hg fix -r .
313 $ hg fix -r .
314 $ hg cat -r tip *
314 $ hg cat -r tip *
315 hello
315 hello
316 world
316 world
317 unimportant
317 unimportant
318 $ hg fix -r . --whole
318 $ hg fix -r . --whole
319 $ hg cat -r tip *
319 $ hg cat -r tip *
320 hello
320 hello
321 world
321 world
322 unimportant
322 unimportant
323 $ hg fix -r . *
323 $ hg fix -r . *
324 $ hg cat -r tip *
324 $ hg cat -r tip *
325 hello
325 hello
326 WORLD
326 WORLD
327 unimportant
327 unimportant
328 $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true
328 $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true
329 2 new content-divergent changesets
329 2 new content-divergent changesets
330 $ hg cat -r tip *
330 $ hg cat -r tip *
331 HELLO
331 HELLO
332 WORLD
332 WORLD
333 unimportant
333 unimportant
334
334
335 $ cd ..
335 $ cd ..
336
336
337 Fixing the working directory should still work if there are no revisions.
337 Fixing the working directory should still work if there are no revisions.
338
338
339 $ hg init norevisions
339 $ hg init norevisions
340 $ cd norevisions
340 $ cd norevisions
341
341
342 $ printf "something\n" > something.whole
342 $ printf "something\n" > something.whole
343 $ hg add
343 $ hg add
344 adding something.whole
344 adding something.whole
345 $ hg fix --working-dir
345 $ hg fix --working-dir
346 $ cat something.whole
346 $ cat something.whole
347 SOMETHING
347 SOMETHING
348
348
349 $ cd ..
349 $ cd ..
350
350
351 Test the effect of fixing the working directory for each possible status, with
351 Test the effect of fixing the working directory for each possible status, with
352 and without providing explicit file arguments.
352 and without providing explicit file arguments.
353
353
354 $ hg init implicitlyfixstatus
354 $ hg init implicitlyfixstatus
355 $ cd implicitlyfixstatus
355 $ cd implicitlyfixstatus
356
356
357 $ printf "modified\n" > modified.whole
357 $ printf "modified\n" > modified.whole
358 $ printf "removed\n" > removed.whole
358 $ printf "removed\n" > removed.whole
359 $ printf "deleted\n" > deleted.whole
359 $ printf "deleted\n" > deleted.whole
360 $ printf "clean\n" > clean.whole
360 $ printf "clean\n" > clean.whole
361 $ printf "ignored.whole" > .hgignore
361 $ printf "ignored.whole" > .hgignore
362 $ hg commit -Aqm "stuff"
362 $ hg commit -Aqm "stuff"
363
363
364 $ printf "modified!!!\n" > modified.whole
364 $ printf "modified!!!\n" > modified.whole
365 $ printf "unknown\n" > unknown.whole
365 $ printf "unknown\n" > unknown.whole
366 $ printf "ignored\n" > ignored.whole
366 $ printf "ignored\n" > ignored.whole
367 $ printf "added\n" > added.whole
367 $ printf "added\n" > added.whole
368 $ hg add added.whole
368 $ hg add added.whole
369 $ hg remove removed.whole
369 $ hg remove removed.whole
370 $ rm deleted.whole
370 $ rm deleted.whole
371
371
372 $ hg status --all
372 $ hg status --all
373 M modified.whole
373 M modified.whole
374 A added.whole
374 A added.whole
375 R removed.whole
375 R removed.whole
376 ! deleted.whole
376 ! deleted.whole
377 ? unknown.whole
377 ? unknown.whole
378 I ignored.whole
378 I ignored.whole
379 C .hgignore
379 C .hgignore
380 C clean.whole
380 C clean.whole
381
381
382 $ hg fix --working-dir
382 $ hg fix --working-dir
383
383
384 $ hg status --all
384 $ hg status --all
385 M modified.whole
385 M modified.whole
386 A added.whole
386 A added.whole
387 R removed.whole
387 R removed.whole
388 ! deleted.whole
388 ! deleted.whole
389 ? unknown.whole
389 ? unknown.whole
390 I ignored.whole
390 I ignored.whole
391 C .hgignore
391 C .hgignore
392 C clean.whole
392 C clean.whole
393
393
394 $ cat *.whole
394 $ cat *.whole
395 ADDED
395 ADDED
396 clean
396 clean
397 ignored
397 ignored
398 MODIFIED!!!
398 MODIFIED!!!
399 unknown
399 unknown
400
400
401 $ printf "modified!!!\n" > modified.whole
401 $ printf "modified!!!\n" > modified.whole
402 $ printf "added\n" > added.whole
402 $ printf "added\n" > added.whole
403
403
404 Listing the files explicitly causes untracked files to also be fixed, but
404 Listing the files explicitly causes untracked files to also be fixed, but
405 ignored files are still unaffected.
405 ignored files are still unaffected.
406
406
407 $ hg fix --working-dir *.whole
407 $ hg fix --working-dir *.whole
408
408
409 $ hg status --all
409 $ hg status --all
410 M clean.whole
410 M clean.whole
411 M modified.whole
411 M modified.whole
412 A added.whole
412 A added.whole
413 R removed.whole
413 R removed.whole
414 ! deleted.whole
414 ! deleted.whole
415 ? unknown.whole
415 ? unknown.whole
416 I ignored.whole
416 I ignored.whole
417 C .hgignore
417 C .hgignore
418
418
419 $ cat *.whole
419 $ cat *.whole
420 ADDED
420 ADDED
421 CLEAN
421 CLEAN
422 ignored
422 ignored
423 MODIFIED!!!
423 MODIFIED!!!
424 UNKNOWN
424 UNKNOWN
425
425
426 $ cd ..
426 $ cd ..
427
427
428 Test that incremental fixing works on files with additions, deletions, and
428 Test that incremental fixing works on files with additions, deletions, and
429 changes in multiple line ranges. Note that deletions do not generally cause
429 changes in multiple line ranges. Note that deletions do not generally cause
430 neighboring lines to be fixed, so we don't return a line range for purely
430 neighboring lines to be fixed, so we don't return a line range for purely
431 deleted sections. In the future we should support a :deletion config that
431 deleted sections. In the future we should support a :deletion config that
432 allows fixers to know where deletions are located.
432 allows fixers to know where deletions are located.
433
433
434 $ hg init incrementalfixedlines
434 $ hg init incrementalfixedlines
435 $ cd incrementalfixedlines
435 $ cd incrementalfixedlines
436
436
437 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
437 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
438 $ hg commit -Aqm "foo"
438 $ hg commit -Aqm "foo"
439 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
439 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
440
440
441 $ hg --config "fix.fail:command=echo" \
441 $ hg --config "fix.fail:command=echo" \
442 > --config "fix.fail:linerange={first}:{last}" \
442 > --config "fix.fail:linerange={first}:{last}" \
443 > --config "fix.fail:pattern=foo.txt" \
443 > --config "fix.fail:pattern=foo.txt" \
444 > fix --working-dir
444 > fix --working-dir
445 $ cat foo.txt
445 $ cat foo.txt
446 1:1 4:6 8:8
446 1:1 4:6 8:8
447
447
448 $ cd ..
448 $ cd ..
449
449
450 Test that --whole fixes all lines regardless of the diffs present.
450 Test that --whole fixes all lines regardless of the diffs present.
451
451
452 $ hg init wholeignoresdiffs
452 $ hg init wholeignoresdiffs
453 $ cd wholeignoresdiffs
453 $ cd wholeignoresdiffs
454
454
455 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
455 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
456 $ hg commit -Aqm "foo"
456 $ hg commit -Aqm "foo"
457 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
457 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
458
458
459 $ hg fix --working-dir
459 $ hg fix --working-dir
460 $ cat foo.changed
460 $ cat foo.changed
461 ZZ
461 ZZ
462 a
462 a
463 c
463 c
464 DD
464 DD
465 EE
465 EE
466 FF
466 FF
467 f
467 f
468 GG
468 GG
469
469
470 $ hg fix --working-dir --whole
470 $ hg fix --working-dir --whole
471 $ cat foo.changed
471 $ cat foo.changed
472 ZZ
472 ZZ
473 A
473 A
474 C
474 C
475 DD
475 DD
476 EE
476 EE
477 FF
477 FF
478 F
478 F
479 GG
479 GG
480
480
481 $ cd ..
481 $ cd ..
482
482
483 We should do nothing with symlinks, and their targets should be unaffected. Any
483 We should do nothing with symlinks, and their targets should be unaffected. Any
484 other behavior would be more complicated to implement and harder to document.
484 other behavior would be more complicated to implement and harder to document.
485
485
486 #if symlink
486 #if symlink
487 $ hg init dontmesswithsymlinks
487 $ hg init dontmesswithsymlinks
488 $ cd dontmesswithsymlinks
488 $ cd dontmesswithsymlinks
489
489
490 $ printf "hello\n" > hello.whole
490 $ printf "hello\n" > hello.whole
491 $ ln -s hello.whole hellolink
491 $ ln -s hello.whole hellolink
492 $ hg add
492 $ hg add
493 adding hello.whole
493 adding hello.whole
494 adding hellolink
494 adding hellolink
495 $ hg fix --working-dir hellolink
495 $ hg fix --working-dir hellolink
496 $ hg status
496 $ hg status
497 A hello.whole
497 A hello.whole
498 A hellolink
498 A hellolink
499
499
500 $ cd ..
500 $ cd ..
501 #endif
501 #endif
502
502
503 We should allow fixers to run on binary files, even though this doesn't sound
503 We should allow fixers to run on binary files, even though this doesn't sound
504 like a common use case. There's not much benefit to disallowing it, and users
504 like a common use case. There's not much benefit to disallowing it, and users
505 can add "and not binary()" to their filesets if needed. The Mercurial
505 can add "and not binary()" to their filesets if needed. The Mercurial
506 philosophy is generally to not handle binary files specially anyway.
506 philosophy is generally to not handle binary files specially anyway.
507
507
508 $ hg init cantouchbinaryfiles
508 $ hg init cantouchbinaryfiles
509 $ cd cantouchbinaryfiles
509 $ cd cantouchbinaryfiles
510
510
511 $ printf "hello\0\n" > hello.whole
511 $ printf "hello\0\n" > hello.whole
512 $ hg add
512 $ hg add
513 adding hello.whole
513 adding hello.whole
514 $ hg fix --working-dir 'set:binary()'
514 $ hg fix --working-dir 'set:binary()'
515 $ cat hello.whole
515 $ cat hello.whole
516 HELLO\x00 (esc)
516 HELLO\x00 (esc)
517
517
518 $ cd ..
518 $ cd ..
519
519
520 We have a config for the maximum size of file we will attempt to fix. This can
520 We have a config for the maximum size of file we will attempt to fix. This can
521 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
521 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
522 could happen by accident without a well considered configuration. A more
522 could happen by accident without a well considered configuration. A more
523 precise configuration could use the size() fileset function if one global limit
523 precise configuration could use the size() fileset function if one global limit
524 is undesired.
524 is undesired.
525
525
526 $ hg init maxfilesize
526 $ hg init maxfilesize
527 $ cd maxfilesize
527 $ cd maxfilesize
528
528
529 $ printf "this file is huge\n" > hello.whole
529 $ printf "this file is huge\n" > hello.whole
530 $ hg add
530 $ hg add
531 adding hello.whole
531 adding hello.whole
532 $ hg --config fix.maxfilesize=10 fix --working-dir
532 $ hg --config fix.maxfilesize=10 fix --working-dir
533 ignoring file larger than 10 bytes: hello.whole
533 ignoring file larger than 10 bytes: hello.whole
534 $ cat hello.whole
534 $ cat hello.whole
535 this file is huge
535 this file is huge
536
536
537 $ cd ..
537 $ cd ..
538
538
539 If we specify a file to fix, other files should be left alone, even if they
539 If we specify a file to fix, other files should be left alone, even if they
540 have changes.
540 have changes.
541
541
542 $ hg init fixonlywhatitellyouto
542 $ hg init fixonlywhatitellyouto
543 $ cd fixonlywhatitellyouto
543 $ cd fixonlywhatitellyouto
544
544
545 $ printf "fix me!\n" > fixme.whole
545 $ printf "fix me!\n" > fixme.whole
546 $ printf "not me.\n" > notme.whole
546 $ printf "not me.\n" > notme.whole
547 $ hg add
547 $ hg add
548 adding fixme.whole
548 adding fixme.whole
549 adding notme.whole
549 adding notme.whole
550 $ hg fix --working-dir fixme.whole
550 $ hg fix --working-dir fixme.whole
551 $ cat *.whole
551 $ cat *.whole
552 FIX ME!
552 FIX ME!
553 not me.
553 not me.
554
554
555 $ cd ..
555 $ cd ..
556
556
557 If we try to fix a missing file, we still fix other files.
557 If we try to fix a missing file, we still fix other files.
558
558
559 $ hg init fixmissingfile
559 $ hg init fixmissingfile
560 $ cd fixmissingfile
560 $ cd fixmissingfile
561
561
562 $ printf "fix me!\n" > foo.whole
562 $ printf "fix me!\n" > foo.whole
563 $ hg add
563 $ hg add
564 adding foo.whole
564 adding foo.whole
565 $ hg fix --working-dir foo.whole bar.whole
565 $ hg fix --working-dir foo.whole bar.whole
566 bar.whole: $ENOENT$
566 bar.whole: $ENOENT$
567 $ cat *.whole
567 $ cat *.whole
568 FIX ME!
568 FIX ME!
569
569
570 $ cd ..
570 $ cd ..
571
571
572 Specifying a directory name should fix all its files and subdirectories.
572 Specifying a directory name should fix all its files and subdirectories.
573
573
574 $ hg init fixdirectory
574 $ hg init fixdirectory
575 $ cd fixdirectory
575 $ cd fixdirectory
576
576
577 $ mkdir -p dir1/dir2
577 $ mkdir -p dir1/dir2
578 $ printf "foo\n" > foo.whole
578 $ printf "foo\n" > foo.whole
579 $ printf "bar\n" > dir1/bar.whole
579 $ printf "bar\n" > dir1/bar.whole
580 $ printf "baz\n" > dir1/dir2/baz.whole
580 $ printf "baz\n" > dir1/dir2/baz.whole
581 $ hg add
581 $ hg add
582 adding dir1/bar.whole
582 adding dir1/bar.whole
583 adding dir1/dir2/baz.whole
583 adding dir1/dir2/baz.whole
584 adding foo.whole
584 adding foo.whole
585 $ hg fix --working-dir dir1
585 $ hg fix --working-dir dir1
586 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
586 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
587 foo
587 foo
588 BAR
588 BAR
589 BAZ
589 BAZ
590
590
591 $ cd ..
591 $ cd ..
592
592
593 Fixing a file in the working directory that needs no fixes should not actually
593 Fixing a file in the working directory that needs no fixes should not actually
594 write back to the file, so for example the mtime shouldn't change.
594 write back to the file, so for example the mtime shouldn't change.
595
595
596 $ hg init donttouchunfixedfiles
596 $ hg init donttouchunfixedfiles
597 $ cd donttouchunfixedfiles
597 $ cd donttouchunfixedfiles
598
598
599 $ printf "NO FIX NEEDED\n" > foo.whole
599 $ printf "NO FIX NEEDED\n" > foo.whole
600 $ hg add
600 $ hg add
601 adding foo.whole
601 adding foo.whole
602 $ cp -p foo.whole foo.whole.orig
602 $ cp -p foo.whole foo.whole.orig
603 $ cp -p foo.whole.orig foo.whole
603 $ cp -p foo.whole.orig foo.whole
604 $ sleep 2 # mtime has a resolution of one or two seconds.
604 $ sleep 2 # mtime has a resolution of one or two seconds.
605 $ hg fix --working-dir
605 $ hg fix --working-dir
606 $ f foo.whole.orig --newer foo.whole
606 $ f foo.whole.orig --newer foo.whole
607 foo.whole.orig: newer than foo.whole
607 foo.whole.orig: newer than foo.whole
608
608
609 $ cd ..
609 $ cd ..
610
610
611 When a fixer prints to stderr, we don't assume that it has failed. We show the
611 When a fixer prints to stderr, we don't assume that it has failed. We show the
612 error messages to the user, and we still let the fixer affect the file it was
612 error messages to the user, and we still let the fixer affect the file it was
613 fixing if its exit code is zero. Some code formatters might emit error messages
613 fixing if its exit code is zero. Some code formatters might emit error messages
614 on stderr and nothing on stdout, which would cause us the clear the file,
614 on stderr and nothing on stdout, which would cause us the clear the file,
615 except that they also exit with a non-zero code. We show the user which fixer
615 except that they also exit with a non-zero code. We show the user which fixer
616 emitted the stderr, and which revision, but we assume that the fixer will print
616 emitted the stderr, and which revision, but we assume that the fixer will print
617 the filename if it is relevant (since the issue may be non-specific). There is
617 the filename if it is relevant (since the issue may be non-specific). There is
618 also a config to abort (without affecting any files whatsoever) if we see any
618 also a config to abort (without affecting any files whatsoever) if we see any
619 tool with a non-zero exit status.
619 tool with a non-zero exit status.
620
620
621 $ hg init showstderr
621 $ hg init showstderr
622 $ cd showstderr
622 $ cd showstderr
623
623
624 $ printf "hello\n" > hello.txt
624 $ printf "hello\n" > hello.txt
625 $ hg add
625 $ hg add
626 adding hello.txt
626 adding hello.txt
627 $ cat > $TESTTMP/work.sh <<'EOF'
627 $ cat > $TESTTMP/work.sh <<'EOF'
628 > printf 'HELLO\n'
628 > printf 'HELLO\n'
629 > printf "$@: some\nerror that didn't stop the tool" >&2
629 > printf "$@: some\nerror that didn't stop the tool" >&2
630 > exit 0 # success despite the stderr output
630 > exit 0 # success despite the stderr output
631 > EOF
631 > EOF
632 $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \
632 $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \
633 > --config "fix.work:pattern=hello.txt" \
633 > --config "fix.work:pattern=hello.txt" \
634 > fix --working-dir
634 > fix --working-dir
635 [wdir] work: hello.txt: some
635 [wdir] work: hello.txt: some
636 [wdir] work: error that didn't stop the tool
636 [wdir] work: error that didn't stop the tool
637 $ cat hello.txt
637 $ cat hello.txt
638 HELLO
638 HELLO
639
639
640 $ printf "goodbye\n" > hello.txt
640 $ printf "goodbye\n" > hello.txt
641 $ printf "foo\n" > foo.whole
641 $ printf "foo\n" > foo.whole
642 $ hg add
642 $ hg add
643 adding foo.whole
643 adding foo.whole
644 $ cat > $TESTTMP/fail.sh <<'EOF'
644 $ cat > $TESTTMP/fail.sh <<'EOF'
645 > printf 'GOODBYE\n'
645 > printf 'GOODBYE\n'
646 > printf "$@: some\nerror that did stop the tool\n" >&2
646 > printf "$@: some\nerror that did stop the tool\n" >&2
647 > exit 42 # success despite the stdout output
647 > exit 42 # success despite the stdout output
648 > EOF
648 > EOF
649 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
649 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
650 > --config "fix.fail:pattern=hello.txt" \
650 > --config "fix.fail:pattern=hello.txt" \
651 > --config "fix.failure=abort" \
651 > --config "fix.failure=abort" \
652 > fix --working-dir
652 > fix --working-dir
653 [wdir] fail: hello.txt: some
653 [wdir] fail: hello.txt: some
654 [wdir] fail: error that did stop the tool
654 [wdir] fail: error that did stop the tool
655 abort: no fixes will be applied
655 abort: no fixes will be applied
656 (use --config fix.failure=continue to apply any successful fixes anyway)
656 (use --config fix.failure=continue to apply any successful fixes anyway)
657 [255]
657 [255]
658 $ cat hello.txt
658 $ cat hello.txt
659 goodbye
659 goodbye
660 $ cat foo.whole
660 $ cat foo.whole
661 foo
661 foo
662
662
663 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
663 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
664 > --config "fix.fail:pattern=hello.txt" \
664 > --config "fix.fail:pattern=hello.txt" \
665 > fix --working-dir
665 > fix --working-dir
666 [wdir] fail: hello.txt: some
666 [wdir] fail: hello.txt: some
667 [wdir] fail: error that did stop the tool
667 [wdir] fail: error that did stop the tool
668 $ cat hello.txt
668 $ cat hello.txt
669 goodbye
669 goodbye
670 $ cat foo.whole
670 $ cat foo.whole
671 FOO
671 FOO
672
672
673 $ hg --config "fix.fail:command=exit 42" \
673 $ hg --config "fix.fail:command=exit 42" \
674 > --config "fix.fail:pattern=hello.txt" \
674 > --config "fix.fail:pattern=hello.txt" \
675 > fix --working-dir
675 > fix --working-dir
676 [wdir] fail: exited with status 42
676 [wdir] fail: exited with status 42
677
677
678 $ cd ..
678 $ cd ..
679
679
680 Fixing the working directory and its parent revision at the same time should
680 Fixing the working directory and its parent revision at the same time should
681 check out the replacement revision for the parent. This prevents any new
681 check out the replacement revision for the parent. This prevents any new
682 uncommitted changes from appearing. We test this for a clean working directory
682 uncommitted changes from appearing. We test this for a clean working directory
683 and a dirty one. In both cases, all lines/files changed since the grandparent
683 and a dirty one. In both cases, all lines/files changed since the grandparent
684 will be fixed. The grandparent is the "baserev" for both the parent and the
684 will be fixed. The grandparent is the "baserev" for both the parent and the
685 working copy.
685 working copy.
686
686
687 $ hg init fixdotandcleanwdir
687 $ hg init fixdotandcleanwdir
688 $ cd fixdotandcleanwdir
688 $ cd fixdotandcleanwdir
689
689
690 $ printf "hello\n" > hello.whole
690 $ printf "hello\n" > hello.whole
691 $ printf "world\n" > world.whole
691 $ printf "world\n" > world.whole
692 $ hg commit -Aqm "the parent commit"
692 $ hg commit -Aqm "the parent commit"
693
693
694 $ hg parents --template '{rev} {desc}\n'
694 $ hg parents --template '{rev} {desc}\n'
695 0 the parent commit
695 0 the parent commit
696 $ hg fix --working-dir -r .
696 $ hg fix --working-dir -r .
697 $ hg parents --template '{rev} {desc}\n'
697 $ hg parents --template '{rev} {desc}\n'
698 1 the parent commit
698 1 the parent commit
699 $ hg cat -r . *.whole
699 $ hg cat -r . *.whole
700 HELLO
700 HELLO
701 WORLD
701 WORLD
702 $ cat *.whole
702 $ cat *.whole
703 HELLO
703 HELLO
704 WORLD
704 WORLD
705 $ hg status
705 $ hg status
706
706
707 $ cd ..
707 $ cd ..
708
708
709 Same test with a dirty working copy.
709 Same test with a dirty working copy.
710
710
711 $ hg init fixdotanddirtywdir
711 $ hg init fixdotanddirtywdir
712 $ cd fixdotanddirtywdir
712 $ cd fixdotanddirtywdir
713
713
714 $ printf "hello\n" > hello.whole
714 $ printf "hello\n" > hello.whole
715 $ printf "world\n" > world.whole
715 $ printf "world\n" > world.whole
716 $ hg commit -Aqm "the parent commit"
716 $ hg commit -Aqm "the parent commit"
717
717
718 $ printf "hello,\n" > hello.whole
718 $ printf "hello,\n" > hello.whole
719 $ printf "world!\n" > world.whole
719 $ printf "world!\n" > world.whole
720
720
721 $ hg parents --template '{rev} {desc}\n'
721 $ hg parents --template '{rev} {desc}\n'
722 0 the parent commit
722 0 the parent commit
723 $ hg fix --working-dir -r .
723 $ hg fix --working-dir -r .
724 $ hg parents --template '{rev} {desc}\n'
724 $ hg parents --template '{rev} {desc}\n'
725 1 the parent commit
725 1 the parent commit
726 $ hg cat -r . *.whole
726 $ hg cat -r . *.whole
727 HELLO
727 HELLO
728 WORLD
728 WORLD
729 $ cat *.whole
729 $ cat *.whole
730 HELLO,
730 HELLO,
731 WORLD!
731 WORLD!
732 $ hg status
732 $ hg status
733 M hello.whole
733 M hello.whole
734 M world.whole
734 M world.whole
735
735
736 $ cd ..
736 $ cd ..
737
737
738 When we have a chain of commits that change mutually exclusive lines of code,
738 When we have a chain of commits that change mutually exclusive lines of code,
739 we should be able to do incremental fixing that causes each commit in the chain
739 we should be able to do incremental fixing that causes each commit in the chain
740 to include fixes made to the previous commits. This prevents children from
740 to include fixes made to the previous commits. This prevents children from
741 backing out the fixes made in their parents. A dirty working directory is
741 backing out the fixes made in their parents. A dirty working directory is
742 conceptually similar to another commit in the chain.
742 conceptually similar to another commit in the chain.
743
743
744 $ hg init incrementallyfixchain
744 $ hg init incrementallyfixchain
745 $ cd incrementallyfixchain
745 $ cd incrementallyfixchain
746
746
747 $ cat > file.changed <<EOF
747 $ cat > file.changed <<EOF
748 > first
748 > first
749 > second
749 > second
750 > third
750 > third
751 > fourth
751 > fourth
752 > fifth
752 > fifth
753 > EOF
753 > EOF
754 $ hg commit -Aqm "the common ancestor (the baserev)"
754 $ hg commit -Aqm "the common ancestor (the baserev)"
755 $ cat > file.changed <<EOF
755 $ cat > file.changed <<EOF
756 > first (changed)
756 > first (changed)
757 > second
757 > second
758 > third
758 > third
759 > fourth
759 > fourth
760 > fifth
760 > fifth
761 > EOF
761 > EOF
762 $ hg commit -Aqm "the first commit to fix"
762 $ hg commit -Aqm "the first commit to fix"
763 $ cat > file.changed <<EOF
763 $ cat > file.changed <<EOF
764 > first (changed)
764 > first (changed)
765 > second
765 > second
766 > third (changed)
766 > third (changed)
767 > fourth
767 > fourth
768 > fifth
768 > fifth
769 > EOF
769 > EOF
770 $ hg commit -Aqm "the second commit to fix"
770 $ hg commit -Aqm "the second commit to fix"
771 $ cat > file.changed <<EOF
771 $ cat > file.changed <<EOF
772 > first (changed)
772 > first (changed)
773 > second
773 > second
774 > third (changed)
774 > third (changed)
775 > fourth
775 > fourth
776 > fifth (changed)
776 > fifth (changed)
777 > EOF
777 > EOF
778
778
779 $ hg fix -r . -r '.^' --working-dir
779 $ hg fix -r . -r '.^' --working-dir
780
780
781 $ hg parents --template '{rev}\n'
781 $ hg parents --template '{rev}\n'
782 4
782 4
783 $ hg cat -r '.^^' file.changed
783 $ hg cat -r '.^^' file.changed
784 first
784 first
785 second
785 second
786 third
786 third
787 fourth
787 fourth
788 fifth
788 fifth
789 $ hg cat -r '.^' file.changed
789 $ hg cat -r '.^' file.changed
790 FIRST (CHANGED)
790 FIRST (CHANGED)
791 second
791 second
792 third
792 third
793 fourth
793 fourth
794 fifth
794 fifth
795 $ hg cat -r . file.changed
795 $ hg cat -r . file.changed
796 FIRST (CHANGED)
796 FIRST (CHANGED)
797 second
797 second
798 THIRD (CHANGED)
798 THIRD (CHANGED)
799 fourth
799 fourth
800 fifth
800 fifth
801 $ cat file.changed
801 $ cat file.changed
802 FIRST (CHANGED)
802 FIRST (CHANGED)
803 second
803 second
804 THIRD (CHANGED)
804 THIRD (CHANGED)
805 fourth
805 fourth
806 FIFTH (CHANGED)
806 FIFTH (CHANGED)
807
807
808 $ cd ..
808 $ cd ..
809
809
810 If we incrementally fix a merge commit, we should fix any lines that changed
810 If we incrementally fix a merge commit, we should fix any lines that changed
811 versus either parent. You could imagine only fixing the intersection or some
811 versus either parent. You could imagine only fixing the intersection or some
812 other subset, but this is necessary if either parent is being fixed. It
812 other subset, but this is necessary if either parent is being fixed. It
813 prevents us from forgetting fixes made in either parent.
813 prevents us from forgetting fixes made in either parent.
814
814
815 $ hg init incrementallyfixmergecommit
815 $ hg init incrementallyfixmergecommit
816 $ cd incrementallyfixmergecommit
816 $ cd incrementallyfixmergecommit
817
817
818 $ printf "a\nb\nc\n" > file.changed
818 $ printf "a\nb\nc\n" > file.changed
819 $ hg commit -Aqm "ancestor"
819 $ hg commit -Aqm "ancestor"
820
820
821 $ printf "aa\nb\nc\n" > file.changed
821 $ printf "aa\nb\nc\n" > file.changed
822 $ hg commit -m "change a"
822 $ hg commit -m "change a"
823
823
824 $ hg checkout '.^'
824 $ hg checkout '.^'
825 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
825 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
826 $ printf "a\nb\ncc\n" > file.changed
826 $ printf "a\nb\ncc\n" > file.changed
827 $ hg commit -m "change c"
827 $ hg commit -m "change c"
828 created new head
828 created new head
829
829
830 $ hg merge
830 $ hg merge
831 merging file.changed
831 merging file.changed
832 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
832 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
833 (branch merge, don't forget to commit)
833 (branch merge, don't forget to commit)
834 $ hg commit -m "merge"
834 $ hg commit -m "merge"
835 $ hg cat -r . file.changed
835 $ hg cat -r . file.changed
836 aa
836 aa
837 b
837 b
838 cc
838 cc
839
839
840 $ hg fix -r . --working-dir
840 $ hg fix -r . --working-dir
841 $ hg cat -r . file.changed
841 $ hg cat -r . file.changed
842 AA
842 AA
843 b
843 b
844 CC
844 CC
845
845
846 $ cd ..
846 $ cd ..
847
847
848 Abort fixing revisions if there is an unfinished operation. We don't want to
848 Abort fixing revisions if there is an unfinished operation. We don't want to
849 make things worse by editing files or stripping/obsoleting things. Also abort
849 make things worse by editing files or stripping/obsoleting things. Also abort
850 fixing the working directory if there are unresolved merge conflicts.
850 fixing the working directory if there are unresolved merge conflicts.
851
851
852 $ hg init abortunresolved
852 $ hg init abortunresolved
853 $ cd abortunresolved
853 $ cd abortunresolved
854
854
855 $ echo "foo1" > foo.whole
855 $ echo "foo1" > foo.whole
856 $ hg commit -Aqm "foo 1"
856 $ hg commit -Aqm "foo 1"
857
857
858 $ hg update null
858 $ hg update null
859 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
859 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
860 $ echo "foo2" > foo.whole
860 $ echo "foo2" > foo.whole
861 $ hg commit -Aqm "foo 2"
861 $ hg commit -Aqm "foo 2"
862
862
863 $ hg --config extensions.rebase= rebase -r 1 -d 0
863 $ hg --config extensions.rebase= rebase -r 1 -d 0
864 rebasing 1:c3b6dc0e177a "foo 2" (tip)
864 rebasing 1:c3b6dc0e177a "foo 2" (tip)
865 merging foo.whole
865 merging foo.whole
866 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
866 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
867 unresolved conflicts (see hg resolve, then hg rebase --continue)
867 unresolved conflicts (see hg resolve, then hg rebase --continue)
868 [1]
868 [1]
869
869
870 $ hg --config extensions.rebase= fix --working-dir
870 $ hg --config extensions.rebase= fix --working-dir
871 abort: unresolved conflicts
871 abort: unresolved conflicts
872 (use 'hg resolve')
872 (use 'hg resolve')
873 [255]
873 [255]
874
874
875 $ hg --config extensions.rebase= fix -r .
875 $ hg --config extensions.rebase= fix -r .
876 abort: rebase in progress
876 abort: rebase in progress
877 (use 'hg rebase --continue' or 'hg rebase --abort')
877 (use 'hg rebase --continue' or 'hg rebase --abort')
878 [255]
878 [255]
879
879
880 $ cd ..
880 $ cd ..
881
881
882 When fixing a file that was renamed, we should diff against the source of the
882 When fixing a file that was renamed, we should diff against the source of the
883 rename for incremental fixing and we should correctly reproduce the rename in
883 rename for incremental fixing and we should correctly reproduce the rename in
884 the replacement revision.
884 the replacement revision.
885
885
886 $ hg init fixrenamecommit
886 $ hg init fixrenamecommit
887 $ cd fixrenamecommit
887 $ cd fixrenamecommit
888
888
889 $ printf "a\nb\nc\n" > source.changed
889 $ printf "a\nb\nc\n" > source.changed
890 $ hg commit -Aqm "source revision"
890 $ hg commit -Aqm "source revision"
891 $ hg move source.changed dest.changed
891 $ hg move source.changed dest.changed
892 $ printf "a\nb\ncc\n" > dest.changed
892 $ printf "a\nb\ncc\n" > dest.changed
893 $ hg commit -m "dest revision"
893 $ hg commit -m "dest revision"
894
894
895 $ hg fix -r .
895 $ hg fix -r .
896 $ hg log -r tip --copies --template "{file_copies}\n"
896 $ hg log -r tip --copies --template "{file_copies}\n"
897 dest.changed (source.changed)
897 dest.changed (source.changed)
898 $ hg cat -r tip dest.changed
898 $ hg cat -r tip dest.changed
899 a
899 a
900 b
900 b
901 CC
901 CC
902
902
903 $ cd ..
903 $ cd ..
904
904
905 When fixing revisions that remove files we must ensure that the replacement
905 When fixing revisions that remove files we must ensure that the replacement
906 actually removes the file, whereas it could accidentally leave it unchanged or
906 actually removes the file, whereas it could accidentally leave it unchanged or
907 write an empty string to it.
907 write an empty string to it.
908
908
909 $ hg init fixremovedfile
909 $ hg init fixremovedfile
910 $ cd fixremovedfile
910 $ cd fixremovedfile
911
911
912 $ printf "foo\n" > foo.whole
912 $ printf "foo\n" > foo.whole
913 $ printf "bar\n" > bar.whole
913 $ printf "bar\n" > bar.whole
914 $ hg commit -Aqm "add files"
914 $ hg commit -Aqm "add files"
915 $ hg remove bar.whole
915 $ hg remove bar.whole
916 $ hg commit -m "remove file"
916 $ hg commit -m "remove file"
917 $ hg status --change .
917 $ hg status --change .
918 R bar.whole
918 R bar.whole
919 $ hg fix -r . foo.whole
919 $ hg fix -r . foo.whole
920 $ hg status --change tip
920 $ hg status --change tip
921 M foo.whole
921 M foo.whole
922 R bar.whole
922 R bar.whole
923
923
924 $ cd ..
924 $ cd ..
925
925
926 If fixing a revision finds no fixes to make, no replacement revision should be
926 If fixing a revision finds no fixes to make, no replacement revision should be
927 created.
927 created.
928
928
929 $ hg init nofixesneeded
929 $ hg init nofixesneeded
930 $ cd nofixesneeded
930 $ cd nofixesneeded
931
931
932 $ printf "FOO\n" > foo.whole
932 $ printf "FOO\n" > foo.whole
933 $ hg commit -Aqm "add file"
933 $ hg commit -Aqm "add file"
934 $ hg log --template '{rev}\n'
934 $ hg log --template '{rev}\n'
935 0
935 0
936 $ hg fix -r .
936 $ hg fix -r .
937 $ hg log --template '{rev}\n'
937 $ hg log --template '{rev}\n'
938 0
938 0
939
939
940 $ cd ..
940 $ cd ..
941
941
942 If fixing a commit reverts all the changes in the commit, we replace it with a
942 If fixing a commit reverts all the changes in the commit, we replace it with a
943 commit that changes no files.
943 commit that changes no files.
944
944
945 $ hg init nochangesleft
945 $ hg init nochangesleft
946 $ cd nochangesleft
946 $ cd nochangesleft
947
947
948 $ printf "FOO\n" > foo.whole
948 $ printf "FOO\n" > foo.whole
949 $ hg commit -Aqm "add file"
949 $ hg commit -Aqm "add file"
950 $ printf "foo\n" > foo.whole
950 $ printf "foo\n" > foo.whole
951 $ hg commit -m "edit file"
951 $ hg commit -m "edit file"
952 $ hg status --change .
952 $ hg status --change .
953 M foo.whole
953 M foo.whole
954 $ hg fix -r .
954 $ hg fix -r .
955 $ hg status --change tip
955 $ hg status --change tip
956
956
957 $ cd ..
957 $ cd ..
958
958
959 If we fix a parent and child revision together, the child revision must be
959 If we fix a parent and child revision together, the child revision must be
960 replaced if the parent is replaced, even if the diffs of the child needed no
960 replaced if the parent is replaced, even if the diffs of the child needed no
961 fixes. However, we're free to not replace revisions that need no fixes and have
961 fixes. However, we're free to not replace revisions that need no fixes and have
962 no ancestors that are replaced.
962 no ancestors that are replaced.
963
963
964 $ hg init mustreplacechild
964 $ hg init mustreplacechild
965 $ cd mustreplacechild
965 $ cd mustreplacechild
966
966
967 $ printf "FOO\n" > foo.whole
967 $ printf "FOO\n" > foo.whole
968 $ hg commit -Aqm "add foo"
968 $ hg commit -Aqm "add foo"
969 $ printf "foo\n" > foo.whole
969 $ printf "foo\n" > foo.whole
970 $ hg commit -m "edit foo"
970 $ hg commit -m "edit foo"
971 $ printf "BAR\n" > bar.whole
971 $ printf "BAR\n" > bar.whole
972 $ hg commit -Aqm "add bar"
972 $ hg commit -Aqm "add bar"
973
973
974 $ hg log --graph --template '{rev} {files}'
974 $ hg log --graph --template '{rev} {files}'
975 @ 2 bar.whole
975 @ 2 bar.whole
976 |
976 |
977 o 1 foo.whole
977 o 1 foo.whole
978 |
978 |
979 o 0 foo.whole
979 o 0 foo.whole
980
980
981 $ hg fix -r 0:2
981 $ hg fix -r 0:2
982 $ hg log --graph --template '{rev} {files}'
982 $ hg log --graph --template '{rev} {files}'
983 o 4 bar.whole
983 o 4 bar.whole
984 |
984 |
985 o 3
985 o 3
986 |
986 |
987 | @ 2 bar.whole
987 | @ 2 bar.whole
988 | |
988 | |
989 | x 1 foo.whole
989 | x 1 foo.whole
990 |/
990 |/
991 o 0 foo.whole
991 o 0 foo.whole
992
992
993
993
994 $ cd ..
994 $ cd ..
995
995
996 It's also possible that the child needs absolutely no changes, but we still
996 It's also possible that the child needs absolutely no changes, but we still
997 need to replace it to update its parent. If we skipped replacing the child
997 need to replace it to update its parent. If we skipped replacing the child
998 because it had no file content changes, it would become an orphan for no good
998 because it had no file content changes, it would become an orphan for no good
999 reason.
999 reason.
1000
1000
1001 $ hg init mustreplacechildevenifnop
1001 $ hg init mustreplacechildevenifnop
1002 $ cd mustreplacechildevenifnop
1002 $ cd mustreplacechildevenifnop
1003
1003
1004 $ printf "Foo\n" > foo.whole
1004 $ printf "Foo\n" > foo.whole
1005 $ hg commit -Aqm "add a bad foo"
1005 $ hg commit -Aqm "add a bad foo"
1006 $ printf "FOO\n" > foo.whole
1006 $ printf "FOO\n" > foo.whole
1007 $ hg commit -m "add a good foo"
1007 $ hg commit -m "add a good foo"
1008 $ hg fix -r . -r '.^'
1008 $ hg fix -r . -r '.^'
1009 $ hg log --graph --template '{rev} {desc}'
1009 $ hg log --graph --template '{rev} {desc}'
1010 o 3 add a good foo
1010 o 3 add a good foo
1011 |
1011 |
1012 o 2 add a bad foo
1012 o 2 add a bad foo
1013
1013
1014 @ 1 add a good foo
1014 @ 1 add a good foo
1015 |
1015 |
1016 x 0 add a bad foo
1016 x 0 add a bad foo
1017
1017
1018
1018
1019 $ cd ..
1019 $ cd ..
1020
1020
1021 Similar to the case above, the child revision may become empty as a result of
1021 Similar to the case above, the child revision may become empty as a result of
1022 fixing its parent. We should still create an empty replacement child.
1022 fixing its parent. We should still create an empty replacement child.
1023 TODO: determine how this should interact with ui.allowemptycommit given that
1023 TODO: determine how this should interact with ui.allowemptycommit given that
1024 the empty replacement could have children.
1024 the empty replacement could have children.
1025
1025
1026 $ hg init mustreplacechildevenifempty
1026 $ hg init mustreplacechildevenifempty
1027 $ cd mustreplacechildevenifempty
1027 $ cd mustreplacechildevenifempty
1028
1028
1029 $ printf "foo\n" > foo.whole
1029 $ printf "foo\n" > foo.whole
1030 $ hg commit -Aqm "add foo"
1030 $ hg commit -Aqm "add foo"
1031 $ printf "Foo\n" > foo.whole
1031 $ printf "Foo\n" > foo.whole
1032 $ hg commit -m "edit foo"
1032 $ hg commit -m "edit foo"
1033 $ hg fix -r . -r '.^'
1033 $ hg fix -r . -r '.^'
1034 $ hg log --graph --template '{rev} {desc}\n' --stat
1034 $ hg log --graph --template '{rev} {desc}\n' --stat
1035 o 3 edit foo
1035 o 3 edit foo
1036 |
1036 |
1037 o 2 add foo
1037 o 2 add foo
1038 foo.whole | 1 +
1038 foo.whole | 1 +
1039 1 files changed, 1 insertions(+), 0 deletions(-)
1039 1 files changed, 1 insertions(+), 0 deletions(-)
1040
1040
1041 @ 1 edit foo
1041 @ 1 edit foo
1042 | foo.whole | 2 +-
1042 | foo.whole | 2 +-
1043 | 1 files changed, 1 insertions(+), 1 deletions(-)
1043 | 1 files changed, 1 insertions(+), 1 deletions(-)
1044 |
1044 |
1045 x 0 add foo
1045 x 0 add foo
1046 foo.whole | 1 +
1046 foo.whole | 1 +
1047 1 files changed, 1 insertions(+), 0 deletions(-)
1047 1 files changed, 1 insertions(+), 0 deletions(-)
1048
1048
1049
1049
1050 $ cd ..
1050 $ cd ..
1051
1051
1052 Fixing a secret commit should replace it with another secret commit.
1052 Fixing a secret commit should replace it with another secret commit.
1053
1053
1054 $ hg init fixsecretcommit
1054 $ hg init fixsecretcommit
1055 $ cd fixsecretcommit
1055 $ cd fixsecretcommit
1056
1056
1057 $ printf "foo\n" > foo.whole
1057 $ printf "foo\n" > foo.whole
1058 $ hg commit -Aqm "add foo" --secret
1058 $ hg commit -Aqm "add foo" --secret
1059 $ hg fix -r .
1059 $ hg fix -r .
1060 $ hg log --template '{rev} {phase}\n'
1060 $ hg log --template '{rev} {phase}\n'
1061 1 secret
1061 1 secret
1062 0 secret
1062 0 secret
1063
1063
1064 $ cd ..
1064 $ cd ..
1065
1065
1066 We should also preserve phase when fixing a draft commit while the user has
1066 We should also preserve phase when fixing a draft commit while the user has
1067 their default set to secret.
1067 their default set to secret.
1068
1068
1069 $ hg init respectphasesnewcommit
1069 $ hg init respectphasesnewcommit
1070 $ cd respectphasesnewcommit
1070 $ cd respectphasesnewcommit
1071
1071
1072 $ printf "foo\n" > foo.whole
1072 $ printf "foo\n" > foo.whole
1073 $ hg commit -Aqm "add foo"
1073 $ hg commit -Aqm "add foo"
1074 $ hg --config phases.newcommit=secret fix -r .
1074 $ hg --config phases.newcommit=secret fix -r .
1075 $ hg log --template '{rev} {phase}\n'
1075 $ hg log --template '{rev} {phase}\n'
1076 1 draft
1076 1 draft
1077 0 draft
1077 0 draft
1078
1078
1079 $ cd ..
1079 $ cd ..
1080
1080
1081 Debug output should show what fixer commands are being subprocessed, which is
1081 Debug output should show what fixer commands are being subprocessed, which is
1082 useful for anyone trying to set up a new config.
1082 useful for anyone trying to set up a new config.
1083
1083
1084 $ hg init debugoutput
1084 $ hg init debugoutput
1085 $ cd debugoutput
1085 $ cd debugoutput
1086
1086
1087 $ printf "foo\nbar\nbaz\n" > foo.changed
1087 $ printf "foo\nbar\nbaz\n" > foo.changed
1088 $ hg commit -Aqm "foo"
1088 $ hg commit -Aqm "foo"
1089 $ printf "Foo\nbar\nBaz\n" > foo.changed
1089 $ printf "Foo\nbar\nBaz\n" > foo.changed
1090 $ hg --debug fix --working-dir
1090 $ hg --debug fix --working-dir
1091 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
1091 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
1092
1092
1093 $ cd ..
1093 $ cd ..
1094
1094
1095 Fixing an obsolete revision can cause divergence, so we abort unless the user
1095 Fixing an obsolete revision can cause divergence, so we abort unless the user
1096 configures to allow it. This is not yet smart enough to know whether there is a
1096 configures to allow it. This is not yet smart enough to know whether there is a
1097 successor, but even then it is not likely intentional or idiomatic to fix an
1097 successor, but even then it is not likely intentional or idiomatic to fix an
1098 obsolete revision.
1098 obsolete revision.
1099
1099
1100 $ hg init abortobsoleterev
1100 $ hg init abortobsoleterev
1101 $ cd abortobsoleterev
1101 $ cd abortobsoleterev
1102
1102
1103 $ printf "foo\n" > foo.changed
1103 $ printf "foo\n" > foo.changed
1104 $ hg commit -Aqm "foo"
1104 $ hg commit -Aqm "foo"
1105 $ hg debugobsolete `hg parents --template '{node}'`
1105 $ hg debugobsolete `hg parents --template '{node}'`
1106 1 new obsolescence markers
1106 1 new obsolescence markers
1107 obsoleted 1 changesets
1107 obsoleted 1 changesets
1108 $ hg --hidden fix -r 0
1108 $ hg --hidden fix -r 0
1109 abort: fixing obsolete revision could cause divergence
1109 abort: fixing obsolete revision could cause divergence
1110 [255]
1110 [255]
1111
1111
1112 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
1112 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
1113 $ hg cat -r tip foo.changed
1113 $ hg cat -r tip foo.changed
1114 FOO
1114 FOO
1115
1115
1116 $ cd ..
1116 $ cd ..
1117
1117
1118 Test all of the available substitution values for fixer commands.
1118 Test all of the available substitution values for fixer commands.
1119
1119
1120 $ hg init substitution
1120 $ hg init substitution
1121 $ cd substitution
1121 $ cd substitution
1122
1122
1123 $ mkdir foo
1123 $ mkdir foo
1124 $ printf "hello\ngoodbye\n" > foo/bar
1124 $ printf "hello\ngoodbye\n" > foo/bar
1125 $ hg add
1125 $ hg add
1126 adding foo/bar
1126 adding foo/bar
1127 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
1127 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
1128 > --config "fix.fail:linerange='{first}' '{last}'" \
1128 > --config "fix.fail:linerange='{first}' '{last}'" \
1129 > --config "fix.fail:pattern=foo/bar" \
1129 > --config "fix.fail:pattern=foo/bar" \
1130 > fix --working-dir
1130 > fix --working-dir
1131 $ cat foo/bar
1131 $ cat foo/bar
1132 foo/bar
1132 foo/bar
1133 bar
1133 bar
1134 1
1134 1
1135 2
1135 2
1136
1136
1137 $ cd ..
1137 $ cd ..
1138
1138
1139 The --base flag should allow picking the revisions to diff against for changed
1139 The --base flag should allow picking the revisions to diff against for changed
1140 files and incremental line formatting.
1140 files and incremental line formatting.
1141
1141
1142 $ hg init baseflag
1142 $ hg init baseflag
1143 $ cd baseflag
1143 $ cd baseflag
1144
1144
1145 $ printf "one\ntwo\n" > foo.changed
1145 $ printf "one\ntwo\n" > foo.changed
1146 $ printf "bar\n" > bar.changed
1146 $ printf "bar\n" > bar.changed
1147 $ hg commit -Aqm "first"
1147 $ hg commit -Aqm "first"
1148 $ printf "one\nTwo\n" > foo.changed
1148 $ printf "one\nTwo\n" > foo.changed
1149 $ hg commit -m "second"
1149 $ hg commit -m "second"
1150 $ hg fix -w --base .
1150 $ hg fix -w --base .
1151 $ hg status
1151 $ hg status
1152 $ hg fix -w --base null
1152 $ hg fix -w --base null
1153 $ cat foo.changed
1153 $ cat foo.changed
1154 ONE
1154 ONE
1155 TWO
1155 TWO
1156 $ cat bar.changed
1156 $ cat bar.changed
1157 BAR
1157 BAR
1158
1158
1159 $ cd ..
1159 $ cd ..
1160
1160
1161 If the user asks to fix the parent of another commit, they are asking to create
1161 If the user asks to fix the parent of another commit, they are asking to create
1162 an orphan. We must respect experimental.evolution.allowunstable.
1162 an orphan. We must respect experimental.evolution.allowunstable.
1163
1163
1164 $ hg init allowunstable
1164 $ hg init allowunstable
1165 $ cd allowunstable
1165 $ cd allowunstable
1166
1166
1167 $ printf "one\n" > foo.whole
1167 $ printf "one\n" > foo.whole
1168 $ hg commit -Aqm "first"
1168 $ hg commit -Aqm "first"
1169 $ printf "two\n" > foo.whole
1169 $ printf "two\n" > foo.whole
1170 $ hg commit -m "second"
1170 $ hg commit -m "second"
1171 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1171 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1172 abort: can only fix a changeset together with all its descendants
1172 abort: can only fix a changeset together with all its descendants
1173 [255]
1173 [255]
1174 $ hg fix -r '.^'
1174 $ hg fix -r '.^'
1175 1 new orphan changesets
1175 1 new orphan changesets
1176 $ hg cat -r 2 foo.whole
1176 $ hg cat -r 2 foo.whole
1177 ONE
1177 ONE
1178
1178
1179 $ cd ..
1179 $ cd ..
1180
1180
1181 The --base flag affects the set of files being fixed. So while the --whole flag
1181 The --base flag affects the set of files being fixed. So while the --whole flag
1182 makes the base irrelevant for changed line ranges, it still changes the
1182 makes the base irrelevant for changed line ranges, it still changes the
1183 meaning and effect of the command. In this example, no files or lines are fixed
1183 meaning and effect of the command. In this example, no files or lines are fixed
1184 until we specify the base, but then we do fix unchanged lines.
1184 until we specify the base, but then we do fix unchanged lines.
1185
1185
1186 $ hg init basewhole
1186 $ hg init basewhole
1187 $ cd basewhole
1187 $ cd basewhole
1188 $ printf "foo1\n" > foo.changed
1188 $ printf "foo1\n" > foo.changed
1189 $ hg commit -Aqm "first"
1189 $ hg commit -Aqm "first"
1190 $ printf "foo2\n" >> foo.changed
1190 $ printf "foo2\n" >> foo.changed
1191 $ printf "bar\n" > bar.changed
1191 $ printf "bar\n" > bar.changed
1192 $ hg commit -Aqm "second"
1192 $ hg commit -Aqm "second"
1193
1193
1194 $ hg fix --working-dir --whole
1194 $ hg fix --working-dir --whole
1195 $ cat *.changed
1195 $ cat *.changed
1196 bar
1196 bar
1197 foo1
1197 foo1
1198 foo2
1198 foo2
1199
1199
1200 $ hg fix --working-dir --base 0 --whole
1200 $ hg fix --working-dir --base 0 --whole
1201 $ cat *.changed
1201 $ cat *.changed
1202 BAR
1202 BAR
1203 FOO1
1203 FOO1
1204 FOO2
1204 FOO2
1205
1205
1206 $ cd ..
1206 $ cd ..
1207
1207
1208 The execution order of tools can be controlled. This example doesn't work if
1208 The execution order of tools can be controlled. This example doesn't work if
1209 you sort after truncating, but the config defines the correct order while the
1209 you sort after truncating, but the config defines the correct order while the
1210 definitions are out of order (which might imply the incorrect order given the
1210 definitions are out of order (which might imply the incorrect order given the
1211 implementation of fix). The goal is to use multiple tools to select the lowest
1211 implementation of fix). The goal is to use multiple tools to select the lowest
1212 5 numbers in the file.
1212 5 numbers in the file.
1213
1213
1214 $ hg init priorityexample
1214 $ hg init priorityexample
1215 $ cd priorityexample
1215 $ cd priorityexample
1216
1216
1217 $ cat >> .hg/hgrc <<EOF
1217 $ cat >> .hg/hgrc <<EOF
1218 > [fix]
1218 > [fix]
1219 > head:command = head -n 5
1219 > head:command = head -n 5
1220 > head:pattern = numbers.txt
1220 > head:pattern = numbers.txt
1221 > head:priority = 1
1221 > head:priority = 1
1222 > sort:command = sort -n
1222 > sort:command = sort -n
1223 > sort:pattern = numbers.txt
1223 > sort:pattern = numbers.txt
1224 > sort:priority = 2
1224 > sort:priority = 2
1225 > EOF
1225 > EOF
1226
1226
1227 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1227 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1228 $ hg add -q
1228 $ hg add -q
1229 $ hg fix -w
1229 $ hg fix -w
1230 $ cat numbers.txt
1230 $ cat numbers.txt
1231 0
1231 0
1232 1
1232 1
1233 2
1233 2
1234 3
1234 3
1235 4
1235 4
1236
1236
1237 And of course we should be able to break this by reversing the execution order.
1237 And of course we should be able to break this by reversing the execution order.
1238 Test negative priorities while we're at it.
1238 Test negative priorities while we're at it.
1239
1239
1240 $ cat >> .hg/hgrc <<EOF
1240 $ cat >> .hg/hgrc <<EOF
1241 > [fix]
1241 > [fix]
1242 > head:priority = -1
1242 > head:priority = -1
1243 > sort:priority = -2
1243 > sort:priority = -2
1244 > EOF
1244 > EOF
1245 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1245 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1246 $ hg fix -w
1246 $ hg fix -w
1247 $ cat numbers.txt
1247 $ cat numbers.txt
1248 2
1248 2
1249 3
1249 3
1250 6
1250 6
1251 7
1251 7
1252 8
1252 8
1253
1253
1254 $ cd ..
1254 $ cd ..
1255
1255
1256 It's possible for repeated applications of a fixer tool to create cycles in the
1256 It's possible for repeated applications of a fixer tool to create cycles in the
1257 generated content of a file. For example, two users with different versions of
1257 generated content of a file. For example, two users with different versions of
1258 a code formatter might fight over the formatting when they run hg fix. In the
1258 a code formatter might fight over the formatting when they run hg fix. In the
1259 absence of other changes, this means we could produce commits with the same
1259 absence of other changes, this means we could produce commits with the same
1260 hash in subsequent runs of hg fix. This is a problem unless we support
1260 hash in subsequent runs of hg fix. This is a problem unless we support
1261 obsolescence cycles well. We avoid this by adding an extra field to the
1261 obsolescence cycles well. We avoid this by adding an extra field to the
1262 successor which forces it to have a new hash. That's why this test creates
1262 successor which forces it to have a new hash. That's why this test creates
1263 three revisions instead of two.
1263 three revisions instead of two.
1264
1264
1265 $ hg init cyclictool
1265 $ hg init cyclictool
1266 $ cd cyclictool
1266 $ cd cyclictool
1267
1267
1268 $ cat >> .hg/hgrc <<EOF
1268 $ cat >> .hg/hgrc <<EOF
1269 > [fix]
1269 > [fix]
1270 > swapletters:command = tr ab ba
1270 > swapletters:command = tr ab ba
1271 > swapletters:pattern = foo
1271 > swapletters:pattern = foo
1272 > EOF
1272 > EOF
1273
1273
1274 $ echo ab > foo
1274 $ echo ab > foo
1275 $ hg commit -Aqm foo
1275 $ hg commit -Aqm foo
1276
1276
1277 $ hg fix -r 0
1277 $ hg fix -r 0
1278 $ hg fix -r 1
1278 $ hg fix -r 1
1279
1279
1280 $ hg cat -r 0 foo --hidden
1280 $ hg cat -r 0 foo --hidden
1281 ab
1281 ab
1282 $ hg cat -r 1 foo --hidden
1282 $ hg cat -r 1 foo --hidden
1283 ba
1283 ba
1284 $ hg cat -r 2 foo
1284 $ hg cat -r 2 foo
1285 ab
1285 ab
1286
1286
1287 $ cd ..
1287 $ cd ..
1288
1288
1289 We run fixer tools in the repo root so they can look for config files or other
1289 We run fixer tools in the repo root so they can look for config files or other
1290 important things in the working directory. This does NOT mean we are
1290 important things in the working directory. This does NOT mean we are
1291 reconstructing a working copy of every revision being fixed; we're just giving
1291 reconstructing a working copy of every revision being fixed; we're just giving
1292 the tool knowledge of the repo's location in case it can do something
1292 the tool knowledge of the repo's location in case it can do something
1293 reasonable with that.
1293 reasonable with that.
1294
1294
1295 $ hg init subprocesscwd
1295 $ hg init subprocesscwd
1296 $ cd subprocesscwd
1296 $ cd subprocesscwd
1297
1297
1298 $ cat >> .hg/hgrc <<EOF
1298 $ cat >> .hg/hgrc <<EOF
1299 > [fix]
1299 > [fix]
1300 > printcwd:command = "$PYTHON" -c "import os; print(os.getcwd())"
1300 > printcwd:command = "$PYTHON" -c "import os; print(os.getcwd())"
1301 > printcwd:pattern = path:foo/bar
1301 > printcwd:pattern = path:foo/bar
1302 > EOF
1302 > EOF
1303
1303
1304 $ mkdir foo
1304 $ mkdir foo
1305 $ printf "bar\n" > foo/bar
1305 $ printf "bar\n" > foo/bar
1306 $ hg commit -Aqm blah
1306 $ hg commit -Aqm blah
1307
1307
1308 $ hg fix -w -r . foo/bar
1308 $ hg fix -w -r . foo/bar
1309 $ hg cat -r tip foo/bar
1309 $ hg cat -r tip foo/bar
1310 $TESTTMP/subprocesscwd
1310 $TESTTMP/subprocesscwd
1311 $ cat foo/bar
1311 $ cat foo/bar
1312 $TESTTMP/subprocesscwd
1312 $TESTTMP/subprocesscwd
1313
1313
1314 $ cd foo
1314 $ cd foo
1315
1315
1316 $ hg fix -w -r . bar
1316 $ hg fix -w -r . bar
1317 $ hg cat -r tip bar
1317 $ hg cat -r tip bar
1318 $TESTTMP/subprocesscwd
1318 $TESTTMP/subprocesscwd
1319 $ cat bar
1319 $ cat bar
1320 $TESTTMP/subprocesscwd
1320 $TESTTMP/subprocesscwd
1321
1321
1322 $ cd ../..
1322 $ cd ../..
1323
1323
1324 Tools configured without a pattern are ignored. It would be too dangerous to
1324 Tools configured without a pattern are ignored. It would be too dangerous to
1325 run them on all files, because this might happen while testing a configuration
1325 run them on all files, because this might happen while testing a configuration
1326 that also deletes all of the file content. There is no reasonable subset of the
1326 that also deletes all of the file content. There is no reasonable subset of the
1327 files to use as a default. Users should be explicit about what files are
1327 files to use as a default. Users should be explicit about what files are
1328 affected by a tool. This test also confirms that we don't crash when the
1328 affected by a tool. This test also confirms that we don't crash when the
1329 pattern config is missing, and that we only warn about it once.
1329 pattern config is missing, and that we only warn about it once.
1330
1330
1331 $ hg init nopatternconfigured
1331 $ hg init nopatternconfigured
1332 $ cd nopatternconfigured
1332 $ cd nopatternconfigured
1333
1333
1334 $ printf "foo" > foo
1334 $ printf "foo" > foo
1335 $ printf "bar" > bar
1335 $ printf "bar" > bar
1336 $ hg add -q
1336 $ hg add -q
1337 $ hg fix --debug --working-dir --config "fix.nopattern:command=echo fixed"
1337 $ hg fix --debug --working-dir --config "fix.nopattern:command=echo fixed"
1338 fixer tool has no pattern configuration: nopattern
1338 fixer tool has no pattern configuration: nopattern
1339 $ cat foo bar
1339 $ cat foo bar
1340 foobar (no-eol)
1340 foobar (no-eol)
1341
1341
1342 $ cd ..
1342 $ cd ..
1343
1343
1344 Tools can be disabled. Disabled tools do nothing but print a debug message.
1345
1346 $ hg init disabled
1347 $ cd disabled
1348
1349 $ printf "foo\n" > foo
1350 $ hg add -q
1351 $ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \
1352 > --config "fix.disabled:pattern=foo" \
1353 > --config "fix.disabled:enabled=false"
1354 ignoring disabled fixer tool: disabled
1355 $ cat foo
1356 foo
1357
1358 $ cd ..
1359
1344 Test that we can configure a fixer to affect all files regardless of the cwd.
1360 Test that we can configure a fixer to affect all files regardless of the cwd.
1345 The way we invoke matching must not prohibit this.
1361 The way we invoke matching must not prohibit this.
1346
1362
1347 $ hg init affectallfiles
1363 $ hg init affectallfiles
1348 $ cd affectallfiles
1364 $ cd affectallfiles
1349
1365
1350 $ mkdir foo bar
1366 $ mkdir foo bar
1351 $ printf "foo" > foo/file
1367 $ printf "foo" > foo/file
1352 $ printf "bar" > bar/file
1368 $ printf "bar" > bar/file
1353 $ printf "baz" > baz_file
1369 $ printf "baz" > baz_file
1354 $ hg add -q
1370 $ hg add -q
1355
1371
1356 $ cd bar
1372 $ cd bar
1357 $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \
1373 $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \
1358 > --config "fix.cooltool:pattern=rootglob:**"
1374 > --config "fix.cooltool:pattern=rootglob:**"
1359 $ cd ..
1375 $ cd ..
1360
1376
1361 $ cat foo/file
1377 $ cat foo/file
1362 fixed
1378 fixed
1363 $ cat bar/file
1379 $ cat bar/file
1364 fixed
1380 fixed
1365 $ cat baz_file
1381 $ cat baz_file
1366 fixed
1382 fixed
1367
1383
1368 $ cd ..
1384 $ cd ..
1369
1385
1370 Tools should be able to run on unchanged files, even if they set :linerange.
1386 Tools should be able to run on unchanged files, even if they set :linerange.
1371 This includes a corner case where deleted chunks of a file are not considered
1387 This includes a corner case where deleted chunks of a file are not considered
1372 changes.
1388 changes.
1373
1389
1374 $ hg init skipclean
1390 $ hg init skipclean
1375 $ cd skipclean
1391 $ cd skipclean
1376
1392
1377 $ printf "a\nb\nc\n" > foo
1393 $ printf "a\nb\nc\n" > foo
1378 $ printf "a\nb\nc\n" > bar
1394 $ printf "a\nb\nc\n" > bar
1379 $ printf "a\nb\nc\n" > baz
1395 $ printf "a\nb\nc\n" > baz
1380 $ hg commit -Aqm "base"
1396 $ hg commit -Aqm "base"
1381
1397
1382 $ printf "a\nc\n" > foo
1398 $ printf "a\nc\n" > foo
1383 $ printf "a\nx\nc\n" > baz
1399 $ printf "a\nx\nc\n" > baz
1384
1400
1385 $ cat >> print.py <<EOF
1401 $ cat >> print.py <<EOF
1386 > import sys
1402 > import sys
1387 > for a in sys.argv[1:]:
1403 > for a in sys.argv[1:]:
1388 > print(a)
1404 > print(a)
1389 > EOF
1405 > EOF
1390
1406
1391 $ hg fix --working-dir foo bar baz \
1407 $ hg fix --working-dir foo bar baz \
1392 > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \
1408 > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \
1393 > --config 'fix.changedlines:linerange="{first} through {last}"' \
1409 > --config 'fix.changedlines:linerange="{first} through {last}"' \
1394 > --config 'fix.changedlines:pattern=rootglob:**' \
1410 > --config 'fix.changedlines:pattern=rootglob:**' \
1395 > --config 'fix.changedlines:skipclean=false'
1411 > --config 'fix.changedlines:skipclean=false'
1396
1412
1397 $ cat foo
1413 $ cat foo
1398 Line ranges:
1414 Line ranges:
1399 $ cat bar
1415 $ cat bar
1400 Line ranges:
1416 Line ranges:
1401 $ cat baz
1417 $ cat baz
1402 Line ranges:
1418 Line ranges:
1403 2 through 2
1419 2 through 2
1404
1420
1405 $ cd ..
1421 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now