##// END OF EJS Templates
fix: allow fixing untracked files when given as arguments...
Danny Hooper -
r42175:7f6b375a default
parent child Browse files
Show More
@@ -1,689 +1,687 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 -n
65 sort:command = sort -n
66 head:command = head -n 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:
286 continue
287 fctx = fixctx[path]
285 fctx = fixctx[path]
288 if fctx.islink():
286 if fctx.islink():
289 continue
287 continue
290 if fctx.size() > maxfilesize:
288 if fctx.size() > maxfilesize:
291 ui.warn(_('ignoring file larger than %s: %s\n') %
289 ui.warn(_('ignoring file larger than %s: %s\n') %
292 (util.bytecount(maxfilesize), path))
290 (util.bytecount(maxfilesize), path))
293 continue
291 continue
294 workqueue.append((rev, path))
292 workqueue.append((rev, path))
295 numitems[rev] += 1
293 numitems[rev] += 1
296 return workqueue, numitems
294 return workqueue, numitems
297
295
298 def getrevstofix(ui, repo, opts):
296 def getrevstofix(ui, repo, opts):
299 """Returns the set of revision numbers that should be fixed"""
297 """Returns the set of revision numbers that should be fixed"""
300 revs = set(scmutil.revrange(repo, opts['rev']))
298 revs = set(scmutil.revrange(repo, opts['rev']))
301 for rev in revs:
299 for rev in revs:
302 checkfixablectx(ui, repo, repo[rev])
300 checkfixablectx(ui, repo, repo[rev])
303 if revs:
301 if revs:
304 cmdutil.checkunfinished(repo)
302 cmdutil.checkunfinished(repo)
305 checknodescendants(repo, revs)
303 checknodescendants(repo, revs)
306 if opts.get('working_dir'):
304 if opts.get('working_dir'):
307 revs.add(wdirrev)
305 revs.add(wdirrev)
308 if list(merge.mergestate.read(repo).unresolved()):
306 if list(merge.mergestate.read(repo).unresolved()):
309 raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
307 raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
310 if not revs:
308 if not revs:
311 raise error.Abort(
309 raise error.Abort(
312 'no changesets specified', hint='use --rev or --working-dir')
310 'no changesets specified', hint='use --rev or --working-dir')
313 return revs
311 return revs
314
312
315 def checknodescendants(repo, revs):
313 def checknodescendants(repo, revs):
316 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
314 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
317 repo.revs('(%ld::) - (%ld)', revs, revs)):
315 repo.revs('(%ld::) - (%ld)', revs, revs)):
318 raise error.Abort(_('can only fix a changeset together '
316 raise error.Abort(_('can only fix a changeset together '
319 'with all its descendants'))
317 'with all its descendants'))
320
318
321 def checkfixablectx(ui, repo, ctx):
319 def checkfixablectx(ui, repo, ctx):
322 """Aborts if the revision shouldn't be replaced with a fixed one."""
320 """Aborts if the revision shouldn't be replaced with a fixed one."""
323 if not ctx.mutable():
321 if not ctx.mutable():
324 raise error.Abort('can\'t fix immutable changeset %s' %
322 raise error.Abort('can\'t fix immutable changeset %s' %
325 (scmutil.formatchangeid(ctx),))
323 (scmutil.formatchangeid(ctx),))
326 if ctx.obsolete():
324 if ctx.obsolete():
327 # It would be better to actually check if the revision has a successor.
325 # It would be better to actually check if the revision has a successor.
328 allowdivergence = ui.configbool('experimental',
326 allowdivergence = ui.configbool('experimental',
329 'evolution.allowdivergence')
327 'evolution.allowdivergence')
330 if not allowdivergence:
328 if not allowdivergence:
331 raise error.Abort('fixing obsolete revision could cause divergence')
329 raise error.Abort('fixing obsolete revision could cause divergence')
332
330
333 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
331 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
334 """Returns the set of files that should be fixed in a context
332 """Returns the set of files that should be fixed in a context
335
333
336 The result depends on the base contexts; we include any file that has
334 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
335 changed relative to any of the base contexts. Base contexts should be
338 ancestors of the context being fixed.
336 ancestors of the context being fixed.
339 """
337 """
340 files = set()
338 files = set()
341 for basectx in basectxs:
339 for basectx in basectxs:
342 stat = basectx.status(fixctx, match=match, listclean=bool(pats),
340 stat = basectx.status(fixctx, match=match, listclean=bool(pats),
343 listunknown=bool(pats))
341 listunknown=bool(pats))
344 files.update(
342 files.update(
345 set(itertools.chain(stat.added, stat.modified, stat.clean,
343 set(itertools.chain(stat.added, stat.modified, stat.clean,
346 stat.unknown)))
344 stat.unknown)))
347 return files
345 return files
348
346
349 def lineranges(opts, path, basectxs, fixctx, content2):
347 def lineranges(opts, path, basectxs, fixctx, content2):
350 """Returns the set of line ranges that should be fixed in a file
348 """Returns the set of line ranges that should be fixed in a file
351
349
352 Of the form [(10, 20), (30, 40)].
350 Of the form [(10, 20), (30, 40)].
353
351
354 This depends on the given base contexts; we must consider lines that have
352 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
353 changed versus any of the base contexts, and whether the file has been
356 renamed versus any of them.
354 renamed versus any of them.
357
355
358 Another way to understand this is that we exclude line ranges that are
356 Another way to understand this is that we exclude line ranges that are
359 common to the file in all base contexts.
357 common to the file in all base contexts.
360 """
358 """
361 if opts.get('whole'):
359 if opts.get('whole'):
362 # Return a range containing all lines. Rely on the diff implementation's
360 # 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.
361 # idea of how many lines are in the file, instead of reimplementing it.
364 return difflineranges('', content2)
362 return difflineranges('', content2)
365
363
366 rangeslist = []
364 rangeslist = []
367 for basectx in basectxs:
365 for basectx in basectxs:
368 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
366 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
369 if basepath in basectx:
367 if basepath in basectx:
370 content1 = basectx[basepath].data()
368 content1 = basectx[basepath].data()
371 else:
369 else:
372 content1 = ''
370 content1 = ''
373 rangeslist.extend(difflineranges(content1, content2))
371 rangeslist.extend(difflineranges(content1, content2))
374 return unionranges(rangeslist)
372 return unionranges(rangeslist)
375
373
376 def unionranges(rangeslist):
374 def unionranges(rangeslist):
377 """Return the union of some closed intervals
375 """Return the union of some closed intervals
378
376
379 >>> unionranges([])
377 >>> unionranges([])
380 []
378 []
381 >>> unionranges([(1, 100)])
379 >>> unionranges([(1, 100)])
382 [(1, 100)]
380 [(1, 100)]
383 >>> unionranges([(1, 100), (1, 100)])
381 >>> unionranges([(1, 100), (1, 100)])
384 [(1, 100)]
382 [(1, 100)]
385 >>> unionranges([(1, 100), (2, 100)])
383 >>> unionranges([(1, 100), (2, 100)])
386 [(1, 100)]
384 [(1, 100)]
387 >>> unionranges([(1, 99), (1, 100)])
385 >>> unionranges([(1, 99), (1, 100)])
388 [(1, 100)]
386 [(1, 100)]
389 >>> unionranges([(1, 100), (40, 60)])
387 >>> unionranges([(1, 100), (40, 60)])
390 [(1, 100)]
388 [(1, 100)]
391 >>> unionranges([(1, 49), (50, 100)])
389 >>> unionranges([(1, 49), (50, 100)])
392 [(1, 100)]
390 [(1, 100)]
393 >>> unionranges([(1, 48), (50, 100)])
391 >>> unionranges([(1, 48), (50, 100)])
394 [(1, 48), (50, 100)]
392 [(1, 48), (50, 100)]
395 >>> unionranges([(1, 2), (3, 4), (5, 6)])
393 >>> unionranges([(1, 2), (3, 4), (5, 6)])
396 [(1, 6)]
394 [(1, 6)]
397 """
395 """
398 rangeslist = sorted(set(rangeslist))
396 rangeslist = sorted(set(rangeslist))
399 unioned = []
397 unioned = []
400 if rangeslist:
398 if rangeslist:
401 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
399 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
402 for a, b in rangeslist:
400 for a, b in rangeslist:
403 c, d = unioned[-1]
401 c, d = unioned[-1]
404 if a > d + 1:
402 if a > d + 1:
405 unioned.append((a, b))
403 unioned.append((a, b))
406 else:
404 else:
407 unioned[-1] = (c, max(b, d))
405 unioned[-1] = (c, max(b, d))
408 return unioned
406 return unioned
409
407
410 def difflineranges(content1, content2):
408 def difflineranges(content1, content2):
411 """Return list of line number ranges in content2 that differ from content1.
409 """Return list of line number ranges in content2 that differ from content1.
412
410
413 Line numbers are 1-based. The numbers are the first and last line contained
411 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
412 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
413 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
414 present in content1. Relies on mdiff's idea of where the line endings are in
417 the string.
415 the string.
418
416
419 >>> from mercurial import pycompat
417 >>> from mercurial import pycompat
420 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
418 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
421 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
419 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
422 >>> difflineranges2(b'', b'')
420 >>> difflineranges2(b'', b'')
423 []
421 []
424 >>> difflineranges2(b'a', b'')
422 >>> difflineranges2(b'a', b'')
425 []
423 []
426 >>> difflineranges2(b'', b'A')
424 >>> difflineranges2(b'', b'A')
427 [(1, 1)]
425 [(1, 1)]
428 >>> difflineranges2(b'a', b'a')
426 >>> difflineranges2(b'a', b'a')
429 []
427 []
430 >>> difflineranges2(b'a', b'A')
428 >>> difflineranges2(b'a', b'A')
431 [(1, 1)]
429 [(1, 1)]
432 >>> difflineranges2(b'ab', b'')
430 >>> difflineranges2(b'ab', b'')
433 []
431 []
434 >>> difflineranges2(b'', b'AB')
432 >>> difflineranges2(b'', b'AB')
435 [(1, 2)]
433 [(1, 2)]
436 >>> difflineranges2(b'abc', b'ac')
434 >>> difflineranges2(b'abc', b'ac')
437 []
435 []
438 >>> difflineranges2(b'ab', b'aCb')
436 >>> difflineranges2(b'ab', b'aCb')
439 [(2, 2)]
437 [(2, 2)]
440 >>> difflineranges2(b'abc', b'aBc')
438 >>> difflineranges2(b'abc', b'aBc')
441 [(2, 2)]
439 [(2, 2)]
442 >>> difflineranges2(b'ab', b'AB')
440 >>> difflineranges2(b'ab', b'AB')
443 [(1, 2)]
441 [(1, 2)]
444 >>> difflineranges2(b'abcde', b'aBcDe')
442 >>> difflineranges2(b'abcde', b'aBcDe')
445 [(2, 2), (4, 4)]
443 [(2, 2), (4, 4)]
446 >>> difflineranges2(b'abcde', b'aBCDe')
444 >>> difflineranges2(b'abcde', b'aBCDe')
447 [(2, 4)]
445 [(2, 4)]
448 """
446 """
449 ranges = []
447 ranges = []
450 for lines, kind in mdiff.allblocks(content1, content2):
448 for lines, kind in mdiff.allblocks(content1, content2):
451 firstline, lastline = lines[2:4]
449 firstline, lastline = lines[2:4]
452 if kind == '!' and firstline != lastline:
450 if kind == '!' and firstline != lastline:
453 ranges.append((firstline + 1, lastline))
451 ranges.append((firstline + 1, lastline))
454 return ranges
452 return ranges
455
453
456 def getbasectxs(repo, opts, revstofix):
454 def getbasectxs(repo, opts, revstofix):
457 """Returns a map of the base contexts for each revision
455 """Returns a map of the base contexts for each revision
458
456
459 The base contexts determine which lines are considered modified when we
457 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
458 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
459 files we attempt to fix, so it is important to compute this even when
462 --whole is used.
460 --whole is used.
463 """
461 """
464 # The --base flag overrides the usual logic, and we give every revision
462 # The --base flag overrides the usual logic, and we give every revision
465 # exactly the set of baserevs that the user specified.
463 # exactly the set of baserevs that the user specified.
466 if opts.get('base'):
464 if opts.get('base'):
467 baserevs = set(scmutil.revrange(repo, opts.get('base')))
465 baserevs = set(scmutil.revrange(repo, opts.get('base')))
468 if not baserevs:
466 if not baserevs:
469 baserevs = {nullrev}
467 baserevs = {nullrev}
470 basectxs = {repo[rev] for rev in baserevs}
468 basectxs = {repo[rev] for rev in baserevs}
471 return {rev: basectxs for rev in revstofix}
469 return {rev: basectxs for rev in revstofix}
472
470
473 # Proceed in topological order so that we can easily determine each
471 # Proceed in topological order so that we can easily determine each
474 # revision's baserevs by looking at its parents and their baserevs.
472 # revision's baserevs by looking at its parents and their baserevs.
475 basectxs = collections.defaultdict(set)
473 basectxs = collections.defaultdict(set)
476 for rev in sorted(revstofix):
474 for rev in sorted(revstofix):
477 ctx = repo[rev]
475 ctx = repo[rev]
478 for pctx in ctx.parents():
476 for pctx in ctx.parents():
479 if pctx.rev() in basectxs:
477 if pctx.rev() in basectxs:
480 basectxs[rev].update(basectxs[pctx.rev()])
478 basectxs[rev].update(basectxs[pctx.rev()])
481 else:
479 else:
482 basectxs[rev].add(pctx)
480 basectxs[rev].add(pctx)
483 return basectxs
481 return basectxs
484
482
485 def fixfile(ui, opts, fixers, fixctx, path, basectxs):
483 def fixfile(ui, opts, fixers, fixctx, path, basectxs):
486 """Run any configured fixers that should affect the file in this context
484 """Run any configured fixers that should affect the file in this context
487
485
488 Returns the file content that results from applying the fixers in some order
486 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
487 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
488 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).
489 (i.e. they will only avoid lines that are common to all basectxs).
492
490
493 A fixer tool's stdout will become the file's new content if and only if it
491 A fixer tool's stdout will become the file's new content if and only if it
494 exits with code zero.
492 exits with code zero.
495 """
493 """
496 newdata = fixctx[path].data()
494 newdata = fixctx[path].data()
497 for fixername, fixer in fixers.iteritems():
495 for fixername, fixer in fixers.iteritems():
498 if fixer.affects(opts, fixctx, path):
496 if fixer.affects(opts, fixctx, path):
499 rangesfn = lambda: lineranges(opts, path, basectxs, fixctx, newdata)
497 rangesfn = lambda: lineranges(opts, path, basectxs, fixctx, newdata)
500 command = fixer.command(ui, path, rangesfn)
498 command = fixer.command(ui, path, rangesfn)
501 if command is None:
499 if command is None:
502 continue
500 continue
503 ui.debug('subprocess: %s\n' % (command,))
501 ui.debug('subprocess: %s\n' % (command,))
504 proc = subprocess.Popen(
502 proc = subprocess.Popen(
505 procutil.tonativestr(command),
503 procutil.tonativestr(command),
506 shell=True,
504 shell=True,
507 cwd=procutil.tonativestr(b'/'),
505 cwd=procutil.tonativestr(b'/'),
508 stdin=subprocess.PIPE,
506 stdin=subprocess.PIPE,
509 stdout=subprocess.PIPE,
507 stdout=subprocess.PIPE,
510 stderr=subprocess.PIPE)
508 stderr=subprocess.PIPE)
511 newerdata, stderr = proc.communicate(newdata)
509 newerdata, stderr = proc.communicate(newdata)
512 if stderr:
510 if stderr:
513 showstderr(ui, fixctx.rev(), fixername, stderr)
511 showstderr(ui, fixctx.rev(), fixername, stderr)
514 if proc.returncode == 0:
512 if proc.returncode == 0:
515 newdata = newerdata
513 newdata = newerdata
516 else:
514 else:
517 if not stderr:
515 if not stderr:
518 message = _('exited with status %d\n') % (proc.returncode,)
516 message = _('exited with status %d\n') % (proc.returncode,)
519 showstderr(ui, fixctx.rev(), fixername, message)
517 showstderr(ui, fixctx.rev(), fixername, message)
520 checktoolfailureaction(
518 checktoolfailureaction(
521 ui, _('no fixes will be applied'),
519 ui, _('no fixes will be applied'),
522 hint=_('use --config fix.failure=continue to apply any '
520 hint=_('use --config fix.failure=continue to apply any '
523 'successful fixes anyway'))
521 'successful fixes anyway'))
524 return newdata
522 return newdata
525
523
526 def showstderr(ui, rev, fixername, stderr):
524 def showstderr(ui, rev, fixername, stderr):
527 """Writes the lines of the stderr string as warnings on the ui
525 """Writes the lines of the stderr string as warnings on the ui
528
526
529 Uses the revision number and fixername to give more context to each line of
527 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
528 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
529 space and would tend to be included in the error message if they were
532 relevant.
530 relevant.
533 """
531 """
534 for line in re.split('[\r\n]+', stderr):
532 for line in re.split('[\r\n]+', stderr):
535 if line:
533 if line:
536 ui.warn(('['))
534 ui.warn(('['))
537 if rev is None:
535 if rev is None:
538 ui.warn(_('wdir'), label='evolve.rev')
536 ui.warn(_('wdir'), label='evolve.rev')
539 else:
537 else:
540 ui.warn((str(rev)), label='evolve.rev')
538 ui.warn((str(rev)), label='evolve.rev')
541 ui.warn(('] %s: %s\n') % (fixername, line))
539 ui.warn(('] %s: %s\n') % (fixername, line))
542
540
543 def writeworkingdir(repo, ctx, filedata, replacements):
541 def writeworkingdir(repo, ctx, filedata, replacements):
544 """Write new content to the working copy and check out the new p1 if any
542 """Write new content to the working copy and check out the new p1 if any
545
543
546 We check out a new revision if and only if we fixed something in both the
544 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
545 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
546 update/merge, and means that the working directory simply isn't affected
549 unless the --working-dir flag is given.
547 unless the --working-dir flag is given.
550
548
551 Directly updates the dirstate for the affected files.
549 Directly updates the dirstate for the affected files.
552 """
550 """
553 for path, data in filedata.iteritems():
551 for path, data in filedata.iteritems():
554 fctx = ctx[path]
552 fctx = ctx[path]
555 fctx.write(data, fctx.flags())
553 fctx.write(data, fctx.flags())
556 if repo.dirstate[path] == 'n':
554 if repo.dirstate[path] == 'n':
557 repo.dirstate.normallookup(path)
555 repo.dirstate.normallookup(path)
558
556
559 oldparentnodes = repo.dirstate.parents()
557 oldparentnodes = repo.dirstate.parents()
560 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
558 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
561 if newparentnodes != oldparentnodes:
559 if newparentnodes != oldparentnodes:
562 repo.setparents(*newparentnodes)
560 repo.setparents(*newparentnodes)
563
561
564 def replacerev(ui, repo, ctx, filedata, replacements):
562 def replacerev(ui, repo, ctx, filedata, replacements):
565 """Commit a new revision like the given one, but with file content changes
563 """Commit a new revision like the given one, but with file content changes
566
564
567 "ctx" is the original revision to be replaced by a modified one.
565 "ctx" is the original revision to be replaced by a modified one.
568
566
569 "filedata" is a dict that maps paths to their new file content. All other
567 "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.
568 paths will be recreated from the original revision without changes.
571 "filedata" may contain paths that didn't exist in the original revision;
569 "filedata" may contain paths that didn't exist in the original revision;
572 they will be added.
570 they will be added.
573
571
574 "replacements" is a dict that maps a single node to a single node, and it is
572 "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
573 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.
574 one. No entry is added if the replacement's node already exists.
577
575
578 The new revision has the same parents as the old one, unless those parents
576 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
577 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,
578 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.
579 there is no need to rebase them into the original topology later.
582 """
580 """
583
581
584 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
582 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
585 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
583 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
586 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
584 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
587 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
585 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
588
586
589 # We don't want to create a revision that has no changes from the original,
587 # 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.
588 # but we should if the original revision's parent has been replaced.
591 # Otherwise, we would produce an orphan that needs no actual human
589 # 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
590 # 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
591 # un-needed revision because the extra field added below produces a new hash
594 # regardless of file content changes.
592 # regardless of file content changes.
595 if (not filedata and
593 if (not filedata and
596 p1ctx.node() not in replacements and
594 p1ctx.node() not in replacements and
597 p2ctx.node() not in replacements):
595 p2ctx.node() not in replacements):
598 return
596 return
599
597
600 def filectxfn(repo, memctx, path):
598 def filectxfn(repo, memctx, path):
601 if path not in ctx:
599 if path not in ctx:
602 return None
600 return None
603 fctx = ctx[path]
601 fctx = ctx[path]
604 copysource = fctx.copysource()
602 copysource = fctx.copysource()
605 return context.memfilectx(
603 return context.memfilectx(
606 repo,
604 repo,
607 memctx,
605 memctx,
608 path=fctx.path(),
606 path=fctx.path(),
609 data=filedata.get(path, fctx.data()),
607 data=filedata.get(path, fctx.data()),
610 islink=fctx.islink(),
608 islink=fctx.islink(),
611 isexec=fctx.isexec(),
609 isexec=fctx.isexec(),
612 copysource=copysource)
610 copysource=copysource)
613
611
614 extra = ctx.extra().copy()
612 extra = ctx.extra().copy()
615 extra['fix_source'] = ctx.hex()
613 extra['fix_source'] = ctx.hex()
616
614
617 memctx = context.memctx(
615 memctx = context.memctx(
618 repo,
616 repo,
619 parents=(newp1node, newp2node),
617 parents=(newp1node, newp2node),
620 text=ctx.description(),
618 text=ctx.description(),
621 files=set(ctx.files()) | set(filedata.keys()),
619 files=set(ctx.files()) | set(filedata.keys()),
622 filectxfn=filectxfn,
620 filectxfn=filectxfn,
623 user=ctx.user(),
621 user=ctx.user(),
624 date=ctx.date(),
622 date=ctx.date(),
625 extra=extra,
623 extra=extra,
626 branch=ctx.branch(),
624 branch=ctx.branch(),
627 editor=None)
625 editor=None)
628 sucnode = memctx.commit()
626 sucnode = memctx.commit()
629 prenode = ctx.node()
627 prenode = ctx.node()
630 if prenode == sucnode:
628 if prenode == sucnode:
631 ui.debug('node %s already existed\n' % (ctx.hex()))
629 ui.debug('node %s already existed\n' % (ctx.hex()))
632 else:
630 else:
633 replacements[ctx.node()] = sucnode
631 replacements[ctx.node()] = sucnode
634
632
635 def getfixers(ui):
633 def getfixers(ui):
636 """Returns a map of configured fixer tools indexed by their names
634 """Returns a map of configured fixer tools indexed by their names
637
635
638 Each value is a Fixer object with methods that implement the behavior of the
636 Each value is a Fixer object with methods that implement the behavior of the
639 fixer's config suboptions. Does not validate the config values.
637 fixer's config suboptions. Does not validate the config values.
640 """
638 """
641 fixers = {}
639 fixers = {}
642 for name in fixernames(ui):
640 for name in fixernames(ui):
643 fixers[name] = Fixer()
641 fixers[name] = Fixer()
644 attrs = ui.configsuboptions('fix', name)[1]
642 attrs = ui.configsuboptions('fix', name)[1]
645 if 'fileset' in attrs and 'pattern' not in attrs:
643 if 'fileset' in attrs and 'pattern' not in attrs:
646 ui.warn(_('the fix.tool:fileset config name is deprecated; '
644 ui.warn(_('the fix.tool:fileset config name is deprecated; '
647 'please rename it to fix.tool:pattern\n'))
645 'please rename it to fix.tool:pattern\n'))
648 attrs['pattern'] = attrs['fileset']
646 attrs['pattern'] = attrs['fileset']
649 for key, default in FIXER_ATTRS.items():
647 for key, default in FIXER_ATTRS.items():
650 setattr(fixers[name], pycompat.sysstr('_' + key),
648 setattr(fixers[name], pycompat.sysstr('_' + key),
651 attrs.get(key, default))
649 attrs.get(key, default))
652 fixers[name]._priority = int(fixers[name]._priority)
650 fixers[name]._priority = int(fixers[name]._priority)
653 return collections.OrderedDict(
651 return collections.OrderedDict(
654 sorted(fixers.items(), key=lambda item: item[1]._priority,
652 sorted(fixers.items(), key=lambda item: item[1]._priority,
655 reverse=True))
653 reverse=True))
656
654
657 def fixernames(ui):
655 def fixernames(ui):
658 """Returns the names of [fix] config options that have suboptions"""
656 """Returns the names of [fix] config options that have suboptions"""
659 names = set()
657 names = set()
660 for k, v in ui.configitems('fix'):
658 for k, v in ui.configitems('fix'):
661 if ':' in k:
659 if ':' in k:
662 names.add(k.split(':', 1)[0])
660 names.add(k.split(':', 1)[0])
663 return names
661 return names
664
662
665 class Fixer(object):
663 class Fixer(object):
666 """Wraps the raw config values for a fixer with methods"""
664 """Wraps the raw config values for a fixer with methods"""
667
665
668 def affects(self, opts, fixctx, path):
666 def affects(self, opts, fixctx, path):
669 """Should this fixer run on the file at the given path and context?"""
667 """Should this fixer run on the file at the given path and context?"""
670 return scmutil.match(fixctx, [self._pattern], opts)(path)
668 return scmutil.match(fixctx, [self._pattern], opts)(path)
671
669
672 def command(self, ui, path, rangesfn):
670 def command(self, ui, path, rangesfn):
673 """A shell command to use to invoke this fixer on the given file/lines
671 """A shell command to use to invoke this fixer on the given file/lines
674
672
675 May return None if there is no appropriate command to run for the given
673 May return None if there is no appropriate command to run for the given
676 parameters.
674 parameters.
677 """
675 """
678 expand = cmdutil.rendercommandtemplate
676 expand = cmdutil.rendercommandtemplate
679 parts = [expand(ui, self._command,
677 parts = [expand(ui, self._command,
680 {'rootpath': path, 'basename': os.path.basename(path)})]
678 {'rootpath': path, 'basename': os.path.basename(path)})]
681 if self._linerange:
679 if self._linerange:
682 ranges = rangesfn()
680 ranges = rangesfn()
683 if not ranges:
681 if not ranges:
684 # No line ranges to fix, so don't run the fixer.
682 # No line ranges to fix, so don't run the fixer.
685 return None
683 return None
686 for first, last in ranges:
684 for first, last in ranges:
687 parts.append(expand(ui, self._linerange,
685 parts.append(expand(ui, self._linerange,
688 {'first': first, 'last': last}))
686 {'first': first, 'last': last}))
689 return ' '.join(parts)
687 return ' '.join(parts)
@@ -1,1231 +1,1234 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 -n
176 sort:command = sort -n
177 head:command = head -n 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
358 Listing the files explicitly causes untracked files to also be fixed, but
359 ignored files are still unaffected.
360
357 $ hg fix --working-dir *.whole
361 $ hg fix --working-dir *.whole
358
362
359 $ hg status --all
363 $ hg status --all
360 M clean.whole
364 M clean.whole
361 M modified.whole
365 M modified.whole
362 A added.whole
366 A added.whole
363 R removed.whole
367 R removed.whole
364 ! deleted.whole
368 ! deleted.whole
365 ? unknown.whole
369 ? unknown.whole
366 I ignored.whole
370 I ignored.whole
367 C .hgignore
371 C .hgignore
368
372
369 It would be better if this also fixed the unknown file.
370 $ cat *.whole
373 $ cat *.whole
371 ADDED
374 ADDED
372 CLEAN
375 CLEAN
373 ignored
376 ignored
374 MODIFIED!!!
377 MODIFIED!!!
375 unknown
378 UNKNOWN
376
379
377 $ cd ..
380 $ cd ..
378
381
379 Test that incremental fixing works on files with additions, deletions, and
382 Test that incremental fixing works on files with additions, deletions, and
380 changes in multiple line ranges. Note that deletions do not generally cause
383 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
384 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
385 deleted sections. In the future we should support a :deletion config that
383 allows fixers to know where deletions are located.
386 allows fixers to know where deletions are located.
384
387
385 $ hg init incrementalfixedlines
388 $ hg init incrementalfixedlines
386 $ cd incrementalfixedlines
389 $ cd incrementalfixedlines
387
390
388 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
391 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
389 $ hg commit -Aqm "foo"
392 $ hg commit -Aqm "foo"
390 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
393 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
391
394
392 $ hg --config "fix.fail:command=echo" \
395 $ hg --config "fix.fail:command=echo" \
393 > --config "fix.fail:linerange={first}:{last}" \
396 > --config "fix.fail:linerange={first}:{last}" \
394 > --config "fix.fail:pattern=foo.txt" \
397 > --config "fix.fail:pattern=foo.txt" \
395 > fix --working-dir
398 > fix --working-dir
396 $ cat foo.txt
399 $ cat foo.txt
397 1:1 4:6 8:8
400 1:1 4:6 8:8
398
401
399 $ cd ..
402 $ cd ..
400
403
401 Test that --whole fixes all lines regardless of the diffs present.
404 Test that --whole fixes all lines regardless of the diffs present.
402
405
403 $ hg init wholeignoresdiffs
406 $ hg init wholeignoresdiffs
404 $ cd wholeignoresdiffs
407 $ cd wholeignoresdiffs
405
408
406 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
409 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
407 $ hg commit -Aqm "foo"
410 $ hg commit -Aqm "foo"
408 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
411 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
409 $ hg fix --working-dir --whole
412 $ hg fix --working-dir --whole
410 $ cat foo.changed
413 $ cat foo.changed
411 ZZ
414 ZZ
412 A
415 A
413 C
416 C
414 DD
417 DD
415 EE
418 EE
416 FF
419 FF
417 F
420 F
418 GG
421 GG
419
422
420 $ cd ..
423 $ cd ..
421
424
422 We should do nothing with symlinks, and their targets should be unaffected. Any
425 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.
426 other behavior would be more complicated to implement and harder to document.
424
427
425 #if symlink
428 #if symlink
426 $ hg init dontmesswithsymlinks
429 $ hg init dontmesswithsymlinks
427 $ cd dontmesswithsymlinks
430 $ cd dontmesswithsymlinks
428
431
429 $ printf "hello\n" > hello.whole
432 $ printf "hello\n" > hello.whole
430 $ ln -s hello.whole hellolink
433 $ ln -s hello.whole hellolink
431 $ hg add
434 $ hg add
432 adding hello.whole
435 adding hello.whole
433 adding hellolink
436 adding hellolink
434 $ hg fix --working-dir hellolink
437 $ hg fix --working-dir hellolink
435 $ hg status
438 $ hg status
436 A hello.whole
439 A hello.whole
437 A hellolink
440 A hellolink
438
441
439 $ cd ..
442 $ cd ..
440 #endif
443 #endif
441
444
442 We should allow fixers to run on binary files, even though this doesn't sound
445 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
446 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
447 can add "and not binary()" to their filesets if needed. The Mercurial
445 philosophy is generally to not handle binary files specially anyway.
448 philosophy is generally to not handle binary files specially anyway.
446
449
447 $ hg init cantouchbinaryfiles
450 $ hg init cantouchbinaryfiles
448 $ cd cantouchbinaryfiles
451 $ cd cantouchbinaryfiles
449
452
450 $ printf "hello\0\n" > hello.whole
453 $ printf "hello\0\n" > hello.whole
451 $ hg add
454 $ hg add
452 adding hello.whole
455 adding hello.whole
453 $ hg fix --working-dir 'set:binary()'
456 $ hg fix --working-dir 'set:binary()'
454 $ cat hello.whole
457 $ cat hello.whole
455 HELLO\x00 (esc)
458 HELLO\x00 (esc)
456
459
457 $ cd ..
460 $ cd ..
458
461
459 We have a config for the maximum size of file we will attempt to fix. This can
462 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
463 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
461 could happen by accident without a well considered configuration. A more
464 could happen by accident without a well considered configuration. A more
462 precise configuration could use the size() fileset function if one global limit
465 precise configuration could use the size() fileset function if one global limit
463 is undesired.
466 is undesired.
464
467
465 $ hg init maxfilesize
468 $ hg init maxfilesize
466 $ cd maxfilesize
469 $ cd maxfilesize
467
470
468 $ printf "this file is huge\n" > hello.whole
471 $ printf "this file is huge\n" > hello.whole
469 $ hg add
472 $ hg add
470 adding hello.whole
473 adding hello.whole
471 $ hg --config fix.maxfilesize=10 fix --working-dir
474 $ hg --config fix.maxfilesize=10 fix --working-dir
472 ignoring file larger than 10 bytes: hello.whole
475 ignoring file larger than 10 bytes: hello.whole
473 $ cat hello.whole
476 $ cat hello.whole
474 this file is huge
477 this file is huge
475
478
476 $ cd ..
479 $ cd ..
477
480
478 If we specify a file to fix, other files should be left alone, even if they
481 If we specify a file to fix, other files should be left alone, even if they
479 have changes.
482 have changes.
480
483
481 $ hg init fixonlywhatitellyouto
484 $ hg init fixonlywhatitellyouto
482 $ cd fixonlywhatitellyouto
485 $ cd fixonlywhatitellyouto
483
486
484 $ printf "fix me!\n" > fixme.whole
487 $ printf "fix me!\n" > fixme.whole
485 $ printf "not me.\n" > notme.whole
488 $ printf "not me.\n" > notme.whole
486 $ hg add
489 $ hg add
487 adding fixme.whole
490 adding fixme.whole
488 adding notme.whole
491 adding notme.whole
489 $ hg fix --working-dir fixme.whole
492 $ hg fix --working-dir fixme.whole
490 $ cat *.whole
493 $ cat *.whole
491 FIX ME!
494 FIX ME!
492 not me.
495 not me.
493
496
494 $ cd ..
497 $ cd ..
495
498
496 Specifying a directory name should fix all its files and subdirectories.
499 Specifying a directory name should fix all its files and subdirectories.
497
500
498 $ hg init fixdirectory
501 $ hg init fixdirectory
499 $ cd fixdirectory
502 $ cd fixdirectory
500
503
501 $ mkdir -p dir1/dir2
504 $ mkdir -p dir1/dir2
502 $ printf "foo\n" > foo.whole
505 $ printf "foo\n" > foo.whole
503 $ printf "bar\n" > dir1/bar.whole
506 $ printf "bar\n" > dir1/bar.whole
504 $ printf "baz\n" > dir1/dir2/baz.whole
507 $ printf "baz\n" > dir1/dir2/baz.whole
505 $ hg add
508 $ hg add
506 adding dir1/bar.whole
509 adding dir1/bar.whole
507 adding dir1/dir2/baz.whole
510 adding dir1/dir2/baz.whole
508 adding foo.whole
511 adding foo.whole
509 $ hg fix --working-dir dir1
512 $ hg fix --working-dir dir1
510 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
513 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
511 foo
514 foo
512 BAR
515 BAR
513 BAZ
516 BAZ
514
517
515 $ cd ..
518 $ cd ..
516
519
517 Fixing a file in the working directory that needs no fixes should not actually
520 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.
521 write back to the file, so for example the mtime shouldn't change.
519
522
520 $ hg init donttouchunfixedfiles
523 $ hg init donttouchunfixedfiles
521 $ cd donttouchunfixedfiles
524 $ cd donttouchunfixedfiles
522
525
523 $ printf "NO FIX NEEDED\n" > foo.whole
526 $ printf "NO FIX NEEDED\n" > foo.whole
524 $ hg add
527 $ hg add
525 adding foo.whole
528 adding foo.whole
526 $ cp -p foo.whole foo.whole.orig
529 $ cp -p foo.whole foo.whole.orig
527 $ cp -p foo.whole.orig foo.whole
530 $ cp -p foo.whole.orig foo.whole
528 $ sleep 2 # mtime has a resolution of one or two seconds.
531 $ sleep 2 # mtime has a resolution of one or two seconds.
529 $ hg fix --working-dir
532 $ hg fix --working-dir
530 $ f foo.whole.orig --newer foo.whole
533 $ f foo.whole.orig --newer foo.whole
531 foo.whole.orig: newer than foo.whole
534 foo.whole.orig: newer than foo.whole
532
535
533 $ cd ..
536 $ cd ..
534
537
535 When a fixer prints to stderr, we don't assume that it has failed. We show the
538 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
539 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
540 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,
541 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
542 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
543 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
544 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
545 also a config to abort (without affecting any files whatsoever) if we see any
543 tool with a non-zero exit status.
546 tool with a non-zero exit status.
544
547
545 $ hg init showstderr
548 $ hg init showstderr
546 $ cd showstderr
549 $ cd showstderr
547
550
548 $ printf "hello\n" > hello.txt
551 $ printf "hello\n" > hello.txt
549 $ hg add
552 $ hg add
550 adding hello.txt
553 adding hello.txt
551 $ cat > $TESTTMP/work.sh <<'EOF'
554 $ cat > $TESTTMP/work.sh <<'EOF'
552 > printf 'HELLO\n'
555 > printf 'HELLO\n'
553 > printf "$@: some\nerror that didn't stop the tool" >&2
556 > printf "$@: some\nerror that didn't stop the tool" >&2
554 > exit 0 # success despite the stderr output
557 > exit 0 # success despite the stderr output
555 > EOF
558 > EOF
556 $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \
559 $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \
557 > --config "fix.work:pattern=hello.txt" \
560 > --config "fix.work:pattern=hello.txt" \
558 > fix --working-dir
561 > fix --working-dir
559 [wdir] work: hello.txt: some
562 [wdir] work: hello.txt: some
560 [wdir] work: error that didn't stop the tool
563 [wdir] work: error that didn't stop the tool
561 $ cat hello.txt
564 $ cat hello.txt
562 HELLO
565 HELLO
563
566
564 $ printf "goodbye\n" > hello.txt
567 $ printf "goodbye\n" > hello.txt
565 $ printf "foo\n" > foo.whole
568 $ printf "foo\n" > foo.whole
566 $ hg add
569 $ hg add
567 adding foo.whole
570 adding foo.whole
568 $ cat > $TESTTMP/fail.sh <<'EOF'
571 $ cat > $TESTTMP/fail.sh <<'EOF'
569 > printf 'GOODBYE\n'
572 > printf 'GOODBYE\n'
570 > printf "$@: some\nerror that did stop the tool\n" >&2
573 > printf "$@: some\nerror that did stop the tool\n" >&2
571 > exit 42 # success despite the stdout output
574 > exit 42 # success despite the stdout output
572 > EOF
575 > EOF
573 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
576 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
574 > --config "fix.fail:pattern=hello.txt" \
577 > --config "fix.fail:pattern=hello.txt" \
575 > --config "fix.failure=abort" \
578 > --config "fix.failure=abort" \
576 > fix --working-dir
579 > fix --working-dir
577 [wdir] fail: hello.txt: some
580 [wdir] fail: hello.txt: some
578 [wdir] fail: error that did stop the tool
581 [wdir] fail: error that did stop the tool
579 abort: no fixes will be applied
582 abort: no fixes will be applied
580 (use --config fix.failure=continue to apply any successful fixes anyway)
583 (use --config fix.failure=continue to apply any successful fixes anyway)
581 [255]
584 [255]
582 $ cat hello.txt
585 $ cat hello.txt
583 goodbye
586 goodbye
584 $ cat foo.whole
587 $ cat foo.whole
585 foo
588 foo
586
589
587 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
590 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
588 > --config "fix.fail:pattern=hello.txt" \
591 > --config "fix.fail:pattern=hello.txt" \
589 > fix --working-dir
592 > fix --working-dir
590 [wdir] fail: hello.txt: some
593 [wdir] fail: hello.txt: some
591 [wdir] fail: error that did stop the tool
594 [wdir] fail: error that did stop the tool
592 $ cat hello.txt
595 $ cat hello.txt
593 goodbye
596 goodbye
594 $ cat foo.whole
597 $ cat foo.whole
595 FOO
598 FOO
596
599
597 $ hg --config "fix.fail:command=exit 42" \
600 $ hg --config "fix.fail:command=exit 42" \
598 > --config "fix.fail:pattern=hello.txt" \
601 > --config "fix.fail:pattern=hello.txt" \
599 > fix --working-dir
602 > fix --working-dir
600 [wdir] fail: exited with status 42
603 [wdir] fail: exited with status 42
601
604
602 $ cd ..
605 $ cd ..
603
606
604 Fixing the working directory and its parent revision at the same time should
607 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
608 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
609 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
610 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
611 will be fixed. The grandparent is the "baserev" for both the parent and the
609 working copy.
612 working copy.
610
613
611 $ hg init fixdotandcleanwdir
614 $ hg init fixdotandcleanwdir
612 $ cd fixdotandcleanwdir
615 $ cd fixdotandcleanwdir
613
616
614 $ printf "hello\n" > hello.whole
617 $ printf "hello\n" > hello.whole
615 $ printf "world\n" > world.whole
618 $ printf "world\n" > world.whole
616 $ hg commit -Aqm "the parent commit"
619 $ hg commit -Aqm "the parent commit"
617
620
618 $ hg parents --template '{rev} {desc}\n'
621 $ hg parents --template '{rev} {desc}\n'
619 0 the parent commit
622 0 the parent commit
620 $ hg fix --working-dir -r .
623 $ hg fix --working-dir -r .
621 $ hg parents --template '{rev} {desc}\n'
624 $ hg parents --template '{rev} {desc}\n'
622 1 the parent commit
625 1 the parent commit
623 $ hg cat -r . *.whole
626 $ hg cat -r . *.whole
624 HELLO
627 HELLO
625 WORLD
628 WORLD
626 $ cat *.whole
629 $ cat *.whole
627 HELLO
630 HELLO
628 WORLD
631 WORLD
629 $ hg status
632 $ hg status
630
633
631 $ cd ..
634 $ cd ..
632
635
633 Same test with a dirty working copy.
636 Same test with a dirty working copy.
634
637
635 $ hg init fixdotanddirtywdir
638 $ hg init fixdotanddirtywdir
636 $ cd fixdotanddirtywdir
639 $ cd fixdotanddirtywdir
637
640
638 $ printf "hello\n" > hello.whole
641 $ printf "hello\n" > hello.whole
639 $ printf "world\n" > world.whole
642 $ printf "world\n" > world.whole
640 $ hg commit -Aqm "the parent commit"
643 $ hg commit -Aqm "the parent commit"
641
644
642 $ printf "hello,\n" > hello.whole
645 $ printf "hello,\n" > hello.whole
643 $ printf "world!\n" > world.whole
646 $ printf "world!\n" > world.whole
644
647
645 $ hg parents --template '{rev} {desc}\n'
648 $ hg parents --template '{rev} {desc}\n'
646 0 the parent commit
649 0 the parent commit
647 $ hg fix --working-dir -r .
650 $ hg fix --working-dir -r .
648 $ hg parents --template '{rev} {desc}\n'
651 $ hg parents --template '{rev} {desc}\n'
649 1 the parent commit
652 1 the parent commit
650 $ hg cat -r . *.whole
653 $ hg cat -r . *.whole
651 HELLO
654 HELLO
652 WORLD
655 WORLD
653 $ cat *.whole
656 $ cat *.whole
654 HELLO,
657 HELLO,
655 WORLD!
658 WORLD!
656 $ hg status
659 $ hg status
657 M hello.whole
660 M hello.whole
658 M world.whole
661 M world.whole
659
662
660 $ cd ..
663 $ cd ..
661
664
662 When we have a chain of commits that change mutually exclusive lines of code,
665 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
666 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
667 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
668 backing out the fixes made in their parents. A dirty working directory is
666 conceptually similar to another commit in the chain.
669 conceptually similar to another commit in the chain.
667
670
668 $ hg init incrementallyfixchain
671 $ hg init incrementallyfixchain
669 $ cd incrementallyfixchain
672 $ cd incrementallyfixchain
670
673
671 $ cat > file.changed <<EOF
674 $ cat > file.changed <<EOF
672 > first
675 > first
673 > second
676 > second
674 > third
677 > third
675 > fourth
678 > fourth
676 > fifth
679 > fifth
677 > EOF
680 > EOF
678 $ hg commit -Aqm "the common ancestor (the baserev)"
681 $ hg commit -Aqm "the common ancestor (the baserev)"
679 $ cat > file.changed <<EOF
682 $ cat > file.changed <<EOF
680 > first (changed)
683 > first (changed)
681 > second
684 > second
682 > third
685 > third
683 > fourth
686 > fourth
684 > fifth
687 > fifth
685 > EOF
688 > EOF
686 $ hg commit -Aqm "the first commit to fix"
689 $ hg commit -Aqm "the first commit to fix"
687 $ cat > file.changed <<EOF
690 $ cat > file.changed <<EOF
688 > first (changed)
691 > first (changed)
689 > second
692 > second
690 > third (changed)
693 > third (changed)
691 > fourth
694 > fourth
692 > fifth
695 > fifth
693 > EOF
696 > EOF
694 $ hg commit -Aqm "the second commit to fix"
697 $ hg commit -Aqm "the second commit to fix"
695 $ cat > file.changed <<EOF
698 $ cat > file.changed <<EOF
696 > first (changed)
699 > first (changed)
697 > second
700 > second
698 > third (changed)
701 > third (changed)
699 > fourth
702 > fourth
700 > fifth (changed)
703 > fifth (changed)
701 > EOF
704 > EOF
702
705
703 $ hg fix -r . -r '.^' --working-dir
706 $ hg fix -r . -r '.^' --working-dir
704
707
705 $ hg parents --template '{rev}\n'
708 $ hg parents --template '{rev}\n'
706 4
709 4
707 $ hg cat -r '.^^' file.changed
710 $ hg cat -r '.^^' file.changed
708 first
711 first
709 second
712 second
710 third
713 third
711 fourth
714 fourth
712 fifth
715 fifth
713 $ hg cat -r '.^' file.changed
716 $ hg cat -r '.^' file.changed
714 FIRST (CHANGED)
717 FIRST (CHANGED)
715 second
718 second
716 third
719 third
717 fourth
720 fourth
718 fifth
721 fifth
719 $ hg cat -r . file.changed
722 $ hg cat -r . file.changed
720 FIRST (CHANGED)
723 FIRST (CHANGED)
721 second
724 second
722 THIRD (CHANGED)
725 THIRD (CHANGED)
723 fourth
726 fourth
724 fifth
727 fifth
725 $ cat file.changed
728 $ cat file.changed
726 FIRST (CHANGED)
729 FIRST (CHANGED)
727 second
730 second
728 THIRD (CHANGED)
731 THIRD (CHANGED)
729 fourth
732 fourth
730 FIFTH (CHANGED)
733 FIFTH (CHANGED)
731
734
732 $ cd ..
735 $ cd ..
733
736
734 If we incrementally fix a merge commit, we should fix any lines that changed
737 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
738 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
739 other subset, but this is necessary if either parent is being fixed. It
737 prevents us from forgetting fixes made in either parent.
740 prevents us from forgetting fixes made in either parent.
738
741
739 $ hg init incrementallyfixmergecommit
742 $ hg init incrementallyfixmergecommit
740 $ cd incrementallyfixmergecommit
743 $ cd incrementallyfixmergecommit
741
744
742 $ printf "a\nb\nc\n" > file.changed
745 $ printf "a\nb\nc\n" > file.changed
743 $ hg commit -Aqm "ancestor"
746 $ hg commit -Aqm "ancestor"
744
747
745 $ printf "aa\nb\nc\n" > file.changed
748 $ printf "aa\nb\nc\n" > file.changed
746 $ hg commit -m "change a"
749 $ hg commit -m "change a"
747
750
748 $ hg checkout '.^'
751 $ hg checkout '.^'
749 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
752 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
750 $ printf "a\nb\ncc\n" > file.changed
753 $ printf "a\nb\ncc\n" > file.changed
751 $ hg commit -m "change c"
754 $ hg commit -m "change c"
752 created new head
755 created new head
753
756
754 $ hg merge
757 $ hg merge
755 merging file.changed
758 merging file.changed
756 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
759 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
757 (branch merge, don't forget to commit)
760 (branch merge, don't forget to commit)
758 $ hg commit -m "merge"
761 $ hg commit -m "merge"
759 $ hg cat -r . file.changed
762 $ hg cat -r . file.changed
760 aa
763 aa
761 b
764 b
762 cc
765 cc
763
766
764 $ hg fix -r . --working-dir
767 $ hg fix -r . --working-dir
765 $ hg cat -r . file.changed
768 $ hg cat -r . file.changed
766 AA
769 AA
767 b
770 b
768 CC
771 CC
769
772
770 $ cd ..
773 $ cd ..
771
774
772 Abort fixing revisions if there is an unfinished operation. We don't want to
775 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
776 make things worse by editing files or stripping/obsoleting things. Also abort
774 fixing the working directory if there are unresolved merge conflicts.
777 fixing the working directory if there are unresolved merge conflicts.
775
778
776 $ hg init abortunresolved
779 $ hg init abortunresolved
777 $ cd abortunresolved
780 $ cd abortunresolved
778
781
779 $ echo "foo1" > foo.whole
782 $ echo "foo1" > foo.whole
780 $ hg commit -Aqm "foo 1"
783 $ hg commit -Aqm "foo 1"
781
784
782 $ hg update null
785 $ hg update null
783 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
786 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
784 $ echo "foo2" > foo.whole
787 $ echo "foo2" > foo.whole
785 $ hg commit -Aqm "foo 2"
788 $ hg commit -Aqm "foo 2"
786
789
787 $ hg --config extensions.rebase= rebase -r 1 -d 0
790 $ hg --config extensions.rebase= rebase -r 1 -d 0
788 rebasing 1:c3b6dc0e177a "foo 2" (tip)
791 rebasing 1:c3b6dc0e177a "foo 2" (tip)
789 merging foo.whole
792 merging foo.whole
790 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
793 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
791 unresolved conflicts (see hg resolve, then hg rebase --continue)
794 unresolved conflicts (see hg resolve, then hg rebase --continue)
792 [1]
795 [1]
793
796
794 $ hg --config extensions.rebase= fix --working-dir
797 $ hg --config extensions.rebase= fix --working-dir
795 abort: unresolved conflicts
798 abort: unresolved conflicts
796 (use 'hg resolve')
799 (use 'hg resolve')
797 [255]
800 [255]
798
801
799 $ hg --config extensions.rebase= fix -r .
802 $ hg --config extensions.rebase= fix -r .
800 abort: rebase in progress
803 abort: rebase in progress
801 (use 'hg rebase --continue' or 'hg rebase --abort')
804 (use 'hg rebase --continue' or 'hg rebase --abort')
802 [255]
805 [255]
803
806
804 When fixing a file that was renamed, we should diff against the source of the
807 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
808 rename for incremental fixing and we should correctly reproduce the rename in
806 the replacement revision.
809 the replacement revision.
807
810
808 $ hg init fixrenamecommit
811 $ hg init fixrenamecommit
809 $ cd fixrenamecommit
812 $ cd fixrenamecommit
810
813
811 $ printf "a\nb\nc\n" > source.changed
814 $ printf "a\nb\nc\n" > source.changed
812 $ hg commit -Aqm "source revision"
815 $ hg commit -Aqm "source revision"
813 $ hg move source.changed dest.changed
816 $ hg move source.changed dest.changed
814 $ printf "a\nb\ncc\n" > dest.changed
817 $ printf "a\nb\ncc\n" > dest.changed
815 $ hg commit -m "dest revision"
818 $ hg commit -m "dest revision"
816
819
817 $ hg fix -r .
820 $ hg fix -r .
818 $ hg log -r tip --copies --template "{file_copies}\n"
821 $ hg log -r tip --copies --template "{file_copies}\n"
819 dest.changed (source.changed)
822 dest.changed (source.changed)
820 $ hg cat -r tip dest.changed
823 $ hg cat -r tip dest.changed
821 a
824 a
822 b
825 b
823 CC
826 CC
824
827
825 $ cd ..
828 $ cd ..
826
829
827 When fixing revisions that remove files we must ensure that the replacement
830 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
831 actually removes the file, whereas it could accidentally leave it unchanged or
829 write an empty string to it.
832 write an empty string to it.
830
833
831 $ hg init fixremovedfile
834 $ hg init fixremovedfile
832 $ cd fixremovedfile
835 $ cd fixremovedfile
833
836
834 $ printf "foo\n" > foo.whole
837 $ printf "foo\n" > foo.whole
835 $ printf "bar\n" > bar.whole
838 $ printf "bar\n" > bar.whole
836 $ hg commit -Aqm "add files"
839 $ hg commit -Aqm "add files"
837 $ hg remove bar.whole
840 $ hg remove bar.whole
838 $ hg commit -m "remove file"
841 $ hg commit -m "remove file"
839 $ hg status --change .
842 $ hg status --change .
840 R bar.whole
843 R bar.whole
841 $ hg fix -r . foo.whole
844 $ hg fix -r . foo.whole
842 $ hg status --change tip
845 $ hg status --change tip
843 M foo.whole
846 M foo.whole
844 R bar.whole
847 R bar.whole
845
848
846 $ cd ..
849 $ cd ..
847
850
848 If fixing a revision finds no fixes to make, no replacement revision should be
851 If fixing a revision finds no fixes to make, no replacement revision should be
849 created.
852 created.
850
853
851 $ hg init nofixesneeded
854 $ hg init nofixesneeded
852 $ cd nofixesneeded
855 $ cd nofixesneeded
853
856
854 $ printf "FOO\n" > foo.whole
857 $ printf "FOO\n" > foo.whole
855 $ hg commit -Aqm "add file"
858 $ hg commit -Aqm "add file"
856 $ hg log --template '{rev}\n'
859 $ hg log --template '{rev}\n'
857 0
860 0
858 $ hg fix -r .
861 $ hg fix -r .
859 $ hg log --template '{rev}\n'
862 $ hg log --template '{rev}\n'
860 0
863 0
861
864
862 $ cd ..
865 $ cd ..
863
866
864 If fixing a commit reverts all the changes in the commit, we replace it with a
867 If fixing a commit reverts all the changes in the commit, we replace it with a
865 commit that changes no files.
868 commit that changes no files.
866
869
867 $ hg init nochangesleft
870 $ hg init nochangesleft
868 $ cd nochangesleft
871 $ cd nochangesleft
869
872
870 $ printf "FOO\n" > foo.whole
873 $ printf "FOO\n" > foo.whole
871 $ hg commit -Aqm "add file"
874 $ hg commit -Aqm "add file"
872 $ printf "foo\n" > foo.whole
875 $ printf "foo\n" > foo.whole
873 $ hg commit -m "edit file"
876 $ hg commit -m "edit file"
874 $ hg status --change .
877 $ hg status --change .
875 M foo.whole
878 M foo.whole
876 $ hg fix -r .
879 $ hg fix -r .
877 $ hg status --change tip
880 $ hg status --change tip
878
881
879 $ cd ..
882 $ cd ..
880
883
881 If we fix a parent and child revision together, the child revision must be
884 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
885 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
886 fixes. However, we're free to not replace revisions that need no fixes and have
884 no ancestors that are replaced.
887 no ancestors that are replaced.
885
888
886 $ hg init mustreplacechild
889 $ hg init mustreplacechild
887 $ cd mustreplacechild
890 $ cd mustreplacechild
888
891
889 $ printf "FOO\n" > foo.whole
892 $ printf "FOO\n" > foo.whole
890 $ hg commit -Aqm "add foo"
893 $ hg commit -Aqm "add foo"
891 $ printf "foo\n" > foo.whole
894 $ printf "foo\n" > foo.whole
892 $ hg commit -m "edit foo"
895 $ hg commit -m "edit foo"
893 $ printf "BAR\n" > bar.whole
896 $ printf "BAR\n" > bar.whole
894 $ hg commit -Aqm "add bar"
897 $ hg commit -Aqm "add bar"
895
898
896 $ hg log --graph --template '{rev} {files}'
899 $ hg log --graph --template '{rev} {files}'
897 @ 2 bar.whole
900 @ 2 bar.whole
898 |
901 |
899 o 1 foo.whole
902 o 1 foo.whole
900 |
903 |
901 o 0 foo.whole
904 o 0 foo.whole
902
905
903 $ hg fix -r 0:2
906 $ hg fix -r 0:2
904 $ hg log --graph --template '{rev} {files}'
907 $ hg log --graph --template '{rev} {files}'
905 o 4 bar.whole
908 o 4 bar.whole
906 |
909 |
907 o 3
910 o 3
908 |
911 |
909 | @ 2 bar.whole
912 | @ 2 bar.whole
910 | |
913 | |
911 | x 1 foo.whole
914 | x 1 foo.whole
912 |/
915 |/
913 o 0 foo.whole
916 o 0 foo.whole
914
917
915
918
916 $ cd ..
919 $ cd ..
917
920
918 It's also possible that the child needs absolutely no changes, but we still
921 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
922 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
923 because it had no file content changes, it would become an orphan for no good
921 reason.
924 reason.
922
925
923 $ hg init mustreplacechildevenifnop
926 $ hg init mustreplacechildevenifnop
924 $ cd mustreplacechildevenifnop
927 $ cd mustreplacechildevenifnop
925
928
926 $ printf "Foo\n" > foo.whole
929 $ printf "Foo\n" > foo.whole
927 $ hg commit -Aqm "add a bad foo"
930 $ hg commit -Aqm "add a bad foo"
928 $ printf "FOO\n" > foo.whole
931 $ printf "FOO\n" > foo.whole
929 $ hg commit -m "add a good foo"
932 $ hg commit -m "add a good foo"
930 $ hg fix -r . -r '.^'
933 $ hg fix -r . -r '.^'
931 $ hg log --graph --template '{rev} {desc}'
934 $ hg log --graph --template '{rev} {desc}'
932 o 3 add a good foo
935 o 3 add a good foo
933 |
936 |
934 o 2 add a bad foo
937 o 2 add a bad foo
935
938
936 @ 1 add a good foo
939 @ 1 add a good foo
937 |
940 |
938 x 0 add a bad foo
941 x 0 add a bad foo
939
942
940
943
941 $ cd ..
944 $ cd ..
942
945
943 Similar to the case above, the child revision may become empty as a result of
946 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.
947 fixing its parent. We should still create an empty replacement child.
945 TODO: determine how this should interact with ui.allowemptycommit given that
948 TODO: determine how this should interact with ui.allowemptycommit given that
946 the empty replacement could have children.
949 the empty replacement could have children.
947
950
948 $ hg init mustreplacechildevenifempty
951 $ hg init mustreplacechildevenifempty
949 $ cd mustreplacechildevenifempty
952 $ cd mustreplacechildevenifempty
950
953
951 $ printf "foo\n" > foo.whole
954 $ printf "foo\n" > foo.whole
952 $ hg commit -Aqm "add foo"
955 $ hg commit -Aqm "add foo"
953 $ printf "Foo\n" > foo.whole
956 $ printf "Foo\n" > foo.whole
954 $ hg commit -m "edit foo"
957 $ hg commit -m "edit foo"
955 $ hg fix -r . -r '.^'
958 $ hg fix -r . -r '.^'
956 $ hg log --graph --template '{rev} {desc}\n' --stat
959 $ hg log --graph --template '{rev} {desc}\n' --stat
957 o 3 edit foo
960 o 3 edit foo
958 |
961 |
959 o 2 add foo
962 o 2 add foo
960 foo.whole | 1 +
963 foo.whole | 1 +
961 1 files changed, 1 insertions(+), 0 deletions(-)
964 1 files changed, 1 insertions(+), 0 deletions(-)
962
965
963 @ 1 edit foo
966 @ 1 edit foo
964 | foo.whole | 2 +-
967 | foo.whole | 2 +-
965 | 1 files changed, 1 insertions(+), 1 deletions(-)
968 | 1 files changed, 1 insertions(+), 1 deletions(-)
966 |
969 |
967 x 0 add foo
970 x 0 add foo
968 foo.whole | 1 +
971 foo.whole | 1 +
969 1 files changed, 1 insertions(+), 0 deletions(-)
972 1 files changed, 1 insertions(+), 0 deletions(-)
970
973
971
974
972 $ cd ..
975 $ cd ..
973
976
974 Fixing a secret commit should replace it with another secret commit.
977 Fixing a secret commit should replace it with another secret commit.
975
978
976 $ hg init fixsecretcommit
979 $ hg init fixsecretcommit
977 $ cd fixsecretcommit
980 $ cd fixsecretcommit
978
981
979 $ printf "foo\n" > foo.whole
982 $ printf "foo\n" > foo.whole
980 $ hg commit -Aqm "add foo" --secret
983 $ hg commit -Aqm "add foo" --secret
981 $ hg fix -r .
984 $ hg fix -r .
982 $ hg log --template '{rev} {phase}\n'
985 $ hg log --template '{rev} {phase}\n'
983 1 secret
986 1 secret
984 0 secret
987 0 secret
985
988
986 $ cd ..
989 $ cd ..
987
990
988 We should also preserve phase when fixing a draft commit while the user has
991 We should also preserve phase when fixing a draft commit while the user has
989 their default set to secret.
992 their default set to secret.
990
993
991 $ hg init respectphasesnewcommit
994 $ hg init respectphasesnewcommit
992 $ cd respectphasesnewcommit
995 $ cd respectphasesnewcommit
993
996
994 $ printf "foo\n" > foo.whole
997 $ printf "foo\n" > foo.whole
995 $ hg commit -Aqm "add foo"
998 $ hg commit -Aqm "add foo"
996 $ hg --config phases.newcommit=secret fix -r .
999 $ hg --config phases.newcommit=secret fix -r .
997 $ hg log --template '{rev} {phase}\n'
1000 $ hg log --template '{rev} {phase}\n'
998 1 draft
1001 1 draft
999 0 draft
1002 0 draft
1000
1003
1001 $ cd ..
1004 $ cd ..
1002
1005
1003 Debug output should show what fixer commands are being subprocessed, which is
1006 Debug output should show what fixer commands are being subprocessed, which is
1004 useful for anyone trying to set up a new config.
1007 useful for anyone trying to set up a new config.
1005
1008
1006 $ hg init debugoutput
1009 $ hg init debugoutput
1007 $ cd debugoutput
1010 $ cd debugoutput
1008
1011
1009 $ printf "foo\nbar\nbaz\n" > foo.changed
1012 $ printf "foo\nbar\nbaz\n" > foo.changed
1010 $ hg commit -Aqm "foo"
1013 $ hg commit -Aqm "foo"
1011 $ printf "Foo\nbar\nBaz\n" > foo.changed
1014 $ printf "Foo\nbar\nBaz\n" > foo.changed
1012 $ hg --debug fix --working-dir
1015 $ hg --debug fix --working-dir
1013 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
1016 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
1014
1017
1015 $ cd ..
1018 $ cd ..
1016
1019
1017 Fixing an obsolete revision can cause divergence, so we abort unless the user
1020 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
1021 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
1022 successor, but even then it is not likely intentional or idiomatic to fix an
1020 obsolete revision.
1023 obsolete revision.
1021
1024
1022 $ hg init abortobsoleterev
1025 $ hg init abortobsoleterev
1023 $ cd abortobsoleterev
1026 $ cd abortobsoleterev
1024
1027
1025 $ printf "foo\n" > foo.changed
1028 $ printf "foo\n" > foo.changed
1026 $ hg commit -Aqm "foo"
1029 $ hg commit -Aqm "foo"
1027 $ hg debugobsolete `hg parents --template '{node}'`
1030 $ hg debugobsolete `hg parents --template '{node}'`
1028 obsoleted 1 changesets
1031 obsoleted 1 changesets
1029 $ hg --hidden fix -r 0
1032 $ hg --hidden fix -r 0
1030 abort: fixing obsolete revision could cause divergence
1033 abort: fixing obsolete revision could cause divergence
1031 [255]
1034 [255]
1032
1035
1033 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
1036 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
1034 $ hg cat -r tip foo.changed
1037 $ hg cat -r tip foo.changed
1035 FOO
1038 FOO
1036
1039
1037 $ cd ..
1040 $ cd ..
1038
1041
1039 Test all of the available substitution values for fixer commands.
1042 Test all of the available substitution values for fixer commands.
1040
1043
1041 $ hg init substitution
1044 $ hg init substitution
1042 $ cd substitution
1045 $ cd substitution
1043
1046
1044 $ mkdir foo
1047 $ mkdir foo
1045 $ printf "hello\ngoodbye\n" > foo/bar
1048 $ printf "hello\ngoodbye\n" > foo/bar
1046 $ hg add
1049 $ hg add
1047 adding foo/bar
1050 adding foo/bar
1048 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
1051 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
1049 > --config "fix.fail:linerange='{first}' '{last}'" \
1052 > --config "fix.fail:linerange='{first}' '{last}'" \
1050 > --config "fix.fail:pattern=foo/bar" \
1053 > --config "fix.fail:pattern=foo/bar" \
1051 > fix --working-dir
1054 > fix --working-dir
1052 $ cat foo/bar
1055 $ cat foo/bar
1053 foo/bar
1056 foo/bar
1054 bar
1057 bar
1055 1
1058 1
1056 2
1059 2
1057
1060
1058 $ cd ..
1061 $ cd ..
1059
1062
1060 The --base flag should allow picking the revisions to diff against for changed
1063 The --base flag should allow picking the revisions to diff against for changed
1061 files and incremental line formatting.
1064 files and incremental line formatting.
1062
1065
1063 $ hg init baseflag
1066 $ hg init baseflag
1064 $ cd baseflag
1067 $ cd baseflag
1065
1068
1066 $ printf "one\ntwo\n" > foo.changed
1069 $ printf "one\ntwo\n" > foo.changed
1067 $ printf "bar\n" > bar.changed
1070 $ printf "bar\n" > bar.changed
1068 $ hg commit -Aqm "first"
1071 $ hg commit -Aqm "first"
1069 $ printf "one\nTwo\n" > foo.changed
1072 $ printf "one\nTwo\n" > foo.changed
1070 $ hg commit -m "second"
1073 $ hg commit -m "second"
1071 $ hg fix -w --base .
1074 $ hg fix -w --base .
1072 $ hg status
1075 $ hg status
1073 $ hg fix -w --base null
1076 $ hg fix -w --base null
1074 $ cat foo.changed
1077 $ cat foo.changed
1075 ONE
1078 ONE
1076 TWO
1079 TWO
1077 $ cat bar.changed
1080 $ cat bar.changed
1078 BAR
1081 BAR
1079
1082
1080 $ cd ..
1083 $ cd ..
1081
1084
1082 If the user asks to fix the parent of another commit, they are asking to create
1085 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.
1086 an orphan. We must respect experimental.evolution.allowunstable.
1084
1087
1085 $ hg init allowunstable
1088 $ hg init allowunstable
1086 $ cd allowunstable
1089 $ cd allowunstable
1087
1090
1088 $ printf "one\n" > foo.whole
1091 $ printf "one\n" > foo.whole
1089 $ hg commit -Aqm "first"
1092 $ hg commit -Aqm "first"
1090 $ printf "two\n" > foo.whole
1093 $ printf "two\n" > foo.whole
1091 $ hg commit -m "second"
1094 $ hg commit -m "second"
1092 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1095 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1093 abort: can only fix a changeset together with all its descendants
1096 abort: can only fix a changeset together with all its descendants
1094 [255]
1097 [255]
1095 $ hg fix -r '.^'
1098 $ hg fix -r '.^'
1096 1 new orphan changesets
1099 1 new orphan changesets
1097 $ hg cat -r 2 foo.whole
1100 $ hg cat -r 2 foo.whole
1098 ONE
1101 ONE
1099
1102
1100 $ cd ..
1103 $ cd ..
1101
1104
1102 The --base flag affects the set of files being fixed. So while the --whole flag
1105 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
1106 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
1107 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.
1108 until we specify the base, but then we do fix unchanged lines.
1106
1109
1107 $ hg init basewhole
1110 $ hg init basewhole
1108 $ cd basewhole
1111 $ cd basewhole
1109 $ printf "foo1\n" > foo.changed
1112 $ printf "foo1\n" > foo.changed
1110 $ hg commit -Aqm "first"
1113 $ hg commit -Aqm "first"
1111 $ printf "foo2\n" >> foo.changed
1114 $ printf "foo2\n" >> foo.changed
1112 $ printf "bar\n" > bar.changed
1115 $ printf "bar\n" > bar.changed
1113 $ hg commit -Aqm "second"
1116 $ hg commit -Aqm "second"
1114
1117
1115 $ hg fix --working-dir --whole
1118 $ hg fix --working-dir --whole
1116 $ cat *.changed
1119 $ cat *.changed
1117 bar
1120 bar
1118 foo1
1121 foo1
1119 foo2
1122 foo2
1120
1123
1121 $ hg fix --working-dir --base 0 --whole
1124 $ hg fix --working-dir --base 0 --whole
1122 $ cat *.changed
1125 $ cat *.changed
1123 BAR
1126 BAR
1124 FOO1
1127 FOO1
1125 FOO2
1128 FOO2
1126
1129
1127 $ cd ..
1130 $ cd ..
1128
1131
1129 The :fileset subconfig was a misnomer, so we renamed it to :pattern. We will
1132 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
1133 still accept :fileset by itself as if it were :pattern, but this will issue a
1131 warning.
1134 warning.
1132
1135
1133 $ hg init filesetispattern
1136 $ hg init filesetispattern
1134 $ cd filesetispattern
1137 $ cd filesetispattern
1135
1138
1136 $ printf "foo\n" > foo.whole
1139 $ printf "foo\n" > foo.whole
1137 $ printf "first\nsecond\n" > bar.txt
1140 $ printf "first\nsecond\n" > bar.txt
1138 $ hg add -q
1141 $ hg add -q
1139 $ hg fix -w --config fix.sometool:fileset=bar.txt \
1142 $ hg fix -w --config fix.sometool:fileset=bar.txt \
1140 > --config fix.sometool:command="sort -r"
1143 > --config fix.sometool:command="sort -r"
1141 the fix.tool:fileset config name is deprecated; please rename it to fix.tool:pattern
1144 the fix.tool:fileset config name is deprecated; please rename it to fix.tool:pattern
1142
1145
1143 $ cat foo.whole
1146 $ cat foo.whole
1144 FOO
1147 FOO
1145 $ cat bar.txt
1148 $ cat bar.txt
1146 second
1149 second
1147 first
1150 first
1148
1151
1149 $ cd ..
1152 $ cd ..
1150
1153
1151 The execution order of tools can be controlled. This example doesn't work if
1154 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
1155 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
1156 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
1157 implementation of fix). The goal is to use multiple tools to select the lowest
1155 5 numbers in the file.
1158 5 numbers in the file.
1156
1159
1157 $ hg init priorityexample
1160 $ hg init priorityexample
1158 $ cd priorityexample
1161 $ cd priorityexample
1159
1162
1160 $ cat >> .hg/hgrc <<EOF
1163 $ cat >> .hg/hgrc <<EOF
1161 > [fix]
1164 > [fix]
1162 > head:command = head -n 5
1165 > head:command = head -n 5
1163 > head:pattern = numbers.txt
1166 > head:pattern = numbers.txt
1164 > head:priority = 1
1167 > head:priority = 1
1165 > sort:command = sort -n
1168 > sort:command = sort -n
1166 > sort:pattern = numbers.txt
1169 > sort:pattern = numbers.txt
1167 > sort:priority = 2
1170 > sort:priority = 2
1168 > EOF
1171 > EOF
1169
1172
1170 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1173 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1171 $ hg add -q
1174 $ hg add -q
1172 $ hg fix -w
1175 $ hg fix -w
1173 $ cat numbers.txt
1176 $ cat numbers.txt
1174 0
1177 0
1175 1
1178 1
1176 2
1179 2
1177 3
1180 3
1178 4
1181 4
1179
1182
1180 And of course we should be able to break this by reversing the execution order.
1183 And of course we should be able to break this by reversing the execution order.
1181 Test negative priorities while we're at it.
1184 Test negative priorities while we're at it.
1182
1185
1183 $ cat >> .hg/hgrc <<EOF
1186 $ cat >> .hg/hgrc <<EOF
1184 > [fix]
1187 > [fix]
1185 > head:priority = -1
1188 > head:priority = -1
1186 > sort:priority = -2
1189 > sort:priority = -2
1187 > EOF
1190 > EOF
1188 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1191 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1189 $ hg fix -w
1192 $ hg fix -w
1190 $ cat numbers.txt
1193 $ cat numbers.txt
1191 2
1194 2
1192 3
1195 3
1193 6
1196 6
1194 7
1197 7
1195 8
1198 8
1196
1199
1197 $ cd ..
1200 $ cd ..
1198
1201
1199 It's possible for repeated applications of a fixer tool to create cycles in the
1202 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
1203 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
1204 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
1205 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
1206 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
1207 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
1208 successor which forces it to have a new hash. That's why this test creates
1206 three revisions instead of two.
1209 three revisions instead of two.
1207
1210
1208 $ hg init cyclictool
1211 $ hg init cyclictool
1209 $ cd cyclictool
1212 $ cd cyclictool
1210
1213
1211 $ cat >> .hg/hgrc <<EOF
1214 $ cat >> .hg/hgrc <<EOF
1212 > [fix]
1215 > [fix]
1213 > swapletters:command = tr ab ba
1216 > swapletters:command = tr ab ba
1214 > swapletters:pattern = foo
1217 > swapletters:pattern = foo
1215 > EOF
1218 > EOF
1216
1219
1217 $ echo ab > foo
1220 $ echo ab > foo
1218 $ hg commit -Aqm foo
1221 $ hg commit -Aqm foo
1219
1222
1220 $ hg fix -r 0
1223 $ hg fix -r 0
1221 $ hg fix -r 1
1224 $ hg fix -r 1
1222
1225
1223 $ hg cat -r 0 foo --hidden
1226 $ hg cat -r 0 foo --hidden
1224 ab
1227 ab
1225 $ hg cat -r 1 foo --hidden
1228 $ hg cat -r 1 foo --hidden
1226 ba
1229 ba
1227 $ hg cat -r 2 foo
1230 $ hg cat -r 2 foo
1228 ab
1231 ab
1229
1232
1230 $ cd ..
1233 $ cd ..
1231
1234
General Comments 0
You need to be logged in to leave comments. Login now