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