##// END OF EJS Templates
fix: highlight the required configuration and behavior of the fixer tools...
Matt Harbison -
r51335:f4ff5558 stable
parent child Browse files
Show More
@@ -1,958 +1,959
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 Fixer tools are run in the repository's root directory. This allows them to read
13 configuration files from the working copy, or even write to the working copy.
14 The working copy is not updated to match the revision being fixed. In fact,
15 several revisions may be fixed in parallel. Writes to the working copy are not
16 amended into the revision being fixed; fixer tools MUST always read content to
17 be fixed from stdin, and write fixed file content back to stdout.
18
12 Here is an example configuration that causes :hg:`fix` to apply automatic
19 Here is an example configuration that causes :hg:`fix` to apply automatic
13 formatting fixes to modified lines in C++ code::
20 formatting fixes to modified lines in C++ code::
14
21
15 [fix]
22 [fix]
16 clang-format:command=clang-format --assume-filename={rootpath}
23 clang-format:command=clang-format --assume-filename={rootpath}
17 clang-format:linerange=--lines={first}:{last}
24 clang-format:linerange=--lines={first}:{last}
18 clang-format:pattern=set:**.cpp or **.hpp
25 clang-format:pattern=set:**.cpp or **.hpp
19
26
20 The :command suboption forms the first part of the shell command that will be
27 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
28 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
29 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
30 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
31 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
32 status but no standard error output. Some values may be substituted into the
26 command::
33 command::
27
34
28 {rootpath} The path of the file being fixed, relative to the repo root
35 {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
36 {basename} The name of the file being fixed, without the directory path
30
37
31 If the :linerange suboption is set, the tool will only be run if there are
38 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
39 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
40 command once for every range of changed lines in the file. Some values may be
34 substituted into the command::
41 substituted into the command::
35
42
36 {first} The 1-based line number of the first line in the modified range
43 {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
44 {last} The 1-based line number of the last line in the modified range
38
45
39 Deleted sections of a file will be ignored by :linerange, because there is no
46 Deleted sections of a file will be ignored by :linerange, because there is no
40 corresponding line range in the version being fixed.
47 corresponding line range in the version being fixed.
41
48
42 By default, tools that set :linerange will only be executed if there is at least
49 By default, tools that set :linerange will only be executed if there is at least
43 one changed line range. This is meant to prevent accidents like running a code
50 one changed line range. This is meant to prevent accidents like running a code
44 formatter in such a way that it unexpectedly reformats the whole file. If such a
51 formatter in such a way that it unexpectedly reformats the whole file. If such a
45 tool needs to operate on unchanged files, it should set the :skipclean suboption
52 tool needs to operate on unchanged files, it should set the :skipclean suboption
46 to false.
53 to false.
47
54
48 The :pattern suboption determines which files will be passed through each
55 The :pattern suboption determines which files will be passed through each
49 configured tool. See :hg:`help patterns` for possible values. However, all
56 configured tool. See :hg:`help patterns` for possible values. However, all
50 patterns are relative to the repo root, even if that text says they are relative
57 patterns are relative to the repo root, even if that text says they are relative
51 to the current working directory. If there are file arguments to :hg:`fix`, the
58 to the current working directory. If there are file arguments to :hg:`fix`, the
52 intersection of these patterns is used.
59 intersection of these patterns is used.
53
60
54 There is also a configurable limit for the maximum size of file that will be
61 There is also a configurable limit for the maximum size of file that will be
55 processed by :hg:`fix`::
62 processed by :hg:`fix`::
56
63
57 [fix]
64 [fix]
58 maxfilesize = 2MB
65 maxfilesize = 2MB
59
66
60 Normally, execution of configured tools will continue after a failure (indicated
67 Normally, execution of configured tools will continue after a failure (indicated
61 by a non-zero exit status). It can also be configured to abort after the first
68 by a non-zero exit status). It can also be configured to abort after the first
62 such failure, so that no files will be affected if any tool fails. This abort
69 such failure, so that no files will be affected if any tool fails. This abort
63 will also cause :hg:`fix` to exit with a non-zero status::
70 will also cause :hg:`fix` to exit with a non-zero status::
64
71
65 [fix]
72 [fix]
66 failure = abort
73 failure = abort
67
74
68 When multiple tools are configured to affect a file, they execute in an order
75 When multiple tools are configured to affect a file, they execute in an order
69 defined by the :priority suboption. The priority suboption has a default value
76 defined by the :priority suboption. The priority suboption has a default value
70 of zero for each tool. Tools are executed in order of descending priority. The
77 of zero for each tool. Tools are executed in order of descending priority. The
71 execution order of tools with equal priority is unspecified. For example, you
78 execution order of tools with equal priority is unspecified. For example, you
72 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
79 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
73 in a text file by ensuring that 'sort' runs before 'head'::
80 in a text file by ensuring that 'sort' runs before 'head'::
74
81
75 [fix]
82 [fix]
76 sort:command = sort -n
83 sort:command = sort -n
77 head:command = head -n 10
84 head:command = head -n 10
78 sort:pattern = numbers.txt
85 sort:pattern = numbers.txt
79 head:pattern = numbers.txt
86 head:pattern = numbers.txt
80 sort:priority = 2
87 sort:priority = 2
81 head:priority = 1
88 head:priority = 1
82
89
83 To account for changes made by each tool, the line numbers used for incremental
90 To account for changes made by each tool, the line numbers used for incremental
84 formatting are recomputed before executing the next tool. So, each tool may see
91 formatting are recomputed before executing the next tool. So, each tool may see
85 different values for the arguments added by the :linerange suboption.
92 different values for the arguments added by the :linerange suboption.
86
93
87 Each fixer tool is allowed to return some metadata in addition to the fixed file
94 Each fixer tool is allowed to return some metadata in addition to the fixed file
88 content. The metadata must be placed before the file content on stdout,
95 content. The metadata must be placed before the file content on stdout,
89 separated from the file content by a zero byte. The metadata is parsed as a JSON
96 separated from the file content by a zero byte. The metadata is parsed as a JSON
90 value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer tool
97 value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer tool
91 is expected to produce this metadata encoding if and only if the :metadata
98 is expected to produce this metadata encoding if and only if the :metadata
92 suboption is true::
99 suboption is true::
93
100
94 [fix]
101 [fix]
95 tool:command = tool --prepend-json-metadata
102 tool:command = tool --prepend-json-metadata
96 tool:metadata = true
103 tool:metadata = true
97
104
98 The metadata values are passed to hooks, which can be used to print summaries or
105 The metadata values are passed to hooks, which can be used to print summaries or
99 perform other post-fixing work. The supported hooks are::
106 perform other post-fixing work. The supported hooks are::
100
107
101 "postfixfile"
108 "postfixfile"
102 Run once for each file in each revision where any fixer tools made changes
109 Run once for each file in each revision where any fixer tools made changes
103 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
110 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
104 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
111 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
105 tools that affected the file. Fixer tools that didn't affect the file have a
112 tools that affected the file. Fixer tools that didn't affect the file have a
106 value of None. Only fixer tools that executed are present in the metadata.
113 value of None. Only fixer tools that executed are present in the metadata.
107
114
108 "postfix"
115 "postfix"
109 Run once after all files and revisions have been handled. Provides
116 Run once after all files and revisions have been handled. Provides
110 "$HG_REPLACEMENTS" with information about what revisions were created and
117 "$HG_REPLACEMENTS" with information about what revisions were created and
111 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
118 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
112 files in the working copy were updated. Provides a list "$HG_METADATA"
119 files in the working copy were updated. Provides a list "$HG_METADATA"
113 mapping fixer tool names to lists of metadata values returned from
120 mapping fixer tool names to lists of metadata values returned from
114 executions that modified a file. This aggregates the same metadata
121 executions that modified a file. This aggregates the same metadata
115 previously passed to the "postfixfile" hook.
122 previously passed to the "postfixfile" hook.
116
117 Fixer tools are run in the repository's root directory. This allows them to read
118 configuration files from the working copy, or even write to the working copy.
119 The working copy is not updated to match the revision being fixed. In fact,
120 several revisions may be fixed in parallel. Writes to the working copy are not
121 amended into the revision being fixed; fixer tools should always write fixed
122 file content back to stdout as documented above.
123 """
123 """
124
124
125
125
126 import collections
126 import collections
127 import itertools
127 import itertools
128 import os
128 import os
129 import re
129 import re
130 import subprocess
130 import subprocess
131
131
132 from mercurial.i18n import _
132 from mercurial.i18n import _
133 from mercurial.node import (
133 from mercurial.node import (
134 nullid,
134 nullid,
135 nullrev,
135 nullrev,
136 wdirrev,
136 wdirrev,
137 )
137 )
138
138
139 from mercurial.utils import procutil
139 from mercurial.utils import procutil
140
140
141 from mercurial import (
141 from mercurial import (
142 cmdutil,
142 cmdutil,
143 context,
143 context,
144 copies,
144 copies,
145 error,
145 error,
146 logcmdutil,
146 logcmdutil,
147 match as matchmod,
147 match as matchmod,
148 mdiff,
148 mdiff,
149 merge,
149 merge,
150 mergestate as mergestatemod,
150 mergestate as mergestatemod,
151 pycompat,
151 pycompat,
152 registrar,
152 registrar,
153 rewriteutil,
153 rewriteutil,
154 scmutil,
154 scmutil,
155 util,
155 util,
156 worker,
156 worker,
157 )
157 )
158
158
159 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
159 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
160 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
160 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
161 # be specifying the version(s) of Mercurial they are tested with, or
161 # be specifying the version(s) of Mercurial they are tested with, or
162 # leave the attribute unspecified.
162 # leave the attribute unspecified.
163 testedwith = b'ships-with-hg-core'
163 testedwith = b'ships-with-hg-core'
164
164
165 cmdtable = {}
165 cmdtable = {}
166 command = registrar.command(cmdtable)
166 command = registrar.command(cmdtable)
167
167
168 configtable = {}
168 configtable = {}
169 configitem = registrar.configitem(configtable)
169 configitem = registrar.configitem(configtable)
170
170
171 # Register the suboptions allowed for each configured fixer, and default values.
171 # Register the suboptions allowed for each configured fixer, and default values.
172 FIXER_ATTRS = {
172 FIXER_ATTRS = {
173 b'command': None,
173 b'command': None,
174 b'linerange': None,
174 b'linerange': None,
175 b'pattern': None,
175 b'pattern': None,
176 b'priority': 0,
176 b'priority': 0,
177 b'metadata': False,
177 b'metadata': False,
178 b'skipclean': True,
178 b'skipclean': True,
179 b'enabled': True,
179 b'enabled': True,
180 }
180 }
181
181
182 for key, default in FIXER_ATTRS.items():
182 for key, default in FIXER_ATTRS.items():
183 configitem(b'fix', b'.*:%s$' % key, default=default, generic=True)
183 configitem(b'fix', b'.*:%s$' % key, default=default, generic=True)
184
184
185 # A good default size allows most source code files to be fixed, but avoids
185 # A good default size allows most source code files to be fixed, but avoids
186 # letting fixer tools choke on huge inputs, which could be surprising to the
186 # letting fixer tools choke on huge inputs, which could be surprising to the
187 # user.
187 # user.
188 configitem(b'fix', b'maxfilesize', default=b'2MB')
188 configitem(b'fix', b'maxfilesize', default=b'2MB')
189
189
190 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
190 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
191 # This helps users do shell scripts that stop when a fixer tool signals a
191 # This helps users do shell scripts that stop when a fixer tool signals a
192 # problem.
192 # problem.
193 configitem(b'fix', b'failure', default=b'continue')
193 configitem(b'fix', b'failure', default=b'continue')
194
194
195
195
196 def checktoolfailureaction(ui, message, hint=None):
196 def checktoolfailureaction(ui, message, hint=None):
197 """Abort with 'message' if fix.failure=abort"""
197 """Abort with 'message' if fix.failure=abort"""
198 action = ui.config(b'fix', b'failure')
198 action = ui.config(b'fix', b'failure')
199 if action not in (b'continue', b'abort'):
199 if action not in (b'continue', b'abort'):
200 raise error.Abort(
200 raise error.Abort(
201 _(b'unknown fix.failure action: %s') % (action,),
201 _(b'unknown fix.failure action: %s') % (action,),
202 hint=_(b'use "continue" or "abort"'),
202 hint=_(b'use "continue" or "abort"'),
203 )
203 )
204 if action == b'abort':
204 if action == b'abort':
205 raise error.Abort(message, hint=hint)
205 raise error.Abort(message, hint=hint)
206
206
207
207
208 allopt = (b'', b'all', False, _(b'fix all non-public non-obsolete revisions'))
208 allopt = (b'', b'all', False, _(b'fix all non-public non-obsolete revisions'))
209 baseopt = (
209 baseopt = (
210 b'',
210 b'',
211 b'base',
211 b'base',
212 [],
212 [],
213 _(
213 _(
214 b'revisions to diff against (overrides automatic '
214 b'revisions to diff against (overrides automatic '
215 b'selection, and applies to every revision being '
215 b'selection, and applies to every revision being '
216 b'fixed)'
216 b'fixed)'
217 ),
217 ),
218 _(b'REV'),
218 _(b'REV'),
219 )
219 )
220 revopt = (b'r', b'rev', [], _(b'revisions to fix (ADVANCED)'), _(b'REV'))
220 revopt = (b'r', b'rev', [], _(b'revisions to fix (ADVANCED)'), _(b'REV'))
221 sourceopt = (
221 sourceopt = (
222 b's',
222 b's',
223 b'source',
223 b'source',
224 [],
224 [],
225 _(b'fix the specified revisions and their descendants'),
225 _(b'fix the specified revisions and their descendants'),
226 _(b'REV'),
226 _(b'REV'),
227 )
227 )
228 wdiropt = (b'w', b'working-dir', False, _(b'fix the working directory'))
228 wdiropt = (b'w', b'working-dir', False, _(b'fix the working directory'))
229 wholeopt = (b'', b'whole', False, _(b'always fix every line of a file'))
229 wholeopt = (b'', b'whole', False, _(b'always fix every line of a file'))
230 usage = _(b'[OPTION]... [FILE]...')
230 usage = _(b'[OPTION]... [FILE]...')
231
231
232
232
233 @command(
233 @command(
234 b'fix',
234 b'fix',
235 [allopt, baseopt, revopt, sourceopt, wdiropt, wholeopt],
235 [allopt, baseopt, revopt, sourceopt, wdiropt, wholeopt],
236 usage,
236 usage,
237 helpcategory=command.CATEGORY_FILE_CONTENTS,
237 helpcategory=command.CATEGORY_FILE_CONTENTS,
238 )
238 )
239 def fix(ui, repo, *pats, **opts):
239 def fix(ui, repo, *pats, **opts):
240 """rewrite file content in changesets or working directory
240 """rewrite file content in changesets or working directory
241
241
242 Runs any configured tools to fix the content of files. Only affects files
242 Runs any configured tools to fix the content of files. (See
243 :hg:`help -e fix` for details about configuring tools.) Only affects files
243 with changes, unless file arguments are provided. Only affects changed lines
244 with changes, unless file arguments are provided. Only affects changed lines
244 of files, unless the --whole flag is used. Some tools may always affect the
245 of files, unless the --whole flag is used. Some tools may always affect the
245 whole file regardless of --whole.
246 whole file regardless of --whole.
246
247
247 If --working-dir is used, files with uncommitted changes in the working copy
248 If --working-dir is used, files with uncommitted changes in the working copy
248 will be fixed. Note that no backup are made.
249 will be fixed. Note that no backup are made.
249
250
250 If revisions are specified with --source, those revisions and their
251 If revisions are specified with --source, those revisions and their
251 descendants will be checked, and they may be replaced with new revisions
252 descendants will be checked, and they may be replaced with new revisions
252 that have fixed file content. By automatically including the descendants,
253 that have fixed file content. By automatically including the descendants,
253 no merging, rebasing, or evolution will be required. If an ancestor of the
254 no merging, rebasing, or evolution will be required. If an ancestor of the
254 working copy is included, then the working copy itself will also be fixed,
255 working copy is included, then the working copy itself will also be fixed,
255 and the working copy will be updated to the fixed parent.
256 and the working copy will be updated to the fixed parent.
256
257
257 When determining what lines of each file to fix at each revision, the whole
258 When determining what lines of each file to fix at each revision, the whole
258 set of revisions being fixed is considered, so that fixes to earlier
259 set of revisions being fixed is considered, so that fixes to earlier
259 revisions are not forgotten in later ones. The --base flag can be used to
260 revisions are not forgotten in later ones. The --base flag can be used to
260 override this default behavior, though it is not usually desirable to do so.
261 override this default behavior, though it is not usually desirable to do so.
261 """
262 """
262 opts = pycompat.byteskwargs(opts)
263 opts = pycompat.byteskwargs(opts)
263 cmdutil.check_at_most_one_arg(opts, b'all', b'source', b'rev')
264 cmdutil.check_at_most_one_arg(opts, b'all', b'source', b'rev')
264 cmdutil.check_incompatible_arguments(
265 cmdutil.check_incompatible_arguments(
265 opts, b'working_dir', [b'all', b'source']
266 opts, b'working_dir', [b'all', b'source']
266 )
267 )
267
268
268 with repo.wlock(), repo.lock(), repo.transaction(b'fix'):
269 with repo.wlock(), repo.lock(), repo.transaction(b'fix'):
269 revstofix = getrevstofix(ui, repo, opts)
270 revstofix = getrevstofix(ui, repo, opts)
270 basectxs = getbasectxs(repo, opts, revstofix)
271 basectxs = getbasectxs(repo, opts, revstofix)
271 workqueue, numitems = getworkqueue(
272 workqueue, numitems = getworkqueue(
272 ui, repo, pats, opts, revstofix, basectxs
273 ui, repo, pats, opts, revstofix, basectxs
273 )
274 )
274 basepaths = getbasepaths(repo, opts, workqueue, basectxs)
275 basepaths = getbasepaths(repo, opts, workqueue, basectxs)
275 fixers = getfixers(ui)
276 fixers = getfixers(ui)
276
277
277 # Rather than letting each worker independently fetch the files
278 # Rather than letting each worker independently fetch the files
278 # (which also would add complications for shared/keepalive
279 # (which also would add complications for shared/keepalive
279 # connections), prefetch them all first.
280 # connections), prefetch them all first.
280 _prefetchfiles(repo, workqueue, basepaths)
281 _prefetchfiles(repo, workqueue, basepaths)
281
282
282 # There are no data dependencies between the workers fixing each file
283 # There are no data dependencies between the workers fixing each file
283 # revision, so we can use all available parallelism.
284 # revision, so we can use all available parallelism.
284 def getfixes(items):
285 def getfixes(items):
285 for srcrev, path, dstrevs in items:
286 for srcrev, path, dstrevs in items:
286 ctx = repo[srcrev]
287 ctx = repo[srcrev]
287 olddata = ctx[path].data()
288 olddata = ctx[path].data()
288 metadata, newdata = fixfile(
289 metadata, newdata = fixfile(
289 ui,
290 ui,
290 repo,
291 repo,
291 opts,
292 opts,
292 fixers,
293 fixers,
293 ctx,
294 ctx,
294 path,
295 path,
295 basepaths,
296 basepaths,
296 basectxs[srcrev],
297 basectxs[srcrev],
297 )
298 )
298 # We ungroup the work items now, because the code that consumes
299 # We ungroup the work items now, because the code that consumes
299 # these results has to handle each dstrev separately, and in
300 # these results has to handle each dstrev separately, and in
300 # topological order. Because these are handled in topological
301 # topological order. Because these are handled in topological
301 # order, it's important that we pass around references to
302 # order, it's important that we pass around references to
302 # "newdata" instead of copying it. Otherwise, we would be
303 # "newdata" instead of copying it. Otherwise, we would be
303 # keeping more copies of file content in memory at a time than
304 # keeping more copies of file content in memory at a time than
304 # if we hadn't bothered to group/deduplicate the work items.
305 # if we hadn't bothered to group/deduplicate the work items.
305 data = newdata if newdata != olddata else None
306 data = newdata if newdata != olddata else None
306 for dstrev in dstrevs:
307 for dstrev in dstrevs:
307 yield (dstrev, path, metadata, data)
308 yield (dstrev, path, metadata, data)
308
309
309 results = worker.worker(
310 results = worker.worker(
310 ui, 1.0, getfixes, tuple(), workqueue, threadsafe=False
311 ui, 1.0, getfixes, tuple(), workqueue, threadsafe=False
311 )
312 )
312
313
313 # We have to hold on to the data for each successor revision in memory
314 # We have to hold on to the data for each successor revision in memory
314 # until all its parents are committed. We ensure this by committing and
315 # until all its parents are committed. We ensure this by committing and
315 # freeing memory for the revisions in some topological order. This
316 # freeing memory for the revisions in some topological order. This
316 # leaves a little bit of memory efficiency on the table, but also makes
317 # leaves a little bit of memory efficiency on the table, but also makes
317 # the tests deterministic. It might also be considered a feature since
318 # the tests deterministic. It might also be considered a feature since
318 # it makes the results more easily reproducible.
319 # it makes the results more easily reproducible.
319 filedata = collections.defaultdict(dict)
320 filedata = collections.defaultdict(dict)
320 aggregatemetadata = collections.defaultdict(list)
321 aggregatemetadata = collections.defaultdict(list)
321 replacements = {}
322 replacements = {}
322 wdirwritten = False
323 wdirwritten = False
323 commitorder = sorted(revstofix, reverse=True)
324 commitorder = sorted(revstofix, reverse=True)
324 with ui.makeprogress(
325 with ui.makeprogress(
325 topic=_(b'fixing'), unit=_(b'files'), total=sum(numitems.values())
326 topic=_(b'fixing'), unit=_(b'files'), total=sum(numitems.values())
326 ) as progress:
327 ) as progress:
327 for rev, path, filerevmetadata, newdata in results:
328 for rev, path, filerevmetadata, newdata in results:
328 progress.increment(item=path)
329 progress.increment(item=path)
329 for fixername, fixermetadata in filerevmetadata.items():
330 for fixername, fixermetadata in filerevmetadata.items():
330 aggregatemetadata[fixername].append(fixermetadata)
331 aggregatemetadata[fixername].append(fixermetadata)
331 if newdata is not None:
332 if newdata is not None:
332 filedata[rev][path] = newdata
333 filedata[rev][path] = newdata
333 hookargs = {
334 hookargs = {
334 b'rev': rev,
335 b'rev': rev,
335 b'path': path,
336 b'path': path,
336 b'metadata': filerevmetadata,
337 b'metadata': filerevmetadata,
337 }
338 }
338 repo.hook(
339 repo.hook(
339 b'postfixfile',
340 b'postfixfile',
340 throw=False,
341 throw=False,
341 **pycompat.strkwargs(hookargs)
342 **pycompat.strkwargs(hookargs)
342 )
343 )
343 numitems[rev] -= 1
344 numitems[rev] -= 1
344 # Apply the fixes for this and any other revisions that are
345 # Apply the fixes for this and any other revisions that are
345 # ready and sitting at the front of the queue. Using a loop here
346 # ready and sitting at the front of the queue. Using a loop here
346 # prevents the queue from being blocked by the first revision to
347 # prevents the queue from being blocked by the first revision to
347 # be ready out of order.
348 # be ready out of order.
348 while commitorder and not numitems[commitorder[-1]]:
349 while commitorder and not numitems[commitorder[-1]]:
349 rev = commitorder.pop()
350 rev = commitorder.pop()
350 ctx = repo[rev]
351 ctx = repo[rev]
351 if rev == wdirrev:
352 if rev == wdirrev:
352 writeworkingdir(repo, ctx, filedata[rev], replacements)
353 writeworkingdir(repo, ctx, filedata[rev], replacements)
353 wdirwritten = bool(filedata[rev])
354 wdirwritten = bool(filedata[rev])
354 else:
355 else:
355 replacerev(ui, repo, ctx, filedata[rev], replacements)
356 replacerev(ui, repo, ctx, filedata[rev], replacements)
356 del filedata[rev]
357 del filedata[rev]
357
358
358 cleanup(repo, replacements, wdirwritten)
359 cleanup(repo, replacements, wdirwritten)
359 hookargs = {
360 hookargs = {
360 b'replacements': replacements,
361 b'replacements': replacements,
361 b'wdirwritten': wdirwritten,
362 b'wdirwritten': wdirwritten,
362 b'metadata': aggregatemetadata,
363 b'metadata': aggregatemetadata,
363 }
364 }
364 repo.hook(b'postfix', throw=True, **pycompat.strkwargs(hookargs))
365 repo.hook(b'postfix', throw=True, **pycompat.strkwargs(hookargs))
365
366
366
367
367 def cleanup(repo, replacements, wdirwritten):
368 def cleanup(repo, replacements, wdirwritten):
368 """Calls scmutil.cleanupnodes() with the given replacements.
369 """Calls scmutil.cleanupnodes() with the given replacements.
369
370
370 "replacements" is a dict from nodeid to nodeid, with one key and one value
371 "replacements" is a dict from nodeid to nodeid, with one key and one value
371 for every revision that was affected by fixing. This is slightly different
372 for every revision that was affected by fixing. This is slightly different
372 from cleanupnodes().
373 from cleanupnodes().
373
374
374 "wdirwritten" is a bool which tells whether the working copy was affected by
375 "wdirwritten" is a bool which tells whether the working copy was affected by
375 fixing, since it has no entry in "replacements".
376 fixing, since it has no entry in "replacements".
376
377
377 Useful as a hook point for extending "hg fix" with output summarizing the
378 Useful as a hook point for extending "hg fix" with output summarizing the
378 effects of the command, though we choose not to output anything here.
379 effects of the command, though we choose not to output anything here.
379 """
380 """
380 replacements = {prec: [succ] for prec, succ in replacements.items()}
381 replacements = {prec: [succ] for prec, succ in replacements.items()}
381 scmutil.cleanupnodes(repo, replacements, b'fix', fixphase=True)
382 scmutil.cleanupnodes(repo, replacements, b'fix', fixphase=True)
382
383
383
384
384 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
385 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
385 """Constructs a list of files to fix and which revisions each fix applies to
386 """Constructs a list of files to fix and which revisions each fix applies to
386
387
387 To avoid duplicating work, there is usually only one work item for each file
388 To avoid duplicating work, there is usually only one work item for each file
388 revision that might need to be fixed. There can be multiple work items per
389 revision that might need to be fixed. There can be multiple work items per
389 file revision if the same file needs to be fixed in multiple changesets with
390 file revision if the same file needs to be fixed in multiple changesets with
390 different baserevs. Each work item also contains a list of changesets where
391 different baserevs. Each work item also contains a list of changesets where
391 the file's data should be replaced with the fixed data. The work items for
392 the file's data should be replaced with the fixed data. The work items for
392 earlier changesets come earlier in the work queue, to improve pipelining by
393 earlier changesets come earlier in the work queue, to improve pipelining by
393 allowing the first changeset to be replaced while fixes are still being
394 allowing the first changeset to be replaced while fixes are still being
394 computed for later changesets.
395 computed for later changesets.
395
396
396 Also returned is a map from changesets to the count of work items that might
397 Also returned is a map from changesets to the count of work items that might
397 affect each changeset. This is used later to count when all of a changeset's
398 affect each changeset. This is used later to count when all of a changeset's
398 work items have been finished, without having to inspect the remaining work
399 work items have been finished, without having to inspect the remaining work
399 queue in each worker subprocess.
400 queue in each worker subprocess.
400
401
401 The example work item (1, "foo/bar.txt", (1, 2, 3)) means that the data of
402 The example work item (1, "foo/bar.txt", (1, 2, 3)) means that the data of
402 bar.txt should be read from revision 1, then fixed, and written back to
403 bar.txt should be read from revision 1, then fixed, and written back to
403 revisions 1, 2 and 3. Revision 1 is called the "srcrev" and the list of
404 revisions 1, 2 and 3. Revision 1 is called the "srcrev" and the list of
404 revisions is called the "dstrevs". In practice the srcrev is always one of
405 revisions is called the "dstrevs". In practice the srcrev is always one of
405 the dstrevs, and we make that choice when constructing the work item so that
406 the dstrevs, and we make that choice when constructing the work item so that
406 the choice can't be made inconsistently later on. The dstrevs should all
407 the choice can't be made inconsistently later on. The dstrevs should all
407 have the same file revision for the given path, so the choice of srcrev is
408 have the same file revision for the given path, so the choice of srcrev is
408 arbitrary. The wdirrev can be a dstrev and a srcrev.
409 arbitrary. The wdirrev can be a dstrev and a srcrev.
409 """
410 """
410 dstrevmap = collections.defaultdict(list)
411 dstrevmap = collections.defaultdict(list)
411 numitems = collections.defaultdict(int)
412 numitems = collections.defaultdict(int)
412 maxfilesize = ui.configbytes(b'fix', b'maxfilesize')
413 maxfilesize = ui.configbytes(b'fix', b'maxfilesize')
413 for rev in sorted(revstofix):
414 for rev in sorted(revstofix):
414 fixctx = repo[rev]
415 fixctx = repo[rev]
415 match = scmutil.match(fixctx, pats, opts)
416 match = scmutil.match(fixctx, pats, opts)
416 for path in sorted(
417 for path in sorted(
417 pathstofix(ui, repo, pats, opts, match, basectxs[rev], fixctx)
418 pathstofix(ui, repo, pats, opts, match, basectxs[rev], fixctx)
418 ):
419 ):
419 fctx = fixctx[path]
420 fctx = fixctx[path]
420 if fctx.islink():
421 if fctx.islink():
421 continue
422 continue
422 if fctx.size() > maxfilesize:
423 if fctx.size() > maxfilesize:
423 ui.warn(
424 ui.warn(
424 _(b'ignoring file larger than %s: %s\n')
425 _(b'ignoring file larger than %s: %s\n')
425 % (util.bytecount(maxfilesize), path)
426 % (util.bytecount(maxfilesize), path)
426 )
427 )
427 continue
428 continue
428 baserevs = tuple(ctx.rev() for ctx in basectxs[rev])
429 baserevs = tuple(ctx.rev() for ctx in basectxs[rev])
429 dstrevmap[(fctx.filerev(), baserevs, path)].append(rev)
430 dstrevmap[(fctx.filerev(), baserevs, path)].append(rev)
430 numitems[rev] += 1
431 numitems[rev] += 1
431 workqueue = [
432 workqueue = [
432 (min(dstrevs), path, dstrevs)
433 (min(dstrevs), path, dstrevs)
433 for (_filerev, _baserevs, path), dstrevs in dstrevmap.items()
434 for (_filerev, _baserevs, path), dstrevs in dstrevmap.items()
434 ]
435 ]
435 # Move work items for earlier changesets to the front of the queue, so we
436 # Move work items for earlier changesets to the front of the queue, so we
436 # might be able to replace those changesets (in topological order) while
437 # might be able to replace those changesets (in topological order) while
437 # we're still processing later work items. Note the min() in the previous
438 # we're still processing later work items. Note the min() in the previous
438 # expression, which means we don't need a custom comparator here. The path
439 # expression, which means we don't need a custom comparator here. The path
439 # is also important in the sort order to make the output order stable. There
440 # is also important in the sort order to make the output order stable. There
440 # are some situations where this doesn't help much, but some situations
441 # are some situations where this doesn't help much, but some situations
441 # where it lets us buffer O(1) files instead of O(n) files.
442 # where it lets us buffer O(1) files instead of O(n) files.
442 workqueue.sort()
443 workqueue.sort()
443 return workqueue, numitems
444 return workqueue, numitems
444
445
445
446
446 def getrevstofix(ui, repo, opts):
447 def getrevstofix(ui, repo, opts):
447 """Returns the set of revision numbers that should be fixed"""
448 """Returns the set of revision numbers that should be fixed"""
448 if opts[b'all']:
449 if opts[b'all']:
449 revs = repo.revs(b'(not public() and not obsolete()) or wdir()')
450 revs = repo.revs(b'(not public() and not obsolete()) or wdir()')
450 elif opts[b'source']:
451 elif opts[b'source']:
451 source_revs = logcmdutil.revrange(repo, opts[b'source'])
452 source_revs = logcmdutil.revrange(repo, opts[b'source'])
452 revs = set(repo.revs(b'(%ld::) - obsolete()', source_revs))
453 revs = set(repo.revs(b'(%ld::) - obsolete()', source_revs))
453 if wdirrev in source_revs:
454 if wdirrev in source_revs:
454 # `wdir()::` is currently empty, so manually add wdir
455 # `wdir()::` is currently empty, so manually add wdir
455 revs.add(wdirrev)
456 revs.add(wdirrev)
456 if repo[b'.'].rev() in revs:
457 if repo[b'.'].rev() in revs:
457 revs.add(wdirrev)
458 revs.add(wdirrev)
458 else:
459 else:
459 revs = set(logcmdutil.revrange(repo, opts[b'rev']))
460 revs = set(logcmdutil.revrange(repo, opts[b'rev']))
460 if opts.get(b'working_dir'):
461 if opts.get(b'working_dir'):
461 revs.add(wdirrev)
462 revs.add(wdirrev)
462 # Allow fixing only wdir() even if there's an unfinished operation
463 # Allow fixing only wdir() even if there's an unfinished operation
463 if not (len(revs) == 1 and wdirrev in revs):
464 if not (len(revs) == 1 and wdirrev in revs):
464 cmdutil.checkunfinished(repo)
465 cmdutil.checkunfinished(repo)
465 rewriteutil.precheck(repo, revs, b'fix')
466 rewriteutil.precheck(repo, revs, b'fix')
466 if (
467 if (
467 wdirrev in revs
468 wdirrev in revs
468 and mergestatemod.mergestate.read(repo).unresolvedcount()
469 and mergestatemod.mergestate.read(repo).unresolvedcount()
469 ):
470 ):
470 raise error.Abort(b'unresolved conflicts', hint=b"use 'hg resolve'")
471 raise error.Abort(b'unresolved conflicts', hint=b"use 'hg resolve'")
471 if not revs:
472 if not revs:
472 raise error.Abort(
473 raise error.Abort(
473 b'no changesets specified', hint=b'use --source or --working-dir'
474 b'no changesets specified', hint=b'use --source or --working-dir'
474 )
475 )
475 return revs
476 return revs
476
477
477
478
478 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
479 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
479 """Returns the set of files that should be fixed in a context
480 """Returns the set of files that should be fixed in a context
480
481
481 The result depends on the base contexts; we include any file that has
482 The result depends on the base contexts; we include any file that has
482 changed relative to any of the base contexts. Base contexts should be
483 changed relative to any of the base contexts. Base contexts should be
483 ancestors of the context being fixed.
484 ancestors of the context being fixed.
484 """
485 """
485 files = set()
486 files = set()
486 for basectx in basectxs:
487 for basectx in basectxs:
487 stat = basectx.status(
488 stat = basectx.status(
488 fixctx, match=match, listclean=bool(pats), listunknown=bool(pats)
489 fixctx, match=match, listclean=bool(pats), listunknown=bool(pats)
489 )
490 )
490 files.update(
491 files.update(
491 set(
492 set(
492 itertools.chain(
493 itertools.chain(
493 stat.added, stat.modified, stat.clean, stat.unknown
494 stat.added, stat.modified, stat.clean, stat.unknown
494 )
495 )
495 )
496 )
496 )
497 )
497 return files
498 return files
498
499
499
500
500 def lineranges(opts, path, basepaths, basectxs, fixctx, content2):
501 def lineranges(opts, path, basepaths, basectxs, fixctx, content2):
501 """Returns the set of line ranges that should be fixed in a file
502 """Returns the set of line ranges that should be fixed in a file
502
503
503 Of the form [(10, 20), (30, 40)].
504 Of the form [(10, 20), (30, 40)].
504
505
505 This depends on the given base contexts; we must consider lines that have
506 This depends on the given base contexts; we must consider lines that have
506 changed versus any of the base contexts, and whether the file has been
507 changed versus any of the base contexts, and whether the file has been
507 renamed versus any of them.
508 renamed versus any of them.
508
509
509 Another way to understand this is that we exclude line ranges that are
510 Another way to understand this is that we exclude line ranges that are
510 common to the file in all base contexts.
511 common to the file in all base contexts.
511 """
512 """
512 if opts.get(b'whole'):
513 if opts.get(b'whole'):
513 # Return a range containing all lines. Rely on the diff implementation's
514 # Return a range containing all lines. Rely on the diff implementation's
514 # idea of how many lines are in the file, instead of reimplementing it.
515 # idea of how many lines are in the file, instead of reimplementing it.
515 return difflineranges(b'', content2)
516 return difflineranges(b'', content2)
516
517
517 rangeslist = []
518 rangeslist = []
518 for basectx in basectxs:
519 for basectx in basectxs:
519 basepath = basepaths.get((basectx.rev(), fixctx.rev(), path), path)
520 basepath = basepaths.get((basectx.rev(), fixctx.rev(), path), path)
520
521
521 if basepath in basectx:
522 if basepath in basectx:
522 content1 = basectx[basepath].data()
523 content1 = basectx[basepath].data()
523 else:
524 else:
524 content1 = b''
525 content1 = b''
525 rangeslist.extend(difflineranges(content1, content2))
526 rangeslist.extend(difflineranges(content1, content2))
526 return unionranges(rangeslist)
527 return unionranges(rangeslist)
527
528
528
529
529 def getbasepaths(repo, opts, workqueue, basectxs):
530 def getbasepaths(repo, opts, workqueue, basectxs):
530 if opts.get(b'whole'):
531 if opts.get(b'whole'):
531 # Base paths will never be fetched for line range determination.
532 # Base paths will never be fetched for line range determination.
532 return {}
533 return {}
533
534
534 basepaths = {}
535 basepaths = {}
535 for srcrev, path, _dstrevs in workqueue:
536 for srcrev, path, _dstrevs in workqueue:
536 fixctx = repo[srcrev]
537 fixctx = repo[srcrev]
537 for basectx in basectxs[srcrev]:
538 for basectx in basectxs[srcrev]:
538 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
539 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
539 if basepath in basectx:
540 if basepath in basectx:
540 basepaths[(basectx.rev(), fixctx.rev(), path)] = basepath
541 basepaths[(basectx.rev(), fixctx.rev(), path)] = basepath
541 return basepaths
542 return basepaths
542
543
543
544
544 def unionranges(rangeslist):
545 def unionranges(rangeslist):
545 """Return the union of some closed intervals
546 """Return the union of some closed intervals
546
547
547 >>> unionranges([])
548 >>> unionranges([])
548 []
549 []
549 >>> unionranges([(1, 100)])
550 >>> unionranges([(1, 100)])
550 [(1, 100)]
551 [(1, 100)]
551 >>> unionranges([(1, 100), (1, 100)])
552 >>> unionranges([(1, 100), (1, 100)])
552 [(1, 100)]
553 [(1, 100)]
553 >>> unionranges([(1, 100), (2, 100)])
554 >>> unionranges([(1, 100), (2, 100)])
554 [(1, 100)]
555 [(1, 100)]
555 >>> unionranges([(1, 99), (1, 100)])
556 >>> unionranges([(1, 99), (1, 100)])
556 [(1, 100)]
557 [(1, 100)]
557 >>> unionranges([(1, 100), (40, 60)])
558 >>> unionranges([(1, 100), (40, 60)])
558 [(1, 100)]
559 [(1, 100)]
559 >>> unionranges([(1, 49), (50, 100)])
560 >>> unionranges([(1, 49), (50, 100)])
560 [(1, 100)]
561 [(1, 100)]
561 >>> unionranges([(1, 48), (50, 100)])
562 >>> unionranges([(1, 48), (50, 100)])
562 [(1, 48), (50, 100)]
563 [(1, 48), (50, 100)]
563 >>> unionranges([(1, 2), (3, 4), (5, 6)])
564 >>> unionranges([(1, 2), (3, 4), (5, 6)])
564 [(1, 6)]
565 [(1, 6)]
565 """
566 """
566 rangeslist = sorted(set(rangeslist))
567 rangeslist = sorted(set(rangeslist))
567 unioned = []
568 unioned = []
568 if rangeslist:
569 if rangeslist:
569 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
570 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
570 for a, b in rangeslist:
571 for a, b in rangeslist:
571 c, d = unioned[-1]
572 c, d = unioned[-1]
572 if a > d + 1:
573 if a > d + 1:
573 unioned.append((a, b))
574 unioned.append((a, b))
574 else:
575 else:
575 unioned[-1] = (c, max(b, d))
576 unioned[-1] = (c, max(b, d))
576 return unioned
577 return unioned
577
578
578
579
579 def difflineranges(content1, content2):
580 def difflineranges(content1, content2):
580 """Return list of line number ranges in content2 that differ from content1.
581 """Return list of line number ranges in content2 that differ from content1.
581
582
582 Line numbers are 1-based. The numbers are the first and last line contained
583 Line numbers are 1-based. The numbers are the first and last line contained
583 in the range. Single-line ranges have the same line number for the first and
584 in the range. Single-line ranges have the same line number for the first and
584 last line. Excludes any empty ranges that result from lines that are only
585 last line. Excludes any empty ranges that result from lines that are only
585 present in content1. Relies on mdiff's idea of where the line endings are in
586 present in content1. Relies on mdiff's idea of where the line endings are in
586 the string.
587 the string.
587
588
588 >>> from mercurial import pycompat
589 >>> from mercurial import pycompat
589 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
590 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
590 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
591 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
591 >>> difflineranges2(b'', b'')
592 >>> difflineranges2(b'', b'')
592 []
593 []
593 >>> difflineranges2(b'a', b'')
594 >>> difflineranges2(b'a', b'')
594 []
595 []
595 >>> difflineranges2(b'', b'A')
596 >>> difflineranges2(b'', b'A')
596 [(1, 1)]
597 [(1, 1)]
597 >>> difflineranges2(b'a', b'a')
598 >>> difflineranges2(b'a', b'a')
598 []
599 []
599 >>> difflineranges2(b'a', b'A')
600 >>> difflineranges2(b'a', b'A')
600 [(1, 1)]
601 [(1, 1)]
601 >>> difflineranges2(b'ab', b'')
602 >>> difflineranges2(b'ab', b'')
602 []
603 []
603 >>> difflineranges2(b'', b'AB')
604 >>> difflineranges2(b'', b'AB')
604 [(1, 2)]
605 [(1, 2)]
605 >>> difflineranges2(b'abc', b'ac')
606 >>> difflineranges2(b'abc', b'ac')
606 []
607 []
607 >>> difflineranges2(b'ab', b'aCb')
608 >>> difflineranges2(b'ab', b'aCb')
608 [(2, 2)]
609 [(2, 2)]
609 >>> difflineranges2(b'abc', b'aBc')
610 >>> difflineranges2(b'abc', b'aBc')
610 [(2, 2)]
611 [(2, 2)]
611 >>> difflineranges2(b'ab', b'AB')
612 >>> difflineranges2(b'ab', b'AB')
612 [(1, 2)]
613 [(1, 2)]
613 >>> difflineranges2(b'abcde', b'aBcDe')
614 >>> difflineranges2(b'abcde', b'aBcDe')
614 [(2, 2), (4, 4)]
615 [(2, 2), (4, 4)]
615 >>> difflineranges2(b'abcde', b'aBCDe')
616 >>> difflineranges2(b'abcde', b'aBCDe')
616 [(2, 4)]
617 [(2, 4)]
617 """
618 """
618 ranges = []
619 ranges = []
619 for lines, kind in mdiff.allblocks(content1, content2):
620 for lines, kind in mdiff.allblocks(content1, content2):
620 firstline, lastline = lines[2:4]
621 firstline, lastline = lines[2:4]
621 if kind == b'!' and firstline != lastline:
622 if kind == b'!' and firstline != lastline:
622 ranges.append((firstline + 1, lastline))
623 ranges.append((firstline + 1, lastline))
623 return ranges
624 return ranges
624
625
625
626
626 def getbasectxs(repo, opts, revstofix):
627 def getbasectxs(repo, opts, revstofix):
627 """Returns a map of the base contexts for each revision
628 """Returns a map of the base contexts for each revision
628
629
629 The base contexts determine which lines are considered modified when we
630 The base contexts determine which lines are considered modified when we
630 attempt to fix just the modified lines in a file. It also determines which
631 attempt to fix just the modified lines in a file. It also determines which
631 files we attempt to fix, so it is important to compute this even when
632 files we attempt to fix, so it is important to compute this even when
632 --whole is used.
633 --whole is used.
633 """
634 """
634 # The --base flag overrides the usual logic, and we give every revision
635 # The --base flag overrides the usual logic, and we give every revision
635 # exactly the set of baserevs that the user specified.
636 # exactly the set of baserevs that the user specified.
636 if opts.get(b'base'):
637 if opts.get(b'base'):
637 baserevs = set(logcmdutil.revrange(repo, opts.get(b'base')))
638 baserevs = set(logcmdutil.revrange(repo, opts.get(b'base')))
638 if not baserevs:
639 if not baserevs:
639 baserevs = {nullrev}
640 baserevs = {nullrev}
640 basectxs = {repo[rev] for rev in baserevs}
641 basectxs = {repo[rev] for rev in baserevs}
641 return {rev: basectxs for rev in revstofix}
642 return {rev: basectxs for rev in revstofix}
642
643
643 # Proceed in topological order so that we can easily determine each
644 # Proceed in topological order so that we can easily determine each
644 # revision's baserevs by looking at its parents and their baserevs.
645 # revision's baserevs by looking at its parents and their baserevs.
645 basectxs = collections.defaultdict(set)
646 basectxs = collections.defaultdict(set)
646 for rev in sorted(revstofix):
647 for rev in sorted(revstofix):
647 ctx = repo[rev]
648 ctx = repo[rev]
648 for pctx in ctx.parents():
649 for pctx in ctx.parents():
649 if pctx.rev() in basectxs:
650 if pctx.rev() in basectxs:
650 basectxs[rev].update(basectxs[pctx.rev()])
651 basectxs[rev].update(basectxs[pctx.rev()])
651 else:
652 else:
652 basectxs[rev].add(pctx)
653 basectxs[rev].add(pctx)
653 return basectxs
654 return basectxs
654
655
655
656
656 def _prefetchfiles(repo, workqueue, basepaths):
657 def _prefetchfiles(repo, workqueue, basepaths):
657 toprefetch = set()
658 toprefetch = set()
658
659
659 # Prefetch the files that will be fixed.
660 # Prefetch the files that will be fixed.
660 for srcrev, path, _dstrevs in workqueue:
661 for srcrev, path, _dstrevs in workqueue:
661 if srcrev == wdirrev:
662 if srcrev == wdirrev:
662 continue
663 continue
663 toprefetch.add((srcrev, path))
664 toprefetch.add((srcrev, path))
664
665
665 # Prefetch the base contents for lineranges().
666 # Prefetch the base contents for lineranges().
666 for (baserev, fixrev, path), basepath in basepaths.items():
667 for (baserev, fixrev, path), basepath in basepaths.items():
667 toprefetch.add((baserev, basepath))
668 toprefetch.add((baserev, basepath))
668
669
669 if toprefetch:
670 if toprefetch:
670 scmutil.prefetchfiles(
671 scmutil.prefetchfiles(
671 repo,
672 repo,
672 [
673 [
673 (rev, scmutil.matchfiles(repo, [path]))
674 (rev, scmutil.matchfiles(repo, [path]))
674 for rev, path in toprefetch
675 for rev, path in toprefetch
675 ],
676 ],
676 )
677 )
677
678
678
679
679 def fixfile(ui, repo, opts, fixers, fixctx, path, basepaths, basectxs):
680 def fixfile(ui, repo, opts, fixers, fixctx, path, basepaths, basectxs):
680 """Run any configured fixers that should affect the file in this context
681 """Run any configured fixers that should affect the file in this context
681
682
682 Returns the file content that results from applying the fixers in some order
683 Returns the file content that results from applying the fixers in some order
683 starting with the file's content in the fixctx. Fixers that support line
684 starting with the file's content in the fixctx. Fixers that support line
684 ranges will affect lines that have changed relative to any of the basectxs
685 ranges will affect lines that have changed relative to any of the basectxs
685 (i.e. they will only avoid lines that are common to all basectxs).
686 (i.e. they will only avoid lines that are common to all basectxs).
686
687
687 A fixer tool's stdout will become the file's new content if and only if it
688 A fixer tool's stdout will become the file's new content if and only if it
688 exits with code zero. The fixer tool's working directory is the repository's
689 exits with code zero. The fixer tool's working directory is the repository's
689 root.
690 root.
690 """
691 """
691 metadata = {}
692 metadata = {}
692 newdata = fixctx[path].data()
693 newdata = fixctx[path].data()
693 for fixername, fixer in fixers.items():
694 for fixername, fixer in fixers.items():
694 if fixer.affects(opts, fixctx, path):
695 if fixer.affects(opts, fixctx, path):
695 ranges = lineranges(
696 ranges = lineranges(
696 opts, path, basepaths, basectxs, fixctx, newdata
697 opts, path, basepaths, basectxs, fixctx, newdata
697 )
698 )
698 command = fixer.command(ui, path, ranges)
699 command = fixer.command(ui, path, ranges)
699 if command is None:
700 if command is None:
700 continue
701 continue
701 msg = b'fixing: %s - %s - %s\n'
702 msg = b'fixing: %s - %s - %s\n'
702 msg %= (fixctx, fixername, path)
703 msg %= (fixctx, fixername, path)
703 ui.debug(msg)
704 ui.debug(msg)
704 ui.debug(b'subprocess: %s\n' % (command,))
705 ui.debug(b'subprocess: %s\n' % (command,))
705 proc = subprocess.Popen(
706 proc = subprocess.Popen(
706 procutil.tonativestr(command),
707 procutil.tonativestr(command),
707 shell=True,
708 shell=True,
708 cwd=procutil.tonativestr(repo.root),
709 cwd=procutil.tonativestr(repo.root),
709 stdin=subprocess.PIPE,
710 stdin=subprocess.PIPE,
710 stdout=subprocess.PIPE,
711 stdout=subprocess.PIPE,
711 stderr=subprocess.PIPE,
712 stderr=subprocess.PIPE,
712 )
713 )
713 stdout, stderr = proc.communicate(newdata)
714 stdout, stderr = proc.communicate(newdata)
714 if stderr:
715 if stderr:
715 showstderr(ui, fixctx.rev(), fixername, stderr)
716 showstderr(ui, fixctx.rev(), fixername, stderr)
716 newerdata = stdout
717 newerdata = stdout
717 if fixer.shouldoutputmetadata():
718 if fixer.shouldoutputmetadata():
718 try:
719 try:
719 metadatajson, newerdata = stdout.split(b'\0', 1)
720 metadatajson, newerdata = stdout.split(b'\0', 1)
720 metadata[fixername] = pycompat.json_loads(metadatajson)
721 metadata[fixername] = pycompat.json_loads(metadatajson)
721 except ValueError:
722 except ValueError:
722 ui.warn(
723 ui.warn(
723 _(b'ignored invalid output from fixer tool: %s\n')
724 _(b'ignored invalid output from fixer tool: %s\n')
724 % (fixername,)
725 % (fixername,)
725 )
726 )
726 continue
727 continue
727 else:
728 else:
728 metadata[fixername] = None
729 metadata[fixername] = None
729 if proc.returncode == 0:
730 if proc.returncode == 0:
730 newdata = newerdata
731 newdata = newerdata
731 else:
732 else:
732 if not stderr:
733 if not stderr:
733 message = _(b'exited with status %d\n') % (proc.returncode,)
734 message = _(b'exited with status %d\n') % (proc.returncode,)
734 showstderr(ui, fixctx.rev(), fixername, message)
735 showstderr(ui, fixctx.rev(), fixername, message)
735 checktoolfailureaction(
736 checktoolfailureaction(
736 ui,
737 ui,
737 _(b'no fixes will be applied'),
738 _(b'no fixes will be applied'),
738 hint=_(
739 hint=_(
739 b'use --config fix.failure=continue to apply any '
740 b'use --config fix.failure=continue to apply any '
740 b'successful fixes anyway'
741 b'successful fixes anyway'
741 ),
742 ),
742 )
743 )
743 return metadata, newdata
744 return metadata, newdata
744
745
745
746
746 def showstderr(ui, rev, fixername, stderr):
747 def showstderr(ui, rev, fixername, stderr):
747 """Writes the lines of the stderr string as warnings on the ui
748 """Writes the lines of the stderr string as warnings on the ui
748
749
749 Uses the revision number and fixername to give more context to each line of
750 Uses the revision number and fixername to give more context to each line of
750 the error message. Doesn't include file names, since those take up a lot of
751 the error message. Doesn't include file names, since those take up a lot of
751 space and would tend to be included in the error message if they were
752 space and would tend to be included in the error message if they were
752 relevant.
753 relevant.
753 """
754 """
754 for line in re.split(b'[\r\n]+', stderr):
755 for line in re.split(b'[\r\n]+', stderr):
755 if line:
756 if line:
756 ui.warn(b'[')
757 ui.warn(b'[')
757 if rev is None:
758 if rev is None:
758 ui.warn(_(b'wdir'), label=b'evolve.rev')
759 ui.warn(_(b'wdir'), label=b'evolve.rev')
759 else:
760 else:
760 ui.warn(b'%d' % rev, label=b'evolve.rev')
761 ui.warn(b'%d' % rev, label=b'evolve.rev')
761 ui.warn(b'] %s: %s\n' % (fixername, line))
762 ui.warn(b'] %s: %s\n' % (fixername, line))
762
763
763
764
764 def writeworkingdir(repo, ctx, filedata, replacements):
765 def writeworkingdir(repo, ctx, filedata, replacements):
765 """Write new content to the working copy and check out the new p1 if any
766 """Write new content to the working copy and check out the new p1 if any
766
767
767 We check out a new revision if and only if we fixed something in both the
768 We check out a new revision if and only if we fixed something in both the
768 working directory and its parent revision. This avoids the need for a full
769 working directory and its parent revision. This avoids the need for a full
769 update/merge, and means that the working directory simply isn't affected
770 update/merge, and means that the working directory simply isn't affected
770 unless the --working-dir flag is given.
771 unless the --working-dir flag is given.
771
772
772 Directly updates the dirstate for the affected files.
773 Directly updates the dirstate for the affected files.
773 """
774 """
774 for path, data in filedata.items():
775 for path, data in filedata.items():
775 fctx = ctx[path]
776 fctx = ctx[path]
776 fctx.write(data, fctx.flags())
777 fctx.write(data, fctx.flags())
777
778
778 oldp1 = repo.dirstate.p1()
779 oldp1 = repo.dirstate.p1()
779 newp1 = replacements.get(oldp1, oldp1)
780 newp1 = replacements.get(oldp1, oldp1)
780 if newp1 != oldp1:
781 if newp1 != oldp1:
781 assert repo.dirstate.p2() == nullid
782 assert repo.dirstate.p2() == nullid
782 with repo.dirstate.changing_parents(repo):
783 with repo.dirstate.changing_parents(repo):
783 scmutil.movedirstate(repo, repo[newp1])
784 scmutil.movedirstate(repo, repo[newp1])
784
785
785
786
786 def replacerev(ui, repo, ctx, filedata, replacements):
787 def replacerev(ui, repo, ctx, filedata, replacements):
787 """Commit a new revision like the given one, but with file content changes
788 """Commit a new revision like the given one, but with file content changes
788
789
789 "ctx" is the original revision to be replaced by a modified one.
790 "ctx" is the original revision to be replaced by a modified one.
790
791
791 "filedata" is a dict that maps paths to their new file content. All other
792 "filedata" is a dict that maps paths to their new file content. All other
792 paths will be recreated from the original revision without changes.
793 paths will be recreated from the original revision without changes.
793 "filedata" may contain paths that didn't exist in the original revision;
794 "filedata" may contain paths that didn't exist in the original revision;
794 they will be added.
795 they will be added.
795
796
796 "replacements" is a dict that maps a single node to a single node, and it is
797 "replacements" is a dict that maps a single node to a single node, and it is
797 updated to indicate the original revision is replaced by the newly created
798 updated to indicate the original revision is replaced by the newly created
798 one. No entry is added if the replacement's node already exists.
799 one. No entry is added if the replacement's node already exists.
799
800
800 The new revision has the same parents as the old one, unless those parents
801 The new revision has the same parents as the old one, unless those parents
801 have already been replaced, in which case those replacements are the parents
802 have already been replaced, in which case those replacements are the parents
802 of this new revision. Thus, if revisions are replaced in topological order,
803 of this new revision. Thus, if revisions are replaced in topological order,
803 there is no need to rebase them into the original topology later.
804 there is no need to rebase them into the original topology later.
804 """
805 """
805
806
806 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
807 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
807 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
808 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
808 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
809 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
809 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
810 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
810
811
811 # We don't want to create a revision that has no changes from the original,
812 # We don't want to create a revision that has no changes from the original,
812 # but we should if the original revision's parent has been replaced.
813 # but we should if the original revision's parent has been replaced.
813 # Otherwise, we would produce an orphan that needs no actual human
814 # Otherwise, we would produce an orphan that needs no actual human
814 # intervention to evolve. We can't rely on commit() to avoid creating the
815 # intervention to evolve. We can't rely on commit() to avoid creating the
815 # un-needed revision because the extra field added below produces a new hash
816 # un-needed revision because the extra field added below produces a new hash
816 # regardless of file content changes.
817 # regardless of file content changes.
817 if (
818 if (
818 not filedata
819 not filedata
819 and p1ctx.node() not in replacements
820 and p1ctx.node() not in replacements
820 and p2ctx.node() not in replacements
821 and p2ctx.node() not in replacements
821 ):
822 ):
822 return
823 return
823
824
824 extra = ctx.extra().copy()
825 extra = ctx.extra().copy()
825 extra[b'fix_source'] = ctx.hex()
826 extra[b'fix_source'] = ctx.hex()
826
827
827 wctx = context.overlayworkingctx(repo)
828 wctx = context.overlayworkingctx(repo)
828 wctx.setbase(repo[newp1node])
829 wctx.setbase(repo[newp1node])
829 merge.revert_to(ctx, wc=wctx)
830 merge.revert_to(ctx, wc=wctx)
830 copies.graftcopies(wctx, ctx, ctx.p1())
831 copies.graftcopies(wctx, ctx, ctx.p1())
831
832
832 for path in filedata.keys():
833 for path in filedata.keys():
833 fctx = ctx[path]
834 fctx = ctx[path]
834 copysource = fctx.copysource()
835 copysource = fctx.copysource()
835 wctx.write(path, filedata[path], flags=fctx.flags())
836 wctx.write(path, filedata[path], flags=fctx.flags())
836 if copysource:
837 if copysource:
837 wctx.markcopied(path, copysource)
838 wctx.markcopied(path, copysource)
838
839
839 desc = rewriteutil.update_hash_refs(
840 desc = rewriteutil.update_hash_refs(
840 repo,
841 repo,
841 ctx.description(),
842 ctx.description(),
842 {oldnode: [newnode] for oldnode, newnode in replacements.items()},
843 {oldnode: [newnode] for oldnode, newnode in replacements.items()},
843 )
844 )
844
845
845 memctx = wctx.tomemctx(
846 memctx = wctx.tomemctx(
846 text=desc,
847 text=desc,
847 branch=ctx.branch(),
848 branch=ctx.branch(),
848 extra=extra,
849 extra=extra,
849 date=ctx.date(),
850 date=ctx.date(),
850 parents=(newp1node, newp2node),
851 parents=(newp1node, newp2node),
851 user=ctx.user(),
852 user=ctx.user(),
852 )
853 )
853
854
854 sucnode = memctx.commit()
855 sucnode = memctx.commit()
855 prenode = ctx.node()
856 prenode = ctx.node()
856 if prenode == sucnode:
857 if prenode == sucnode:
857 ui.debug(b'node %s already existed\n' % (ctx.hex()))
858 ui.debug(b'node %s already existed\n' % (ctx.hex()))
858 else:
859 else:
859 replacements[ctx.node()] = sucnode
860 replacements[ctx.node()] = sucnode
860
861
861
862
862 def getfixers(ui):
863 def getfixers(ui):
863 """Returns a map of configured fixer tools indexed by their names
864 """Returns a map of configured fixer tools indexed by their names
864
865
865 Each value is a Fixer object with methods that implement the behavior of the
866 Each value is a Fixer object with methods that implement the behavior of the
866 fixer's config suboptions. Does not validate the config values.
867 fixer's config suboptions. Does not validate the config values.
867 """
868 """
868 fixers = {}
869 fixers = {}
869 for name in fixernames(ui):
870 for name in fixernames(ui):
870 enabled = ui.configbool(b'fix', name + b':enabled')
871 enabled = ui.configbool(b'fix', name + b':enabled')
871 command = ui.config(b'fix', name + b':command')
872 command = ui.config(b'fix', name + b':command')
872 pattern = ui.config(b'fix', name + b':pattern')
873 pattern = ui.config(b'fix', name + b':pattern')
873 linerange = ui.config(b'fix', name + b':linerange')
874 linerange = ui.config(b'fix', name + b':linerange')
874 priority = ui.configint(b'fix', name + b':priority')
875 priority = ui.configint(b'fix', name + b':priority')
875 metadata = ui.configbool(b'fix', name + b':metadata')
876 metadata = ui.configbool(b'fix', name + b':metadata')
876 skipclean = ui.configbool(b'fix', name + b':skipclean')
877 skipclean = ui.configbool(b'fix', name + b':skipclean')
877 # Don't use a fixer if it has no pattern configured. It would be
878 # Don't use a fixer if it has no pattern configured. It would be
878 # dangerous to let it affect all files. It would be pointless to let it
879 # dangerous to let it affect all files. It would be pointless to let it
879 # affect no files. There is no reasonable subset of files to use as the
880 # affect no files. There is no reasonable subset of files to use as the
880 # default.
881 # default.
881 if command is None:
882 if command is None:
882 ui.warn(
883 ui.warn(
883 _(b'fixer tool has no command configuration: %s\n') % (name,)
884 _(b'fixer tool has no command configuration: %s\n') % (name,)
884 )
885 )
885 elif pattern is None:
886 elif pattern is None:
886 ui.warn(
887 ui.warn(
887 _(b'fixer tool has no pattern configuration: %s\n') % (name,)
888 _(b'fixer tool has no pattern configuration: %s\n') % (name,)
888 )
889 )
889 elif not enabled:
890 elif not enabled:
890 ui.debug(b'ignoring disabled fixer tool: %s\n' % (name,))
891 ui.debug(b'ignoring disabled fixer tool: %s\n' % (name,))
891 else:
892 else:
892 fixers[name] = Fixer(
893 fixers[name] = Fixer(
893 command, pattern, linerange, priority, metadata, skipclean
894 command, pattern, linerange, priority, metadata, skipclean
894 )
895 )
895 return collections.OrderedDict(
896 return collections.OrderedDict(
896 sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
897 sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
897 )
898 )
898
899
899
900
900 def fixernames(ui):
901 def fixernames(ui):
901 """Returns the names of [fix] config options that have suboptions"""
902 """Returns the names of [fix] config options that have suboptions"""
902 names = set()
903 names = set()
903 for k, v in ui.configitems(b'fix'):
904 for k, v in ui.configitems(b'fix'):
904 if b':' in k:
905 if b':' in k:
905 names.add(k.split(b':', 1)[0])
906 names.add(k.split(b':', 1)[0])
906 return names
907 return names
907
908
908
909
909 class Fixer:
910 class Fixer:
910 """Wraps the raw config values for a fixer with methods"""
911 """Wraps the raw config values for a fixer with methods"""
911
912
912 def __init__(
913 def __init__(
913 self, command, pattern, linerange, priority, metadata, skipclean
914 self, command, pattern, linerange, priority, metadata, skipclean
914 ):
915 ):
915 self._command = command
916 self._command = command
916 self._pattern = pattern
917 self._pattern = pattern
917 self._linerange = linerange
918 self._linerange = linerange
918 self._priority = priority
919 self._priority = priority
919 self._metadata = metadata
920 self._metadata = metadata
920 self._skipclean = skipclean
921 self._skipclean = skipclean
921
922
922 def affects(self, opts, fixctx, path):
923 def affects(self, opts, fixctx, path):
923 """Should this fixer run on the file at the given path and context?"""
924 """Should this fixer run on the file at the given path and context?"""
924 repo = fixctx.repo()
925 repo = fixctx.repo()
925 matcher = matchmod.match(
926 matcher = matchmod.match(
926 repo.root, repo.root, [self._pattern], ctx=fixctx
927 repo.root, repo.root, [self._pattern], ctx=fixctx
927 )
928 )
928 return matcher(path)
929 return matcher(path)
929
930
930 def shouldoutputmetadata(self):
931 def shouldoutputmetadata(self):
931 """Should the stdout of this fixer start with JSON and a null byte?"""
932 """Should the stdout of this fixer start with JSON and a null byte?"""
932 return self._metadata
933 return self._metadata
933
934
934 def command(self, ui, path, ranges):
935 def command(self, ui, path, ranges):
935 """A shell command to use to invoke this fixer on the given file/lines
936 """A shell command to use to invoke this fixer on the given file/lines
936
937
937 May return None if there is no appropriate command to run for the given
938 May return None if there is no appropriate command to run for the given
938 parameters.
939 parameters.
939 """
940 """
940 expand = cmdutil.rendercommandtemplate
941 expand = cmdutil.rendercommandtemplate
941 parts = [
942 parts = [
942 expand(
943 expand(
943 ui,
944 ui,
944 self._command,
945 self._command,
945 {b'rootpath': path, b'basename': os.path.basename(path)},
946 {b'rootpath': path, b'basename': os.path.basename(path)},
946 )
947 )
947 ]
948 ]
948 if self._linerange:
949 if self._linerange:
949 if self._skipclean and not ranges:
950 if self._skipclean and not ranges:
950 # No line ranges to fix, so don't run the fixer.
951 # No line ranges to fix, so don't run the fixer.
951 return None
952 return None
952 for first, last in ranges:
953 for first, last in ranges:
953 parts.append(
954 parts.append(
954 expand(
955 expand(
955 ui, self._linerange, {b'first': first, b'last': last}
956 ui, self._linerange, {b'first': first, b'last': last}
956 )
957 )
957 )
958 )
958 return b' '.join(parts)
959 return b' '.join(parts)
@@ -1,1854 +1,1855
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 re
6 > import re
7 > import sys
7 > import sys
8 > from mercurial.utils.procutil import setbinary
8 > from mercurial.utils.procutil import setbinary
9 > setbinary(sys.stdin)
9 > setbinary(sys.stdin)
10 > setbinary(sys.stdout)
10 > setbinary(sys.stdout)
11 > stdin = getattr(sys.stdin, 'buffer', sys.stdin)
11 > stdin = getattr(sys.stdin, 'buffer', sys.stdin)
12 > stdout = getattr(sys.stdout, 'buffer', sys.stdout)
12 > stdout = getattr(sys.stdout, 'buffer', sys.stdout)
13 > lines = set()
13 > lines = set()
14 > def format(text):
14 > def format(text):
15 > return re.sub(b' +', b' ', text.upper())
15 > return re.sub(b' +', b' ', text.upper())
16 > for arg in sys.argv[1:]:
16 > for arg in sys.argv[1:]:
17 > if arg == 'all':
17 > if arg == 'all':
18 > stdout.write(format(stdin.read()))
18 > stdout.write(format(stdin.read()))
19 > sys.exit(0)
19 > sys.exit(0)
20 > else:
20 > else:
21 > first, last = arg.split('-')
21 > first, last = arg.split('-')
22 > lines.update(range(int(first), int(last) + 1))
22 > lines.update(range(int(first), int(last) + 1))
23 > for i, line in enumerate(stdin.readlines()):
23 > for i, line in enumerate(stdin.readlines()):
24 > if i + 1 in lines:
24 > if i + 1 in lines:
25 > stdout.write(format(line))
25 > stdout.write(format(line))
26 > else:
26 > else:
27 > stdout.write(line)
27 > stdout.write(line)
28 > EOF
28 > EOF
29 $ TESTLINES="foo\nbar\nbaz\nqux\n"
29 $ TESTLINES="foo\nbar\nbaz\nqux\n"
30 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY
30 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY
31 foo
31 foo
32 bar
32 bar
33 baz
33 baz
34 qux
34 qux
35 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY all
35 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY all
36 FOO
36 FOO
37 BAR
37 BAR
38 BAZ
38 BAZ
39 QUX
39 QUX
40 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-1
40 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-1
41 FOO
41 FOO
42 bar
42 bar
43 baz
43 baz
44 qux
44 qux
45 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-2
45 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-2
46 FOO
46 FOO
47 BAR
47 BAR
48 baz
48 baz
49 qux
49 qux
50 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-3
50 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-3
51 foo
51 foo
52 BAR
52 BAR
53 BAZ
53 BAZ
54 qux
54 qux
55 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-2 4-4
55 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-2 4-4
56 foo
56 foo
57 BAR
57 BAR
58 baz
58 baz
59 QUX
59 QUX
60
60
61 Set up the config with two simple fixers: one that fixes specific line ranges,
61 Set up the config with two simple fixers: one that fixes specific line ranges,
62 and one that always fixes the whole file. They both "fix" files by converting
62 and one that always fixes the whole file. They both "fix" files by converting
63 letters to uppercase. They use different file extensions, so each test case can
63 letters to uppercase. They use different file extensions, so each test case can
64 choose which behavior to use by naming files.
64 choose which behavior to use by naming files.
65
65
66 $ cat >> $HGRCPATH <<EOF
66 $ cat >> $HGRCPATH <<EOF
67 > [extensions]
67 > [extensions]
68 > fix =
68 > fix =
69 > [experimental]
69 > [experimental]
70 > evolution.createmarkers=True
70 > evolution.createmarkers=True
71 > evolution.allowunstable=True
71 > evolution.allowunstable=True
72 > [fix]
72 > [fix]
73 > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY all
73 > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY all
74 > uppercase-whole-file:pattern=set:**.whole
74 > uppercase-whole-file:pattern=set:**.whole
75 > uppercase-changed-lines:command="$PYTHON" $UPPERCASEPY
75 > uppercase-changed-lines:command="$PYTHON" $UPPERCASEPY
76 > uppercase-changed-lines:linerange={first}-{last}
76 > uppercase-changed-lines:linerange={first}-{last}
77 > uppercase-changed-lines:pattern=set:**.changed
77 > uppercase-changed-lines:pattern=set:**.changed
78 > EOF
78 > EOF
79
79
80 Help text for fix.
80 Help text for fix.
81
81
82 $ hg help fix
82 $ hg help fix
83 hg fix [OPTION]... [FILE]...
83 hg fix [OPTION]... [FILE]...
84
84
85 rewrite file content in changesets or working directory
85 rewrite file content in changesets or working directory
86
86
87 Runs any configured tools to fix the content of files. Only affects files
87 Runs any configured tools to fix the content of files. (See 'hg help -e
88 with changes, unless file arguments are provided. Only affects changed
88 fix' for details about configuring tools.) Only affects files with
89 lines of files, unless the --whole flag is used. Some tools may always
89 changes, unless file arguments are provided. Only affects changed lines of
90 affect the whole file regardless of --whole.
90 files, unless the --whole flag is used. Some tools may always affect the
91 whole file regardless of --whole.
91
92
92 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
93 copy will be fixed. Note that no backup are made.
94 copy will be fixed. Note that no backup are made.
94
95
95 If revisions are specified with --source, those revisions and their
96 If revisions are specified with --source, those revisions and their
96 descendants will be checked, and they may be replaced with new revisions
97 descendants will be checked, and they may be replaced with new revisions
97 that have fixed file content. By automatically including the descendants,
98 that have fixed file content. By automatically including the descendants,
98 no merging, rebasing, or evolution will be required. If an ancestor of the
99 no merging, rebasing, or evolution will be required. If an ancestor of the
99 working copy is included, then the working copy itself will also be fixed,
100 working copy is included, then the working copy itself will also be fixed,
100 and the working copy will be updated to the fixed parent.
101 and the working copy will be updated to the fixed parent.
101
102
102 When determining what lines of each file to fix at each revision, the
103 When determining what lines of each file to fix at each revision, the
103 whole set of revisions being fixed is considered, so that fixes to earlier
104 whole set of revisions being fixed is considered, so that fixes to earlier
104 revisions are not forgotten in later ones. The --base flag can be used to
105 revisions are not forgotten in later ones. The --base flag can be used to
105 override this default behavior, though it is not usually desirable to do
106 override this default behavior, though it is not usually desirable to do
106 so.
107 so.
107
108
108 (use 'hg help -e fix' to show help for the fix extension)
109 (use 'hg help -e fix' to show help for the fix extension)
109
110
110 options ([+] can be repeated):
111 options ([+] can be repeated):
111
112
112 --all fix all non-public non-obsolete revisions
113 --all fix all non-public non-obsolete revisions
113 --base REV [+] revisions to diff against (overrides automatic selection,
114 --base REV [+] revisions to diff against (overrides automatic selection,
114 and applies to every revision being fixed)
115 and applies to every revision being fixed)
115 -s --source REV [+] fix the specified revisions and their descendants
116 -s --source REV [+] fix the specified revisions and their descendants
116 -w --working-dir fix the working directory
117 -w --working-dir fix the working directory
117 --whole always fix every line of a file
118 --whole always fix every line of a file
118
119
119 (some details hidden, use --verbose to show complete help)
120 (some details hidden, use --verbose to show complete help)
120
121
121 $ hg help -e fix
122 $ hg help -e fix
122 fix extension - rewrite file content in changesets or working copy
123 fix extension - rewrite file content in changesets or working copy
123 (EXPERIMENTAL)
124 (EXPERIMENTAL)
124
125
125 Provides a command that runs configured tools on the contents of modified
126 Provides a command that runs configured tools on the contents of modified
126 files, writing back any fixes to the working copy or replacing changesets.
127 files, writing back any fixes to the working copy or replacing changesets.
127
128
129 Fixer tools are run in the repository's root directory. This allows them to
130 read configuration files from the working copy, or even write to the working
131 copy. The working copy is not updated to match the revision being fixed. In
132 fact, several revisions may be fixed in parallel. Writes to the working copy
133 are not amended into the revision being fixed; fixer tools MUST always read
134 content to be fixed from stdin, and write fixed file content back to stdout.
135
128 Here is an example configuration that causes 'hg fix' to apply automatic
136 Here is an example configuration that causes 'hg fix' to apply automatic
129 formatting fixes to modified lines in C++ code:
137 formatting fixes to modified lines in C++ code:
130
138
131 [fix]
139 [fix]
132 clang-format:command=clang-format --assume-filename={rootpath}
140 clang-format:command=clang-format --assume-filename={rootpath}
133 clang-format:linerange=--lines={first}:{last}
141 clang-format:linerange=--lines={first}:{last}
134 clang-format:pattern=set:**.cpp or **.hpp
142 clang-format:pattern=set:**.cpp or **.hpp
135
143
136 The :command suboption forms the first part of the shell command that will be
144 The :command suboption forms the first part of the shell command that will be
137 used to fix a file. The content of the file is passed on standard input, and
145 used to fix a file. The content of the file is passed on standard input, and
138 the fixed file content is expected on standard output. Any output on standard
146 the fixed file content is expected on standard output. Any output on standard
139 error will be displayed as a warning. If the exit status is not zero, the file
147 error will be displayed as a warning. If the exit status is not zero, the file
140 will not be affected. A placeholder warning is displayed if there is a non-
148 will not be affected. A placeholder warning is displayed if there is a non-
141 zero exit status but no standard error output. Some values may be substituted
149 zero exit status but no standard error output. Some values may be substituted
142 into the command:
150 into the command:
143
151
144 {rootpath} The path of the file being fixed, relative to the repo root
152 {rootpath} The path of the file being fixed, relative to the repo root
145 {basename} The name of the file being fixed, without the directory path
153 {basename} The name of the file being fixed, without the directory path
146
154
147 If the :linerange suboption is set, the tool will only be run if there are
155 If the :linerange suboption is set, the tool will only be run if there are
148 changed lines in a file. The value of this suboption is appended to the shell
156 changed lines in a file. The value of this suboption is appended to the shell
149 command once for every range of changed lines in the file. Some values may be
157 command once for every range of changed lines in the file. Some values may be
150 substituted into the command:
158 substituted into the command:
151
159
152 {first} The 1-based line number of the first line in the modified range
160 {first} The 1-based line number of the first line in the modified range
153 {last} The 1-based line number of the last line in the modified range
161 {last} The 1-based line number of the last line in the modified range
154
162
155 Deleted sections of a file will be ignored by :linerange, because there is no
163 Deleted sections of a file will be ignored by :linerange, because there is no
156 corresponding line range in the version being fixed.
164 corresponding line range in the version being fixed.
157
165
158 By default, tools that set :linerange will only be executed if there is at
166 By default, tools that set :linerange will only be executed if there is at
159 least one changed line range. This is meant to prevent accidents like running
167 least one changed line range. This is meant to prevent accidents like running
160 a code formatter in such a way that it unexpectedly reformats the whole file.
168 a code formatter in such a way that it unexpectedly reformats the whole file.
161 If such a tool needs to operate on unchanged files, it should set the
169 If such a tool needs to operate on unchanged files, it should set the
162 :skipclean suboption to false.
170 :skipclean suboption to false.
163
171
164 The :pattern suboption determines which files will be passed through each
172 The :pattern suboption determines which files will be passed through each
165 configured tool. See 'hg help patterns' for possible values. However, all
173 configured tool. See 'hg help patterns' for possible values. However, all
166 patterns are relative to the repo root, even if that text says they are
174 patterns are relative to the repo root, even if that text says they are
167 relative to the current working directory. If there are file arguments to 'hg
175 relative to the current working directory. If there are file arguments to 'hg
168 fix', the intersection of these patterns is used.
176 fix', the intersection of these patterns is used.
169
177
170 There is also a configurable limit for the maximum size of file that will be
178 There is also a configurable limit for the maximum size of file that will be
171 processed by 'hg fix':
179 processed by 'hg fix':
172
180
173 [fix]
181 [fix]
174 maxfilesize = 2MB
182 maxfilesize = 2MB
175
183
176 Normally, execution of configured tools will continue after a failure
184 Normally, execution of configured tools will continue after a failure
177 (indicated by a non-zero exit status). It can also be configured to abort
185 (indicated by a non-zero exit status). It can also be configured to abort
178 after the first such failure, so that no files will be affected if any tool
186 after the first such failure, so that no files will be affected if any tool
179 fails. This abort will also cause 'hg fix' to exit with a non-zero status:
187 fails. This abort will also cause 'hg fix' to exit with a non-zero status:
180
188
181 [fix]
189 [fix]
182 failure = abort
190 failure = abort
183
191
184 When multiple tools are configured to affect a file, they execute in an order
192 When multiple tools are configured to affect a file, they execute in an order
185 defined by the :priority suboption. The priority suboption has a default value
193 defined by the :priority suboption. The priority suboption has a default value
186 of zero for each tool. Tools are executed in order of descending priority. The
194 of zero for each tool. Tools are executed in order of descending priority. The
187 execution order of tools with equal priority is unspecified. For example, you
195 execution order of tools with equal priority is unspecified. For example, you
188 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
196 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
189 in a text file by ensuring that 'sort' runs before 'head':
197 in a text file by ensuring that 'sort' runs before 'head':
190
198
191 [fix]
199 [fix]
192 sort:command = sort -n
200 sort:command = sort -n
193 head:command = head -n 10
201 head:command = head -n 10
194 sort:pattern = numbers.txt
202 sort:pattern = numbers.txt
195 head:pattern = numbers.txt
203 head:pattern = numbers.txt
196 sort:priority = 2
204 sort:priority = 2
197 head:priority = 1
205 head:priority = 1
198
206
199 To account for changes made by each tool, the line numbers used for
207 To account for changes made by each tool, the line numbers used for
200 incremental formatting are recomputed before executing the next tool. So, each
208 incremental formatting are recomputed before executing the next tool. So, each
201 tool may see different values for the arguments added by the :linerange
209 tool may see different values for the arguments added by the :linerange
202 suboption.
210 suboption.
203
211
204 Each fixer tool is allowed to return some metadata in addition to the fixed
212 Each fixer tool is allowed to return some metadata in addition to the fixed
205 file content. The metadata must be placed before the file content on stdout,
213 file content. The metadata must be placed before the file content on stdout,
206 separated from the file content by a zero byte. The metadata is parsed as a
214 separated from the file content by a zero byte. The metadata is parsed as a
207 JSON value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer
215 JSON value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer
208 tool is expected to produce this metadata encoding if and only if the
216 tool is expected to produce this metadata encoding if and only if the
209 :metadata suboption is true:
217 :metadata suboption is true:
210
218
211 [fix]
219 [fix]
212 tool:command = tool --prepend-json-metadata
220 tool:command = tool --prepend-json-metadata
213 tool:metadata = true
221 tool:metadata = true
214
222
215 The metadata values are passed to hooks, which can be used to print summaries
223 The metadata values are passed to hooks, which can be used to print summaries
216 or perform other post-fixing work. The supported hooks are:
224 or perform other post-fixing work. The supported hooks are:
217
225
218 "postfixfile"
226 "postfixfile"
219 Run once for each file in each revision where any fixer tools made changes
227 Run once for each file in each revision where any fixer tools made changes
220 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
228 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
221 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
229 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
222 tools that affected the file. Fixer tools that didn't affect the file have a
230 tools that affected the file. Fixer tools that didn't affect the file have a
223 value of None. Only fixer tools that executed are present in the metadata.
231 value of None. Only fixer tools that executed are present in the metadata.
224
232
225 "postfix"
233 "postfix"
226 Run once after all files and revisions have been handled. Provides
234 Run once after all files and revisions have been handled. Provides
227 "$HG_REPLACEMENTS" with information about what revisions were created and
235 "$HG_REPLACEMENTS" with information about what revisions were created and
228 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
236 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
229 files in the working copy were updated. Provides a list "$HG_METADATA"
237 files in the working copy were updated. Provides a list "$HG_METADATA"
230 mapping fixer tool names to lists of metadata values returned from
238 mapping fixer tool names to lists of metadata values returned from
231 executions that modified a file. This aggregates the same metadata
239 executions that modified a file. This aggregates the same metadata
232 previously passed to the "postfixfile" hook.
240 previously passed to the "postfixfile" hook.
233
241
234 Fixer tools are run in the repository's root directory. This allows them to
235 read configuration files from the working copy, or even write to the working
236 copy. The working copy is not updated to match the revision being fixed. In
237 fact, several revisions may be fixed in parallel. Writes to the working copy
238 are not amended into the revision being fixed; fixer tools should always write
239 fixed file content back to stdout as documented above.
240
241 list of commands:
242 list of commands:
242
243
243 fix rewrite file content in changesets or working directory
244 fix rewrite file content in changesets or working directory
244
245
245 (use 'hg help -v -e fix' to show built-in aliases and global options)
246 (use 'hg help -v -e fix' to show built-in aliases and global options)
246
247
247 There is no default behavior in the absence of --rev and --working-dir.
248 There is no default behavior in the absence of --rev and --working-dir.
248
249
249 $ hg init badusage
250 $ hg init badusage
250 $ cd badusage
251 $ cd badusage
251
252
252 $ hg fix
253 $ hg fix
253 abort: no changesets specified
254 abort: no changesets specified
254 (use --source or --working-dir)
255 (use --source or --working-dir)
255 [255]
256 [255]
256 $ hg fix --whole
257 $ hg fix --whole
257 abort: no changesets specified
258 abort: no changesets specified
258 (use --source or --working-dir)
259 (use --source or --working-dir)
259 [255]
260 [255]
260 $ hg fix --base 0
261 $ hg fix --base 0
261 abort: no changesets specified
262 abort: no changesets specified
262 (use --source or --working-dir)
263 (use --source or --working-dir)
263 [255]
264 [255]
264
265
265 Fixing a public revision isn't allowed. It should abort early enough that
266 Fixing a public revision isn't allowed. It should abort early enough that
266 nothing happens, even to the working directory.
267 nothing happens, even to the working directory.
267
268
268 $ printf "hello\n" > hello.whole
269 $ printf "hello\n" > hello.whole
269 $ hg commit -Aqm "hello"
270 $ hg commit -Aqm "hello"
270 $ hg phase -r 0 --public
271 $ hg phase -r 0 --public
271 $ hg fix -r 0
272 $ hg fix -r 0
272 abort: cannot fix public changesets: 6470986d2e7b
273 abort: cannot fix public changesets: 6470986d2e7b
273 (see 'hg help phases' for details)
274 (see 'hg help phases' for details)
274 [10]
275 [10]
275 $ hg fix -r 0 --working-dir
276 $ hg fix -r 0 --working-dir
276 abort: cannot fix public changesets: 6470986d2e7b
277 abort: cannot fix public changesets: 6470986d2e7b
277 (see 'hg help phases' for details)
278 (see 'hg help phases' for details)
278 [10]
279 [10]
279 $ hg cat -r tip hello.whole
280 $ hg cat -r tip hello.whole
280 hello
281 hello
281 $ cat hello.whole
282 $ cat hello.whole
282 hello
283 hello
283
284
284 $ cd ..
285 $ cd ..
285
286
286 Fixing a clean working directory should do nothing. Even the --whole flag
287 Fixing a clean working directory should do nothing. Even the --whole flag
287 shouldn't cause any clean files to be fixed. Specifying a clean file explicitly
288 shouldn't cause any clean files to be fixed. Specifying a clean file explicitly
288 should only fix it if the fixer always fixes the whole file. The combination of
289 should only fix it if the fixer always fixes the whole file. The combination of
289 an explicit filename and --whole should format the entire file regardless.
290 an explicit filename and --whole should format the entire file regardless.
290
291
291 $ hg init fixcleanwdir
292 $ hg init fixcleanwdir
292 $ cd fixcleanwdir
293 $ cd fixcleanwdir
293
294
294 $ printf "hello\n" > hello.changed
295 $ printf "hello\n" > hello.changed
295 $ printf "world\n" > hello.whole
296 $ printf "world\n" > hello.whole
296 $ hg commit -Aqm "foo"
297 $ hg commit -Aqm "foo"
297 $ hg fix --working-dir
298 $ hg fix --working-dir
298 $ hg diff
299 $ hg diff
299 $ hg fix --working-dir --whole
300 $ hg fix --working-dir --whole
300 $ hg diff
301 $ hg diff
301 $ hg fix --working-dir *
302 $ hg fix --working-dir *
302 $ cat *
303 $ cat *
303 hello
304 hello
304 WORLD
305 WORLD
305 $ hg revert --all --no-backup
306 $ hg revert --all --no-backup
306 reverting hello.whole
307 reverting hello.whole
307 $ hg fix --working-dir * --whole
308 $ hg fix --working-dir * --whole
308 $ cat *
309 $ cat *
309 HELLO
310 HELLO
310 WORLD
311 WORLD
311
312
312 The same ideas apply to fixing a revision, so we create a revision that doesn't
313 The same ideas apply to fixing a revision, so we create a revision that doesn't
313 modify either of the files in question and try fixing it. This also tests that
314 modify either of the files in question and try fixing it. This also tests that
314 we ignore a file that doesn't match any configured fixer.
315 we ignore a file that doesn't match any configured fixer.
315
316
316 $ hg revert --all --no-backup
317 $ hg revert --all --no-backup
317 reverting hello.changed
318 reverting hello.changed
318 reverting hello.whole
319 reverting hello.whole
319 $ printf "unimportant\n" > some.file
320 $ printf "unimportant\n" > some.file
320 $ hg commit -Aqm "some other file"
321 $ hg commit -Aqm "some other file"
321
322
322 $ hg fix -r .
323 $ hg fix -r .
323 $ hg cat -r tip *
324 $ hg cat -r tip *
324 hello
325 hello
325 world
326 world
326 unimportant
327 unimportant
327 $ hg fix -r . --whole
328 $ hg fix -r . --whole
328 $ hg cat -r tip *
329 $ hg cat -r tip *
329 hello
330 hello
330 world
331 world
331 unimportant
332 unimportant
332 $ hg fix -r . *
333 $ hg fix -r . *
333 $ hg cat -r tip *
334 $ hg cat -r tip *
334 hello
335 hello
335 WORLD
336 WORLD
336 unimportant
337 unimportant
337 $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true
338 $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true
338 2 new content-divergent changesets
339 2 new content-divergent changesets
339 $ hg cat -r tip *
340 $ hg cat -r tip *
340 HELLO
341 HELLO
341 WORLD
342 WORLD
342 unimportant
343 unimportant
343
344
344 $ cd ..
345 $ cd ..
345
346
346 Fixing the working directory should still work if there are no revisions.
347 Fixing the working directory should still work if there are no revisions.
347
348
348 $ hg init norevisions
349 $ hg init norevisions
349 $ cd norevisions
350 $ cd norevisions
350
351
351 $ printf "something\n" > something.whole
352 $ printf "something\n" > something.whole
352 $ hg add
353 $ hg add
353 adding something.whole
354 adding something.whole
354 $ hg fix --working-dir
355 $ hg fix --working-dir
355 $ cat something.whole
356 $ cat something.whole
356 SOMETHING
357 SOMETHING
357
358
358 $ cd ..
359 $ cd ..
359
360
360 Test that the working copy is reported clean if formatting of the parent makes
361 Test that the working copy is reported clean if formatting of the parent makes
361 it clean.
362 it clean.
362 $ hg init wc-already-formatted
363 $ hg init wc-already-formatted
363 $ cd wc-already-formatted
364 $ cd wc-already-formatted
364
365
365 $ printf "hello world\n" > hello.whole
366 $ printf "hello world\n" > hello.whole
366 $ hg commit -Am initial
367 $ hg commit -Am initial
367 adding hello.whole
368 adding hello.whole
368 $ hg fix -w *
369 $ hg fix -w *
369 $ hg st
370 $ hg st
370 M hello.whole
371 M hello.whole
371 $ hg fix -s . *
372 $ hg fix -s . *
372 $ hg st
373 $ hg st
373 $ hg diff
374 $ hg diff
374
375
375 $ cd ..
376 $ cd ..
376
377
377 Test the effect of fixing the working directory for each possible status, with
378 Test the effect of fixing the working directory for each possible status, with
378 and without providing explicit file arguments.
379 and without providing explicit file arguments.
379
380
380 $ hg init implicitlyfixstatus
381 $ hg init implicitlyfixstatus
381 $ cd implicitlyfixstatus
382 $ cd implicitlyfixstatus
382
383
383 $ printf "modified\n" > modified.whole
384 $ printf "modified\n" > modified.whole
384 $ printf "removed\n" > removed.whole
385 $ printf "removed\n" > removed.whole
385 $ printf "deleted\n" > deleted.whole
386 $ printf "deleted\n" > deleted.whole
386 $ printf "clean\n" > clean.whole
387 $ printf "clean\n" > clean.whole
387 $ printf "ignored.whole" > .hgignore
388 $ printf "ignored.whole" > .hgignore
388 $ hg commit -Aqm "stuff"
389 $ hg commit -Aqm "stuff"
389
390
390 $ printf "modified!!!\n" > modified.whole
391 $ printf "modified!!!\n" > modified.whole
391 $ printf "unknown\n" > unknown.whole
392 $ printf "unknown\n" > unknown.whole
392 $ printf "ignored\n" > ignored.whole
393 $ printf "ignored\n" > ignored.whole
393 $ printf "added\n" > added.whole
394 $ printf "added\n" > added.whole
394 $ hg add added.whole
395 $ hg add added.whole
395 $ hg remove removed.whole
396 $ hg remove removed.whole
396 $ rm deleted.whole
397 $ rm deleted.whole
397
398
398 $ hg status --all
399 $ hg status --all
399 M modified.whole
400 M modified.whole
400 A added.whole
401 A added.whole
401 R removed.whole
402 R removed.whole
402 ! deleted.whole
403 ! deleted.whole
403 ? unknown.whole
404 ? unknown.whole
404 I ignored.whole
405 I ignored.whole
405 C .hgignore
406 C .hgignore
406 C clean.whole
407 C clean.whole
407
408
408 $ hg fix --working-dir
409 $ hg fix --working-dir
409
410
410 $ hg status --all
411 $ hg status --all
411 M modified.whole
412 M modified.whole
412 A added.whole
413 A added.whole
413 R removed.whole
414 R removed.whole
414 ! deleted.whole
415 ! deleted.whole
415 ? unknown.whole
416 ? unknown.whole
416 I ignored.whole
417 I ignored.whole
417 C .hgignore
418 C .hgignore
418 C clean.whole
419 C clean.whole
419
420
420 $ cat *.whole
421 $ cat *.whole
421 ADDED
422 ADDED
422 clean
423 clean
423 ignored
424 ignored
424 MODIFIED!!!
425 MODIFIED!!!
425 unknown
426 unknown
426
427
427 $ printf "modified!!!\n" > modified.whole
428 $ printf "modified!!!\n" > modified.whole
428 $ printf "added\n" > added.whole
429 $ printf "added\n" > added.whole
429
430
430 Listing the files explicitly causes untracked files to also be fixed, but
431 Listing the files explicitly causes untracked files to also be fixed, but
431 ignored files are still unaffected.
432 ignored files are still unaffected.
432
433
433 $ hg fix --working-dir *.whole
434 $ hg fix --working-dir *.whole
434
435
435 $ hg status --all
436 $ hg status --all
436 M clean.whole
437 M clean.whole
437 M modified.whole
438 M modified.whole
438 A added.whole
439 A added.whole
439 R removed.whole
440 R removed.whole
440 ! deleted.whole
441 ! deleted.whole
441 ? unknown.whole
442 ? unknown.whole
442 I ignored.whole
443 I ignored.whole
443 C .hgignore
444 C .hgignore
444
445
445 $ cat *.whole
446 $ cat *.whole
446 ADDED
447 ADDED
447 CLEAN
448 CLEAN
448 ignored
449 ignored
449 MODIFIED!!!
450 MODIFIED!!!
450 UNKNOWN
451 UNKNOWN
451
452
452 $ cd ..
453 $ cd ..
453
454
454 Test that incremental fixing works on files with additions, deletions, and
455 Test that incremental fixing works on files with additions, deletions, and
455 changes in multiple line ranges. Note that deletions do not generally cause
456 changes in multiple line ranges. Note that deletions do not generally cause
456 neighboring lines to be fixed, so we don't return a line range for purely
457 neighboring lines to be fixed, so we don't return a line range for purely
457 deleted sections. In the future we should support a :deletion config that
458 deleted sections. In the future we should support a :deletion config that
458 allows fixers to know where deletions are located.
459 allows fixers to know where deletions are located.
459
460
460 $ hg init incrementalfixedlines
461 $ hg init incrementalfixedlines
461 $ cd incrementalfixedlines
462 $ cd incrementalfixedlines
462
463
463 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
464 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
464 $ hg commit -Aqm "foo"
465 $ hg commit -Aqm "foo"
465 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
466 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
466
467
467 $ hg --config "fix.fail:command=echo" \
468 $ hg --config "fix.fail:command=echo" \
468 > --config "fix.fail:linerange={first}:{last}" \
469 > --config "fix.fail:linerange={first}:{last}" \
469 > --config "fix.fail:pattern=foo.txt" \
470 > --config "fix.fail:pattern=foo.txt" \
470 > fix --working-dir
471 > fix --working-dir
471 $ cat foo.txt
472 $ cat foo.txt
472 1:1 4:6 8:8
473 1:1 4:6 8:8
473
474
474 $ cd ..
475 $ cd ..
475
476
476 Test that --whole fixes all lines regardless of the diffs present.
477 Test that --whole fixes all lines regardless of the diffs present.
477
478
478 $ hg init wholeignoresdiffs
479 $ hg init wholeignoresdiffs
479 $ cd wholeignoresdiffs
480 $ cd wholeignoresdiffs
480
481
481 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
482 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
482 $ hg commit -Aqm "foo"
483 $ hg commit -Aqm "foo"
483 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
484 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
484
485
485 $ hg fix --working-dir
486 $ hg fix --working-dir
486 $ cat foo.changed
487 $ cat foo.changed
487 ZZ
488 ZZ
488 a
489 a
489 c
490 c
490 DD
491 DD
491 EE
492 EE
492 FF
493 FF
493 f
494 f
494 GG
495 GG
495
496
496 $ hg fix --working-dir --whole
497 $ hg fix --working-dir --whole
497 $ cat foo.changed
498 $ cat foo.changed
498 ZZ
499 ZZ
499 A
500 A
500 C
501 C
501 DD
502 DD
502 EE
503 EE
503 FF
504 FF
504 F
505 F
505 GG
506 GG
506
507
507 $ cd ..
508 $ cd ..
508
509
509 We should do nothing with symlinks, and their targets should be unaffected. Any
510 We should do nothing with symlinks, and their targets should be unaffected. Any
510 other behavior would be more complicated to implement and harder to document.
511 other behavior would be more complicated to implement and harder to document.
511
512
512 #if symlink
513 #if symlink
513 $ hg init dontmesswithsymlinks
514 $ hg init dontmesswithsymlinks
514 $ cd dontmesswithsymlinks
515 $ cd dontmesswithsymlinks
515
516
516 $ printf "hello\n" > hello.whole
517 $ printf "hello\n" > hello.whole
517 $ ln -s hello.whole hellolink
518 $ ln -s hello.whole hellolink
518 $ hg add
519 $ hg add
519 adding hello.whole
520 adding hello.whole
520 adding hellolink
521 adding hellolink
521 $ hg fix --working-dir hellolink
522 $ hg fix --working-dir hellolink
522 $ hg status
523 $ hg status
523 A hello.whole
524 A hello.whole
524 A hellolink
525 A hellolink
525
526
526 $ cd ..
527 $ cd ..
527 #endif
528 #endif
528
529
529 We should allow fixers to run on binary files, even though this doesn't sound
530 We should allow fixers to run on binary files, even though this doesn't sound
530 like a common use case. There's not much benefit to disallowing it, and users
531 like a common use case. There's not much benefit to disallowing it, and users
531 can add "and not binary()" to their filesets if needed. The Mercurial
532 can add "and not binary()" to their filesets if needed. The Mercurial
532 philosophy is generally to not handle binary files specially anyway.
533 philosophy is generally to not handle binary files specially anyway.
533
534
534 $ hg init cantouchbinaryfiles
535 $ hg init cantouchbinaryfiles
535 $ cd cantouchbinaryfiles
536 $ cd cantouchbinaryfiles
536
537
537 $ printf "hello\0\n" > hello.whole
538 $ printf "hello\0\n" > hello.whole
538 $ hg add
539 $ hg add
539 adding hello.whole
540 adding hello.whole
540 $ hg fix --working-dir 'set:binary()'
541 $ hg fix --working-dir 'set:binary()'
541 $ cat hello.whole
542 $ cat hello.whole
542 HELLO\x00 (esc)
543 HELLO\x00 (esc)
543
544
544 $ cd ..
545 $ cd ..
545
546
546 We have a config for the maximum size of file we will attempt to fix. This can
547 We have a config for the maximum size of file we will attempt to fix. This can
547 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
548 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
548 could happen by accident without a well considered configuration. A more
549 could happen by accident without a well considered configuration. A more
549 precise configuration could use the size() fileset function if one global limit
550 precise configuration could use the size() fileset function if one global limit
550 is undesired.
551 is undesired.
551
552
552 $ hg init maxfilesize
553 $ hg init maxfilesize
553 $ cd maxfilesize
554 $ cd maxfilesize
554
555
555 $ printf "this file is huge\n" > hello.whole
556 $ printf "this file is huge\n" > hello.whole
556 $ hg add
557 $ hg add
557 adding hello.whole
558 adding hello.whole
558 $ hg --config fix.maxfilesize=10 fix --working-dir
559 $ hg --config fix.maxfilesize=10 fix --working-dir
559 ignoring file larger than 10 bytes: hello.whole
560 ignoring file larger than 10 bytes: hello.whole
560 $ cat hello.whole
561 $ cat hello.whole
561 this file is huge
562 this file is huge
562
563
563 $ cd ..
564 $ cd ..
564
565
565 If we specify a file to fix, other files should be left alone, even if they
566 If we specify a file to fix, other files should be left alone, even if they
566 have changes.
567 have changes.
567
568
568 $ hg init fixonlywhatitellyouto
569 $ hg init fixonlywhatitellyouto
569 $ cd fixonlywhatitellyouto
570 $ cd fixonlywhatitellyouto
570
571
571 $ printf "fix me!\n" > fixme.whole
572 $ printf "fix me!\n" > fixme.whole
572 $ printf "not me.\n" > notme.whole
573 $ printf "not me.\n" > notme.whole
573 $ hg add
574 $ hg add
574 adding fixme.whole
575 adding fixme.whole
575 adding notme.whole
576 adding notme.whole
576 $ hg fix --working-dir fixme.whole
577 $ hg fix --working-dir fixme.whole
577 $ cat *.whole
578 $ cat *.whole
578 FIX ME!
579 FIX ME!
579 not me.
580 not me.
580
581
581 $ cd ..
582 $ cd ..
582
583
583 If we try to fix a missing file, we still fix other files.
584 If we try to fix a missing file, we still fix other files.
584
585
585 $ hg init fixmissingfile
586 $ hg init fixmissingfile
586 $ cd fixmissingfile
587 $ cd fixmissingfile
587
588
588 $ printf "fix me!\n" > foo.whole
589 $ printf "fix me!\n" > foo.whole
589 $ hg add
590 $ hg add
590 adding foo.whole
591 adding foo.whole
591 $ hg fix --working-dir foo.whole bar.whole
592 $ hg fix --working-dir foo.whole bar.whole
592 bar.whole: $ENOENT$
593 bar.whole: $ENOENT$
593 $ cat *.whole
594 $ cat *.whole
594 FIX ME!
595 FIX ME!
595
596
596 $ cd ..
597 $ cd ..
597
598
598 Specifying a directory name should fix all its files and subdirectories.
599 Specifying a directory name should fix all its files and subdirectories.
599
600
600 $ hg init fixdirectory
601 $ hg init fixdirectory
601 $ cd fixdirectory
602 $ cd fixdirectory
602
603
603 $ mkdir -p dir1/dir2
604 $ mkdir -p dir1/dir2
604 $ printf "foo\n" > foo.whole
605 $ printf "foo\n" > foo.whole
605 $ printf "bar\n" > dir1/bar.whole
606 $ printf "bar\n" > dir1/bar.whole
606 $ printf "baz\n" > dir1/dir2/baz.whole
607 $ printf "baz\n" > dir1/dir2/baz.whole
607 $ hg add
608 $ hg add
608 adding dir1/bar.whole
609 adding dir1/bar.whole
609 adding dir1/dir2/baz.whole
610 adding dir1/dir2/baz.whole
610 adding foo.whole
611 adding foo.whole
611 $ hg fix --working-dir dir1
612 $ hg fix --working-dir dir1
612 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
613 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
613 foo
614 foo
614 BAR
615 BAR
615 BAZ
616 BAZ
616
617
617 $ cd ..
618 $ cd ..
618
619
619 Fixing a file in the working directory that needs no fixes should not actually
620 Fixing a file in the working directory that needs no fixes should not actually
620 write back to the file, so for example the mtime shouldn't change.
621 write back to the file, so for example the mtime shouldn't change.
621
622
622 $ hg init donttouchunfixedfiles
623 $ hg init donttouchunfixedfiles
623 $ cd donttouchunfixedfiles
624 $ cd donttouchunfixedfiles
624
625
625 $ printf "NO FIX NEEDED\n" > foo.whole
626 $ printf "NO FIX NEEDED\n" > foo.whole
626 $ hg add
627 $ hg add
627 adding foo.whole
628 adding foo.whole
628 $ cp -p foo.whole foo.whole.orig
629 $ cp -p foo.whole foo.whole.orig
629 $ cp -p foo.whole.orig foo.whole
630 $ cp -p foo.whole.orig foo.whole
630 $ sleep 2 # mtime has a resolution of one or two seconds.
631 $ sleep 2 # mtime has a resolution of one or two seconds.
631 $ hg fix --working-dir
632 $ hg fix --working-dir
632 $ f foo.whole.orig --newer foo.whole
633 $ f foo.whole.orig --newer foo.whole
633 foo.whole.orig: newer than foo.whole
634 foo.whole.orig: newer than foo.whole
634
635
635 $ cd ..
636 $ cd ..
636
637
637 When a fixer prints to stderr, we don't assume that it has failed. We show the
638 When a fixer prints to stderr, we don't assume that it has failed. We show the
638 error messages to the user, and we still let the fixer affect the file it was
639 error messages to the user, and we still let the fixer affect the file it was
639 fixing if its exit code is zero. Some code formatters might emit error messages
640 fixing if its exit code is zero. Some code formatters might emit error messages
640 on stderr and nothing on stdout, which would cause us the clear the file,
641 on stderr and nothing on stdout, which would cause us the clear the file,
641 except that they also exit with a non-zero code. We show the user which fixer
642 except that they also exit with a non-zero code. We show the user which fixer
642 emitted the stderr, and which revision, but we assume that the fixer will print
643 emitted the stderr, and which revision, but we assume that the fixer will print
643 the filename if it is relevant (since the issue may be non-specific). There is
644 the filename if it is relevant (since the issue may be non-specific). There is
644 also a config to abort (without affecting any files whatsoever) if we see any
645 also a config to abort (without affecting any files whatsoever) if we see any
645 tool with a non-zero exit status.
646 tool with a non-zero exit status.
646
647
647 $ hg init showstderr
648 $ hg init showstderr
648 $ cd showstderr
649 $ cd showstderr
649
650
650 $ printf "hello\n" > hello.txt
651 $ printf "hello\n" > hello.txt
651 $ hg add
652 $ hg add
652 adding hello.txt
653 adding hello.txt
653 $ cat > $TESTTMP/work.sh <<'EOF'
654 $ cat > $TESTTMP/work.sh <<'EOF'
654 > printf 'HELLO\n'
655 > printf 'HELLO\n'
655 > printf "$@: some\nerror that didn't stop the tool" >&2
656 > printf "$@: some\nerror that didn't stop the tool" >&2
656 > exit 0 # success despite the stderr output
657 > exit 0 # success despite the stderr output
657 > EOF
658 > EOF
658 $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \
659 $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \
659 > --config "fix.work:pattern=hello.txt" \
660 > --config "fix.work:pattern=hello.txt" \
660 > fix --working-dir
661 > fix --working-dir
661 [wdir] work: hello.txt: some
662 [wdir] work: hello.txt: some
662 [wdir] work: error that didn't stop the tool
663 [wdir] work: error that didn't stop the tool
663 $ cat hello.txt
664 $ cat hello.txt
664 HELLO
665 HELLO
665
666
666 $ printf "goodbye\n" > hello.txt
667 $ printf "goodbye\n" > hello.txt
667 $ printf "foo\n" > foo.whole
668 $ printf "foo\n" > foo.whole
668 $ hg add
669 $ hg add
669 adding foo.whole
670 adding foo.whole
670 $ cat > $TESTTMP/fail.sh <<'EOF'
671 $ cat > $TESTTMP/fail.sh <<'EOF'
671 > printf 'GOODBYE\n'
672 > printf 'GOODBYE\n'
672 > printf "$@: some\nerror that did stop the tool\n" >&2
673 > printf "$@: some\nerror that did stop the tool\n" >&2
673 > exit 42 # success despite the stdout output
674 > exit 42 # success despite the stdout output
674 > EOF
675 > EOF
675 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
676 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
676 > --config "fix.fail:pattern=hello.txt" \
677 > --config "fix.fail:pattern=hello.txt" \
677 > --config "fix.failure=abort" \
678 > --config "fix.failure=abort" \
678 > fix --working-dir
679 > fix --working-dir
679 [wdir] fail: hello.txt: some
680 [wdir] fail: hello.txt: some
680 [wdir] fail: error that did stop the tool
681 [wdir] fail: error that did stop the tool
681 abort: no fixes will be applied
682 abort: no fixes will be applied
682 (use --config fix.failure=continue to apply any successful fixes anyway)
683 (use --config fix.failure=continue to apply any successful fixes anyway)
683 [255]
684 [255]
684 $ cat hello.txt
685 $ cat hello.txt
685 goodbye
686 goodbye
686 $ cat foo.whole
687 $ cat foo.whole
687 foo
688 foo
688
689
689 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
690 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
690 > --config "fix.fail:pattern=hello.txt" \
691 > --config "fix.fail:pattern=hello.txt" \
691 > fix --working-dir
692 > fix --working-dir
692 [wdir] fail: hello.txt: some
693 [wdir] fail: hello.txt: some
693 [wdir] fail: error that did stop the tool
694 [wdir] fail: error that did stop the tool
694 $ cat hello.txt
695 $ cat hello.txt
695 goodbye
696 goodbye
696 $ cat foo.whole
697 $ cat foo.whole
697 FOO
698 FOO
698
699
699 $ hg --config "fix.fail:command=exit 42" \
700 $ hg --config "fix.fail:command=exit 42" \
700 > --config "fix.fail:pattern=hello.txt" \
701 > --config "fix.fail:pattern=hello.txt" \
701 > fix --working-dir
702 > fix --working-dir
702 [wdir] fail: exited with status 42
703 [wdir] fail: exited with status 42
703
704
704 $ cd ..
705 $ cd ..
705
706
706 Fixing the working directory and its parent revision at the same time should
707 Fixing the working directory and its parent revision at the same time should
707 check out the replacement revision for the parent. This prevents any new
708 check out the replacement revision for the parent. This prevents any new
708 uncommitted changes from appearing. We test this for a clean working directory
709 uncommitted changes from appearing. We test this for a clean working directory
709 and a dirty one. In both cases, all lines/files changed since the grandparent
710 and a dirty one. In both cases, all lines/files changed since the grandparent
710 will be fixed. The grandparent is the "baserev" for both the parent and the
711 will be fixed. The grandparent is the "baserev" for both the parent and the
711 working copy.
712 working copy.
712
713
713 $ hg init fixdotandcleanwdir
714 $ hg init fixdotandcleanwdir
714 $ cd fixdotandcleanwdir
715 $ cd fixdotandcleanwdir
715
716
716 $ printf "hello\n" > hello.whole
717 $ printf "hello\n" > hello.whole
717 $ printf "world\n" > world.whole
718 $ printf "world\n" > world.whole
718 $ hg commit -Aqm "the parent commit"
719 $ hg commit -Aqm "the parent commit"
719
720
720 $ hg parents --template '{rev} {desc}\n'
721 $ hg parents --template '{rev} {desc}\n'
721 0 the parent commit
722 0 the parent commit
722 $ hg fix --working-dir -r .
723 $ hg fix --working-dir -r .
723 $ hg parents --template '{rev} {desc}\n'
724 $ hg parents --template '{rev} {desc}\n'
724 1 the parent commit
725 1 the parent commit
725 $ hg cat -r . *.whole
726 $ hg cat -r . *.whole
726 HELLO
727 HELLO
727 WORLD
728 WORLD
728 $ cat *.whole
729 $ cat *.whole
729 HELLO
730 HELLO
730 WORLD
731 WORLD
731 $ hg status
732 $ hg status
732
733
733 $ cd ..
734 $ cd ..
734
735
735 Same test with a dirty working copy.
736 Same test with a dirty working copy.
736
737
737 $ hg init fixdotanddirtywdir
738 $ hg init fixdotanddirtywdir
738 $ cd fixdotanddirtywdir
739 $ cd fixdotanddirtywdir
739
740
740 $ printf "hello\n" > hello.whole
741 $ printf "hello\n" > hello.whole
741 $ printf "world\n" > world.whole
742 $ printf "world\n" > world.whole
742 $ hg commit -Aqm "the parent commit"
743 $ hg commit -Aqm "the parent commit"
743
744
744 $ printf "hello,\n" > hello.whole
745 $ printf "hello,\n" > hello.whole
745 $ printf "world!\n" > world.whole
746 $ printf "world!\n" > world.whole
746
747
747 $ hg parents --template '{rev} {desc}\n'
748 $ hg parents --template '{rev} {desc}\n'
748 0 the parent commit
749 0 the parent commit
749 $ hg fix --working-dir -r .
750 $ hg fix --working-dir -r .
750 $ hg parents --template '{rev} {desc}\n'
751 $ hg parents --template '{rev} {desc}\n'
751 1 the parent commit
752 1 the parent commit
752 $ hg cat -r . *.whole
753 $ hg cat -r . *.whole
753 HELLO
754 HELLO
754 WORLD
755 WORLD
755 $ cat *.whole
756 $ cat *.whole
756 HELLO,
757 HELLO,
757 WORLD!
758 WORLD!
758 $ hg status
759 $ hg status
759 M hello.whole
760 M hello.whole
760 M world.whole
761 M world.whole
761
762
762 $ cd ..
763 $ cd ..
763
764
764 When we have a chain of commits that change mutually exclusive lines of code,
765 When we have a chain of commits that change mutually exclusive lines of code,
765 we should be able to do incremental fixing that causes each commit in the chain
766 we should be able to do incremental fixing that causes each commit in the chain
766 to include fixes made to the previous commits. This prevents children from
767 to include fixes made to the previous commits. This prevents children from
767 backing out the fixes made in their parents. A dirty working directory is
768 backing out the fixes made in their parents. A dirty working directory is
768 conceptually similar to another commit in the chain.
769 conceptually similar to another commit in the chain.
769
770
770 $ hg init incrementallyfixchain
771 $ hg init incrementallyfixchain
771 $ cd incrementallyfixchain
772 $ cd incrementallyfixchain
772
773
773 $ cat > file.changed <<EOF
774 $ cat > file.changed <<EOF
774 > first
775 > first
775 > second
776 > second
776 > third
777 > third
777 > fourth
778 > fourth
778 > fifth
779 > fifth
779 > EOF
780 > EOF
780 $ hg commit -Aqm "the common ancestor (the baserev)"
781 $ hg commit -Aqm "the common ancestor (the baserev)"
781 $ cat > file.changed <<EOF
782 $ cat > file.changed <<EOF
782 > first (changed)
783 > first (changed)
783 > second
784 > second
784 > third
785 > third
785 > fourth
786 > fourth
786 > fifth
787 > fifth
787 > EOF
788 > EOF
788 $ hg commit -Aqm "the first commit to fix"
789 $ hg commit -Aqm "the first commit to fix"
789 $ cat > file.changed <<EOF
790 $ cat > file.changed <<EOF
790 > first (changed)
791 > first (changed)
791 > second
792 > second
792 > third (changed)
793 > third (changed)
793 > fourth
794 > fourth
794 > fifth
795 > fifth
795 > EOF
796 > EOF
796 $ hg commit -Aqm "the second commit to fix"
797 $ hg commit -Aqm "the second commit to fix"
797 $ cat > file.changed <<EOF
798 $ cat > file.changed <<EOF
798 > first (changed)
799 > first (changed)
799 > second
800 > second
800 > third (changed)
801 > third (changed)
801 > fourth
802 > fourth
802 > fifth (changed)
803 > fifth (changed)
803 > EOF
804 > EOF
804
805
805 $ hg fix -r . -r '.^' --working-dir
806 $ hg fix -r . -r '.^' --working-dir
806
807
807 $ hg parents --template '{rev}\n'
808 $ hg parents --template '{rev}\n'
808 4
809 4
809 $ hg cat -r '.^^' file.changed
810 $ hg cat -r '.^^' file.changed
810 first
811 first
811 second
812 second
812 third
813 third
813 fourth
814 fourth
814 fifth
815 fifth
815 $ hg cat -r '.^' file.changed
816 $ hg cat -r '.^' file.changed
816 FIRST (CHANGED)
817 FIRST (CHANGED)
817 second
818 second
818 third
819 third
819 fourth
820 fourth
820 fifth
821 fifth
821 $ hg cat -r . file.changed
822 $ hg cat -r . file.changed
822 FIRST (CHANGED)
823 FIRST (CHANGED)
823 second
824 second
824 THIRD (CHANGED)
825 THIRD (CHANGED)
825 fourth
826 fourth
826 fifth
827 fifth
827 $ cat file.changed
828 $ cat file.changed
828 FIRST (CHANGED)
829 FIRST (CHANGED)
829 second
830 second
830 THIRD (CHANGED)
831 THIRD (CHANGED)
831 fourth
832 fourth
832 FIFTH (CHANGED)
833 FIFTH (CHANGED)
833
834
834 $ cd ..
835 $ cd ..
835
836
836 If we incrementally fix a merge commit, we should fix any lines that changed
837 If we incrementally fix a merge commit, we should fix any lines that changed
837 versus either parent. You could imagine only fixing the intersection or some
838 versus either parent. You could imagine only fixing the intersection or some
838 other subset, but this is necessary if either parent is being fixed. It
839 other subset, but this is necessary if either parent is being fixed. It
839 prevents us from forgetting fixes made in either parent.
840 prevents us from forgetting fixes made in either parent.
840
841
841 $ hg init incrementallyfixmergecommit
842 $ hg init incrementallyfixmergecommit
842 $ cd incrementallyfixmergecommit
843 $ cd incrementallyfixmergecommit
843
844
844 $ printf "a\nb\nc\n" > file.changed
845 $ printf "a\nb\nc\n" > file.changed
845 $ hg commit -Aqm "ancestor"
846 $ hg commit -Aqm "ancestor"
846
847
847 $ printf "aa\nb\nc\n" > file.changed
848 $ printf "aa\nb\nc\n" > file.changed
848 $ hg commit -m "change a"
849 $ hg commit -m "change a"
849
850
850 $ hg checkout '.^'
851 $ hg checkout '.^'
851 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
852 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
852 $ printf "a\nb\ncc\n" > file.changed
853 $ printf "a\nb\ncc\n" > file.changed
853 $ hg commit -m "change c"
854 $ hg commit -m "change c"
854 created new head
855 created new head
855
856
856 $ hg merge
857 $ hg merge
857 merging file.changed
858 merging file.changed
858 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
859 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
859 (branch merge, don't forget to commit)
860 (branch merge, don't forget to commit)
860 $ hg commit -m "merge"
861 $ hg commit -m "merge"
861 $ hg cat -r . file.changed
862 $ hg cat -r . file.changed
862 aa
863 aa
863 b
864 b
864 cc
865 cc
865
866
866 $ hg fix -r . --working-dir
867 $ hg fix -r . --working-dir
867 $ hg cat -r . file.changed
868 $ hg cat -r . file.changed
868 AA
869 AA
869 b
870 b
870 CC
871 CC
871
872
872 $ cd ..
873 $ cd ..
873
874
874 We should be allowed to fix the working (and only the working copy) while
875 We should be allowed to fix the working (and only the working copy) while
875 merging.
876 merging.
876
877
877 $ hg init fixworkingcopywhilemerging
878 $ hg init fixworkingcopywhilemerging
878 $ cd fixworkingcopywhilemerging
879 $ cd fixworkingcopywhilemerging
879
880
880 $ printf "a\nb\nc\n" > file.changed
881 $ printf "a\nb\nc\n" > file.changed
881 $ hg commit -Aqm "ancestor"
882 $ hg commit -Aqm "ancestor"
882
883
883 $ printf "aa\nb\nc\n" > file.changed
884 $ printf "aa\nb\nc\n" > file.changed
884 $ hg commit -m "change a"
885 $ hg commit -m "change a"
885
886
886 $ hg checkout '.^'
887 $ hg checkout '.^'
887 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
888 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
888 $ printf "a\nb\ncc\n" > file.changed
889 $ printf "a\nb\ncc\n" > file.changed
889 $ hg commit -m "change c"
890 $ hg commit -m "change c"
890 created new head
891 created new head
891
892
892 $ hg merge
893 $ hg merge
893 merging file.changed
894 merging file.changed
894 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
895 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
895 (branch merge, don't forget to commit)
896 (branch merge, don't forget to commit)
896 $ cat file.changed
897 $ cat file.changed
897 aa
898 aa
898 b
899 b
899 cc
900 cc
900 Not allowed to fix a parent of the working copy while merging
901 Not allowed to fix a parent of the working copy while merging
901 $ hg fix -r . --working-dir
902 $ hg fix -r . --working-dir
902 abort: outstanding uncommitted merge
903 abort: outstanding uncommitted merge
903 (use 'hg commit' or 'hg merge --abort')
904 (use 'hg commit' or 'hg merge --abort')
904 [20]
905 [20]
905 $ hg fix --working-dir
906 $ hg fix --working-dir
906 $ cat file.changed
907 $ cat file.changed
907 AA
908 AA
908 b
909 b
909 CC
910 CC
910
911
911 $ cd ..
912 $ cd ..
912
913
913 Abort fixing revisions if there is an unfinished operation. We don't want to
914 Abort fixing revisions if there is an unfinished operation. We don't want to
914 make things worse by editing files or stripping/obsoleting things. Also abort
915 make things worse by editing files or stripping/obsoleting things. Also abort
915 fixing the working directory if there are unresolved merge conflicts.
916 fixing the working directory if there are unresolved merge conflicts.
916
917
917 $ hg init abortunresolved
918 $ hg init abortunresolved
918 $ cd abortunresolved
919 $ cd abortunresolved
919
920
920 $ echo "foo1" > foo.whole
921 $ echo "foo1" > foo.whole
921 $ hg commit -Aqm "foo 1"
922 $ hg commit -Aqm "foo 1"
922
923
923 $ hg update null
924 $ hg update null
924 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
925 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
925 $ echo "foo2" > foo.whole
926 $ echo "foo2" > foo.whole
926 $ hg commit -Aqm "foo 2"
927 $ hg commit -Aqm "foo 2"
927
928
928 $ hg --config extensions.rebase= rebase -r 1 -d 0
929 $ hg --config extensions.rebase= rebase -r 1 -d 0
929 rebasing 1:c3b6dc0e177a tip "foo 2"
930 rebasing 1:c3b6dc0e177a tip "foo 2"
930 merging foo.whole
931 merging foo.whole
931 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
932 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
932 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
933 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
933 [240]
934 [240]
934
935
935 $ hg --config extensions.rebase= fix --working-dir
936 $ hg --config extensions.rebase= fix --working-dir
936 abort: unresolved conflicts
937 abort: unresolved conflicts
937 (use 'hg resolve')
938 (use 'hg resolve')
938 [255]
939 [255]
939
940
940 $ hg --config extensions.rebase= fix -r .
941 $ hg --config extensions.rebase= fix -r .
941 abort: rebase in progress
942 abort: rebase in progress
942 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
943 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
943 [20]
944 [20]
944
945
945 $ cd ..
946 $ cd ..
946
947
947 When fixing a file that was renamed, we should diff against the source of the
948 When fixing a file that was renamed, we should diff against the source of the
948 rename for incremental fixing and we should correctly reproduce the rename in
949 rename for incremental fixing and we should correctly reproduce the rename in
949 the replacement revision.
950 the replacement revision.
950
951
951 $ hg init fixrenamecommit
952 $ hg init fixrenamecommit
952 $ cd fixrenamecommit
953 $ cd fixrenamecommit
953
954
954 $ printf "a\nb\nc\n" > source.changed
955 $ printf "a\nb\nc\n" > source.changed
955 $ hg commit -Aqm "source revision"
956 $ hg commit -Aqm "source revision"
956 $ hg move source.changed dest.changed
957 $ hg move source.changed dest.changed
957 $ printf "a\nb\ncc\n" > dest.changed
958 $ printf "a\nb\ncc\n" > dest.changed
958 $ hg commit -m "dest revision"
959 $ hg commit -m "dest revision"
959
960
960 $ hg fix -r .
961 $ hg fix -r .
961 $ hg log -r tip --copies --template "{file_copies}\n"
962 $ hg log -r tip --copies --template "{file_copies}\n"
962 dest.changed (source.changed)
963 dest.changed (source.changed)
963 $ hg cat -r tip dest.changed
964 $ hg cat -r tip dest.changed
964 a
965 a
965 b
966 b
966 CC
967 CC
967
968
968 $ cd ..
969 $ cd ..
969
970
970 When fixing revisions that remove files we must ensure that the replacement
971 When fixing revisions that remove files we must ensure that the replacement
971 actually removes the file, whereas it could accidentally leave it unchanged or
972 actually removes the file, whereas it could accidentally leave it unchanged or
972 write an empty string to it.
973 write an empty string to it.
973
974
974 $ hg init fixremovedfile
975 $ hg init fixremovedfile
975 $ cd fixremovedfile
976 $ cd fixremovedfile
976
977
977 $ printf "foo\n" > foo.whole
978 $ printf "foo\n" > foo.whole
978 $ printf "bar\n" > bar.whole
979 $ printf "bar\n" > bar.whole
979 $ hg commit -Aqm "add files"
980 $ hg commit -Aqm "add files"
980 $ hg remove bar.whole
981 $ hg remove bar.whole
981 $ hg commit -m "remove file"
982 $ hg commit -m "remove file"
982 $ hg status --change .
983 $ hg status --change .
983 R bar.whole
984 R bar.whole
984 $ hg fix -r . foo.whole
985 $ hg fix -r . foo.whole
985 $ hg status --change tip
986 $ hg status --change tip
986 M foo.whole
987 M foo.whole
987 R bar.whole
988 R bar.whole
988
989
989 $ cd ..
990 $ cd ..
990
991
991 If fixing a revision finds no fixes to make, no replacement revision should be
992 If fixing a revision finds no fixes to make, no replacement revision should be
992 created.
993 created.
993
994
994 $ hg init nofixesneeded
995 $ hg init nofixesneeded
995 $ cd nofixesneeded
996 $ cd nofixesneeded
996
997
997 $ printf "FOO\n" > foo.whole
998 $ printf "FOO\n" > foo.whole
998 $ hg commit -Aqm "add file"
999 $ hg commit -Aqm "add file"
999 $ hg log --template '{rev}\n'
1000 $ hg log --template '{rev}\n'
1000 0
1001 0
1001 $ hg fix -r .
1002 $ hg fix -r .
1002 $ hg log --template '{rev}\n'
1003 $ hg log --template '{rev}\n'
1003 0
1004 0
1004
1005
1005 $ cd ..
1006 $ cd ..
1006
1007
1007 If fixing a commit reverts all the changes in the commit, we replace it with a
1008 If fixing a commit reverts all the changes in the commit, we replace it with a
1008 commit that changes no files.
1009 commit that changes no files.
1009
1010
1010 $ hg init nochangesleft
1011 $ hg init nochangesleft
1011 $ cd nochangesleft
1012 $ cd nochangesleft
1012
1013
1013 $ printf "FOO\n" > foo.whole
1014 $ printf "FOO\n" > foo.whole
1014 $ hg commit -Aqm "add file"
1015 $ hg commit -Aqm "add file"
1015 $ printf "foo\n" > foo.whole
1016 $ printf "foo\n" > foo.whole
1016 $ hg commit -m "edit file"
1017 $ hg commit -m "edit file"
1017 $ hg status --change .
1018 $ hg status --change .
1018 M foo.whole
1019 M foo.whole
1019 $ hg fix -r .
1020 $ hg fix -r .
1020 $ hg status --change tip
1021 $ hg status --change tip
1021
1022
1022 $ cd ..
1023 $ cd ..
1023
1024
1024 If we fix a parent and child revision together, the child revision must be
1025 If we fix a parent and child revision together, the child revision must be
1025 replaced if the parent is replaced, even if the diffs of the child needed no
1026 replaced if the parent is replaced, even if the diffs of the child needed no
1026 fixes. However, we're free to not replace revisions that need no fixes and have
1027 fixes. However, we're free to not replace revisions that need no fixes and have
1027 no ancestors that are replaced.
1028 no ancestors that are replaced.
1028
1029
1029 $ hg init mustreplacechild
1030 $ hg init mustreplacechild
1030 $ cd mustreplacechild
1031 $ cd mustreplacechild
1031
1032
1032 $ printf "FOO\n" > foo.whole
1033 $ printf "FOO\n" > foo.whole
1033 $ hg commit -Aqm "add foo"
1034 $ hg commit -Aqm "add foo"
1034 $ printf "foo\n" > foo.whole
1035 $ printf "foo\n" > foo.whole
1035 $ hg commit -m "edit foo"
1036 $ hg commit -m "edit foo"
1036 $ printf "BAR\n" > bar.whole
1037 $ printf "BAR\n" > bar.whole
1037 $ hg commit -Aqm "add bar"
1038 $ hg commit -Aqm "add bar"
1038
1039
1039 $ hg log --graph --template '{rev} {files}'
1040 $ hg log --graph --template '{rev} {files}'
1040 @ 2 bar.whole
1041 @ 2 bar.whole
1041 |
1042 |
1042 o 1 foo.whole
1043 o 1 foo.whole
1043 |
1044 |
1044 o 0 foo.whole
1045 o 0 foo.whole
1045
1046
1046 $ hg fix -r 0:2
1047 $ hg fix -r 0:2
1047 $ hg log --graph --template '{rev} {files}'
1048 $ hg log --graph --template '{rev} {files}'
1048 o 4 bar.whole
1049 o 4 bar.whole
1049 |
1050 |
1050 o 3
1051 o 3
1051 |
1052 |
1052 | @ 2 bar.whole
1053 | @ 2 bar.whole
1053 | |
1054 | |
1054 | x 1 foo.whole
1055 | x 1 foo.whole
1055 |/
1056 |/
1056 o 0 foo.whole
1057 o 0 foo.whole
1057
1058
1058
1059
1059 $ cd ..
1060 $ cd ..
1060
1061
1061 It's also possible that the child needs absolutely no changes, but we still
1062 It's also possible that the child needs absolutely no changes, but we still
1062 need to replace it to update its parent. If we skipped replacing the child
1063 need to replace it to update its parent. If we skipped replacing the child
1063 because it had no file content changes, it would become an orphan for no good
1064 because it had no file content changes, it would become an orphan for no good
1064 reason.
1065 reason.
1065
1066
1066 $ hg init mustreplacechildevenifnop
1067 $ hg init mustreplacechildevenifnop
1067 $ cd mustreplacechildevenifnop
1068 $ cd mustreplacechildevenifnop
1068
1069
1069 $ printf "Foo\n" > foo.whole
1070 $ printf "Foo\n" > foo.whole
1070 $ hg commit -Aqm "add a bad foo"
1071 $ hg commit -Aqm "add a bad foo"
1071 $ printf "FOO\n" > foo.whole
1072 $ printf "FOO\n" > foo.whole
1072 $ hg commit -m "add a good foo"
1073 $ hg commit -m "add a good foo"
1073 $ hg fix -r . -r '.^'
1074 $ hg fix -r . -r '.^'
1074 $ hg log --graph --template '{rev} {desc}'
1075 $ hg log --graph --template '{rev} {desc}'
1075 o 3 add a good foo
1076 o 3 add a good foo
1076 |
1077 |
1077 o 2 add a bad foo
1078 o 2 add a bad foo
1078
1079
1079 @ 1 add a good foo
1080 @ 1 add a good foo
1080 |
1081 |
1081 x 0 add a bad foo
1082 x 0 add a bad foo
1082
1083
1083
1084
1084 $ cd ..
1085 $ cd ..
1085
1086
1086 Similar to the case above, the child revision may become empty as a result of
1087 Similar to the case above, the child revision may become empty as a result of
1087 fixing its parent. We should still create an empty replacement child.
1088 fixing its parent. We should still create an empty replacement child.
1088 TODO: determine how this should interact with ui.allowemptycommit given that
1089 TODO: determine how this should interact with ui.allowemptycommit given that
1089 the empty replacement could have children.
1090 the empty replacement could have children.
1090
1091
1091 $ hg init mustreplacechildevenifempty
1092 $ hg init mustreplacechildevenifempty
1092 $ cd mustreplacechildevenifempty
1093 $ cd mustreplacechildevenifempty
1093
1094
1094 $ printf "foo\n" > foo.whole
1095 $ printf "foo\n" > foo.whole
1095 $ hg commit -Aqm "add foo"
1096 $ hg commit -Aqm "add foo"
1096 $ printf "Foo\n" > foo.whole
1097 $ printf "Foo\n" > foo.whole
1097 $ hg commit -m "edit foo"
1098 $ hg commit -m "edit foo"
1098 $ hg fix -r . -r '.^'
1099 $ hg fix -r . -r '.^'
1099 $ hg log --graph --template '{rev} {desc}\n' --stat
1100 $ hg log --graph --template '{rev} {desc}\n' --stat
1100 o 3 edit foo
1101 o 3 edit foo
1101 |
1102 |
1102 o 2 add foo
1103 o 2 add foo
1103 foo.whole | 1 +
1104 foo.whole | 1 +
1104 1 files changed, 1 insertions(+), 0 deletions(-)
1105 1 files changed, 1 insertions(+), 0 deletions(-)
1105
1106
1106 @ 1 edit foo
1107 @ 1 edit foo
1107 | foo.whole | 2 +-
1108 | foo.whole | 2 +-
1108 | 1 files changed, 1 insertions(+), 1 deletions(-)
1109 | 1 files changed, 1 insertions(+), 1 deletions(-)
1109 |
1110 |
1110 x 0 add foo
1111 x 0 add foo
1111 foo.whole | 1 +
1112 foo.whole | 1 +
1112 1 files changed, 1 insertions(+), 0 deletions(-)
1113 1 files changed, 1 insertions(+), 0 deletions(-)
1113
1114
1114
1115
1115 $ cd ..
1116 $ cd ..
1116
1117
1117 Fixing a secret commit should replace it with another secret commit.
1118 Fixing a secret commit should replace it with another secret commit.
1118
1119
1119 $ hg init fixsecretcommit
1120 $ hg init fixsecretcommit
1120 $ cd fixsecretcommit
1121 $ cd fixsecretcommit
1121
1122
1122 $ printf "foo\n" > foo.whole
1123 $ printf "foo\n" > foo.whole
1123 $ hg commit -Aqm "add foo" --secret
1124 $ hg commit -Aqm "add foo" --secret
1124 $ hg fix -r .
1125 $ hg fix -r .
1125 $ hg log --template '{rev} {phase}\n'
1126 $ hg log --template '{rev} {phase}\n'
1126 1 secret
1127 1 secret
1127 0 secret
1128 0 secret
1128
1129
1129 $ cd ..
1130 $ cd ..
1130
1131
1131 We should also preserve phase when fixing a draft commit while the user has
1132 We should also preserve phase when fixing a draft commit while the user has
1132 their default set to secret.
1133 their default set to secret.
1133
1134
1134 $ hg init respectphasesnewcommit
1135 $ hg init respectphasesnewcommit
1135 $ cd respectphasesnewcommit
1136 $ cd respectphasesnewcommit
1136
1137
1137 $ printf "foo\n" > foo.whole
1138 $ printf "foo\n" > foo.whole
1138 $ hg commit -Aqm "add foo"
1139 $ hg commit -Aqm "add foo"
1139 $ hg --config phases.newcommit=secret fix -r .
1140 $ hg --config phases.newcommit=secret fix -r .
1140 $ hg log --template '{rev} {phase}\n'
1141 $ hg log --template '{rev} {phase}\n'
1141 1 draft
1142 1 draft
1142 0 draft
1143 0 draft
1143
1144
1144 $ cd ..
1145 $ cd ..
1145
1146
1146 Debug output should show what fixer commands are being subprocessed, which is
1147 Debug output should show what fixer commands are being subprocessed, which is
1147 useful for anyone trying to set up a new config.
1148 useful for anyone trying to set up a new config.
1148
1149
1149 $ hg init debugoutput
1150 $ hg init debugoutput
1150 $ cd debugoutput
1151 $ cd debugoutput
1151
1152
1152 $ printf "foo\nbar\nbaz\n" > foo.changed
1153 $ printf "foo\nbar\nbaz\n" > foo.changed
1153 $ hg commit -Aqm "foo"
1154 $ hg commit -Aqm "foo"
1154 $ printf "Foo\nbar\nBaz\n" > foo.changed
1155 $ printf "Foo\nbar\nBaz\n" > foo.changed
1155 $ hg --debug fix --working-dir
1156 $ hg --debug fix --working-dir
1156 fixing: f65cf3136d41+ - uppercase-changed-lines - foo.changed
1157 fixing: f65cf3136d41+ - uppercase-changed-lines - foo.changed
1157 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
1158 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
1158
1159
1159 $ cd ..
1160 $ cd ..
1160
1161
1161 Fixing an obsolete revision can cause divergence, so we abort unless the user
1162 Fixing an obsolete revision can cause divergence, so we abort unless the user
1162 configures to allow it. This is not yet smart enough to know whether there is a
1163 configures to allow it. This is not yet smart enough to know whether there is a
1163 successor, but even then it is not likely intentional or idiomatic to fix an
1164 successor, but even then it is not likely intentional or idiomatic to fix an
1164 obsolete revision.
1165 obsolete revision.
1165
1166
1166 $ hg init abortobsoleterev
1167 $ hg init abortobsoleterev
1167 $ cd abortobsoleterev
1168 $ cd abortobsoleterev
1168
1169
1169 $ printf "foo\n" > foo.changed
1170 $ printf "foo\n" > foo.changed
1170 $ hg commit -Aqm "foo"
1171 $ hg commit -Aqm "foo"
1171 $ hg ci --amend -m rewritten
1172 $ hg ci --amend -m rewritten
1172 $ hg --hidden fix -r 0
1173 $ hg --hidden fix -r 0
1173 abort: cannot fix b87e30dbf19b, as that creates content-divergence with 2e007a78dfb8
1174 abort: cannot fix b87e30dbf19b, as that creates content-divergence with 2e007a78dfb8
1174 (add --verbose for details or see 'hg help evolution.instability')
1175 (add --verbose for details or see 'hg help evolution.instability')
1175 [10]
1176 [10]
1176
1177
1177 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
1178 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
1178 2 new content-divergent changesets
1179 2 new content-divergent changesets
1179 $ hg cat -r tip foo.changed
1180 $ hg cat -r tip foo.changed
1180 FOO
1181 FOO
1181
1182
1182 $ cd ..
1183 $ cd ..
1183
1184
1184 Test all of the available substitution values for fixer commands.
1185 Test all of the available substitution values for fixer commands.
1185
1186
1186 $ hg init substitution
1187 $ hg init substitution
1187 $ cd substitution
1188 $ cd substitution
1188
1189
1189 $ mkdir foo
1190 $ mkdir foo
1190 $ printf "hello\ngoodbye\n" > foo/bar
1191 $ printf "hello\ngoodbye\n" > foo/bar
1191 $ hg add
1192 $ hg add
1192 adding foo/bar
1193 adding foo/bar
1193 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
1194 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
1194 > --config "fix.fail:linerange='{first}' '{last}'" \
1195 > --config "fix.fail:linerange='{first}' '{last}'" \
1195 > --config "fix.fail:pattern=foo/bar" \
1196 > --config "fix.fail:pattern=foo/bar" \
1196 > fix --working-dir
1197 > fix --working-dir
1197 $ cat foo/bar
1198 $ cat foo/bar
1198 foo/bar
1199 foo/bar
1199 bar
1200 bar
1200 1
1201 1
1201 2
1202 2
1202
1203
1203 $ cd ..
1204 $ cd ..
1204
1205
1205 The --base flag should allow picking the revisions to diff against for changed
1206 The --base flag should allow picking the revisions to diff against for changed
1206 files and incremental line formatting.
1207 files and incremental line formatting.
1207
1208
1208 $ hg init baseflag
1209 $ hg init baseflag
1209 $ cd baseflag
1210 $ cd baseflag
1210
1211
1211 $ printf "one\ntwo\n" > foo.changed
1212 $ printf "one\ntwo\n" > foo.changed
1212 $ printf "bar\n" > bar.changed
1213 $ printf "bar\n" > bar.changed
1213 $ hg commit -Aqm "first"
1214 $ hg commit -Aqm "first"
1214 $ printf "one\nTwo\n" > foo.changed
1215 $ printf "one\nTwo\n" > foo.changed
1215 $ hg commit -m "second"
1216 $ hg commit -m "second"
1216 $ hg fix -w --base .
1217 $ hg fix -w --base .
1217 $ hg status
1218 $ hg status
1218 $ hg fix -w --base null
1219 $ hg fix -w --base null
1219 $ cat foo.changed
1220 $ cat foo.changed
1220 ONE
1221 ONE
1221 TWO
1222 TWO
1222 $ cat bar.changed
1223 $ cat bar.changed
1223 BAR
1224 BAR
1224
1225
1225 $ cd ..
1226 $ cd ..
1226
1227
1227 If the user asks to fix the parent of another commit, they are asking to create
1228 If the user asks to fix the parent of another commit, they are asking to create
1228 an orphan. We must respect experimental.evolution.allowunstable.
1229 an orphan. We must respect experimental.evolution.allowunstable.
1229
1230
1230 $ hg init allowunstable
1231 $ hg init allowunstable
1231 $ cd allowunstable
1232 $ cd allowunstable
1232
1233
1233 $ printf "one\n" > foo.whole
1234 $ printf "one\n" > foo.whole
1234 $ hg commit -Aqm "first"
1235 $ hg commit -Aqm "first"
1235 $ printf "two\n" > foo.whole
1236 $ printf "two\n" > foo.whole
1236 $ hg commit -m "second"
1237 $ hg commit -m "second"
1237 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1238 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1238 abort: cannot fix changeset, as that will orphan 1 descendants
1239 abort: cannot fix changeset, as that will orphan 1 descendants
1239 (see 'hg help evolution.instability')
1240 (see 'hg help evolution.instability')
1240 [10]
1241 [10]
1241 $ hg fix -r '.^'
1242 $ hg fix -r '.^'
1242 1 new orphan changesets
1243 1 new orphan changesets
1243 $ hg cat -r 2 foo.whole
1244 $ hg cat -r 2 foo.whole
1244 ONE
1245 ONE
1245
1246
1246 $ cd ..
1247 $ cd ..
1247
1248
1248 The --base flag affects the set of files being fixed. So while the --whole flag
1249 The --base flag affects the set of files being fixed. So while the --whole flag
1249 makes the base irrelevant for changed line ranges, it still changes the
1250 makes the base irrelevant for changed line ranges, it still changes the
1250 meaning and effect of the command. In this example, no files or lines are fixed
1251 meaning and effect of the command. In this example, no files or lines are fixed
1251 until we specify the base, but then we do fix unchanged lines.
1252 until we specify the base, but then we do fix unchanged lines.
1252
1253
1253 $ hg init basewhole
1254 $ hg init basewhole
1254 $ cd basewhole
1255 $ cd basewhole
1255 $ printf "foo1\n" > foo.changed
1256 $ printf "foo1\n" > foo.changed
1256 $ hg commit -Aqm "first"
1257 $ hg commit -Aqm "first"
1257 $ printf "foo2\n" >> foo.changed
1258 $ printf "foo2\n" >> foo.changed
1258 $ printf "bar\n" > bar.changed
1259 $ printf "bar\n" > bar.changed
1259 $ hg commit -Aqm "second"
1260 $ hg commit -Aqm "second"
1260
1261
1261 $ hg fix --working-dir --whole
1262 $ hg fix --working-dir --whole
1262 $ cat *.changed
1263 $ cat *.changed
1263 bar
1264 bar
1264 foo1
1265 foo1
1265 foo2
1266 foo2
1266
1267
1267 $ hg fix --working-dir --base 0 --whole
1268 $ hg fix --working-dir --base 0 --whole
1268 $ cat *.changed
1269 $ cat *.changed
1269 BAR
1270 BAR
1270 FOO1
1271 FOO1
1271 FOO2
1272 FOO2
1272
1273
1273 $ cd ..
1274 $ cd ..
1274
1275
1275 The execution order of tools can be controlled. This example doesn't work if
1276 The execution order of tools can be controlled. This example doesn't work if
1276 you sort after truncating, but the config defines the correct order while the
1277 you sort after truncating, but the config defines the correct order while the
1277 definitions are out of order (which might imply the incorrect order given the
1278 definitions are out of order (which might imply the incorrect order given the
1278 implementation of fix). The goal is to use multiple tools to select the lowest
1279 implementation of fix). The goal is to use multiple tools to select the lowest
1279 5 numbers in the file.
1280 5 numbers in the file.
1280
1281
1281 $ hg init priorityexample
1282 $ hg init priorityexample
1282 $ cd priorityexample
1283 $ cd priorityexample
1283
1284
1284 $ cat >> .hg/hgrc <<EOF
1285 $ cat >> .hg/hgrc <<EOF
1285 > [fix]
1286 > [fix]
1286 > head:command = head -n 5
1287 > head:command = head -n 5
1287 > head:pattern = numbers.txt
1288 > head:pattern = numbers.txt
1288 > head:priority = 1
1289 > head:priority = 1
1289 > sort:command = sort -n
1290 > sort:command = sort -n
1290 > sort:pattern = numbers.txt
1291 > sort:pattern = numbers.txt
1291 > sort:priority = 2
1292 > sort:priority = 2
1292 > EOF
1293 > EOF
1293
1294
1294 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1295 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1295 $ hg add -q
1296 $ hg add -q
1296 $ hg fix -w
1297 $ hg fix -w
1297 $ cat numbers.txt
1298 $ cat numbers.txt
1298 0
1299 0
1299 1
1300 1
1300 2
1301 2
1301 3
1302 3
1302 4
1303 4
1303
1304
1304 And of course we should be able to break this by reversing the execution order.
1305 And of course we should be able to break this by reversing the execution order.
1305 Test negative priorities while we're at it.
1306 Test negative priorities while we're at it.
1306
1307
1307 $ cat >> .hg/hgrc <<EOF
1308 $ cat >> .hg/hgrc <<EOF
1308 > [fix]
1309 > [fix]
1309 > head:priority = -1
1310 > head:priority = -1
1310 > sort:priority = -2
1311 > sort:priority = -2
1311 > EOF
1312 > EOF
1312 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1313 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1313 $ hg fix -w
1314 $ hg fix -w
1314 $ cat numbers.txt
1315 $ cat numbers.txt
1315 2
1316 2
1316 3
1317 3
1317 6
1318 6
1318 7
1319 7
1319 8
1320 8
1320
1321
1321 $ cd ..
1322 $ cd ..
1322
1323
1323 It's possible for repeated applications of a fixer tool to create cycles in the
1324 It's possible for repeated applications of a fixer tool to create cycles in the
1324 generated content of a file. For example, two users with different versions of
1325 generated content of a file. For example, two users with different versions of
1325 a code formatter might fight over the formatting when they run hg fix. In the
1326 a code formatter might fight over the formatting when they run hg fix. In the
1326 absence of other changes, this means we could produce commits with the same
1327 absence of other changes, this means we could produce commits with the same
1327 hash in subsequent runs of hg fix. This is a problem unless we support
1328 hash in subsequent runs of hg fix. This is a problem unless we support
1328 obsolescence cycles well. We avoid this by adding an extra field to the
1329 obsolescence cycles well. We avoid this by adding an extra field to the
1329 successor which forces it to have a new hash. That's why this test creates
1330 successor which forces it to have a new hash. That's why this test creates
1330 three revisions instead of two.
1331 three revisions instead of two.
1331
1332
1332 $ hg init cyclictool
1333 $ hg init cyclictool
1333 $ cd cyclictool
1334 $ cd cyclictool
1334
1335
1335 $ cat >> .hg/hgrc <<EOF
1336 $ cat >> .hg/hgrc <<EOF
1336 > [fix]
1337 > [fix]
1337 > swapletters:command = tr ab ba
1338 > swapletters:command = tr ab ba
1338 > swapletters:pattern = foo
1339 > swapletters:pattern = foo
1339 > EOF
1340 > EOF
1340
1341
1341 $ echo ab > foo
1342 $ echo ab > foo
1342 $ hg commit -Aqm foo
1343 $ hg commit -Aqm foo
1343
1344
1344 $ hg fix -r 0
1345 $ hg fix -r 0
1345 $ hg fix -r 1
1346 $ hg fix -r 1
1346
1347
1347 $ hg cat -r 0 foo --hidden
1348 $ hg cat -r 0 foo --hidden
1348 ab
1349 ab
1349 $ hg cat -r 1 foo --hidden
1350 $ hg cat -r 1 foo --hidden
1350 ba
1351 ba
1351 $ hg cat -r 2 foo
1352 $ hg cat -r 2 foo
1352 ab
1353 ab
1353
1354
1354 $ cd ..
1355 $ cd ..
1355
1356
1356 We run fixer tools in the repo root so they can look for config files or other
1357 We run fixer tools in the repo root so they can look for config files or other
1357 important things in the working directory. This does NOT mean we are
1358 important things in the working directory. This does NOT mean we are
1358 reconstructing a working copy of every revision being fixed; we're just giving
1359 reconstructing a working copy of every revision being fixed; we're just giving
1359 the tool knowledge of the repo's location in case it can do something
1360 the tool knowledge of the repo's location in case it can do something
1360 reasonable with that.
1361 reasonable with that.
1361
1362
1362 $ hg init subprocesscwd
1363 $ hg init subprocesscwd
1363 $ cd subprocesscwd
1364 $ cd subprocesscwd
1364
1365
1365 $ cat >> .hg/hgrc <<EOF
1366 $ cat >> .hg/hgrc <<EOF
1366 > [fix]
1367 > [fix]
1367 > printcwd:command = "$PYTHON" -c "import os; print(os.getcwd())"
1368 > printcwd:command = "$PYTHON" -c "import os; print(os.getcwd())"
1368 > printcwd:pattern = relpath:foo/bar
1369 > printcwd:pattern = relpath:foo/bar
1369 > filesetpwd:command = "$PYTHON" -c "import os; print('fs: ' + os.getcwd())"
1370 > filesetpwd:command = "$PYTHON" -c "import os; print('fs: ' + os.getcwd())"
1370 > filesetpwd:pattern = set:**quux
1371 > filesetpwd:pattern = set:**quux
1371 > EOF
1372 > EOF
1372
1373
1373 $ mkdir foo
1374 $ mkdir foo
1374 $ printf "bar\n" > foo/bar
1375 $ printf "bar\n" > foo/bar
1375 $ printf "quux\n" > quux
1376 $ printf "quux\n" > quux
1376 $ hg commit -Aqm blah
1377 $ hg commit -Aqm blah
1377
1378
1378 $ hg fix -w -r . foo/bar
1379 $ hg fix -w -r . foo/bar
1379 $ hg cat -r tip foo/bar
1380 $ hg cat -r tip foo/bar
1380 $TESTTMP/subprocesscwd
1381 $TESTTMP/subprocesscwd
1381 $ cat foo/bar
1382 $ cat foo/bar
1382 $TESTTMP/subprocesscwd
1383 $TESTTMP/subprocesscwd
1383
1384
1384 $ cd foo
1385 $ cd foo
1385
1386
1386 $ hg fix -w -r . bar
1387 $ hg fix -w -r . bar
1387 $ hg cat -r tip bar ../quux
1388 $ hg cat -r tip bar ../quux
1388 $TESTTMP/subprocesscwd
1389 $TESTTMP/subprocesscwd
1389 quux
1390 quux
1390 $ cat bar ../quux
1391 $ cat bar ../quux
1391 $TESTTMP/subprocesscwd
1392 $TESTTMP/subprocesscwd
1392 quux
1393 quux
1393 $ echo modified > bar
1394 $ echo modified > bar
1394 $ hg fix -w bar
1395 $ hg fix -w bar
1395 $ cat bar
1396 $ cat bar
1396 $TESTTMP/subprocesscwd
1397 $TESTTMP/subprocesscwd
1397
1398
1398 Apparently fixing p1() and its descendants doesn't include wdir() unless
1399 Apparently fixing p1() and its descendants doesn't include wdir() unless
1399 explicitly stated.
1400 explicitly stated.
1400
1401
1401 $ hg fix -r '.::'
1402 $ hg fix -r '.::'
1402 $ hg cat -r . ../quux
1403 $ hg cat -r . ../quux
1403 quux
1404 quux
1404 $ hg cat -r tip ../quux
1405 $ hg cat -r tip ../quux
1405 fs: $TESTTMP/subprocesscwd
1406 fs: $TESTTMP/subprocesscwd
1406 $ cat ../quux
1407 $ cat ../quux
1407 quux
1408 quux
1408
1409
1409 Clean files are not fixed unless explicitly named
1410 Clean files are not fixed unless explicitly named
1410 $ echo 'dirty' > ../quux
1411 $ echo 'dirty' > ../quux
1411
1412
1412 $ hg fix --working-dir
1413 $ hg fix --working-dir
1413 $ cat ../quux
1414 $ cat ../quux
1414 fs: $TESTTMP/subprocesscwd
1415 fs: $TESTTMP/subprocesscwd
1415
1416
1416 $ cd ../..
1417 $ cd ../..
1417
1418
1418 Tools configured without a pattern are ignored. It would be too dangerous to
1419 Tools configured without a pattern are ignored. It would be too dangerous to
1419 run them on all files, because this might happen while testing a configuration
1420 run them on all files, because this might happen while testing a configuration
1420 that also deletes all of the file content. There is no reasonable subset of the
1421 that also deletes all of the file content. There is no reasonable subset of the
1421 files to use as a default. Users should be explicit about what files are
1422 files to use as a default. Users should be explicit about what files are
1422 affected by a tool. This test also confirms that we don't crash when the
1423 affected by a tool. This test also confirms that we don't crash when the
1423 pattern config is missing, and that we only warn about it once.
1424 pattern config is missing, and that we only warn about it once.
1424
1425
1425 $ hg init nopatternconfigured
1426 $ hg init nopatternconfigured
1426 $ cd nopatternconfigured
1427 $ cd nopatternconfigured
1427
1428
1428 $ printf "foo" > foo
1429 $ printf "foo" > foo
1429 $ printf "bar" > bar
1430 $ printf "bar" > bar
1430 $ hg add -q
1431 $ hg add -q
1431 $ hg fix --debug --working-dir --config "fix.nopattern:command=echo fixed"
1432 $ hg fix --debug --working-dir --config "fix.nopattern:command=echo fixed"
1432 fixer tool has no pattern configuration: nopattern
1433 fixer tool has no pattern configuration: nopattern
1433 $ cat foo bar
1434 $ cat foo bar
1434 foobar (no-eol)
1435 foobar (no-eol)
1435 $ hg fix --debug --working-dir --config "fix.nocommand:pattern=foo.bar"
1436 $ hg fix --debug --working-dir --config "fix.nocommand:pattern=foo.bar"
1436 fixer tool has no command configuration: nocommand
1437 fixer tool has no command configuration: nocommand
1437
1438
1438 $ cd ..
1439 $ cd ..
1439
1440
1440 Tools can be disabled. Disabled tools do nothing but print a debug message.
1441 Tools can be disabled. Disabled tools do nothing but print a debug message.
1441
1442
1442 $ hg init disabled
1443 $ hg init disabled
1443 $ cd disabled
1444 $ cd disabled
1444
1445
1445 $ printf "foo\n" > foo
1446 $ printf "foo\n" > foo
1446 $ hg add -q
1447 $ hg add -q
1447 $ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \
1448 $ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \
1448 > --config "fix.disabled:pattern=foo" \
1449 > --config "fix.disabled:pattern=foo" \
1449 > --config "fix.disabled:enabled=false"
1450 > --config "fix.disabled:enabled=false"
1450 ignoring disabled fixer tool: disabled
1451 ignoring disabled fixer tool: disabled
1451 $ cat foo
1452 $ cat foo
1452 foo
1453 foo
1453
1454
1454 $ cd ..
1455 $ cd ..
1455
1456
1456 Test that we can configure a fixer to affect all files regardless of the cwd.
1457 Test that we can configure a fixer to affect all files regardless of the cwd.
1457 The way we invoke matching must not prohibit this.
1458 The way we invoke matching must not prohibit this.
1458
1459
1459 $ hg init affectallfiles
1460 $ hg init affectallfiles
1460 $ cd affectallfiles
1461 $ cd affectallfiles
1461
1462
1462 $ mkdir foo bar
1463 $ mkdir foo bar
1463 $ printf "foo" > foo/file
1464 $ printf "foo" > foo/file
1464 $ printf "bar" > bar/file
1465 $ printf "bar" > bar/file
1465 $ printf "baz" > baz_file
1466 $ printf "baz" > baz_file
1466 $ hg add -q
1467 $ hg add -q
1467
1468
1468 $ cd bar
1469 $ cd bar
1469 $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \
1470 $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \
1470 > --config "fix.cooltool:pattern=glob:**"
1471 > --config "fix.cooltool:pattern=glob:**"
1471 $ cd ..
1472 $ cd ..
1472
1473
1473 $ cat foo/file
1474 $ cat foo/file
1474 fixed
1475 fixed
1475 $ cat bar/file
1476 $ cat bar/file
1476 fixed
1477 fixed
1477 $ cat baz_file
1478 $ cat baz_file
1478 fixed
1479 fixed
1479
1480
1480 $ cd ..
1481 $ cd ..
1481
1482
1482 Tools should be able to run on unchanged files, even if they set :linerange.
1483 Tools should be able to run on unchanged files, even if they set :linerange.
1483 This includes a corner case where deleted chunks of a file are not considered
1484 This includes a corner case where deleted chunks of a file are not considered
1484 changes.
1485 changes.
1485
1486
1486 $ hg init skipclean
1487 $ hg init skipclean
1487 $ cd skipclean
1488 $ cd skipclean
1488
1489
1489 $ printf "a\nb\nc\n" > foo
1490 $ printf "a\nb\nc\n" > foo
1490 $ printf "a\nb\nc\n" > bar
1491 $ printf "a\nb\nc\n" > bar
1491 $ printf "a\nb\nc\n" > baz
1492 $ printf "a\nb\nc\n" > baz
1492 $ hg commit -Aqm "base"
1493 $ hg commit -Aqm "base"
1493
1494
1494 $ printf "a\nc\n" > foo
1495 $ printf "a\nc\n" > foo
1495 $ printf "a\nx\nc\n" > baz
1496 $ printf "a\nx\nc\n" > baz
1496
1497
1497 $ cat >> print.py <<EOF
1498 $ cat >> print.py <<EOF
1498 > import sys
1499 > import sys
1499 > for a in sys.argv[1:]:
1500 > for a in sys.argv[1:]:
1500 > print(a)
1501 > print(a)
1501 > EOF
1502 > EOF
1502
1503
1503 $ hg fix --working-dir foo bar baz \
1504 $ hg fix --working-dir foo bar baz \
1504 > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \
1505 > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \
1505 > --config 'fix.changedlines:linerange="{first} through {last}"' \
1506 > --config 'fix.changedlines:linerange="{first} through {last}"' \
1506 > --config 'fix.changedlines:pattern=glob:**' \
1507 > --config 'fix.changedlines:pattern=glob:**' \
1507 > --config 'fix.changedlines:skipclean=false'
1508 > --config 'fix.changedlines:skipclean=false'
1508
1509
1509 $ cat foo
1510 $ cat foo
1510 Line ranges:
1511 Line ranges:
1511 $ cat bar
1512 $ cat bar
1512 Line ranges:
1513 Line ranges:
1513 $ cat baz
1514 $ cat baz
1514 Line ranges:
1515 Line ranges:
1515 2 through 2
1516 2 through 2
1516
1517
1517 $ cd ..
1518 $ cd ..
1518
1519
1519 Test various cases around merges. We were previously dropping files if they were
1520 Test various cases around merges. We were previously dropping files if they were
1520 created on only the p2 side of the merge, so let's test permutations of:
1521 created on only the p2 side of the merge, so let's test permutations of:
1521 * added, was fixed
1522 * added, was fixed
1522 * added, considered for fixing but was already good
1523 * added, considered for fixing but was already good
1523 * added, not considered for fixing
1524 * added, not considered for fixing
1524 * modified, was fixed
1525 * modified, was fixed
1525 * modified, considered for fixing but was already good
1526 * modified, considered for fixing but was already good
1526 * modified, not considered for fixing
1527 * modified, not considered for fixing
1527
1528
1528 Before the bug was fixed where we would drop files, this test demonstrated the
1529 Before the bug was fixed where we would drop files, this test demonstrated the
1529 following issues:
1530 following issues:
1530 * new_in_r1.ignored, new_in_r1_already_good.changed, and
1531 * new_in_r1.ignored, new_in_r1_already_good.changed, and
1531 > mod_in_r1_already_good.changed were NOT in the manifest for the merge commit
1532 > mod_in_r1_already_good.changed were NOT in the manifest for the merge commit
1532 * mod_in_r1.ignored had its contents from r0, NOT r1.
1533 * mod_in_r1.ignored had its contents from r0, NOT r1.
1533
1534
1534 We're also setting a named branch for every commit to demonstrate that the
1535 We're also setting a named branch for every commit to demonstrate that the
1535 branch is kept intact and there aren't issues updating to another branch in the
1536 branch is kept intact and there aren't issues updating to another branch in the
1536 middle of fix.
1537 middle of fix.
1537
1538
1538 $ hg init merge_keeps_files
1539 $ hg init merge_keeps_files
1539 $ cd merge_keeps_files
1540 $ cd merge_keeps_files
1540 $ for f in r0 mod_in_r1 mod_in_r2 mod_in_merge mod_in_child; do
1541 $ for f in r0 mod_in_r1 mod_in_r2 mod_in_merge mod_in_child; do
1541 > for c in changed whole ignored; do
1542 > for c in changed whole ignored; do
1542 > printf "hello\n" > $f.$c
1543 > printf "hello\n" > $f.$c
1543 > done
1544 > done
1544 > printf "HELLO\n" > "mod_in_${f}_already_good.changed"
1545 > printf "HELLO\n" > "mod_in_${f}_already_good.changed"
1545 > done
1546 > done
1546 $ hg branch -q r0
1547 $ hg branch -q r0
1547 $ hg ci -Aqm 'r0'
1548 $ hg ci -Aqm 'r0'
1548 $ hg phase -p
1549 $ hg phase -p
1549 $ make_test_files() {
1550 $ make_test_files() {
1550 > printf "world\n" >> "mod_in_$1.changed"
1551 > printf "world\n" >> "mod_in_$1.changed"
1551 > printf "world\n" >> "mod_in_$1.whole"
1552 > printf "world\n" >> "mod_in_$1.whole"
1552 > printf "world\n" >> "mod_in_$1.ignored"
1553 > printf "world\n" >> "mod_in_$1.ignored"
1553 > printf "WORLD\n" >> "mod_in_$1_already_good.changed"
1554 > printf "WORLD\n" >> "mod_in_$1_already_good.changed"
1554 > printf "new in $1\n" > "new_in_$1.changed"
1555 > printf "new in $1\n" > "new_in_$1.changed"
1555 > printf "new in $1\n" > "new_in_$1.whole"
1556 > printf "new in $1\n" > "new_in_$1.whole"
1556 > printf "new in $1\n" > "new_in_$1.ignored"
1557 > printf "new in $1\n" > "new_in_$1.ignored"
1557 > printf "ALREADY GOOD, NEW IN THIS REV\n" > "new_in_$1_already_good.changed"
1558 > printf "ALREADY GOOD, NEW IN THIS REV\n" > "new_in_$1_already_good.changed"
1558 > }
1559 > }
1559 $ make_test_commit() {
1560 $ make_test_commit() {
1560 > make_test_files "$1"
1561 > make_test_files "$1"
1561 > hg branch -q "$1"
1562 > hg branch -q "$1"
1562 > hg ci -Aqm "$2"
1563 > hg ci -Aqm "$2"
1563 > }
1564 > }
1564 $ make_test_commit r1 "merge me, pt1"
1565 $ make_test_commit r1 "merge me, pt1"
1565 $ hg co -q ".^"
1566 $ hg co -q ".^"
1566 $ make_test_commit r2 "merge me, pt2"
1567 $ make_test_commit r2 "merge me, pt2"
1567 $ hg merge -qr 1
1568 $ hg merge -qr 1
1568 $ make_test_commit merge "evil merge"
1569 $ make_test_commit merge "evil merge"
1569 $ make_test_commit child "child of merge"
1570 $ make_test_commit child "child of merge"
1570 $ make_test_files wdir
1571 $ make_test_files wdir
1571 $ hg fix -r 'not public()' -w
1572 $ hg fix -r 'not public()' -w
1572 $ hg log -G -T'{rev}:{shortest(node,8)}: branch:{branch} desc:{desc}'
1573 $ hg log -G -T'{rev}:{shortest(node,8)}: branch:{branch} desc:{desc}'
1573 @ 8:c22ce900: branch:child desc:child of merge
1574 @ 8:c22ce900: branch:child desc:child of merge
1574 |
1575 |
1575 o 7:5a30615a: branch:merge desc:evil merge
1576 o 7:5a30615a: branch:merge desc:evil merge
1576 |\
1577 |\
1577 | o 6:4e5acdc4: branch:r2 desc:merge me, pt2
1578 | o 6:4e5acdc4: branch:r2 desc:merge me, pt2
1578 | |
1579 | |
1579 o | 5:eea01878: branch:r1 desc:merge me, pt1
1580 o | 5:eea01878: branch:r1 desc:merge me, pt1
1580 |/
1581 |/
1581 o 0:0c548d87: branch:r0 desc:r0
1582 o 0:0c548d87: branch:r0 desc:r0
1582
1583
1583 $ hg files -r tip
1584 $ hg files -r tip
1584 mod_in_child.changed
1585 mod_in_child.changed
1585 mod_in_child.ignored
1586 mod_in_child.ignored
1586 mod_in_child.whole
1587 mod_in_child.whole
1587 mod_in_child_already_good.changed
1588 mod_in_child_already_good.changed
1588 mod_in_merge.changed
1589 mod_in_merge.changed
1589 mod_in_merge.ignored
1590 mod_in_merge.ignored
1590 mod_in_merge.whole
1591 mod_in_merge.whole
1591 mod_in_merge_already_good.changed
1592 mod_in_merge_already_good.changed
1592 mod_in_mod_in_child_already_good.changed
1593 mod_in_mod_in_child_already_good.changed
1593 mod_in_mod_in_merge_already_good.changed
1594 mod_in_mod_in_merge_already_good.changed
1594 mod_in_mod_in_r1_already_good.changed
1595 mod_in_mod_in_r1_already_good.changed
1595 mod_in_mod_in_r2_already_good.changed
1596 mod_in_mod_in_r2_already_good.changed
1596 mod_in_r0_already_good.changed
1597 mod_in_r0_already_good.changed
1597 mod_in_r1.changed
1598 mod_in_r1.changed
1598 mod_in_r1.ignored
1599 mod_in_r1.ignored
1599 mod_in_r1.whole
1600 mod_in_r1.whole
1600 mod_in_r1_already_good.changed
1601 mod_in_r1_already_good.changed
1601 mod_in_r2.changed
1602 mod_in_r2.changed
1602 mod_in_r2.ignored
1603 mod_in_r2.ignored
1603 mod_in_r2.whole
1604 mod_in_r2.whole
1604 mod_in_r2_already_good.changed
1605 mod_in_r2_already_good.changed
1605 new_in_child.changed
1606 new_in_child.changed
1606 new_in_child.ignored
1607 new_in_child.ignored
1607 new_in_child.whole
1608 new_in_child.whole
1608 new_in_child_already_good.changed
1609 new_in_child_already_good.changed
1609 new_in_merge.changed
1610 new_in_merge.changed
1610 new_in_merge.ignored
1611 new_in_merge.ignored
1611 new_in_merge.whole
1612 new_in_merge.whole
1612 new_in_merge_already_good.changed
1613 new_in_merge_already_good.changed
1613 new_in_r1.changed
1614 new_in_r1.changed
1614 new_in_r1.ignored
1615 new_in_r1.ignored
1615 new_in_r1.whole
1616 new_in_r1.whole
1616 new_in_r1_already_good.changed
1617 new_in_r1_already_good.changed
1617 new_in_r2.changed
1618 new_in_r2.changed
1618 new_in_r2.ignored
1619 new_in_r2.ignored
1619 new_in_r2.whole
1620 new_in_r2.whole
1620 new_in_r2_already_good.changed
1621 new_in_r2_already_good.changed
1621 r0.changed
1622 r0.changed
1622 r0.ignored
1623 r0.ignored
1623 r0.whole
1624 r0.whole
1624 $ for f in "$(hg files -r tip)"; do hg cat -r tip $f -T'{path}:\n{data}\n'; done
1625 $ for f in "$(hg files -r tip)"; do hg cat -r tip $f -T'{path}:\n{data}\n'; done
1625 mod_in_child.changed:
1626 mod_in_child.changed:
1626 hello
1627 hello
1627 WORLD
1628 WORLD
1628
1629
1629 mod_in_child.ignored:
1630 mod_in_child.ignored:
1630 hello
1631 hello
1631 world
1632 world
1632
1633
1633 mod_in_child.whole:
1634 mod_in_child.whole:
1634 HELLO
1635 HELLO
1635 WORLD
1636 WORLD
1636
1637
1637 mod_in_child_already_good.changed:
1638 mod_in_child_already_good.changed:
1638 WORLD
1639 WORLD
1639
1640
1640 mod_in_merge.changed:
1641 mod_in_merge.changed:
1641 hello
1642 hello
1642 WORLD
1643 WORLD
1643
1644
1644 mod_in_merge.ignored:
1645 mod_in_merge.ignored:
1645 hello
1646 hello
1646 world
1647 world
1647
1648
1648 mod_in_merge.whole:
1649 mod_in_merge.whole:
1649 HELLO
1650 HELLO
1650 WORLD
1651 WORLD
1651
1652
1652 mod_in_merge_already_good.changed:
1653 mod_in_merge_already_good.changed:
1653 WORLD
1654 WORLD
1654
1655
1655 mod_in_mod_in_child_already_good.changed:
1656 mod_in_mod_in_child_already_good.changed:
1656 HELLO
1657 HELLO
1657
1658
1658 mod_in_mod_in_merge_already_good.changed:
1659 mod_in_mod_in_merge_already_good.changed:
1659 HELLO
1660 HELLO
1660
1661
1661 mod_in_mod_in_r1_already_good.changed:
1662 mod_in_mod_in_r1_already_good.changed:
1662 HELLO
1663 HELLO
1663
1664
1664 mod_in_mod_in_r2_already_good.changed:
1665 mod_in_mod_in_r2_already_good.changed:
1665 HELLO
1666 HELLO
1666
1667
1667 mod_in_r0_already_good.changed:
1668 mod_in_r0_already_good.changed:
1668 HELLO
1669 HELLO
1669
1670
1670 mod_in_r1.changed:
1671 mod_in_r1.changed:
1671 hello
1672 hello
1672 WORLD
1673 WORLD
1673
1674
1674 mod_in_r1.ignored:
1675 mod_in_r1.ignored:
1675 hello
1676 hello
1676 world
1677 world
1677
1678
1678 mod_in_r1.whole:
1679 mod_in_r1.whole:
1679 HELLO
1680 HELLO
1680 WORLD
1681 WORLD
1681
1682
1682 mod_in_r1_already_good.changed:
1683 mod_in_r1_already_good.changed:
1683 WORLD
1684 WORLD
1684
1685
1685 mod_in_r2.changed:
1686 mod_in_r2.changed:
1686 hello
1687 hello
1687 WORLD
1688 WORLD
1688
1689
1689 mod_in_r2.ignored:
1690 mod_in_r2.ignored:
1690 hello
1691 hello
1691 world
1692 world
1692
1693
1693 mod_in_r2.whole:
1694 mod_in_r2.whole:
1694 HELLO
1695 HELLO
1695 WORLD
1696 WORLD
1696
1697
1697 mod_in_r2_already_good.changed:
1698 mod_in_r2_already_good.changed:
1698 WORLD
1699 WORLD
1699
1700
1700 new_in_child.changed:
1701 new_in_child.changed:
1701 NEW IN CHILD
1702 NEW IN CHILD
1702
1703
1703 new_in_child.ignored:
1704 new_in_child.ignored:
1704 new in child
1705 new in child
1705
1706
1706 new_in_child.whole:
1707 new_in_child.whole:
1707 NEW IN CHILD
1708 NEW IN CHILD
1708
1709
1709 new_in_child_already_good.changed:
1710 new_in_child_already_good.changed:
1710 ALREADY GOOD, NEW IN THIS REV
1711 ALREADY GOOD, NEW IN THIS REV
1711
1712
1712 new_in_merge.changed:
1713 new_in_merge.changed:
1713 NEW IN MERGE
1714 NEW IN MERGE
1714
1715
1715 new_in_merge.ignored:
1716 new_in_merge.ignored:
1716 new in merge
1717 new in merge
1717
1718
1718 new_in_merge.whole:
1719 new_in_merge.whole:
1719 NEW IN MERGE
1720 NEW IN MERGE
1720
1721
1721 new_in_merge_already_good.changed:
1722 new_in_merge_already_good.changed:
1722 ALREADY GOOD, NEW IN THIS REV
1723 ALREADY GOOD, NEW IN THIS REV
1723
1724
1724 new_in_r1.changed:
1725 new_in_r1.changed:
1725 NEW IN R1
1726 NEW IN R1
1726
1727
1727 new_in_r1.ignored:
1728 new_in_r1.ignored:
1728 new in r1
1729 new in r1
1729
1730
1730 new_in_r1.whole:
1731 new_in_r1.whole:
1731 NEW IN R1
1732 NEW IN R1
1732
1733
1733 new_in_r1_already_good.changed:
1734 new_in_r1_already_good.changed:
1734 ALREADY GOOD, NEW IN THIS REV
1735 ALREADY GOOD, NEW IN THIS REV
1735
1736
1736 new_in_r2.changed:
1737 new_in_r2.changed:
1737 NEW IN R2
1738 NEW IN R2
1738
1739
1739 new_in_r2.ignored:
1740 new_in_r2.ignored:
1740 new in r2
1741 new in r2
1741
1742
1742 new_in_r2.whole:
1743 new_in_r2.whole:
1743 NEW IN R2
1744 NEW IN R2
1744
1745
1745 new_in_r2_already_good.changed:
1746 new_in_r2_already_good.changed:
1746 ALREADY GOOD, NEW IN THIS REV
1747 ALREADY GOOD, NEW IN THIS REV
1747
1748
1748 r0.changed:
1749 r0.changed:
1749 hello
1750 hello
1750
1751
1751 r0.ignored:
1752 r0.ignored:
1752 hello
1753 hello
1753
1754
1754 r0.whole:
1755 r0.whole:
1755 hello
1756 hello
1756
1757
1757
1758
1758 We should execute the fixer tools as few times as possible, because they might
1759 We should execute the fixer tools as few times as possible, because they might
1759 be slow or expensive to execute. The inputs to each execution are effectively
1760 be slow or expensive to execute. The inputs to each execution are effectively
1760 the file path, file content, and line ranges. So, we should be able to re-use
1761 the file path, file content, and line ranges. So, we should be able to re-use
1761 results whenever those inputs are repeated. That saves a lot of work when
1762 results whenever those inputs are repeated. That saves a lot of work when
1762 fixing chains of commits that all have the same file revision for a path being
1763 fixing chains of commits that all have the same file revision for a path being
1763 fixed.
1764 fixed.
1764
1765
1765 $ hg init numberofinvocations
1766 $ hg init numberofinvocations
1766 $ cd numberofinvocations
1767 $ cd numberofinvocations
1767
1768
1768 $ printf "bar1" > bar.log
1769 $ printf "bar1" > bar.log
1769 $ printf "baz1" > baz.log
1770 $ printf "baz1" > baz.log
1770 $ printf "foo1" > foo.log
1771 $ printf "foo1" > foo.log
1771 $ printf "qux1" > qux.log
1772 $ printf "qux1" > qux.log
1772 $ hg commit -Aqm "commit1"
1773 $ hg commit -Aqm "commit1"
1773
1774
1774 $ printf "bar2" > bar.log
1775 $ printf "bar2" > bar.log
1775 $ printf "baz2" > baz.log
1776 $ printf "baz2" > baz.log
1776 $ printf "foo2" > foo.log
1777 $ printf "foo2" > foo.log
1777 $ hg commit -Aqm "commit2"
1778 $ hg commit -Aqm "commit2"
1778
1779
1779 $ printf "bar3" > bar.log
1780 $ printf "bar3" > bar.log
1780 $ printf "baz3" > baz.log
1781 $ printf "baz3" > baz.log
1781 $ hg commit -Aqm "commit3"
1782 $ hg commit -Aqm "commit3"
1782
1783
1783 $ printf "bar4" > bar.log
1784 $ printf "bar4" > bar.log
1784
1785
1785 $ LOGFILE=$TESTTMP/log
1786 $ LOGFILE=$TESTTMP/log
1786 $ LOGGER=$TESTTMP/log.py
1787 $ LOGGER=$TESTTMP/log.py
1787 $ cat >> $LOGGER <<EOF
1788 $ cat >> $LOGGER <<EOF
1788 > # Appends the input file's name to the log file.
1789 > # Appends the input file's name to the log file.
1789 > import sys
1790 > import sys
1790 > with open(r'$LOGFILE', 'a') as f:
1791 > with open(r'$LOGFILE', 'a') as f:
1791 > f.write(sys.argv[1] + '\n')
1792 > f.write(sys.argv[1] + '\n')
1792 > sys.stdout.write(sys.stdin.read())
1793 > sys.stdout.write(sys.stdin.read())
1793 > EOF
1794 > EOF
1794
1795
1795 $ hg fix --working-dir -r "all()" \
1796 $ hg fix --working-dir -r "all()" \
1796 > --config "fix.log:command=\"$PYTHON\" \"$LOGGER\" {rootpath}" \
1797 > --config "fix.log:command=\"$PYTHON\" \"$LOGGER\" {rootpath}" \
1797 > --config "fix.log:pattern=glob:**.log"
1798 > --config "fix.log:pattern=glob:**.log"
1798
1799
1799 $ cat $LOGFILE | sort | uniq -c
1800 $ cat $LOGFILE | sort | uniq -c
1800 \s*4 bar.log (re)
1801 \s*4 bar.log (re)
1801 \s*4 baz.log (re)
1802 \s*4 baz.log (re)
1802 \s*3 foo.log (re)
1803 \s*3 foo.log (re)
1803 \s*2 qux.log (re)
1804 \s*2 qux.log (re)
1804
1805
1805 $ cd ..
1806 $ cd ..
1806
1807
1807 For tools that support line ranges, it's wrong to blindly re-use fixed file
1808 For tools that support line ranges, it's wrong to blindly re-use fixed file
1808 content for the same file revision if it appears twice with different baserevs,
1809 content for the same file revision if it appears twice with different baserevs,
1809 because the line ranges could be different. Since computing line ranges is
1810 because the line ranges could be different. Since computing line ranges is
1810 ambiguous, this isn't a matter of correctness, but it affects the usability of
1811 ambiguous, this isn't a matter of correctness, but it affects the usability of
1811 this extension. It could maybe be simpler if baserevs were computed on a
1812 this extension. It could maybe be simpler if baserevs were computed on a
1812 per-file basis to make this situation impossible to construct.
1813 per-file basis to make this situation impossible to construct.
1813
1814
1814 In the following example, we construct two subgraphs with the same file
1815 In the following example, we construct two subgraphs with the same file
1815 revisions, and fix different sub-subgraphs to get different baserevs and
1816 revisions, and fix different sub-subgraphs to get different baserevs and
1816 different changed line ranges. The key precondition is that revisions 1 and 4
1817 different changed line ranges. The key precondition is that revisions 1 and 4
1817 have the same file revision, and the key result is that their successors don't
1818 have the same file revision, and the key result is that their successors don't
1818 have the same file content, because we want to fix different areas of that same
1819 have the same file content, because we want to fix different areas of that same
1819 file revision's content.
1820 file revision's content.
1820
1821
1821 $ hg init differentlineranges
1822 $ hg init differentlineranges
1822 $ cd differentlineranges
1823 $ cd differentlineranges
1823
1824
1824 $ printf "a\nb\n" > file.changed
1825 $ printf "a\nb\n" > file.changed
1825 $ hg commit -Aqm "0 ab"
1826 $ hg commit -Aqm "0 ab"
1826 $ printf "a\nx\n" > file.changed
1827 $ printf "a\nx\n" > file.changed
1827 $ hg commit -Aqm "1 ax"
1828 $ hg commit -Aqm "1 ax"
1828 $ hg remove file.changed
1829 $ hg remove file.changed
1829 $ hg commit -Aqm "2 removed"
1830 $ hg commit -Aqm "2 removed"
1830 $ hg revert file.changed -r 0
1831 $ hg revert file.changed -r 0
1831 $ hg commit -Aqm "3 ab (reverted)"
1832 $ hg commit -Aqm "3 ab (reverted)"
1832 $ hg revert file.changed -r 1
1833 $ hg revert file.changed -r 1
1833 $ hg commit -Aqm "4 ax (reverted)"
1834 $ hg commit -Aqm "4 ax (reverted)"
1834
1835
1835 $ hg manifest --debug --template "{hash}\n" -r 0; \
1836 $ hg manifest --debug --template "{hash}\n" -r 0; \
1836 > hg manifest --debug --template "{hash}\n" -r 3
1837 > hg manifest --debug --template "{hash}\n" -r 3
1837 418f692145676128d2fb518b027ddbac624be76e
1838 418f692145676128d2fb518b027ddbac624be76e
1838 418f692145676128d2fb518b027ddbac624be76e
1839 418f692145676128d2fb518b027ddbac624be76e
1839 $ hg manifest --debug --template "{hash}\n" -r 1; \
1840 $ hg manifest --debug --template "{hash}\n" -r 1; \
1840 > hg manifest --debug --template "{hash}\n" -r 4
1841 > hg manifest --debug --template "{hash}\n" -r 4
1841 09b8b3ce5a507caaa282f7262679e6d04091426c
1842 09b8b3ce5a507caaa282f7262679e6d04091426c
1842 09b8b3ce5a507caaa282f7262679e6d04091426c
1843 09b8b3ce5a507caaa282f7262679e6d04091426c
1843
1844
1844 $ hg fix --working-dir -r 1+3+4
1845 $ hg fix --working-dir -r 1+3+4
1845 3 new orphan changesets
1846 3 new orphan changesets
1846
1847
1847 $ hg cat file.changed -r "successors(1)" --hidden
1848 $ hg cat file.changed -r "successors(1)" --hidden
1848 a
1849 a
1849 X
1850 X
1850 $ hg cat file.changed -r "successors(4)" --hidden
1851 $ hg cat file.changed -r "successors(4)" --hidden
1851 A
1852 A
1852 X
1853 X
1853
1854
1854 $ cd ..
1855 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now