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