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