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