##// END OF EJS Templates
fix: warn when a fixer doesn't have a configured command...
Martin von Zweigbergk -
r43494:d3d1a3af default
parent child Browse files
Show More
@@ -1,874 +1,878 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 procutil
136 from mercurial.utils import procutil
137
137
138 from mercurial import (
138 from mercurial import (
139 cmdutil,
139 cmdutil,
140 context,
140 context,
141 copies,
141 copies,
142 error,
142 error,
143 mdiff,
143 mdiff,
144 merge,
144 merge,
145 obsolete,
145 obsolete,
146 pycompat,
146 pycompat,
147 registrar,
147 registrar,
148 scmutil,
148 scmutil,
149 util,
149 util,
150 worker,
150 worker,
151 )
151 )
152
152
153 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
153 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
154 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
154 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
155 # be specifying the version(s) of Mercurial they are tested with, or
155 # be specifying the version(s) of Mercurial they are tested with, or
156 # leave the attribute unspecified.
156 # leave the attribute unspecified.
157 testedwith = b'ships-with-hg-core'
157 testedwith = b'ships-with-hg-core'
158
158
159 cmdtable = {}
159 cmdtable = {}
160 command = registrar.command(cmdtable)
160 command = registrar.command(cmdtable)
161
161
162 configtable = {}
162 configtable = {}
163 configitem = registrar.configitem(configtable)
163 configitem = registrar.configitem(configtable)
164
164
165 # Register the suboptions allowed for each configured fixer, and default values.
165 # Register the suboptions allowed for each configured fixer, and default values.
166 FIXER_ATTRS = {
166 FIXER_ATTRS = {
167 b'command': None,
167 b'command': None,
168 b'linerange': None,
168 b'linerange': None,
169 b'pattern': None,
169 b'pattern': None,
170 b'priority': 0,
170 b'priority': 0,
171 b'metadata': False,
171 b'metadata': False,
172 b'skipclean': True,
172 b'skipclean': True,
173 b'enabled': True,
173 b'enabled': True,
174 }
174 }
175
175
176 for key, default in FIXER_ATTRS.items():
176 for key, default in FIXER_ATTRS.items():
177 configitem(b'fix', b'.*:%s$' % key, default=default, generic=True)
177 configitem(b'fix', b'.*:%s$' % key, default=default, generic=True)
178
178
179 # A good default size allows most source code files to be fixed, but avoids
179 # A good default size allows most source code files to be fixed, but avoids
180 # letting fixer tools choke on huge inputs, which could be surprising to the
180 # letting fixer tools choke on huge inputs, which could be surprising to the
181 # user.
181 # user.
182 configitem(b'fix', b'maxfilesize', default=b'2MB')
182 configitem(b'fix', b'maxfilesize', default=b'2MB')
183
183
184 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
184 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
185 # This helps users do shell scripts that stop when a fixer tool signals a
185 # This helps users do shell scripts that stop when a fixer tool signals a
186 # problem.
186 # problem.
187 configitem(b'fix', b'failure', default=b'continue')
187 configitem(b'fix', b'failure', default=b'continue')
188
188
189
189
190 def checktoolfailureaction(ui, message, hint=None):
190 def checktoolfailureaction(ui, message, hint=None):
191 """Abort with 'message' if fix.failure=abort"""
191 """Abort with 'message' if fix.failure=abort"""
192 action = ui.config(b'fix', b'failure')
192 action = ui.config(b'fix', b'failure')
193 if action not in (b'continue', b'abort'):
193 if action not in (b'continue', b'abort'):
194 raise error.Abort(
194 raise error.Abort(
195 _(b'unknown fix.failure action: %s') % (action,),
195 _(b'unknown fix.failure action: %s') % (action,),
196 hint=_(b'use "continue" or "abort"'),
196 hint=_(b'use "continue" or "abort"'),
197 )
197 )
198 if action == b'abort':
198 if action == b'abort':
199 raise error.Abort(message, hint=hint)
199 raise error.Abort(message, hint=hint)
200
200
201
201
202 allopt = (b'', b'all', False, _(b'fix all non-public non-obsolete revisions'))
202 allopt = (b'', b'all', False, _(b'fix all non-public non-obsolete revisions'))
203 baseopt = (
203 baseopt = (
204 b'',
204 b'',
205 b'base',
205 b'base',
206 [],
206 [],
207 _(
207 _(
208 b'revisions to diff against (overrides automatic '
208 b'revisions to diff against (overrides automatic '
209 b'selection, and applies to every revision being '
209 b'selection, and applies to every revision being '
210 b'fixed)'
210 b'fixed)'
211 ),
211 ),
212 _(b'REV'),
212 _(b'REV'),
213 )
213 )
214 revopt = (b'r', b'rev', [], _(b'revisions to fix'), _(b'REV'))
214 revopt = (b'r', b'rev', [], _(b'revisions to fix'), _(b'REV'))
215 wdiropt = (b'w', b'working-dir', False, _(b'fix the working directory'))
215 wdiropt = (b'w', b'working-dir', False, _(b'fix the working directory'))
216 wholeopt = (b'', b'whole', False, _(b'always fix every line of a file'))
216 wholeopt = (b'', b'whole', False, _(b'always fix every line of a file'))
217 usage = _(b'[OPTION]... [FILE]...')
217 usage = _(b'[OPTION]... [FILE]...')
218
218
219
219
220 @command(
220 @command(
221 b'fix',
221 b'fix',
222 [allopt, baseopt, revopt, wdiropt, wholeopt],
222 [allopt, baseopt, revopt, wdiropt, wholeopt],
223 usage,
223 usage,
224 helpcategory=command.CATEGORY_FILE_CONTENTS,
224 helpcategory=command.CATEGORY_FILE_CONTENTS,
225 )
225 )
226 def fix(ui, repo, *pats, **opts):
226 def fix(ui, repo, *pats, **opts):
227 """rewrite file content in changesets or working directory
227 """rewrite file content in changesets or working directory
228
228
229 Runs any configured tools to fix the content of files. Only affects files
229 Runs any configured tools to fix the content of files. Only affects files
230 with changes, unless file arguments are provided. Only affects changed lines
230 with changes, unless file arguments are provided. Only affects changed lines
231 of files, unless the --whole flag is used. Some tools may always affect the
231 of files, unless the --whole flag is used. Some tools may always affect the
232 whole file regardless of --whole.
232 whole file regardless of --whole.
233
233
234 If revisions are specified with --rev, those revisions will be checked, and
234 If revisions are specified with --rev, those revisions will be checked, and
235 they may be replaced with new revisions that have fixed file content. It is
235 they may be replaced with new revisions that have fixed file content. It is
236 desirable to specify all descendants of each specified revision, so that the
236 desirable to specify all descendants of each specified revision, so that the
237 fixes propagate to the descendants. If all descendants are fixed at the same
237 fixes propagate to the descendants. If all descendants are fixed at the same
238 time, no merging, rebasing, or evolution will be required.
238 time, no merging, rebasing, or evolution will be required.
239
239
240 If --working-dir is used, files with uncommitted changes in the working copy
240 If --working-dir is used, files with uncommitted changes in the working copy
241 will be fixed. If the checked-out revision is also fixed, the working
241 will be fixed. If the checked-out revision is also fixed, the working
242 directory will update to the replacement revision.
242 directory will update to the replacement revision.
243
243
244 When determining what lines of each file to fix at each revision, the whole
244 When determining what lines of each file to fix at each revision, the whole
245 set of revisions being fixed is considered, so that fixes to earlier
245 set of revisions being fixed is considered, so that fixes to earlier
246 revisions are not forgotten in later ones. The --base flag can be used to
246 revisions are not forgotten in later ones. The --base flag can be used to
247 override this default behavior, though it is not usually desirable to do so.
247 override this default behavior, though it is not usually desirable to do so.
248 """
248 """
249 opts = pycompat.byteskwargs(opts)
249 opts = pycompat.byteskwargs(opts)
250 if opts[b'all']:
250 if opts[b'all']:
251 if opts[b'rev']:
251 if opts[b'rev']:
252 raise error.Abort(_(b'cannot specify both "--rev" and "--all"'))
252 raise error.Abort(_(b'cannot specify both "--rev" and "--all"'))
253 opts[b'rev'] = [b'not public() and not obsolete()']
253 opts[b'rev'] = [b'not public() and not obsolete()']
254 opts[b'working_dir'] = True
254 opts[b'working_dir'] = True
255 with repo.wlock(), repo.lock(), repo.transaction(b'fix'):
255 with repo.wlock(), repo.lock(), repo.transaction(b'fix'):
256 revstofix = getrevstofix(ui, repo, opts)
256 revstofix = getrevstofix(ui, repo, opts)
257 basectxs = getbasectxs(repo, opts, revstofix)
257 basectxs = getbasectxs(repo, opts, revstofix)
258 workqueue, numitems = getworkqueue(
258 workqueue, numitems = getworkqueue(
259 ui, repo, pats, opts, revstofix, basectxs
259 ui, repo, pats, opts, revstofix, basectxs
260 )
260 )
261 fixers = getfixers(ui)
261 fixers = getfixers(ui)
262
262
263 # There are no data dependencies between the workers fixing each file
263 # There are no data dependencies between the workers fixing each file
264 # revision, so we can use all available parallelism.
264 # revision, so we can use all available parallelism.
265 def getfixes(items):
265 def getfixes(items):
266 for rev, path in items:
266 for rev, path in items:
267 ctx = repo[rev]
267 ctx = repo[rev]
268 olddata = ctx[path].data()
268 olddata = ctx[path].data()
269 metadata, newdata = fixfile(
269 metadata, newdata = fixfile(
270 ui, repo, opts, fixers, ctx, path, basectxs[rev]
270 ui, repo, opts, fixers, ctx, path, basectxs[rev]
271 )
271 )
272 # Don't waste memory/time passing unchanged content back, but
272 # Don't waste memory/time passing unchanged content back, but
273 # produce one result per item either way.
273 # produce one result per item either way.
274 yield (
274 yield (
275 rev,
275 rev,
276 path,
276 path,
277 metadata,
277 metadata,
278 newdata if newdata != olddata else None,
278 newdata if newdata != olddata else None,
279 )
279 )
280
280
281 results = worker.worker(
281 results = worker.worker(
282 ui, 1.0, getfixes, tuple(), workqueue, threadsafe=False
282 ui, 1.0, getfixes, tuple(), workqueue, threadsafe=False
283 )
283 )
284
284
285 # We have to hold on to the data for each successor revision in memory
285 # We have to hold on to the data for each successor revision in memory
286 # until all its parents are committed. We ensure this by committing and
286 # until all its parents are committed. We ensure this by committing and
287 # freeing memory for the revisions in some topological order. This
287 # freeing memory for the revisions in some topological order. This
288 # leaves a little bit of memory efficiency on the table, but also makes
288 # leaves a little bit of memory efficiency on the table, but also makes
289 # the tests deterministic. It might also be considered a feature since
289 # the tests deterministic. It might also be considered a feature since
290 # it makes the results more easily reproducible.
290 # it makes the results more easily reproducible.
291 filedata = collections.defaultdict(dict)
291 filedata = collections.defaultdict(dict)
292 aggregatemetadata = collections.defaultdict(list)
292 aggregatemetadata = collections.defaultdict(list)
293 replacements = {}
293 replacements = {}
294 wdirwritten = False
294 wdirwritten = False
295 commitorder = sorted(revstofix, reverse=True)
295 commitorder = sorted(revstofix, reverse=True)
296 with ui.makeprogress(
296 with ui.makeprogress(
297 topic=_(b'fixing'), unit=_(b'files'), total=sum(numitems.values())
297 topic=_(b'fixing'), unit=_(b'files'), total=sum(numitems.values())
298 ) as progress:
298 ) as progress:
299 for rev, path, filerevmetadata, newdata in results:
299 for rev, path, filerevmetadata, newdata in results:
300 progress.increment(item=path)
300 progress.increment(item=path)
301 for fixername, fixermetadata in filerevmetadata.items():
301 for fixername, fixermetadata in filerevmetadata.items():
302 aggregatemetadata[fixername].append(fixermetadata)
302 aggregatemetadata[fixername].append(fixermetadata)
303 if newdata is not None:
303 if newdata is not None:
304 filedata[rev][path] = newdata
304 filedata[rev][path] = newdata
305 hookargs = {
305 hookargs = {
306 b'rev': rev,
306 b'rev': rev,
307 b'path': path,
307 b'path': path,
308 b'metadata': filerevmetadata,
308 b'metadata': filerevmetadata,
309 }
309 }
310 repo.hook(
310 repo.hook(
311 b'postfixfile',
311 b'postfixfile',
312 throw=False,
312 throw=False,
313 **pycompat.strkwargs(hookargs)
313 **pycompat.strkwargs(hookargs)
314 )
314 )
315 numitems[rev] -= 1
315 numitems[rev] -= 1
316 # Apply the fixes for this and any other revisions that are
316 # Apply the fixes for this and any other revisions that are
317 # ready and sitting at the front of the queue. Using a loop here
317 # ready and sitting at the front of the queue. Using a loop here
318 # prevents the queue from being blocked by the first revision to
318 # prevents the queue from being blocked by the first revision to
319 # be ready out of order.
319 # be ready out of order.
320 while commitorder and not numitems[commitorder[-1]]:
320 while commitorder and not numitems[commitorder[-1]]:
321 rev = commitorder.pop()
321 rev = commitorder.pop()
322 ctx = repo[rev]
322 ctx = repo[rev]
323 if rev == wdirrev:
323 if rev == wdirrev:
324 writeworkingdir(repo, ctx, filedata[rev], replacements)
324 writeworkingdir(repo, ctx, filedata[rev], replacements)
325 wdirwritten = bool(filedata[rev])
325 wdirwritten = bool(filedata[rev])
326 else:
326 else:
327 replacerev(ui, repo, ctx, filedata[rev], replacements)
327 replacerev(ui, repo, ctx, filedata[rev], replacements)
328 del filedata[rev]
328 del filedata[rev]
329
329
330 cleanup(repo, replacements, wdirwritten)
330 cleanup(repo, replacements, wdirwritten)
331 hookargs = {
331 hookargs = {
332 b'replacements': replacements,
332 b'replacements': replacements,
333 b'wdirwritten': wdirwritten,
333 b'wdirwritten': wdirwritten,
334 b'metadata': aggregatemetadata,
334 b'metadata': aggregatemetadata,
335 }
335 }
336 repo.hook(b'postfix', throw=True, **pycompat.strkwargs(hookargs))
336 repo.hook(b'postfix', throw=True, **pycompat.strkwargs(hookargs))
337
337
338
338
339 def cleanup(repo, replacements, wdirwritten):
339 def cleanup(repo, replacements, wdirwritten):
340 """Calls scmutil.cleanupnodes() with the given replacements.
340 """Calls scmutil.cleanupnodes() with the given replacements.
341
341
342 "replacements" is a dict from nodeid to nodeid, with one key and one value
342 "replacements" is a dict from nodeid to nodeid, with one key and one value
343 for every revision that was affected by fixing. This is slightly different
343 for every revision that was affected by fixing. This is slightly different
344 from cleanupnodes().
344 from cleanupnodes().
345
345
346 "wdirwritten" is a bool which tells whether the working copy was affected by
346 "wdirwritten" is a bool which tells whether the working copy was affected by
347 fixing, since it has no entry in "replacements".
347 fixing, since it has no entry in "replacements".
348
348
349 Useful as a hook point for extending "hg fix" with output summarizing the
349 Useful as a hook point for extending "hg fix" with output summarizing the
350 effects of the command, though we choose not to output anything here.
350 effects of the command, though we choose not to output anything here.
351 """
351 """
352 replacements = {
352 replacements = {
353 prec: [succ] for prec, succ in pycompat.iteritems(replacements)
353 prec: [succ] for prec, succ in pycompat.iteritems(replacements)
354 }
354 }
355 scmutil.cleanupnodes(repo, replacements, b'fix', fixphase=True)
355 scmutil.cleanupnodes(repo, replacements, b'fix', fixphase=True)
356
356
357
357
358 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
358 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
359 """"Constructs the list of files to be fixed at specific revisions
359 """"Constructs the list of files to be fixed at specific revisions
360
360
361 It is up to the caller how to consume the work items, and the only
361 It is up to the caller how to consume the work items, and the only
362 dependence between them is that replacement revisions must be committed in
362 dependence between them is that replacement revisions must be committed in
363 topological order. Each work item represents a file in the working copy or
363 topological order. Each work item represents a file in the working copy or
364 in some revision that should be fixed and written back to the working copy
364 in some revision that should be fixed and written back to the working copy
365 or into a replacement revision.
365 or into a replacement revision.
366
366
367 Work items for the same revision are grouped together, so that a worker
367 Work items for the same revision are grouped together, so that a worker
368 pool starting with the first N items in parallel is likely to finish the
368 pool starting with the first N items in parallel is likely to finish the
369 first revision's work before other revisions. This can allow us to write
369 first revision's work before other revisions. This can allow us to write
370 the result to disk and reduce memory footprint. At time of writing, the
370 the result to disk and reduce memory footprint. At time of writing, the
371 partition strategy in worker.py seems favorable to this. We also sort the
371 partition strategy in worker.py seems favorable to this. We also sort the
372 items by ascending revision number to match the order in which we commit
372 items by ascending revision number to match the order in which we commit
373 the fixes later.
373 the fixes later.
374 """
374 """
375 workqueue = []
375 workqueue = []
376 numitems = collections.defaultdict(int)
376 numitems = collections.defaultdict(int)
377 maxfilesize = ui.configbytes(b'fix', b'maxfilesize')
377 maxfilesize = ui.configbytes(b'fix', b'maxfilesize')
378 for rev in sorted(revstofix):
378 for rev in sorted(revstofix):
379 fixctx = repo[rev]
379 fixctx = repo[rev]
380 match = scmutil.match(fixctx, pats, opts)
380 match = scmutil.match(fixctx, pats, opts)
381 for path in sorted(
381 for path in sorted(
382 pathstofix(ui, repo, pats, opts, match, basectxs[rev], fixctx)
382 pathstofix(ui, repo, pats, opts, match, basectxs[rev], fixctx)
383 ):
383 ):
384 fctx = fixctx[path]
384 fctx = fixctx[path]
385 if fctx.islink():
385 if fctx.islink():
386 continue
386 continue
387 if fctx.size() > maxfilesize:
387 if fctx.size() > maxfilesize:
388 ui.warn(
388 ui.warn(
389 _(b'ignoring file larger than %s: %s\n')
389 _(b'ignoring file larger than %s: %s\n')
390 % (util.bytecount(maxfilesize), path)
390 % (util.bytecount(maxfilesize), path)
391 )
391 )
392 continue
392 continue
393 workqueue.append((rev, path))
393 workqueue.append((rev, path))
394 numitems[rev] += 1
394 numitems[rev] += 1
395 return workqueue, numitems
395 return workqueue, numitems
396
396
397
397
398 def getrevstofix(ui, repo, opts):
398 def getrevstofix(ui, repo, opts):
399 """Returns the set of revision numbers that should be fixed"""
399 """Returns the set of revision numbers that should be fixed"""
400 revs = set(scmutil.revrange(repo, opts[b'rev']))
400 revs = set(scmutil.revrange(repo, opts[b'rev']))
401 for rev in revs:
401 for rev in revs:
402 checkfixablectx(ui, repo, repo[rev])
402 checkfixablectx(ui, repo, repo[rev])
403 if revs:
403 if revs:
404 cmdutil.checkunfinished(repo)
404 cmdutil.checkunfinished(repo)
405 checknodescendants(repo, revs)
405 checknodescendants(repo, revs)
406 if opts.get(b'working_dir'):
406 if opts.get(b'working_dir'):
407 revs.add(wdirrev)
407 revs.add(wdirrev)
408 if list(merge.mergestate.read(repo).unresolved()):
408 if list(merge.mergestate.read(repo).unresolved()):
409 raise error.Abort(b'unresolved conflicts', hint=b"use 'hg resolve'")
409 raise error.Abort(b'unresolved conflicts', hint=b"use 'hg resolve'")
410 if not revs:
410 if not revs:
411 raise error.Abort(
411 raise error.Abort(
412 b'no changesets specified', hint=b'use --rev or --working-dir'
412 b'no changesets specified', hint=b'use --rev or --working-dir'
413 )
413 )
414 return revs
414 return revs
415
415
416
416
417 def checknodescendants(repo, revs):
417 def checknodescendants(repo, revs):
418 if not obsolete.isenabled(repo, obsolete.allowunstableopt) and repo.revs(
418 if not obsolete.isenabled(repo, obsolete.allowunstableopt) and repo.revs(
419 b'(%ld::) - (%ld)', revs, revs
419 b'(%ld::) - (%ld)', revs, revs
420 ):
420 ):
421 raise error.Abort(
421 raise error.Abort(
422 _(b'can only fix a changeset together with all its descendants')
422 _(b'can only fix a changeset together with all its descendants')
423 )
423 )
424
424
425
425
426 def checkfixablectx(ui, repo, ctx):
426 def checkfixablectx(ui, repo, ctx):
427 """Aborts if the revision shouldn't be replaced with a fixed one."""
427 """Aborts if the revision shouldn't be replaced with a fixed one."""
428 if not ctx.mutable():
428 if not ctx.mutable():
429 raise error.Abort(
429 raise error.Abort(
430 b'can\'t fix immutable changeset %s'
430 b'can\'t fix immutable changeset %s'
431 % (scmutil.formatchangeid(ctx),)
431 % (scmutil.formatchangeid(ctx),)
432 )
432 )
433 if ctx.obsolete():
433 if ctx.obsolete():
434 # It would be better to actually check if the revision has a successor.
434 # It would be better to actually check if the revision has a successor.
435 allowdivergence = ui.configbool(
435 allowdivergence = ui.configbool(
436 b'experimental', b'evolution.allowdivergence'
436 b'experimental', b'evolution.allowdivergence'
437 )
437 )
438 if not allowdivergence:
438 if not allowdivergence:
439 raise error.Abort(
439 raise error.Abort(
440 b'fixing obsolete revision could cause divergence'
440 b'fixing obsolete revision could cause divergence'
441 )
441 )
442
442
443
443
444 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
444 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
445 """Returns the set of files that should be fixed in a context
445 """Returns the set of files that should be fixed in a context
446
446
447 The result depends on the base contexts; we include any file that has
447 The result depends on the base contexts; we include any file that has
448 changed relative to any of the base contexts. Base contexts should be
448 changed relative to any of the base contexts. Base contexts should be
449 ancestors of the context being fixed.
449 ancestors of the context being fixed.
450 """
450 """
451 files = set()
451 files = set()
452 for basectx in basectxs:
452 for basectx in basectxs:
453 stat = basectx.status(
453 stat = basectx.status(
454 fixctx, match=match, listclean=bool(pats), listunknown=bool(pats)
454 fixctx, match=match, listclean=bool(pats), listunknown=bool(pats)
455 )
455 )
456 files.update(
456 files.update(
457 set(
457 set(
458 itertools.chain(
458 itertools.chain(
459 stat.added, stat.modified, stat.clean, stat.unknown
459 stat.added, stat.modified, stat.clean, stat.unknown
460 )
460 )
461 )
461 )
462 )
462 )
463 return files
463 return files
464
464
465
465
466 def lineranges(opts, path, basectxs, fixctx, content2):
466 def lineranges(opts, path, basectxs, fixctx, content2):
467 """Returns the set of line ranges that should be fixed in a file
467 """Returns the set of line ranges that should be fixed in a file
468
468
469 Of the form [(10, 20), (30, 40)].
469 Of the form [(10, 20), (30, 40)].
470
470
471 This depends on the given base contexts; we must consider lines that have
471 This depends on the given base contexts; we must consider lines that have
472 changed versus any of the base contexts, and whether the file has been
472 changed versus any of the base contexts, and whether the file has been
473 renamed versus any of them.
473 renamed versus any of them.
474
474
475 Another way to understand this is that we exclude line ranges that are
475 Another way to understand this is that we exclude line ranges that are
476 common to the file in all base contexts.
476 common to the file in all base contexts.
477 """
477 """
478 if opts.get(b'whole'):
478 if opts.get(b'whole'):
479 # Return a range containing all lines. Rely on the diff implementation's
479 # Return a range containing all lines. Rely on the diff implementation's
480 # idea of how many lines are in the file, instead of reimplementing it.
480 # idea of how many lines are in the file, instead of reimplementing it.
481 return difflineranges(b'', content2)
481 return difflineranges(b'', content2)
482
482
483 rangeslist = []
483 rangeslist = []
484 for basectx in basectxs:
484 for basectx in basectxs:
485 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
485 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
486 if basepath in basectx:
486 if basepath in basectx:
487 content1 = basectx[basepath].data()
487 content1 = basectx[basepath].data()
488 else:
488 else:
489 content1 = b''
489 content1 = b''
490 rangeslist.extend(difflineranges(content1, content2))
490 rangeslist.extend(difflineranges(content1, content2))
491 return unionranges(rangeslist)
491 return unionranges(rangeslist)
492
492
493
493
494 def unionranges(rangeslist):
494 def unionranges(rangeslist):
495 """Return the union of some closed intervals
495 """Return the union of some closed intervals
496
496
497 >>> unionranges([])
497 >>> unionranges([])
498 []
498 []
499 >>> unionranges([(1, 100)])
499 >>> unionranges([(1, 100)])
500 [(1, 100)]
500 [(1, 100)]
501 >>> unionranges([(1, 100), (1, 100)])
501 >>> unionranges([(1, 100), (1, 100)])
502 [(1, 100)]
502 [(1, 100)]
503 >>> unionranges([(1, 100), (2, 100)])
503 >>> unionranges([(1, 100), (2, 100)])
504 [(1, 100)]
504 [(1, 100)]
505 >>> unionranges([(1, 99), (1, 100)])
505 >>> unionranges([(1, 99), (1, 100)])
506 [(1, 100)]
506 [(1, 100)]
507 >>> unionranges([(1, 100), (40, 60)])
507 >>> unionranges([(1, 100), (40, 60)])
508 [(1, 100)]
508 [(1, 100)]
509 >>> unionranges([(1, 49), (50, 100)])
509 >>> unionranges([(1, 49), (50, 100)])
510 [(1, 100)]
510 [(1, 100)]
511 >>> unionranges([(1, 48), (50, 100)])
511 >>> unionranges([(1, 48), (50, 100)])
512 [(1, 48), (50, 100)]
512 [(1, 48), (50, 100)]
513 >>> unionranges([(1, 2), (3, 4), (5, 6)])
513 >>> unionranges([(1, 2), (3, 4), (5, 6)])
514 [(1, 6)]
514 [(1, 6)]
515 """
515 """
516 rangeslist = sorted(set(rangeslist))
516 rangeslist = sorted(set(rangeslist))
517 unioned = []
517 unioned = []
518 if rangeslist:
518 if rangeslist:
519 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
519 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
520 for a, b in rangeslist:
520 for a, b in rangeslist:
521 c, d = unioned[-1]
521 c, d = unioned[-1]
522 if a > d + 1:
522 if a > d + 1:
523 unioned.append((a, b))
523 unioned.append((a, b))
524 else:
524 else:
525 unioned[-1] = (c, max(b, d))
525 unioned[-1] = (c, max(b, d))
526 return unioned
526 return unioned
527
527
528
528
529 def difflineranges(content1, content2):
529 def difflineranges(content1, content2):
530 """Return list of line number ranges in content2 that differ from content1.
530 """Return list of line number ranges in content2 that differ from content1.
531
531
532 Line numbers are 1-based. The numbers are the first and last line contained
532 Line numbers are 1-based. The numbers are the first and last line contained
533 in the range. Single-line ranges have the same line number for the first and
533 in the range. Single-line ranges have the same line number for the first and
534 last line. Excludes any empty ranges that result from lines that are only
534 last line. Excludes any empty ranges that result from lines that are only
535 present in content1. Relies on mdiff's idea of where the line endings are in
535 present in content1. Relies on mdiff's idea of where the line endings are in
536 the string.
536 the string.
537
537
538 >>> from mercurial import pycompat
538 >>> from mercurial import pycompat
539 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
539 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
540 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
540 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
541 >>> difflineranges2(b'', b'')
541 >>> difflineranges2(b'', b'')
542 []
542 []
543 >>> difflineranges2(b'a', b'')
543 >>> difflineranges2(b'a', b'')
544 []
544 []
545 >>> difflineranges2(b'', b'A')
545 >>> difflineranges2(b'', b'A')
546 [(1, 1)]
546 [(1, 1)]
547 >>> difflineranges2(b'a', b'a')
547 >>> difflineranges2(b'a', b'a')
548 []
548 []
549 >>> difflineranges2(b'a', b'A')
549 >>> difflineranges2(b'a', b'A')
550 [(1, 1)]
550 [(1, 1)]
551 >>> difflineranges2(b'ab', b'')
551 >>> difflineranges2(b'ab', b'')
552 []
552 []
553 >>> difflineranges2(b'', b'AB')
553 >>> difflineranges2(b'', b'AB')
554 [(1, 2)]
554 [(1, 2)]
555 >>> difflineranges2(b'abc', b'ac')
555 >>> difflineranges2(b'abc', b'ac')
556 []
556 []
557 >>> difflineranges2(b'ab', b'aCb')
557 >>> difflineranges2(b'ab', b'aCb')
558 [(2, 2)]
558 [(2, 2)]
559 >>> difflineranges2(b'abc', b'aBc')
559 >>> difflineranges2(b'abc', b'aBc')
560 [(2, 2)]
560 [(2, 2)]
561 >>> difflineranges2(b'ab', b'AB')
561 >>> difflineranges2(b'ab', b'AB')
562 [(1, 2)]
562 [(1, 2)]
563 >>> difflineranges2(b'abcde', b'aBcDe')
563 >>> difflineranges2(b'abcde', b'aBcDe')
564 [(2, 2), (4, 4)]
564 [(2, 2), (4, 4)]
565 >>> difflineranges2(b'abcde', b'aBCDe')
565 >>> difflineranges2(b'abcde', b'aBCDe')
566 [(2, 4)]
566 [(2, 4)]
567 """
567 """
568 ranges = []
568 ranges = []
569 for lines, kind in mdiff.allblocks(content1, content2):
569 for lines, kind in mdiff.allblocks(content1, content2):
570 firstline, lastline = lines[2:4]
570 firstline, lastline = lines[2:4]
571 if kind == b'!' and firstline != lastline:
571 if kind == b'!' and firstline != lastline:
572 ranges.append((firstline + 1, lastline))
572 ranges.append((firstline + 1, lastline))
573 return ranges
573 return ranges
574
574
575
575
576 def getbasectxs(repo, opts, revstofix):
576 def getbasectxs(repo, opts, revstofix):
577 """Returns a map of the base contexts for each revision
577 """Returns a map of the base contexts for each revision
578
578
579 The base contexts determine which lines are considered modified when we
579 The base contexts determine which lines are considered modified when we
580 attempt to fix just the modified lines in a file. It also determines which
580 attempt to fix just the modified lines in a file. It also determines which
581 files we attempt to fix, so it is important to compute this even when
581 files we attempt to fix, so it is important to compute this even when
582 --whole is used.
582 --whole is used.
583 """
583 """
584 # The --base flag overrides the usual logic, and we give every revision
584 # The --base flag overrides the usual logic, and we give every revision
585 # exactly the set of baserevs that the user specified.
585 # exactly the set of baserevs that the user specified.
586 if opts.get(b'base'):
586 if opts.get(b'base'):
587 baserevs = set(scmutil.revrange(repo, opts.get(b'base')))
587 baserevs = set(scmutil.revrange(repo, opts.get(b'base')))
588 if not baserevs:
588 if not baserevs:
589 baserevs = {nullrev}
589 baserevs = {nullrev}
590 basectxs = {repo[rev] for rev in baserevs}
590 basectxs = {repo[rev] for rev in baserevs}
591 return {rev: basectxs for rev in revstofix}
591 return {rev: basectxs for rev in revstofix}
592
592
593 # Proceed in topological order so that we can easily determine each
593 # Proceed in topological order so that we can easily determine each
594 # revision's baserevs by looking at its parents and their baserevs.
594 # revision's baserevs by looking at its parents and their baserevs.
595 basectxs = collections.defaultdict(set)
595 basectxs = collections.defaultdict(set)
596 for rev in sorted(revstofix):
596 for rev in sorted(revstofix):
597 ctx = repo[rev]
597 ctx = repo[rev]
598 for pctx in ctx.parents():
598 for pctx in ctx.parents():
599 if pctx.rev() in basectxs:
599 if pctx.rev() in basectxs:
600 basectxs[rev].update(basectxs[pctx.rev()])
600 basectxs[rev].update(basectxs[pctx.rev()])
601 else:
601 else:
602 basectxs[rev].add(pctx)
602 basectxs[rev].add(pctx)
603 return basectxs
603 return basectxs
604
604
605
605
606 def fixfile(ui, repo, opts, fixers, fixctx, path, basectxs):
606 def fixfile(ui, repo, opts, fixers, fixctx, path, basectxs):
607 """Run any configured fixers that should affect the file in this context
607 """Run any configured fixers that should affect the file in this context
608
608
609 Returns the file content that results from applying the fixers in some order
609 Returns the file content that results from applying the fixers in some order
610 starting with the file's content in the fixctx. Fixers that support line
610 starting with the file's content in the fixctx. Fixers that support line
611 ranges will affect lines that have changed relative to any of the basectxs
611 ranges will affect lines that have changed relative to any of the basectxs
612 (i.e. they will only avoid lines that are common to all basectxs).
612 (i.e. they will only avoid lines that are common to all basectxs).
613
613
614 A fixer tool's stdout will become the file's new content if and only if it
614 A fixer tool's stdout will become the file's new content if and only if it
615 exits with code zero. The fixer tool's working directory is the repository's
615 exits with code zero. The fixer tool's working directory is the repository's
616 root.
616 root.
617 """
617 """
618 metadata = {}
618 metadata = {}
619 newdata = fixctx[path].data()
619 newdata = fixctx[path].data()
620 for fixername, fixer in pycompat.iteritems(fixers):
620 for fixername, fixer in pycompat.iteritems(fixers):
621 if fixer.affects(opts, fixctx, path):
621 if fixer.affects(opts, fixctx, path):
622 ranges = lineranges(opts, path, basectxs, fixctx, newdata)
622 ranges = lineranges(opts, path, basectxs, fixctx, newdata)
623 command = fixer.command(ui, path, ranges)
623 command = fixer.command(ui, path, ranges)
624 if command is None:
624 if command is None:
625 continue
625 continue
626 ui.debug(b'subprocess: %s\n' % (command,))
626 ui.debug(b'subprocess: %s\n' % (command,))
627 proc = subprocess.Popen(
627 proc = subprocess.Popen(
628 procutil.tonativestr(command),
628 procutil.tonativestr(command),
629 shell=True,
629 shell=True,
630 cwd=procutil.tonativestr(repo.root),
630 cwd=procutil.tonativestr(repo.root),
631 stdin=subprocess.PIPE,
631 stdin=subprocess.PIPE,
632 stdout=subprocess.PIPE,
632 stdout=subprocess.PIPE,
633 stderr=subprocess.PIPE,
633 stderr=subprocess.PIPE,
634 )
634 )
635 stdout, stderr = proc.communicate(newdata)
635 stdout, stderr = proc.communicate(newdata)
636 if stderr:
636 if stderr:
637 showstderr(ui, fixctx.rev(), fixername, stderr)
637 showstderr(ui, fixctx.rev(), fixername, stderr)
638 newerdata = stdout
638 newerdata = stdout
639 if fixer.shouldoutputmetadata():
639 if fixer.shouldoutputmetadata():
640 try:
640 try:
641 metadatajson, newerdata = stdout.split(b'\0', 1)
641 metadatajson, newerdata = stdout.split(b'\0', 1)
642 metadata[fixername] = json.loads(metadatajson)
642 metadata[fixername] = json.loads(metadatajson)
643 except ValueError:
643 except ValueError:
644 ui.warn(
644 ui.warn(
645 _(b'ignored invalid output from fixer tool: %s\n')
645 _(b'ignored invalid output from fixer tool: %s\n')
646 % (fixername,)
646 % (fixername,)
647 )
647 )
648 continue
648 continue
649 else:
649 else:
650 metadata[fixername] = None
650 metadata[fixername] = None
651 if proc.returncode == 0:
651 if proc.returncode == 0:
652 newdata = newerdata
652 newdata = newerdata
653 else:
653 else:
654 if not stderr:
654 if not stderr:
655 message = _(b'exited with status %d\n') % (proc.returncode,)
655 message = _(b'exited with status %d\n') % (proc.returncode,)
656 showstderr(ui, fixctx.rev(), fixername, message)
656 showstderr(ui, fixctx.rev(), fixername, message)
657 checktoolfailureaction(
657 checktoolfailureaction(
658 ui,
658 ui,
659 _(b'no fixes will be applied'),
659 _(b'no fixes will be applied'),
660 hint=_(
660 hint=_(
661 b'use --config fix.failure=continue to apply any '
661 b'use --config fix.failure=continue to apply any '
662 b'successful fixes anyway'
662 b'successful fixes anyway'
663 ),
663 ),
664 )
664 )
665 return metadata, newdata
665 return metadata, newdata
666
666
667
667
668 def showstderr(ui, rev, fixername, stderr):
668 def showstderr(ui, rev, fixername, stderr):
669 """Writes the lines of the stderr string as warnings on the ui
669 """Writes the lines of the stderr string as warnings on the ui
670
670
671 Uses the revision number and fixername to give more context to each line of
671 Uses the revision number and fixername to give more context to each line of
672 the error message. Doesn't include file names, since those take up a lot of
672 the error message. Doesn't include file names, since those take up a lot of
673 space and would tend to be included in the error message if they were
673 space and would tend to be included in the error message if they were
674 relevant.
674 relevant.
675 """
675 """
676 for line in re.split(b'[\r\n]+', stderr):
676 for line in re.split(b'[\r\n]+', stderr):
677 if line:
677 if line:
678 ui.warn(b'[')
678 ui.warn(b'[')
679 if rev is None:
679 if rev is None:
680 ui.warn(_(b'wdir'), label=b'evolve.rev')
680 ui.warn(_(b'wdir'), label=b'evolve.rev')
681 else:
681 else:
682 ui.warn((str(rev)), label=b'evolve.rev')
682 ui.warn((str(rev)), label=b'evolve.rev')
683 ui.warn(b'] %s: %s\n' % (fixername, line))
683 ui.warn(b'] %s: %s\n' % (fixername, line))
684
684
685
685
686 def writeworkingdir(repo, ctx, filedata, replacements):
686 def writeworkingdir(repo, ctx, filedata, replacements):
687 """Write new content to the working copy and check out the new p1 if any
687 """Write new content to the working copy and check out the new p1 if any
688
688
689 We check out a new revision if and only if we fixed something in both the
689 We check out a new revision if and only if we fixed something in both the
690 working directory and its parent revision. This avoids the need for a full
690 working directory and its parent revision. This avoids the need for a full
691 update/merge, and means that the working directory simply isn't affected
691 update/merge, and means that the working directory simply isn't affected
692 unless the --working-dir flag is given.
692 unless the --working-dir flag is given.
693
693
694 Directly updates the dirstate for the affected files.
694 Directly updates the dirstate for the affected files.
695 """
695 """
696 for path, data in pycompat.iteritems(filedata):
696 for path, data in pycompat.iteritems(filedata):
697 fctx = ctx[path]
697 fctx = ctx[path]
698 fctx.write(data, fctx.flags())
698 fctx.write(data, fctx.flags())
699 if repo.dirstate[path] == b'n':
699 if repo.dirstate[path] == b'n':
700 repo.dirstate.normallookup(path)
700 repo.dirstate.normallookup(path)
701
701
702 oldparentnodes = repo.dirstate.parents()
702 oldparentnodes = repo.dirstate.parents()
703 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
703 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
704 if newparentnodes != oldparentnodes:
704 if newparentnodes != oldparentnodes:
705 repo.setparents(*newparentnodes)
705 repo.setparents(*newparentnodes)
706
706
707
707
708 def replacerev(ui, repo, ctx, filedata, replacements):
708 def replacerev(ui, repo, ctx, filedata, replacements):
709 """Commit a new revision like the given one, but with file content changes
709 """Commit a new revision like the given one, but with file content changes
710
710
711 "ctx" is the original revision to be replaced by a modified one.
711 "ctx" is the original revision to be replaced by a modified one.
712
712
713 "filedata" is a dict that maps paths to their new file content. All other
713 "filedata" is a dict that maps paths to their new file content. All other
714 paths will be recreated from the original revision without changes.
714 paths will be recreated from the original revision without changes.
715 "filedata" may contain paths that didn't exist in the original revision;
715 "filedata" may contain paths that didn't exist in the original revision;
716 they will be added.
716 they will be added.
717
717
718 "replacements" is a dict that maps a single node to a single node, and it is
718 "replacements" is a dict that maps a single node to a single node, and it is
719 updated to indicate the original revision is replaced by the newly created
719 updated to indicate the original revision is replaced by the newly created
720 one. No entry is added if the replacement's node already exists.
720 one. No entry is added if the replacement's node already exists.
721
721
722 The new revision has the same parents as the old one, unless those parents
722 The new revision has the same parents as the old one, unless those parents
723 have already been replaced, in which case those replacements are the parents
723 have already been replaced, in which case those replacements are the parents
724 of this new revision. Thus, if revisions are replaced in topological order,
724 of this new revision. Thus, if revisions are replaced in topological order,
725 there is no need to rebase them into the original topology later.
725 there is no need to rebase them into the original topology later.
726 """
726 """
727
727
728 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
728 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
729 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
729 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
730 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
730 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
731 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
731 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
732
732
733 # We don't want to create a revision that has no changes from the original,
733 # We don't want to create a revision that has no changes from the original,
734 # but we should if the original revision's parent has been replaced.
734 # but we should if the original revision's parent has been replaced.
735 # Otherwise, we would produce an orphan that needs no actual human
735 # Otherwise, we would produce an orphan that needs no actual human
736 # intervention to evolve. We can't rely on commit() to avoid creating the
736 # intervention to evolve. We can't rely on commit() to avoid creating the
737 # un-needed revision because the extra field added below produces a new hash
737 # un-needed revision because the extra field added below produces a new hash
738 # regardless of file content changes.
738 # regardless of file content changes.
739 if (
739 if (
740 not filedata
740 not filedata
741 and p1ctx.node() not in replacements
741 and p1ctx.node() not in replacements
742 and p2ctx.node() not in replacements
742 and p2ctx.node() not in replacements
743 ):
743 ):
744 return
744 return
745
745
746 def filectxfn(repo, memctx, path):
746 def filectxfn(repo, memctx, path):
747 if path not in ctx:
747 if path not in ctx:
748 return None
748 return None
749 fctx = ctx[path]
749 fctx = ctx[path]
750 copysource = fctx.copysource()
750 copysource = fctx.copysource()
751 return context.memfilectx(
751 return context.memfilectx(
752 repo,
752 repo,
753 memctx,
753 memctx,
754 path=fctx.path(),
754 path=fctx.path(),
755 data=filedata.get(path, fctx.data()),
755 data=filedata.get(path, fctx.data()),
756 islink=fctx.islink(),
756 islink=fctx.islink(),
757 isexec=fctx.isexec(),
757 isexec=fctx.isexec(),
758 copysource=copysource,
758 copysource=copysource,
759 )
759 )
760
760
761 extra = ctx.extra().copy()
761 extra = ctx.extra().copy()
762 extra[b'fix_source'] = ctx.hex()
762 extra[b'fix_source'] = ctx.hex()
763
763
764 memctx = context.memctx(
764 memctx = context.memctx(
765 repo,
765 repo,
766 parents=(newp1node, newp2node),
766 parents=(newp1node, newp2node),
767 text=ctx.description(),
767 text=ctx.description(),
768 files=set(ctx.files()) | set(filedata.keys()),
768 files=set(ctx.files()) | set(filedata.keys()),
769 filectxfn=filectxfn,
769 filectxfn=filectxfn,
770 user=ctx.user(),
770 user=ctx.user(),
771 date=ctx.date(),
771 date=ctx.date(),
772 extra=extra,
772 extra=extra,
773 branch=ctx.branch(),
773 branch=ctx.branch(),
774 editor=None,
774 editor=None,
775 )
775 )
776 sucnode = memctx.commit()
776 sucnode = memctx.commit()
777 prenode = ctx.node()
777 prenode = ctx.node()
778 if prenode == sucnode:
778 if prenode == sucnode:
779 ui.debug(b'node %s already existed\n' % (ctx.hex()))
779 ui.debug(b'node %s already existed\n' % (ctx.hex()))
780 else:
780 else:
781 replacements[ctx.node()] = sucnode
781 replacements[ctx.node()] = sucnode
782
782
783
783
784 def getfixers(ui):
784 def getfixers(ui):
785 """Returns a map of configured fixer tools indexed by their names
785 """Returns a map of configured fixer tools indexed by their names
786
786
787 Each value is a Fixer object with methods that implement the behavior of the
787 Each value is a Fixer object with methods that implement the behavior of the
788 fixer's config suboptions. Does not validate the config values.
788 fixer's config suboptions. Does not validate the config values.
789 """
789 """
790 fixers = {}
790 fixers = {}
791 for name in fixernames(ui):
791 for name in fixernames(ui):
792 enabled = ui.configbool(b'fix', name + b':enabled')
792 enabled = ui.configbool(b'fix', name + b':enabled')
793 command = ui.config(b'fix', name + b':command')
793 command = ui.config(b'fix', name + b':command')
794 pattern = ui.config(b'fix', name + b':pattern')
794 pattern = ui.config(b'fix', name + b':pattern')
795 linerange = ui.config(b'fix', name + b':linerange')
795 linerange = ui.config(b'fix', name + b':linerange')
796 priority = ui.configint(b'fix', name + b':priority')
796 priority = ui.configint(b'fix', name + b':priority')
797 metadata = ui.configbool(b'fix', name + b':metadata')
797 metadata = ui.configbool(b'fix', name + b':metadata')
798 skipclean = ui.configbool(b'fix', name + b':skipclean')
798 skipclean = ui.configbool(b'fix', name + b':skipclean')
799 # Don't use a fixer if it has no pattern configured. It would be
799 # Don't use a fixer if it has no pattern configured. It would be
800 # dangerous to let it affect all files. It would be pointless to let it
800 # dangerous to let it affect all files. It would be pointless to let it
801 # affect no files. There is no reasonable subset of files to use as the
801 # affect no files. There is no reasonable subset of files to use as the
802 # default.
802 # default.
803 if pattern is None:
803 if command is None:
804 ui.warn(
805 _(b'fixer tool has no command configuration: %s\n') % (name,)
806 )
807 elif pattern is None:
804 ui.warn(
808 ui.warn(
805 _(b'fixer tool has no pattern configuration: %s\n') % (name,)
809 _(b'fixer tool has no pattern configuration: %s\n') % (name,)
806 )
810 )
807 elif not enabled:
811 elif not enabled:
808 ui.debug(b'ignoring disabled fixer tool: %s\n' % (name,))
812 ui.debug(b'ignoring disabled fixer tool: %s\n' % (name,))
809 else:
813 else:
810 fixers[name] = Fixer(
814 fixers[name] = Fixer(
811 command, pattern, linerange, priority, metadata, skipclean
815 command, pattern, linerange, priority, metadata, skipclean
812 )
816 )
813 return collections.OrderedDict(
817 return collections.OrderedDict(
814 sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
818 sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
815 )
819 )
816
820
817
821
818 def fixernames(ui):
822 def fixernames(ui):
819 """Returns the names of [fix] config options that have suboptions"""
823 """Returns the names of [fix] config options that have suboptions"""
820 names = set()
824 names = set()
821 for k, v in ui.configitems(b'fix'):
825 for k, v in ui.configitems(b'fix'):
822 if b':' in k:
826 if b':' in k:
823 names.add(k.split(b':', 1)[0])
827 names.add(k.split(b':', 1)[0])
824 return names
828 return names
825
829
826
830
827 class Fixer(object):
831 class Fixer(object):
828 """Wraps the raw config values for a fixer with methods"""
832 """Wraps the raw config values for a fixer with methods"""
829
833
830 def __init__(
834 def __init__(
831 self, command, pattern, linerange, priority, metadata, skipclean
835 self, command, pattern, linerange, priority, metadata, skipclean
832 ):
836 ):
833 self._command = command
837 self._command = command
834 self._pattern = pattern
838 self._pattern = pattern
835 self._linerange = linerange
839 self._linerange = linerange
836 self._priority = priority
840 self._priority = priority
837 self._metadata = metadata
841 self._metadata = metadata
838 self._skipclean = skipclean
842 self._skipclean = skipclean
839
843
840 def affects(self, opts, fixctx, path):
844 def affects(self, opts, fixctx, path):
841 """Should this fixer run on the file at the given path and context?"""
845 """Should this fixer run on the file at the given path and context?"""
842 return self._pattern is not None and scmutil.match(
846 return self._pattern is not None and scmutil.match(
843 fixctx, [self._pattern], opts
847 fixctx, [self._pattern], opts
844 )(path)
848 )(path)
845
849
846 def shouldoutputmetadata(self):
850 def shouldoutputmetadata(self):
847 """Should the stdout of this fixer start with JSON and a null byte?"""
851 """Should the stdout of this fixer start with JSON and a null byte?"""
848 return self._metadata
852 return self._metadata
849
853
850 def command(self, ui, path, ranges):
854 def command(self, ui, path, ranges):
851 """A shell command to use to invoke this fixer on the given file/lines
855 """A shell command to use to invoke this fixer on the given file/lines
852
856
853 May return None if there is no appropriate command to run for the given
857 May return None if there is no appropriate command to run for the given
854 parameters.
858 parameters.
855 """
859 """
856 expand = cmdutil.rendercommandtemplate
860 expand = cmdutil.rendercommandtemplate
857 parts = [
861 parts = [
858 expand(
862 expand(
859 ui,
863 ui,
860 self._command,
864 self._command,
861 {b'rootpath': path, b'basename': os.path.basename(path)},
865 {b'rootpath': path, b'basename': os.path.basename(path)},
862 )
866 )
863 ]
867 ]
864 if self._linerange:
868 if self._linerange:
865 if self._skipclean and not ranges:
869 if self._skipclean and not ranges:
866 # No line ranges to fix, so don't run the fixer.
870 # No line ranges to fix, so don't run the fixer.
867 return None
871 return None
868 for first, last in ranges:
872 for first, last in ranges:
869 parts.append(
873 parts.append(
870 expand(
874 expand(
871 ui, self._linerange, {b'first': first, b'last': last}
875 ui, self._linerange, {b'first': first, b'last': last}
872 )
876 )
873 )
877 )
874 return b' '.join(parts)
878 return b' '.join(parts)
@@ -1,1421 +1,1423 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 $ hg fix --debug --working-dir --config "fix.nocommand:pattern=foo.bar"
1342 fixer tool has no command configuration: nocommand
1341
1343
1342 $ cd ..
1344 $ cd ..
1343
1345
1344 Tools can be disabled. Disabled tools do nothing but print a debug message.
1346 Tools can be disabled. Disabled tools do nothing but print a debug message.
1345
1347
1346 $ hg init disabled
1348 $ hg init disabled
1347 $ cd disabled
1349 $ cd disabled
1348
1350
1349 $ printf "foo\n" > foo
1351 $ printf "foo\n" > foo
1350 $ hg add -q
1352 $ hg add -q
1351 $ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \
1353 $ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \
1352 > --config "fix.disabled:pattern=foo" \
1354 > --config "fix.disabled:pattern=foo" \
1353 > --config "fix.disabled:enabled=false"
1355 > --config "fix.disabled:enabled=false"
1354 ignoring disabled fixer tool: disabled
1356 ignoring disabled fixer tool: disabled
1355 $ cat foo
1357 $ cat foo
1356 foo
1358 foo
1357
1359
1358 $ cd ..
1360 $ cd ..
1359
1361
1360 Test that we can configure a fixer to affect all files regardless of the cwd.
1362 Test that we can configure a fixer to affect all files regardless of the cwd.
1361 The way we invoke matching must not prohibit this.
1363 The way we invoke matching must not prohibit this.
1362
1364
1363 $ hg init affectallfiles
1365 $ hg init affectallfiles
1364 $ cd affectallfiles
1366 $ cd affectallfiles
1365
1367
1366 $ mkdir foo bar
1368 $ mkdir foo bar
1367 $ printf "foo" > foo/file
1369 $ printf "foo" > foo/file
1368 $ printf "bar" > bar/file
1370 $ printf "bar" > bar/file
1369 $ printf "baz" > baz_file
1371 $ printf "baz" > baz_file
1370 $ hg add -q
1372 $ hg add -q
1371
1373
1372 $ cd bar
1374 $ cd bar
1373 $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \
1375 $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \
1374 > --config "fix.cooltool:pattern=rootglob:**"
1376 > --config "fix.cooltool:pattern=rootglob:**"
1375 $ cd ..
1377 $ cd ..
1376
1378
1377 $ cat foo/file
1379 $ cat foo/file
1378 fixed
1380 fixed
1379 $ cat bar/file
1381 $ cat bar/file
1380 fixed
1382 fixed
1381 $ cat baz_file
1383 $ cat baz_file
1382 fixed
1384 fixed
1383
1385
1384 $ cd ..
1386 $ cd ..
1385
1387
1386 Tools should be able to run on unchanged files, even if they set :linerange.
1388 Tools should be able to run on unchanged files, even if they set :linerange.
1387 This includes a corner case where deleted chunks of a file are not considered
1389 This includes a corner case where deleted chunks of a file are not considered
1388 changes.
1390 changes.
1389
1391
1390 $ hg init skipclean
1392 $ hg init skipclean
1391 $ cd skipclean
1393 $ cd skipclean
1392
1394
1393 $ printf "a\nb\nc\n" > foo
1395 $ printf "a\nb\nc\n" > foo
1394 $ printf "a\nb\nc\n" > bar
1396 $ printf "a\nb\nc\n" > bar
1395 $ printf "a\nb\nc\n" > baz
1397 $ printf "a\nb\nc\n" > baz
1396 $ hg commit -Aqm "base"
1398 $ hg commit -Aqm "base"
1397
1399
1398 $ printf "a\nc\n" > foo
1400 $ printf "a\nc\n" > foo
1399 $ printf "a\nx\nc\n" > baz
1401 $ printf "a\nx\nc\n" > baz
1400
1402
1401 $ cat >> print.py <<EOF
1403 $ cat >> print.py <<EOF
1402 > import sys
1404 > import sys
1403 > for a in sys.argv[1:]:
1405 > for a in sys.argv[1:]:
1404 > print(a)
1406 > print(a)
1405 > EOF
1407 > EOF
1406
1408
1407 $ hg fix --working-dir foo bar baz \
1409 $ hg fix --working-dir foo bar baz \
1408 > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \
1410 > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \
1409 > --config 'fix.changedlines:linerange="{first} through {last}"' \
1411 > --config 'fix.changedlines:linerange="{first} through {last}"' \
1410 > --config 'fix.changedlines:pattern=rootglob:**' \
1412 > --config 'fix.changedlines:pattern=rootglob:**' \
1411 > --config 'fix.changedlines:skipclean=false'
1413 > --config 'fix.changedlines:skipclean=false'
1412
1414
1413 $ cat foo
1415 $ cat foo
1414 Line ranges:
1416 Line ranges:
1415 $ cat bar
1417 $ cat bar
1416 Line ranges:
1418 Line ranges:
1417 $ cat baz
1419 $ cat baz
1418 Line ranges:
1420 Line ranges:
1419 2 through 2
1421 2 through 2
1420
1422
1421 $ cd ..
1423 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now