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