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