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