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