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