##// END OF EJS Templates
fix: add test case that shows why --whole with --base is useful...
Danny Hooper -
r38609:f068495a @71 default
parent child Browse files
Show More
@@ -1,584 +1,586
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:fileset=set:**.cpp or **.hpp
18 clang-format:fileset=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. If there is any output on
22 fixed file content is expected on standard output. If there is any output on
23 standard error, the file will not be affected. Some values may be substituted
23 standard error, the file will not be affected. Some values may be substituted
24 into the command::
24 into the command::
25
25
26 {rootpath} The path of the file being fixed, relative to the repo root
26 {rootpath} The path of the file being fixed, relative to the repo root
27 {basename} The name of the file being fixed, without the directory path
27 {basename} The name of the file being fixed, without the directory path
28
28
29 If the :linerange suboption is set, the tool will only be run if there are
29 If the :linerange suboption is set, the tool will only be run if there are
30 changed lines in a file. The value of this suboption is appended to the shell
30 changed lines in a file. The value of this suboption is appended to the shell
31 command once for every range of changed lines in the file. Some values may be
31 command once for every range of changed lines in the file. Some values may be
32 substituted into the command::
32 substituted into the command::
33
33
34 {first} The 1-based line number of the first line in the modified range
34 {first} The 1-based line number of the first line in the modified range
35 {last} The 1-based line number of the last line in the modified range
35 {last} The 1-based line number of the last line in the modified range
36
36
37 The :fileset suboption determines which files will be passed through each
37 The :fileset suboption determines which files will be passed through each
38 configured tool. See :hg:`help fileset` for possible values. If there are file
38 configured tool. See :hg:`help fileset` for possible values. If there are file
39 arguments to :hg:`fix`, the intersection of these filesets is used.
39 arguments to :hg:`fix`, the intersection of these filesets is used.
40
40
41 There is also a configurable limit for the maximum size of file that will be
41 There is also a configurable limit for the maximum size of file that will be
42 processed by :hg:`fix`::
42 processed by :hg:`fix`::
43
43
44 [fix]
44 [fix]
45 maxfilesize=2MB
45 maxfilesize=2MB
46
46
47 """
47 """
48
48
49 from __future__ import absolute_import
49 from __future__ import absolute_import
50
50
51 import collections
51 import collections
52 import itertools
52 import itertools
53 import os
53 import os
54 import re
54 import re
55 import subprocess
55 import subprocess
56
56
57 from mercurial.i18n import _
57 from mercurial.i18n import _
58 from mercurial.node import nullrev
58 from mercurial.node import nullrev
59 from mercurial.node import wdirrev
59 from mercurial.node import wdirrev
60
60
61 from mercurial import (
61 from mercurial import (
62 cmdutil,
62 cmdutil,
63 context,
63 context,
64 copies,
64 copies,
65 error,
65 error,
66 mdiff,
66 mdiff,
67 merge,
67 merge,
68 obsolete,
68 obsolete,
69 pycompat,
69 pycompat,
70 registrar,
70 registrar,
71 scmutil,
71 scmutil,
72 util,
72 util,
73 worker,
73 worker,
74 )
74 )
75
75
76 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
76 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
77 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
77 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
78 # be specifying the version(s) of Mercurial they are tested with, or
78 # be specifying the version(s) of Mercurial they are tested with, or
79 # leave the attribute unspecified.
79 # leave the attribute unspecified.
80 testedwith = 'ships-with-hg-core'
80 testedwith = 'ships-with-hg-core'
81
81
82 cmdtable = {}
82 cmdtable = {}
83 command = registrar.command(cmdtable)
83 command = registrar.command(cmdtable)
84
84
85 configtable = {}
85 configtable = {}
86 configitem = registrar.configitem(configtable)
86 configitem = registrar.configitem(configtable)
87
87
88 # Register the suboptions allowed for each configured fixer.
88 # Register the suboptions allowed for each configured fixer.
89 FIXER_ATTRS = ('command', 'linerange', 'fileset')
89 FIXER_ATTRS = ('command', 'linerange', 'fileset')
90
90
91 for key in FIXER_ATTRS:
91 for key in FIXER_ATTRS:
92 configitem('fix', '.*(:%s)?' % key, default=None, generic=True)
92 configitem('fix', '.*(:%s)?' % key, default=None, generic=True)
93
93
94 # A good default size allows most source code files to be fixed, but avoids
94 # A good default size allows most source code files to be fixed, but avoids
95 # letting fixer tools choke on huge inputs, which could be surprising to the
95 # letting fixer tools choke on huge inputs, which could be surprising to the
96 # user.
96 # user.
97 configitem('fix', 'maxfilesize', default='2MB')
97 configitem('fix', 'maxfilesize', default='2MB')
98
98
99 @command('fix',
99 @command('fix',
100 [('', 'all', False, _('fix all non-public non-obsolete revisions')),
100 [('', 'all', False, _('fix all non-public non-obsolete revisions')),
101 ('', 'base', [], _('revisions to diff against (overrides automatic '
101 ('', 'base', [], _('revisions to diff against (overrides automatic '
102 'selection, and applies to every revision being '
102 'selection, and applies to every revision being '
103 'fixed)'), _('REV')),
103 'fixed)'), _('REV')),
104 ('r', 'rev', [], _('revisions to fix'), _('REV')),
104 ('r', 'rev', [], _('revisions to fix'), _('REV')),
105 ('w', 'working-dir', False, _('fix the working directory')),
105 ('w', 'working-dir', False, _('fix the working directory')),
106 ('', 'whole', False, _('always fix every line of a file'))],
106 ('', 'whole', False, _('always fix every line of a file'))],
107 _('[OPTION]... [FILE]...'))
107 _('[OPTION]... [FILE]...'))
108 def fix(ui, repo, *pats, **opts):
108 def fix(ui, repo, *pats, **opts):
109 """rewrite file content in changesets or working directory
109 """rewrite file content in changesets or working directory
110
110
111 Runs any configured tools to fix the content of files. Only affects files
111 Runs any configured tools to fix the content of files. Only affects files
112 with changes, unless file arguments are provided. Only affects changed lines
112 with changes, unless file arguments are provided. Only affects changed lines
113 of files, unless the --whole flag is used. Some tools may always affect the
113 of files, unless the --whole flag is used. Some tools may always affect the
114 whole file regardless of --whole.
114 whole file regardless of --whole.
115
115
116 If revisions are specified with --rev, those revisions will be checked, and
116 If revisions are specified with --rev, those revisions will be checked, and
117 they may be replaced with new revisions that have fixed file content. It is
117 they may be replaced with new revisions that have fixed file content. It is
118 desirable to specify all descendants of each specified revision, so that the
118 desirable to specify all descendants of each specified revision, so that the
119 fixes propagate to the descendants. If all descendants are fixed at the same
119 fixes propagate to the descendants. If all descendants are fixed at the same
120 time, no merging, rebasing, or evolution will be required.
120 time, no merging, rebasing, or evolution will be required.
121
121
122 If --working-dir is used, files with uncommitted changes in the working copy
122 If --working-dir is used, files with uncommitted changes in the working copy
123 will be fixed. If the checked-out revision is also fixed, the working
123 will be fixed. If the checked-out revision is also fixed, the working
124 directory will update to the replacement revision.
124 directory will update to the replacement revision.
125
125
126 When determining what lines of each file to fix at each revision, the whole
126 When determining what lines of each file to fix at each revision, the whole
127 set of revisions being fixed is considered, so that fixes to earlier
127 set of revisions being fixed is considered, so that fixes to earlier
128 revisions are not forgotten in later ones. The --base flag can be used to
128 revisions are not forgotten in later ones. The --base flag can be used to
129 override this default behavior, though it is not usually desirable to do so.
129 override this default behavior, though it is not usually desirable to do so.
130 """
130 """
131 opts = pycompat.byteskwargs(opts)
131 opts = pycompat.byteskwargs(opts)
132 if opts['all']:
132 if opts['all']:
133 if opts['rev']:
133 if opts['rev']:
134 raise error.Abort(_('cannot specify both "--rev" and "--all"'))
134 raise error.Abort(_('cannot specify both "--rev" and "--all"'))
135 opts['rev'] = ['not public() and not obsolete()']
135 opts['rev'] = ['not public() and not obsolete()']
136 opts['working_dir'] = True
136 opts['working_dir'] = True
137 with repo.wlock(), repo.lock(), repo.transaction('fix'):
137 with repo.wlock(), repo.lock(), repo.transaction('fix'):
138 revstofix = getrevstofix(ui, repo, opts)
138 revstofix = getrevstofix(ui, repo, opts)
139 basectxs = getbasectxs(repo, opts, revstofix)
139 basectxs = getbasectxs(repo, opts, revstofix)
140 workqueue, numitems = getworkqueue(ui, repo, pats, opts, revstofix,
140 workqueue, numitems = getworkqueue(ui, repo, pats, opts, revstofix,
141 basectxs)
141 basectxs)
142 fixers = getfixers(ui)
142 fixers = getfixers(ui)
143
143
144 # There are no data dependencies between the workers fixing each file
144 # There are no data dependencies between the workers fixing each file
145 # revision, so we can use all available parallelism.
145 # revision, so we can use all available parallelism.
146 def getfixes(items):
146 def getfixes(items):
147 for rev, path in items:
147 for rev, path in items:
148 ctx = repo[rev]
148 ctx = repo[rev]
149 olddata = ctx[path].data()
149 olddata = ctx[path].data()
150 newdata = fixfile(ui, opts, fixers, ctx, path, basectxs[rev])
150 newdata = fixfile(ui, opts, fixers, ctx, path, basectxs[rev])
151 # Don't waste memory/time passing unchanged content back, but
151 # Don't waste memory/time passing unchanged content back, but
152 # produce one result per item either way.
152 # produce one result per item either way.
153 yield (rev, path, newdata if newdata != olddata else None)
153 yield (rev, path, newdata if newdata != olddata else None)
154 results = worker.worker(ui, 1.0, getfixes, tuple(), workqueue)
154 results = worker.worker(ui, 1.0, getfixes, tuple(), workqueue)
155
155
156 # We have to hold on to the data for each successor revision in memory
156 # We have to hold on to the data for each successor revision in memory
157 # until all its parents are committed. We ensure this by committing and
157 # until all its parents are committed. We ensure this by committing and
158 # freeing memory for the revisions in some topological order. This
158 # freeing memory for the revisions in some topological order. This
159 # leaves a little bit of memory efficiency on the table, but also makes
159 # leaves a little bit of memory efficiency on the table, but also makes
160 # the tests deterministic. It might also be considered a feature since
160 # the tests deterministic. It might also be considered a feature since
161 # it makes the results more easily reproducible.
161 # it makes the results more easily reproducible.
162 filedata = collections.defaultdict(dict)
162 filedata = collections.defaultdict(dict)
163 replacements = {}
163 replacements = {}
164 commitorder = sorted(revstofix, reverse=True)
164 commitorder = sorted(revstofix, reverse=True)
165 with ui.makeprogress(topic=_('fixing'), unit=_('files'),
165 with ui.makeprogress(topic=_('fixing'), unit=_('files'),
166 total=sum(numitems.values())) as progress:
166 total=sum(numitems.values())) as progress:
167 for rev, path, newdata in results:
167 for rev, path, newdata in results:
168 progress.increment(item=path)
168 progress.increment(item=path)
169 if newdata is not None:
169 if newdata is not None:
170 filedata[rev][path] = newdata
170 filedata[rev][path] = newdata
171 numitems[rev] -= 1
171 numitems[rev] -= 1
172 # Apply the fixes for this and any other revisions that are
172 # Apply the fixes for this and any other revisions that are
173 # ready and sitting at the front of the queue. Using a loop here
173 # ready and sitting at the front of the queue. Using a loop here
174 # prevents the queue from being blocked by the first revision to
174 # prevents the queue from being blocked by the first revision to
175 # be ready out of order.
175 # be ready out of order.
176 while commitorder and not numitems[commitorder[-1]]:
176 while commitorder and not numitems[commitorder[-1]]:
177 rev = commitorder.pop()
177 rev = commitorder.pop()
178 ctx = repo[rev]
178 ctx = repo[rev]
179 if rev == wdirrev:
179 if rev == wdirrev:
180 writeworkingdir(repo, ctx, filedata[rev], replacements)
180 writeworkingdir(repo, ctx, filedata[rev], replacements)
181 else:
181 else:
182 replacerev(ui, repo, ctx, filedata[rev], replacements)
182 replacerev(ui, repo, ctx, filedata[rev], replacements)
183 del filedata[rev]
183 del filedata[rev]
184
184
185 replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
185 replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
186 scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
186 scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
187
187
188 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
188 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
189 """"Constructs the list of files to be fixed at specific revisions
189 """"Constructs the list of files to be fixed at specific revisions
190
190
191 It is up to the caller how to consume the work items, and the only
191 It is up to the caller how to consume the work items, and the only
192 dependence between them is that replacement revisions must be committed in
192 dependence between them is that replacement revisions must be committed in
193 topological order. Each work item represents a file in the working copy or
193 topological order. Each work item represents a file in the working copy or
194 in some revision that should be fixed and written back to the working copy
194 in some revision that should be fixed and written back to the working copy
195 or into a replacement revision.
195 or into a replacement revision.
196
196
197 Work items for the same revision are grouped together, so that a worker
197 Work items for the same revision are grouped together, so that a worker
198 pool starting with the first N items in parallel is likely to finish the
198 pool starting with the first N items in parallel is likely to finish the
199 first revision's work before other revisions. This can allow us to write
199 first revision's work before other revisions. This can allow us to write
200 the result to disk and reduce memory footprint. At time of writing, the
200 the result to disk and reduce memory footprint. At time of writing, the
201 partition strategy in worker.py seems favorable to this. We also sort the
201 partition strategy in worker.py seems favorable to this. We also sort the
202 items by ascending revision number to match the order in which we commit
202 items by ascending revision number to match the order in which we commit
203 the fixes later.
203 the fixes later.
204 """
204 """
205 workqueue = []
205 workqueue = []
206 numitems = collections.defaultdict(int)
206 numitems = collections.defaultdict(int)
207 maxfilesize = ui.configbytes('fix', 'maxfilesize')
207 maxfilesize = ui.configbytes('fix', 'maxfilesize')
208 for rev in sorted(revstofix):
208 for rev in sorted(revstofix):
209 fixctx = repo[rev]
209 fixctx = repo[rev]
210 match = scmutil.match(fixctx, pats, opts)
210 match = scmutil.match(fixctx, pats, opts)
211 for path in pathstofix(ui, repo, pats, opts, match, basectxs[rev],
211 for path in pathstofix(ui, repo, pats, opts, match, basectxs[rev],
212 fixctx):
212 fixctx):
213 if path not in fixctx:
213 if path not in fixctx:
214 continue
214 continue
215 fctx = fixctx[path]
215 fctx = fixctx[path]
216 if fctx.islink():
216 if fctx.islink():
217 continue
217 continue
218 if fctx.size() > maxfilesize:
218 if fctx.size() > maxfilesize:
219 ui.warn(_('ignoring file larger than %s: %s\n') %
219 ui.warn(_('ignoring file larger than %s: %s\n') %
220 (util.bytecount(maxfilesize), path))
220 (util.bytecount(maxfilesize), path))
221 continue
221 continue
222 workqueue.append((rev, path))
222 workqueue.append((rev, path))
223 numitems[rev] += 1
223 numitems[rev] += 1
224 return workqueue, numitems
224 return workqueue, numitems
225
225
226 def getrevstofix(ui, repo, opts):
226 def getrevstofix(ui, repo, opts):
227 """Returns the set of revision numbers that should be fixed"""
227 """Returns the set of revision numbers that should be fixed"""
228 revs = set(scmutil.revrange(repo, opts['rev']))
228 revs = set(scmutil.revrange(repo, opts['rev']))
229 for rev in revs:
229 for rev in revs:
230 checkfixablectx(ui, repo, repo[rev])
230 checkfixablectx(ui, repo, repo[rev])
231 if revs:
231 if revs:
232 cmdutil.checkunfinished(repo)
232 cmdutil.checkunfinished(repo)
233 checknodescendants(repo, revs)
233 checknodescendants(repo, revs)
234 if opts.get('working_dir'):
234 if opts.get('working_dir'):
235 revs.add(wdirrev)
235 revs.add(wdirrev)
236 if list(merge.mergestate.read(repo).unresolved()):
236 if list(merge.mergestate.read(repo).unresolved()):
237 raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
237 raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
238 if not revs:
238 if not revs:
239 raise error.Abort(
239 raise error.Abort(
240 'no changesets specified', hint='use --rev or --working-dir')
240 'no changesets specified', hint='use --rev or --working-dir')
241 return revs
241 return revs
242
242
243 def checknodescendants(repo, revs):
243 def checknodescendants(repo, revs):
244 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
244 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
245 repo.revs('(%ld::) - (%ld)', revs, revs)):
245 repo.revs('(%ld::) - (%ld)', revs, revs)):
246 raise error.Abort(_('can only fix a changeset together '
246 raise error.Abort(_('can only fix a changeset together '
247 'with all its descendants'))
247 'with all its descendants'))
248
248
249 def checkfixablectx(ui, repo, ctx):
249 def checkfixablectx(ui, repo, ctx):
250 """Aborts if the revision shouldn't be replaced with a fixed one."""
250 """Aborts if the revision shouldn't be replaced with a fixed one."""
251 if not ctx.mutable():
251 if not ctx.mutable():
252 raise error.Abort('can\'t fix immutable changeset %s' %
252 raise error.Abort('can\'t fix immutable changeset %s' %
253 (scmutil.formatchangeid(ctx),))
253 (scmutil.formatchangeid(ctx),))
254 if ctx.obsolete():
254 if ctx.obsolete():
255 # It would be better to actually check if the revision has a successor.
255 # It would be better to actually check if the revision has a successor.
256 allowdivergence = ui.configbool('experimental',
256 allowdivergence = ui.configbool('experimental',
257 'evolution.allowdivergence')
257 'evolution.allowdivergence')
258 if not allowdivergence:
258 if not allowdivergence:
259 raise error.Abort('fixing obsolete revision could cause divergence')
259 raise error.Abort('fixing obsolete revision could cause divergence')
260
260
261 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
261 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
262 """Returns the set of files that should be fixed in a context
262 """Returns the set of files that should be fixed in a context
263
263
264 The result depends on the base contexts; we include any file that has
264 The result depends on the base contexts; we include any file that has
265 changed relative to any of the base contexts. Base contexts should be
265 changed relative to any of the base contexts. Base contexts should be
266 ancestors of the context being fixed.
266 ancestors of the context being fixed.
267 """
267 """
268 files = set()
268 files = set()
269 for basectx in basectxs:
269 for basectx in basectxs:
270 stat = repo.status(
270 stat = repo.status(
271 basectx, fixctx, match=match, clean=bool(pats), unknown=bool(pats))
271 basectx, fixctx, match=match, clean=bool(pats), unknown=bool(pats))
272 files.update(
272 files.update(
273 set(itertools.chain(stat.added, stat.modified, stat.clean,
273 set(itertools.chain(stat.added, stat.modified, stat.clean,
274 stat.unknown)))
274 stat.unknown)))
275 return files
275 return files
276
276
277 def lineranges(opts, path, basectxs, fixctx, content2):
277 def lineranges(opts, path, basectxs, fixctx, content2):
278 """Returns the set of line ranges that should be fixed in a file
278 """Returns the set of line ranges that should be fixed in a file
279
279
280 Of the form [(10, 20), (30, 40)].
280 Of the form [(10, 20), (30, 40)].
281
281
282 This depends on the given base contexts; we must consider lines that have
282 This depends on the given base contexts; we must consider lines that have
283 changed versus any of the base contexts, and whether the file has been
283 changed versus any of the base contexts, and whether the file has been
284 renamed versus any of them.
284 renamed versus any of them.
285
285
286 Another way to understand this is that we exclude line ranges that are
286 Another way to understand this is that we exclude line ranges that are
287 common to the file in all base contexts.
287 common to the file in all base contexts.
288 """
288 """
289 if opts.get('whole'):
289 if opts.get('whole'):
290 # Return a range containing all lines. Rely on the diff implementation's
290 # Return a range containing all lines. Rely on the diff implementation's
291 # idea of how many lines are in the file, instead of reimplementing it.
291 # idea of how many lines are in the file, instead of reimplementing it.
292 return difflineranges('', content2)
292 return difflineranges('', content2)
293
293
294 rangeslist = []
294 rangeslist = []
295 for basectx in basectxs:
295 for basectx in basectxs:
296 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
296 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
297 if basepath in basectx:
297 if basepath in basectx:
298 content1 = basectx[basepath].data()
298 content1 = basectx[basepath].data()
299 else:
299 else:
300 content1 = ''
300 content1 = ''
301 rangeslist.extend(difflineranges(content1, content2))
301 rangeslist.extend(difflineranges(content1, content2))
302 return unionranges(rangeslist)
302 return unionranges(rangeslist)
303
303
304 def unionranges(rangeslist):
304 def unionranges(rangeslist):
305 """Return the union of some closed intervals
305 """Return the union of some closed intervals
306
306
307 >>> unionranges([])
307 >>> unionranges([])
308 []
308 []
309 >>> unionranges([(1, 100)])
309 >>> unionranges([(1, 100)])
310 [(1, 100)]
310 [(1, 100)]
311 >>> unionranges([(1, 100), (1, 100)])
311 >>> unionranges([(1, 100), (1, 100)])
312 [(1, 100)]
312 [(1, 100)]
313 >>> unionranges([(1, 100), (2, 100)])
313 >>> unionranges([(1, 100), (2, 100)])
314 [(1, 100)]
314 [(1, 100)]
315 >>> unionranges([(1, 99), (1, 100)])
315 >>> unionranges([(1, 99), (1, 100)])
316 [(1, 100)]
316 [(1, 100)]
317 >>> unionranges([(1, 100), (40, 60)])
317 >>> unionranges([(1, 100), (40, 60)])
318 [(1, 100)]
318 [(1, 100)]
319 >>> unionranges([(1, 49), (50, 100)])
319 >>> unionranges([(1, 49), (50, 100)])
320 [(1, 100)]
320 [(1, 100)]
321 >>> unionranges([(1, 48), (50, 100)])
321 >>> unionranges([(1, 48), (50, 100)])
322 [(1, 48), (50, 100)]
322 [(1, 48), (50, 100)]
323 >>> unionranges([(1, 2), (3, 4), (5, 6)])
323 >>> unionranges([(1, 2), (3, 4), (5, 6)])
324 [(1, 6)]
324 [(1, 6)]
325 """
325 """
326 rangeslist = sorted(set(rangeslist))
326 rangeslist = sorted(set(rangeslist))
327 unioned = []
327 unioned = []
328 if rangeslist:
328 if rangeslist:
329 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
329 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
330 for a, b in rangeslist:
330 for a, b in rangeslist:
331 c, d = unioned[-1]
331 c, d = unioned[-1]
332 if a > d + 1:
332 if a > d + 1:
333 unioned.append((a, b))
333 unioned.append((a, b))
334 else:
334 else:
335 unioned[-1] = (c, max(b, d))
335 unioned[-1] = (c, max(b, d))
336 return unioned
336 return unioned
337
337
338 def difflineranges(content1, content2):
338 def difflineranges(content1, content2):
339 """Return list of line number ranges in content2 that differ from content1.
339 """Return list of line number ranges in content2 that differ from content1.
340
340
341 Line numbers are 1-based. The numbers are the first and last line contained
341 Line numbers are 1-based. The numbers are the first and last line contained
342 in the range. Single-line ranges have the same line number for the first and
342 in the range. Single-line ranges have the same line number for the first and
343 last line. Excludes any empty ranges that result from lines that are only
343 last line. Excludes any empty ranges that result from lines that are only
344 present in content1. Relies on mdiff's idea of where the line endings are in
344 present in content1. Relies on mdiff's idea of where the line endings are in
345 the string.
345 the string.
346
346
347 >>> from mercurial import pycompat
347 >>> from mercurial import pycompat
348 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
348 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
349 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
349 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
350 >>> difflineranges2(b'', b'')
350 >>> difflineranges2(b'', b'')
351 []
351 []
352 >>> difflineranges2(b'a', b'')
352 >>> difflineranges2(b'a', b'')
353 []
353 []
354 >>> difflineranges2(b'', b'A')
354 >>> difflineranges2(b'', b'A')
355 [(1, 1)]
355 [(1, 1)]
356 >>> difflineranges2(b'a', b'a')
356 >>> difflineranges2(b'a', b'a')
357 []
357 []
358 >>> difflineranges2(b'a', b'A')
358 >>> difflineranges2(b'a', b'A')
359 [(1, 1)]
359 [(1, 1)]
360 >>> difflineranges2(b'ab', b'')
360 >>> difflineranges2(b'ab', b'')
361 []
361 []
362 >>> difflineranges2(b'', b'AB')
362 >>> difflineranges2(b'', b'AB')
363 [(1, 2)]
363 [(1, 2)]
364 >>> difflineranges2(b'abc', b'ac')
364 >>> difflineranges2(b'abc', b'ac')
365 []
365 []
366 >>> difflineranges2(b'ab', b'aCb')
366 >>> difflineranges2(b'ab', b'aCb')
367 [(2, 2)]
367 [(2, 2)]
368 >>> difflineranges2(b'abc', b'aBc')
368 >>> difflineranges2(b'abc', b'aBc')
369 [(2, 2)]
369 [(2, 2)]
370 >>> difflineranges2(b'ab', b'AB')
370 >>> difflineranges2(b'ab', b'AB')
371 [(1, 2)]
371 [(1, 2)]
372 >>> difflineranges2(b'abcde', b'aBcDe')
372 >>> difflineranges2(b'abcde', b'aBcDe')
373 [(2, 2), (4, 4)]
373 [(2, 2), (4, 4)]
374 >>> difflineranges2(b'abcde', b'aBCDe')
374 >>> difflineranges2(b'abcde', b'aBCDe')
375 [(2, 4)]
375 [(2, 4)]
376 """
376 """
377 ranges = []
377 ranges = []
378 for lines, kind in mdiff.allblocks(content1, content2):
378 for lines, kind in mdiff.allblocks(content1, content2):
379 firstline, lastline = lines[2:4]
379 firstline, lastline = lines[2:4]
380 if kind == '!' and firstline != lastline:
380 if kind == '!' and firstline != lastline:
381 ranges.append((firstline + 1, lastline))
381 ranges.append((firstline + 1, lastline))
382 return ranges
382 return ranges
383
383
384 def getbasectxs(repo, opts, revstofix):
384 def getbasectxs(repo, opts, revstofix):
385 """Returns a map of the base contexts for each revision
385 """Returns a map of the base contexts for each revision
386
386
387 The base contexts determine which lines are considered modified when we
387 The base contexts determine which lines are considered modified when we
388 attempt to fix just the modified lines in a file.
388 attempt to fix just the modified lines in a file. It also determines which
389 files we attempt to fix, so it is important to compute this even when
390 --whole is used.
389 """
391 """
390 # The --base flag overrides the usual logic, and we give every revision
392 # The --base flag overrides the usual logic, and we give every revision
391 # exactly the set of baserevs that the user specified.
393 # exactly the set of baserevs that the user specified.
392 if opts.get('base'):
394 if opts.get('base'):
393 baserevs = set(scmutil.revrange(repo, opts.get('base')))
395 baserevs = set(scmutil.revrange(repo, opts.get('base')))
394 if not baserevs:
396 if not baserevs:
395 baserevs = {nullrev}
397 baserevs = {nullrev}
396 basectxs = {repo[rev] for rev in baserevs}
398 basectxs = {repo[rev] for rev in baserevs}
397 return {rev: basectxs for rev in revstofix}
399 return {rev: basectxs for rev in revstofix}
398
400
399 # Proceed in topological order so that we can easily determine each
401 # Proceed in topological order so that we can easily determine each
400 # revision's baserevs by looking at its parents and their baserevs.
402 # revision's baserevs by looking at its parents and their baserevs.
401 basectxs = collections.defaultdict(set)
403 basectxs = collections.defaultdict(set)
402 for rev in sorted(revstofix):
404 for rev in sorted(revstofix):
403 ctx = repo[rev]
405 ctx = repo[rev]
404 for pctx in ctx.parents():
406 for pctx in ctx.parents():
405 if pctx.rev() in basectxs:
407 if pctx.rev() in basectxs:
406 basectxs[rev].update(basectxs[pctx.rev()])
408 basectxs[rev].update(basectxs[pctx.rev()])
407 else:
409 else:
408 basectxs[rev].add(pctx)
410 basectxs[rev].add(pctx)
409 return basectxs
411 return basectxs
410
412
411 def fixfile(ui, opts, fixers, fixctx, path, basectxs):
413 def fixfile(ui, opts, fixers, fixctx, path, basectxs):
412 """Run any configured fixers that should affect the file in this context
414 """Run any configured fixers that should affect the file in this context
413
415
414 Returns the file content that results from applying the fixers in some order
416 Returns the file content that results from applying the fixers in some order
415 starting with the file's content in the fixctx. Fixers that support line
417 starting with the file's content in the fixctx. Fixers that support line
416 ranges will affect lines that have changed relative to any of the basectxs
418 ranges will affect lines that have changed relative to any of the basectxs
417 (i.e. they will only avoid lines that are common to all basectxs).
419 (i.e. they will only avoid lines that are common to all basectxs).
418 """
420 """
419 newdata = fixctx[path].data()
421 newdata = fixctx[path].data()
420 for fixername, fixer in fixers.iteritems():
422 for fixername, fixer in fixers.iteritems():
421 if fixer.affects(opts, fixctx, path):
423 if fixer.affects(opts, fixctx, path):
422 ranges = lineranges(opts, path, basectxs, fixctx, newdata)
424 ranges = lineranges(opts, path, basectxs, fixctx, newdata)
423 command = fixer.command(ui, path, ranges)
425 command = fixer.command(ui, path, ranges)
424 if command is None:
426 if command is None:
425 continue
427 continue
426 ui.debug('subprocess: %s\n' % (command,))
428 ui.debug('subprocess: %s\n' % (command,))
427 proc = subprocess.Popen(
429 proc = subprocess.Popen(
428 command,
430 command,
429 shell=True,
431 shell=True,
430 cwd='/',
432 cwd='/',
431 stdin=subprocess.PIPE,
433 stdin=subprocess.PIPE,
432 stdout=subprocess.PIPE,
434 stdout=subprocess.PIPE,
433 stderr=subprocess.PIPE)
435 stderr=subprocess.PIPE)
434 newerdata, stderr = proc.communicate(newdata)
436 newerdata, stderr = proc.communicate(newdata)
435 if stderr:
437 if stderr:
436 showstderr(ui, fixctx.rev(), fixername, stderr)
438 showstderr(ui, fixctx.rev(), fixername, stderr)
437 else:
439 else:
438 newdata = newerdata
440 newdata = newerdata
439 return newdata
441 return newdata
440
442
441 def showstderr(ui, rev, fixername, stderr):
443 def showstderr(ui, rev, fixername, stderr):
442 """Writes the lines of the stderr string as warnings on the ui
444 """Writes the lines of the stderr string as warnings on the ui
443
445
444 Uses the revision number and fixername to give more context to each line of
446 Uses the revision number and fixername to give more context to each line of
445 the error message. Doesn't include file names, since those take up a lot of
447 the error message. Doesn't include file names, since those take up a lot of
446 space and would tend to be included in the error message if they were
448 space and would tend to be included in the error message if they were
447 relevant.
449 relevant.
448 """
450 """
449 for line in re.split('[\r\n]+', stderr):
451 for line in re.split('[\r\n]+', stderr):
450 if line:
452 if line:
451 ui.warn(('['))
453 ui.warn(('['))
452 if rev is None:
454 if rev is None:
453 ui.warn(_('wdir'), label='evolve.rev')
455 ui.warn(_('wdir'), label='evolve.rev')
454 else:
456 else:
455 ui.warn((str(rev)), label='evolve.rev')
457 ui.warn((str(rev)), label='evolve.rev')
456 ui.warn(('] %s: %s\n') % (fixername, line))
458 ui.warn(('] %s: %s\n') % (fixername, line))
457
459
458 def writeworkingdir(repo, ctx, filedata, replacements):
460 def writeworkingdir(repo, ctx, filedata, replacements):
459 """Write new content to the working copy and check out the new p1 if any
461 """Write new content to the working copy and check out the new p1 if any
460
462
461 We check out a new revision if and only if we fixed something in both the
463 We check out a new revision if and only if we fixed something in both the
462 working directory and its parent revision. This avoids the need for a full
464 working directory and its parent revision. This avoids the need for a full
463 update/merge, and means that the working directory simply isn't affected
465 update/merge, and means that the working directory simply isn't affected
464 unless the --working-dir flag is given.
466 unless the --working-dir flag is given.
465
467
466 Directly updates the dirstate for the affected files.
468 Directly updates the dirstate for the affected files.
467 """
469 """
468 for path, data in filedata.iteritems():
470 for path, data in filedata.iteritems():
469 fctx = ctx[path]
471 fctx = ctx[path]
470 fctx.write(data, fctx.flags())
472 fctx.write(data, fctx.flags())
471 if repo.dirstate[path] == 'n':
473 if repo.dirstate[path] == 'n':
472 repo.dirstate.normallookup(path)
474 repo.dirstate.normallookup(path)
473
475
474 oldparentnodes = repo.dirstate.parents()
476 oldparentnodes = repo.dirstate.parents()
475 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
477 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
476 if newparentnodes != oldparentnodes:
478 if newparentnodes != oldparentnodes:
477 repo.setparents(*newparentnodes)
479 repo.setparents(*newparentnodes)
478
480
479 def replacerev(ui, repo, ctx, filedata, replacements):
481 def replacerev(ui, repo, ctx, filedata, replacements):
480 """Commit a new revision like the given one, but with file content changes
482 """Commit a new revision like the given one, but with file content changes
481
483
482 "ctx" is the original revision to be replaced by a modified one.
484 "ctx" is the original revision to be replaced by a modified one.
483
485
484 "filedata" is a dict that maps paths to their new file content. All other
486 "filedata" is a dict that maps paths to their new file content. All other
485 paths will be recreated from the original revision without changes.
487 paths will be recreated from the original revision without changes.
486 "filedata" may contain paths that didn't exist in the original revision;
488 "filedata" may contain paths that didn't exist in the original revision;
487 they will be added.
489 they will be added.
488
490
489 "replacements" is a dict that maps a single node to a single node, and it is
491 "replacements" is a dict that maps a single node to a single node, and it is
490 updated to indicate the original revision is replaced by the newly created
492 updated to indicate the original revision is replaced by the newly created
491 one. No entry is added if the replacement's node already exists.
493 one. No entry is added if the replacement's node already exists.
492
494
493 The new revision has the same parents as the old one, unless those parents
495 The new revision has the same parents as the old one, unless those parents
494 have already been replaced, in which case those replacements are the parents
496 have already been replaced, in which case those replacements are the parents
495 of this new revision. Thus, if revisions are replaced in topological order,
497 of this new revision. Thus, if revisions are replaced in topological order,
496 there is no need to rebase them into the original topology later.
498 there is no need to rebase them into the original topology later.
497 """
499 """
498
500
499 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
501 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
500 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
502 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
501 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
503 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
502 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
504 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
503
505
504 def filectxfn(repo, memctx, path):
506 def filectxfn(repo, memctx, path):
505 if path not in ctx:
507 if path not in ctx:
506 return None
508 return None
507 fctx = ctx[path]
509 fctx = ctx[path]
508 copied = fctx.renamed()
510 copied = fctx.renamed()
509 if copied:
511 if copied:
510 copied = copied[0]
512 copied = copied[0]
511 return context.memfilectx(
513 return context.memfilectx(
512 repo,
514 repo,
513 memctx,
515 memctx,
514 path=fctx.path(),
516 path=fctx.path(),
515 data=filedata.get(path, fctx.data()),
517 data=filedata.get(path, fctx.data()),
516 islink=fctx.islink(),
518 islink=fctx.islink(),
517 isexec=fctx.isexec(),
519 isexec=fctx.isexec(),
518 copied=copied)
520 copied=copied)
519
521
520 memctx = context.memctx(
522 memctx = context.memctx(
521 repo,
523 repo,
522 parents=(newp1node, newp2node),
524 parents=(newp1node, newp2node),
523 text=ctx.description(),
525 text=ctx.description(),
524 files=set(ctx.files()) | set(filedata.keys()),
526 files=set(ctx.files()) | set(filedata.keys()),
525 filectxfn=filectxfn,
527 filectxfn=filectxfn,
526 user=ctx.user(),
528 user=ctx.user(),
527 date=ctx.date(),
529 date=ctx.date(),
528 extra=ctx.extra(),
530 extra=ctx.extra(),
529 branch=ctx.branch(),
531 branch=ctx.branch(),
530 editor=None)
532 editor=None)
531 sucnode = memctx.commit()
533 sucnode = memctx.commit()
532 prenode = ctx.node()
534 prenode = ctx.node()
533 if prenode == sucnode:
535 if prenode == sucnode:
534 ui.debug('node %s already existed\n' % (ctx.hex()))
536 ui.debug('node %s already existed\n' % (ctx.hex()))
535 else:
537 else:
536 replacements[ctx.node()] = sucnode
538 replacements[ctx.node()] = sucnode
537
539
538 def getfixers(ui):
540 def getfixers(ui):
539 """Returns a map of configured fixer tools indexed by their names
541 """Returns a map of configured fixer tools indexed by their names
540
542
541 Each value is a Fixer object with methods that implement the behavior of the
543 Each value is a Fixer object with methods that implement the behavior of the
542 fixer's config suboptions. Does not validate the config values.
544 fixer's config suboptions. Does not validate the config values.
543 """
545 """
544 result = {}
546 result = {}
545 for name in fixernames(ui):
547 for name in fixernames(ui):
546 result[name] = Fixer()
548 result[name] = Fixer()
547 attrs = ui.configsuboptions('fix', name)[1]
549 attrs = ui.configsuboptions('fix', name)[1]
548 for key in FIXER_ATTRS:
550 for key in FIXER_ATTRS:
549 setattr(result[name], pycompat.sysstr('_' + key),
551 setattr(result[name], pycompat.sysstr('_' + key),
550 attrs.get(key, ''))
552 attrs.get(key, ''))
551 return result
553 return result
552
554
553 def fixernames(ui):
555 def fixernames(ui):
554 """Returns the names of [fix] config options that have suboptions"""
556 """Returns the names of [fix] config options that have suboptions"""
555 names = set()
557 names = set()
556 for k, v in ui.configitems('fix'):
558 for k, v in ui.configitems('fix'):
557 if ':' in k:
559 if ':' in k:
558 names.add(k.split(':', 1)[0])
560 names.add(k.split(':', 1)[0])
559 return names
561 return names
560
562
561 class Fixer(object):
563 class Fixer(object):
562 """Wraps the raw config values for a fixer with methods"""
564 """Wraps the raw config values for a fixer with methods"""
563
565
564 def affects(self, opts, fixctx, path):
566 def affects(self, opts, fixctx, path):
565 """Should this fixer run on the file at the given path and context?"""
567 """Should this fixer run on the file at the given path and context?"""
566 return scmutil.match(fixctx, [self._fileset], opts)(path)
568 return scmutil.match(fixctx, [self._fileset], opts)(path)
567
569
568 def command(self, ui, path, ranges):
570 def command(self, ui, path, ranges):
569 """A shell command to use to invoke this fixer on the given file/lines
571 """A shell command to use to invoke this fixer on the given file/lines
570
572
571 May return None if there is no appropriate command to run for the given
573 May return None if there is no appropriate command to run for the given
572 parameters.
574 parameters.
573 """
575 """
574 expand = cmdutil.rendercommandtemplate
576 expand = cmdutil.rendercommandtemplate
575 parts = [expand(ui, self._command,
577 parts = [expand(ui, self._command,
576 {'rootpath': path, 'basename': os.path.basename(path)})]
578 {'rootpath': path, 'basename': os.path.basename(path)})]
577 if self._linerange:
579 if self._linerange:
578 if not ranges:
580 if not ranges:
579 # No line ranges to fix, so don't run the fixer.
581 # No line ranges to fix, so don't run the fixer.
580 return None
582 return None
581 for first, last in ranges:
583 for first, last in ranges:
582 parts.append(expand(ui, self._linerange,
584 parts.append(expand(ui, self._linerange,
583 {'first': first, 'last': last}))
585 {'first': first, 'last': last}))
584 return ' '.join(parts)
586 return ' '.join(parts)
@@ -1,1029 +1,1055
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:fileset=set:**.whole
69 > uppercase-whole-file:fileset=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:fileset=set:**.changed
72 > uppercase-changed-lines:fileset=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 -w --working-dir fix the working directory
112 --whole always fix every line of a file
112 --whole always fix every line of a file
113
113
114 (some details hidden, use --verbose to show complete help)
114 (some details hidden, use --verbose to show complete help)
115
115
116 $ hg help -e fix
116 $ hg help -e fix
117 fix extension - rewrite file content in changesets or working copy
117 fix extension - rewrite file content in changesets or working copy
118 (EXPERIMENTAL)
118 (EXPERIMENTAL)
119
119
120 Provides a command that runs configured tools on the contents of modified
120 Provides a command that runs configured tools on the contents of modified
121 files, writing back any fixes to the working copy or replacing changesets.
121 files, writing back any fixes to the working copy or replacing changesets.
122
122
123 Here is an example configuration that causes 'hg fix' to apply automatic
123 Here is an example configuration that causes 'hg fix' to apply automatic
124 formatting fixes to modified lines in C++ code:
124 formatting fixes to modified lines in C++ code:
125
125
126 [fix]
126 [fix]
127 clang-format:command=clang-format --assume-filename={rootpath}
127 clang-format:command=clang-format --assume-filename={rootpath}
128 clang-format:linerange=--lines={first}:{last}
128 clang-format:linerange=--lines={first}:{last}
129 clang-format:fileset=set:**.cpp or **.hpp
129 clang-format:fileset=set:**.cpp or **.hpp
130
130
131 The :command suboption forms the first part of the shell command that will be
131 The :command suboption forms the first part of the shell command that will be
132 used to fix a file. The content of the file is passed on standard input, and
132 used to fix a file. The content of the file is passed on standard input, and
133 the fixed file content is expected on standard output. If there is any output
133 the fixed file content is expected on standard output. If there is any output
134 on standard error, the file will not be affected. Some values may be
134 on standard error, the file will not be affected. Some values may be
135 substituted into the command:
135 substituted into the command:
136
136
137 {rootpath} The path of the file being fixed, relative to the repo root
137 {rootpath} The path of the file being fixed, relative to the repo root
138 {basename} The name of the file being fixed, without the directory path
138 {basename} The name of the file being fixed, without the directory path
139
139
140 If the :linerange suboption is set, the tool will only be run if there are
140 If the :linerange suboption is set, the tool will only be run if there are
141 changed lines in a file. The value of this suboption is appended to the shell
141 changed lines in a file. The value of this suboption is appended to the shell
142 command once for every range of changed lines in the file. Some values may be
142 command once for every range of changed lines in the file. Some values may be
143 substituted into the command:
143 substituted into the command:
144
144
145 {first} The 1-based line number of the first line in the modified range
145 {first} The 1-based line number of the first line in the modified range
146 {last} The 1-based line number of the last line in the modified range
146 {last} The 1-based line number of the last line in the modified range
147
147
148 The :fileset suboption determines which files will be passed through each
148 The :fileset suboption determines which files will be passed through each
149 configured tool. See 'hg help fileset' for possible values. If there are file
149 configured tool. See 'hg help fileset' for possible values. If there are file
150 arguments to 'hg fix', the intersection of these filesets is used.
150 arguments to 'hg fix', the intersection of these filesets is used.
151
151
152 There is also a configurable limit for the maximum size of file that will be
152 There is also a configurable limit for the maximum size of file that will be
153 processed by 'hg fix':
153 processed by 'hg fix':
154
154
155 [fix]
155 [fix]
156 maxfilesize=2MB
156 maxfilesize=2MB
157
157
158 list of commands:
158 list of commands:
159
159
160 fix rewrite file content in changesets or working directory
160 fix rewrite file content in changesets or working directory
161
161
162 (use 'hg help -v -e fix' to show built-in aliases and global options)
162 (use 'hg help -v -e fix' to show built-in aliases and global options)
163
163
164 There is no default behavior in the absence of --rev and --working-dir.
164 There is no default behavior in the absence of --rev and --working-dir.
165
165
166 $ hg init badusage
166 $ hg init badusage
167 $ cd badusage
167 $ cd badusage
168
168
169 $ hg fix
169 $ hg fix
170 abort: no changesets specified
170 abort: no changesets specified
171 (use --rev or --working-dir)
171 (use --rev or --working-dir)
172 [255]
172 [255]
173 $ hg fix --whole
173 $ hg fix --whole
174 abort: no changesets specified
174 abort: no changesets specified
175 (use --rev or --working-dir)
175 (use --rev or --working-dir)
176 [255]
176 [255]
177 $ hg fix --base 0
177 $ hg fix --base 0
178 abort: no changesets specified
178 abort: no changesets specified
179 (use --rev or --working-dir)
179 (use --rev or --working-dir)
180 [255]
180 [255]
181
181
182 Fixing a public revision isn't allowed. It should abort early enough that
182 Fixing a public revision isn't allowed. It should abort early enough that
183 nothing happens, even to the working directory.
183 nothing happens, even to the working directory.
184
184
185 $ printf "hello\n" > hello.whole
185 $ printf "hello\n" > hello.whole
186 $ hg commit -Aqm "hello"
186 $ hg commit -Aqm "hello"
187 $ hg phase -r 0 --public
187 $ hg phase -r 0 --public
188 $ hg fix -r 0
188 $ hg fix -r 0
189 abort: can't fix immutable changeset 0:6470986d2e7b
189 abort: can't fix immutable changeset 0:6470986d2e7b
190 [255]
190 [255]
191 $ hg fix -r 0 --working-dir
191 $ hg fix -r 0 --working-dir
192 abort: can't fix immutable changeset 0:6470986d2e7b
192 abort: can't fix immutable changeset 0:6470986d2e7b
193 [255]
193 [255]
194 $ hg cat -r tip hello.whole
194 $ hg cat -r tip hello.whole
195 hello
195 hello
196 $ cat hello.whole
196 $ cat hello.whole
197 hello
197 hello
198
198
199 $ cd ..
199 $ cd ..
200
200
201 Fixing a clean working directory should do nothing. Even the --whole flag
201 Fixing a clean working directory should do nothing. Even the --whole flag
202 shouldn't cause any clean files to be fixed. Specifying a clean file explicitly
202 shouldn't cause any clean files to be fixed. Specifying a clean file explicitly
203 should only fix it if the fixer always fixes the whole file. The combination of
203 should only fix it if the fixer always fixes the whole file. The combination of
204 an explicit filename and --whole should format the entire file regardless.
204 an explicit filename and --whole should format the entire file regardless.
205
205
206 $ hg init fixcleanwdir
206 $ hg init fixcleanwdir
207 $ cd fixcleanwdir
207 $ cd fixcleanwdir
208
208
209 $ printf "hello\n" > hello.changed
209 $ printf "hello\n" > hello.changed
210 $ printf "world\n" > hello.whole
210 $ printf "world\n" > hello.whole
211 $ hg commit -Aqm "foo"
211 $ hg commit -Aqm "foo"
212 $ hg fix --working-dir
212 $ hg fix --working-dir
213 $ hg diff
213 $ hg diff
214 $ hg fix --working-dir --whole
214 $ hg fix --working-dir --whole
215 $ hg diff
215 $ hg diff
216 $ hg fix --working-dir *
216 $ hg fix --working-dir *
217 $ cat *
217 $ cat *
218 hello
218 hello
219 WORLD
219 WORLD
220 $ hg revert --all --no-backup
220 $ hg revert --all --no-backup
221 reverting hello.whole
221 reverting hello.whole
222 $ hg fix --working-dir * --whole
222 $ hg fix --working-dir * --whole
223 $ cat *
223 $ cat *
224 HELLO
224 HELLO
225 WORLD
225 WORLD
226
226
227 The same ideas apply to fixing a revision, so we create a revision that doesn't
227 The same ideas apply to fixing a revision, so we create a revision that doesn't
228 modify either of the files in question and try fixing it. This also tests that
228 modify either of the files in question and try fixing it. This also tests that
229 we ignore a file that doesn't match any configured fixer.
229 we ignore a file that doesn't match any configured fixer.
230
230
231 $ hg revert --all --no-backup
231 $ hg revert --all --no-backup
232 reverting hello.changed
232 reverting hello.changed
233 reverting hello.whole
233 reverting hello.whole
234 $ printf "unimportant\n" > some.file
234 $ printf "unimportant\n" > some.file
235 $ hg commit -Aqm "some other file"
235 $ hg commit -Aqm "some other file"
236
236
237 $ hg fix -r .
237 $ hg fix -r .
238 $ hg cat -r tip *
238 $ hg cat -r tip *
239 hello
239 hello
240 world
240 world
241 unimportant
241 unimportant
242 $ hg fix -r . --whole
242 $ hg fix -r . --whole
243 $ hg cat -r tip *
243 $ hg cat -r tip *
244 hello
244 hello
245 world
245 world
246 unimportant
246 unimportant
247 $ hg fix -r . *
247 $ hg fix -r . *
248 $ hg cat -r tip *
248 $ hg cat -r tip *
249 hello
249 hello
250 WORLD
250 WORLD
251 unimportant
251 unimportant
252 $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true
252 $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true
253 2 new content-divergent changesets
253 2 new content-divergent changesets
254 $ hg cat -r tip *
254 $ hg cat -r tip *
255 HELLO
255 HELLO
256 WORLD
256 WORLD
257 unimportant
257 unimportant
258
258
259 $ cd ..
259 $ cd ..
260
260
261 Fixing the working directory should still work if there are no revisions.
261 Fixing the working directory should still work if there are no revisions.
262
262
263 $ hg init norevisions
263 $ hg init norevisions
264 $ cd norevisions
264 $ cd norevisions
265
265
266 $ printf "something\n" > something.whole
266 $ printf "something\n" > something.whole
267 $ hg add
267 $ hg add
268 adding something.whole
268 adding something.whole
269 $ hg fix --working-dir
269 $ hg fix --working-dir
270 $ cat something.whole
270 $ cat something.whole
271 SOMETHING
271 SOMETHING
272
272
273 $ cd ..
273 $ cd ..
274
274
275 Test the effect of fixing the working directory for each possible status, with
275 Test the effect of fixing the working directory for each possible status, with
276 and without providing explicit file arguments.
276 and without providing explicit file arguments.
277
277
278 $ hg init implicitlyfixstatus
278 $ hg init implicitlyfixstatus
279 $ cd implicitlyfixstatus
279 $ cd implicitlyfixstatus
280
280
281 $ printf "modified\n" > modified.whole
281 $ printf "modified\n" > modified.whole
282 $ printf "removed\n" > removed.whole
282 $ printf "removed\n" > removed.whole
283 $ printf "deleted\n" > deleted.whole
283 $ printf "deleted\n" > deleted.whole
284 $ printf "clean\n" > clean.whole
284 $ printf "clean\n" > clean.whole
285 $ printf "ignored.whole" > .hgignore
285 $ printf "ignored.whole" > .hgignore
286 $ hg commit -Aqm "stuff"
286 $ hg commit -Aqm "stuff"
287
287
288 $ printf "modified!!!\n" > modified.whole
288 $ printf "modified!!!\n" > modified.whole
289 $ printf "unknown\n" > unknown.whole
289 $ printf "unknown\n" > unknown.whole
290 $ printf "ignored\n" > ignored.whole
290 $ printf "ignored\n" > ignored.whole
291 $ printf "added\n" > added.whole
291 $ printf "added\n" > added.whole
292 $ hg add added.whole
292 $ hg add added.whole
293 $ hg remove removed.whole
293 $ hg remove removed.whole
294 $ rm deleted.whole
294 $ rm deleted.whole
295
295
296 $ hg status --all
296 $ hg status --all
297 M modified.whole
297 M modified.whole
298 A added.whole
298 A added.whole
299 R removed.whole
299 R removed.whole
300 ! deleted.whole
300 ! deleted.whole
301 ? unknown.whole
301 ? unknown.whole
302 I ignored.whole
302 I ignored.whole
303 C .hgignore
303 C .hgignore
304 C clean.whole
304 C clean.whole
305
305
306 $ hg fix --working-dir
306 $ hg fix --working-dir
307
307
308 $ hg status --all
308 $ hg status --all
309 M modified.whole
309 M modified.whole
310 A added.whole
310 A added.whole
311 R removed.whole
311 R removed.whole
312 ! deleted.whole
312 ! deleted.whole
313 ? unknown.whole
313 ? unknown.whole
314 I ignored.whole
314 I ignored.whole
315 C .hgignore
315 C .hgignore
316 C clean.whole
316 C clean.whole
317
317
318 $ cat *.whole
318 $ cat *.whole
319 ADDED
319 ADDED
320 clean
320 clean
321 ignored
321 ignored
322 MODIFIED!!!
322 MODIFIED!!!
323 unknown
323 unknown
324
324
325 $ printf "modified!!!\n" > modified.whole
325 $ printf "modified!!!\n" > modified.whole
326 $ printf "added\n" > added.whole
326 $ printf "added\n" > added.whole
327 $ hg fix --working-dir *.whole
327 $ hg fix --working-dir *.whole
328
328
329 $ hg status --all
329 $ hg status --all
330 M clean.whole
330 M clean.whole
331 M modified.whole
331 M modified.whole
332 A added.whole
332 A added.whole
333 R removed.whole
333 R removed.whole
334 ! deleted.whole
334 ! deleted.whole
335 ? unknown.whole
335 ? unknown.whole
336 I ignored.whole
336 I ignored.whole
337 C .hgignore
337 C .hgignore
338
338
339 It would be better if this also fixed the unknown file.
339 It would be better if this also fixed the unknown file.
340 $ cat *.whole
340 $ cat *.whole
341 ADDED
341 ADDED
342 CLEAN
342 CLEAN
343 ignored
343 ignored
344 MODIFIED!!!
344 MODIFIED!!!
345 unknown
345 unknown
346
346
347 $ cd ..
347 $ cd ..
348
348
349 Test that incremental fixing works on files with additions, deletions, and
349 Test that incremental fixing works on files with additions, deletions, and
350 changes in multiple line ranges. Note that deletions do not generally cause
350 changes in multiple line ranges. Note that deletions do not generally cause
351 neighboring lines to be fixed, so we don't return a line range for purely
351 neighboring lines to be fixed, so we don't return a line range for purely
352 deleted sections. In the future we should support a :deletion config that
352 deleted sections. In the future we should support a :deletion config that
353 allows fixers to know where deletions are located.
353 allows fixers to know where deletions are located.
354
354
355 $ hg init incrementalfixedlines
355 $ hg init incrementalfixedlines
356 $ cd incrementalfixedlines
356 $ cd incrementalfixedlines
357
357
358 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
358 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
359 $ hg commit -Aqm "foo"
359 $ hg commit -Aqm "foo"
360 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
360 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
361
361
362 $ hg --config "fix.fail:command=echo" \
362 $ hg --config "fix.fail:command=echo" \
363 > --config "fix.fail:linerange={first}:{last}" \
363 > --config "fix.fail:linerange={first}:{last}" \
364 > --config "fix.fail:fileset=foo.txt" \
364 > --config "fix.fail:fileset=foo.txt" \
365 > fix --working-dir
365 > fix --working-dir
366 $ cat foo.txt
366 $ cat foo.txt
367 1:1 4:6 8:8
367 1:1 4:6 8:8
368
368
369 $ cd ..
369 $ cd ..
370
370
371 Test that --whole fixes all lines regardless of the diffs present.
371 Test that --whole fixes all lines regardless of the diffs present.
372
372
373 $ hg init wholeignoresdiffs
373 $ hg init wholeignoresdiffs
374 $ cd wholeignoresdiffs
374 $ cd wholeignoresdiffs
375
375
376 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
376 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
377 $ hg commit -Aqm "foo"
377 $ hg commit -Aqm "foo"
378 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
378 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
379 $ hg fix --working-dir --whole
379 $ hg fix --working-dir --whole
380 $ cat foo.changed
380 $ cat foo.changed
381 ZZ
381 ZZ
382 A
382 A
383 C
383 C
384 DD
384 DD
385 EE
385 EE
386 FF
386 FF
387 F
387 F
388 GG
388 GG
389
389
390 $ cd ..
390 $ cd ..
391
391
392 We should do nothing with symlinks, and their targets should be unaffected. Any
392 We should do nothing with symlinks, and their targets should be unaffected. Any
393 other behavior would be more complicated to implement and harder to document.
393 other behavior would be more complicated to implement and harder to document.
394
394
395 #if symlink
395 #if symlink
396 $ hg init dontmesswithsymlinks
396 $ hg init dontmesswithsymlinks
397 $ cd dontmesswithsymlinks
397 $ cd dontmesswithsymlinks
398
398
399 $ printf "hello\n" > hello.whole
399 $ printf "hello\n" > hello.whole
400 $ ln -s hello.whole hellolink
400 $ ln -s hello.whole hellolink
401 $ hg add
401 $ hg add
402 adding hello.whole
402 adding hello.whole
403 adding hellolink
403 adding hellolink
404 $ hg fix --working-dir hellolink
404 $ hg fix --working-dir hellolink
405 $ hg status
405 $ hg status
406 A hello.whole
406 A hello.whole
407 A hellolink
407 A hellolink
408
408
409 $ cd ..
409 $ cd ..
410 #endif
410 #endif
411
411
412 We should allow fixers to run on binary files, even though this doesn't sound
412 We should allow fixers to run on binary files, even though this doesn't sound
413 like a common use case. There's not much benefit to disallowing it, and users
413 like a common use case. There's not much benefit to disallowing it, and users
414 can add "and not binary()" to their filesets if needed. The Mercurial
414 can add "and not binary()" to their filesets if needed. The Mercurial
415 philosophy is generally to not handle binary files specially anyway.
415 philosophy is generally to not handle binary files specially anyway.
416
416
417 $ hg init cantouchbinaryfiles
417 $ hg init cantouchbinaryfiles
418 $ cd cantouchbinaryfiles
418 $ cd cantouchbinaryfiles
419
419
420 $ printf "hello\0\n" > hello.whole
420 $ printf "hello\0\n" > hello.whole
421 $ hg add
421 $ hg add
422 adding hello.whole
422 adding hello.whole
423 $ hg fix --working-dir 'set:binary()'
423 $ hg fix --working-dir 'set:binary()'
424 $ cat hello.whole
424 $ cat hello.whole
425 HELLO\x00 (esc)
425 HELLO\x00 (esc)
426
426
427 $ cd ..
427 $ cd ..
428
428
429 We have a config for the maximum size of file we will attempt to fix. This can
429 We have a config for the maximum size of file we will attempt to fix. This can
430 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
430 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
431 could happen by accident without a well considered configuration. A more
431 could happen by accident without a well considered configuration. A more
432 precise configuration could use the size() fileset function if one global limit
432 precise configuration could use the size() fileset function if one global limit
433 is undesired.
433 is undesired.
434
434
435 $ hg init maxfilesize
435 $ hg init maxfilesize
436 $ cd maxfilesize
436 $ cd maxfilesize
437
437
438 $ printf "this file is huge\n" > hello.whole
438 $ printf "this file is huge\n" > hello.whole
439 $ hg add
439 $ hg add
440 adding hello.whole
440 adding hello.whole
441 $ hg --config fix.maxfilesize=10 fix --working-dir
441 $ hg --config fix.maxfilesize=10 fix --working-dir
442 ignoring file larger than 10 bytes: hello.whole
442 ignoring file larger than 10 bytes: hello.whole
443 $ cat hello.whole
443 $ cat hello.whole
444 this file is huge
444 this file is huge
445
445
446 $ cd ..
446 $ cd ..
447
447
448 If we specify a file to fix, other files should be left alone, even if they
448 If we specify a file to fix, other files should be left alone, even if they
449 have changes.
449 have changes.
450
450
451 $ hg init fixonlywhatitellyouto
451 $ hg init fixonlywhatitellyouto
452 $ cd fixonlywhatitellyouto
452 $ cd fixonlywhatitellyouto
453
453
454 $ printf "fix me!\n" > fixme.whole
454 $ printf "fix me!\n" > fixme.whole
455 $ printf "not me.\n" > notme.whole
455 $ printf "not me.\n" > notme.whole
456 $ hg add
456 $ hg add
457 adding fixme.whole
457 adding fixme.whole
458 adding notme.whole
458 adding notme.whole
459 $ hg fix --working-dir fixme.whole
459 $ hg fix --working-dir fixme.whole
460 $ cat *.whole
460 $ cat *.whole
461 FIX ME!
461 FIX ME!
462 not me.
462 not me.
463
463
464 $ cd ..
464 $ cd ..
465
465
466 Specifying a directory name should fix all its files and subdirectories.
466 Specifying a directory name should fix all its files and subdirectories.
467
467
468 $ hg init fixdirectory
468 $ hg init fixdirectory
469 $ cd fixdirectory
469 $ cd fixdirectory
470
470
471 $ mkdir -p dir1/dir2
471 $ mkdir -p dir1/dir2
472 $ printf "foo\n" > foo.whole
472 $ printf "foo\n" > foo.whole
473 $ printf "bar\n" > dir1/bar.whole
473 $ printf "bar\n" > dir1/bar.whole
474 $ printf "baz\n" > dir1/dir2/baz.whole
474 $ printf "baz\n" > dir1/dir2/baz.whole
475 $ hg add
475 $ hg add
476 adding dir1/bar.whole
476 adding dir1/bar.whole
477 adding dir1/dir2/baz.whole
477 adding dir1/dir2/baz.whole
478 adding foo.whole
478 adding foo.whole
479 $ hg fix --working-dir dir1
479 $ hg fix --working-dir dir1
480 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
480 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
481 foo
481 foo
482 BAR
482 BAR
483 BAZ
483 BAZ
484
484
485 $ cd ..
485 $ cd ..
486
486
487 Fixing a file in the working directory that needs no fixes should not actually
487 Fixing a file in the working directory that needs no fixes should not actually
488 write back to the file, so for example the mtime shouldn't change.
488 write back to the file, so for example the mtime shouldn't change.
489
489
490 $ hg init donttouchunfixedfiles
490 $ hg init donttouchunfixedfiles
491 $ cd donttouchunfixedfiles
491 $ cd donttouchunfixedfiles
492
492
493 $ printf "NO FIX NEEDED\n" > foo.whole
493 $ printf "NO FIX NEEDED\n" > foo.whole
494 $ hg add
494 $ hg add
495 adding foo.whole
495 adding foo.whole
496 $ cp -p foo.whole foo.whole.orig
496 $ cp -p foo.whole foo.whole.orig
497 $ cp -p foo.whole.orig foo.whole
497 $ cp -p foo.whole.orig foo.whole
498 $ sleep 2 # mtime has a resolution of one or two seconds.
498 $ sleep 2 # mtime has a resolution of one or two seconds.
499 $ hg fix --working-dir
499 $ hg fix --working-dir
500 $ f foo.whole.orig --newer foo.whole
500 $ f foo.whole.orig --newer foo.whole
501 foo.whole.orig: newer than foo.whole
501 foo.whole.orig: newer than foo.whole
502
502
503 $ cd ..
503 $ cd ..
504
504
505 When a fixer prints to stderr, we assume that it has failed. We should show the
505 When a fixer prints to stderr, we assume that it has failed. We should show the
506 error messages to the user, and we should not let the failing fixer affect the
506 error messages to the user, and we should not let the failing fixer affect the
507 file it was fixing (many code formatters might emit error messages on stderr
507 file it was fixing (many code formatters might emit error messages on stderr
508 and nothing on stdout, which would cause us the clear the file). We show the
508 and nothing on stdout, which would cause us the clear the file). We show the
509 user which fixer failed and which revision, but we assume that the fixer will
509 user which fixer failed and which revision, but we assume that the fixer will
510 print the filename if it is relevant.
510 print the filename if it is relevant.
511
511
512 $ hg init showstderr
512 $ hg init showstderr
513 $ cd showstderr
513 $ cd showstderr
514
514
515 $ printf "hello\n" > hello.txt
515 $ printf "hello\n" > hello.txt
516 $ hg add
516 $ hg add
517 adding hello.txt
517 adding hello.txt
518 $ cat >> $TESTTMP/cmd.sh <<'EOF'
518 $ cat >> $TESTTMP/cmd.sh <<'EOF'
519 > printf 'HELLO\n'
519 > printf 'HELLO\n'
520 > printf "$@: some\nerror" >&2
520 > printf "$@: some\nerror" >&2
521 > EOF
521 > EOF
522 $ hg --config "fix.fail:command=sh $TESTTMP/cmd.sh {rootpath}" \
522 $ hg --config "fix.fail:command=sh $TESTTMP/cmd.sh {rootpath}" \
523 > --config "fix.fail:fileset=hello.txt" \
523 > --config "fix.fail:fileset=hello.txt" \
524 > fix --working-dir
524 > fix --working-dir
525 [wdir] fail: hello.txt: some
525 [wdir] fail: hello.txt: some
526 [wdir] fail: error
526 [wdir] fail: error
527 $ cat hello.txt
527 $ cat hello.txt
528 hello
528 hello
529
529
530 $ cd ..
530 $ cd ..
531
531
532 Fixing the working directory and its parent revision at the same time should
532 Fixing the working directory and its parent revision at the same time should
533 check out the replacement revision for the parent. This prevents any new
533 check out the replacement revision for the parent. This prevents any new
534 uncommitted changes from appearing. We test this for a clean working directory
534 uncommitted changes from appearing. We test this for a clean working directory
535 and a dirty one. In both cases, all lines/files changed since the grandparent
535 and a dirty one. In both cases, all lines/files changed since the grandparent
536 will be fixed. The grandparent is the "baserev" for both the parent and the
536 will be fixed. The grandparent is the "baserev" for both the parent and the
537 working copy.
537 working copy.
538
538
539 $ hg init fixdotandcleanwdir
539 $ hg init fixdotandcleanwdir
540 $ cd fixdotandcleanwdir
540 $ cd fixdotandcleanwdir
541
541
542 $ printf "hello\n" > hello.whole
542 $ printf "hello\n" > hello.whole
543 $ printf "world\n" > world.whole
543 $ printf "world\n" > world.whole
544 $ hg commit -Aqm "the parent commit"
544 $ hg commit -Aqm "the parent commit"
545
545
546 $ hg parents --template '{rev} {desc}\n'
546 $ hg parents --template '{rev} {desc}\n'
547 0 the parent commit
547 0 the parent commit
548 $ hg fix --working-dir -r .
548 $ hg fix --working-dir -r .
549 $ hg parents --template '{rev} {desc}\n'
549 $ hg parents --template '{rev} {desc}\n'
550 1 the parent commit
550 1 the parent commit
551 $ hg cat -r . *.whole
551 $ hg cat -r . *.whole
552 HELLO
552 HELLO
553 WORLD
553 WORLD
554 $ cat *.whole
554 $ cat *.whole
555 HELLO
555 HELLO
556 WORLD
556 WORLD
557 $ hg status
557 $ hg status
558
558
559 $ cd ..
559 $ cd ..
560
560
561 Same test with a dirty working copy.
561 Same test with a dirty working copy.
562
562
563 $ hg init fixdotanddirtywdir
563 $ hg init fixdotanddirtywdir
564 $ cd fixdotanddirtywdir
564 $ cd fixdotanddirtywdir
565
565
566 $ printf "hello\n" > hello.whole
566 $ printf "hello\n" > hello.whole
567 $ printf "world\n" > world.whole
567 $ printf "world\n" > world.whole
568 $ hg commit -Aqm "the parent commit"
568 $ hg commit -Aqm "the parent commit"
569
569
570 $ printf "hello,\n" > hello.whole
570 $ printf "hello,\n" > hello.whole
571 $ printf "world!\n" > world.whole
571 $ printf "world!\n" > world.whole
572
572
573 $ hg parents --template '{rev} {desc}\n'
573 $ hg parents --template '{rev} {desc}\n'
574 0 the parent commit
574 0 the parent commit
575 $ hg fix --working-dir -r .
575 $ hg fix --working-dir -r .
576 $ hg parents --template '{rev} {desc}\n'
576 $ hg parents --template '{rev} {desc}\n'
577 1 the parent commit
577 1 the parent commit
578 $ hg cat -r . *.whole
578 $ hg cat -r . *.whole
579 HELLO
579 HELLO
580 WORLD
580 WORLD
581 $ cat *.whole
581 $ cat *.whole
582 HELLO,
582 HELLO,
583 WORLD!
583 WORLD!
584 $ hg status
584 $ hg status
585 M hello.whole
585 M hello.whole
586 M world.whole
586 M world.whole
587
587
588 $ cd ..
588 $ cd ..
589
589
590 When we have a chain of commits that change mutually exclusive lines of code,
590 When we have a chain of commits that change mutually exclusive lines of code,
591 we should be able to do incremental fixing that causes each commit in the chain
591 we should be able to do incremental fixing that causes each commit in the chain
592 to include fixes made to the previous commits. This prevents children from
592 to include fixes made to the previous commits. This prevents children from
593 backing out the fixes made in their parents. A dirty working directory is
593 backing out the fixes made in their parents. A dirty working directory is
594 conceptually similar to another commit in the chain.
594 conceptually similar to another commit in the chain.
595
595
596 $ hg init incrementallyfixchain
596 $ hg init incrementallyfixchain
597 $ cd incrementallyfixchain
597 $ cd incrementallyfixchain
598
598
599 $ cat > file.changed <<EOF
599 $ cat > file.changed <<EOF
600 > first
600 > first
601 > second
601 > second
602 > third
602 > third
603 > fourth
603 > fourth
604 > fifth
604 > fifth
605 > EOF
605 > EOF
606 $ hg commit -Aqm "the common ancestor (the baserev)"
606 $ hg commit -Aqm "the common ancestor (the baserev)"
607 $ cat > file.changed <<EOF
607 $ cat > file.changed <<EOF
608 > first (changed)
608 > first (changed)
609 > second
609 > second
610 > third
610 > third
611 > fourth
611 > fourth
612 > fifth
612 > fifth
613 > EOF
613 > EOF
614 $ hg commit -Aqm "the first commit to fix"
614 $ hg commit -Aqm "the first commit to fix"
615 $ cat > file.changed <<EOF
615 $ cat > file.changed <<EOF
616 > first (changed)
616 > first (changed)
617 > second
617 > second
618 > third (changed)
618 > third (changed)
619 > fourth
619 > fourth
620 > fifth
620 > fifth
621 > EOF
621 > EOF
622 $ hg commit -Aqm "the second commit to fix"
622 $ hg commit -Aqm "the second commit to fix"
623 $ cat > file.changed <<EOF
623 $ cat > file.changed <<EOF
624 > first (changed)
624 > first (changed)
625 > second
625 > second
626 > third (changed)
626 > third (changed)
627 > fourth
627 > fourth
628 > fifth (changed)
628 > fifth (changed)
629 > EOF
629 > EOF
630
630
631 $ hg fix -r . -r '.^' --working-dir
631 $ hg fix -r . -r '.^' --working-dir
632
632
633 $ hg parents --template '{rev}\n'
633 $ hg parents --template '{rev}\n'
634 4
634 4
635 $ hg cat -r '.^^' file.changed
635 $ hg cat -r '.^^' file.changed
636 first
636 first
637 second
637 second
638 third
638 third
639 fourth
639 fourth
640 fifth
640 fifth
641 $ hg cat -r '.^' file.changed
641 $ hg cat -r '.^' file.changed
642 FIRST (CHANGED)
642 FIRST (CHANGED)
643 second
643 second
644 third
644 third
645 fourth
645 fourth
646 fifth
646 fifth
647 $ hg cat -r . file.changed
647 $ hg cat -r . file.changed
648 FIRST (CHANGED)
648 FIRST (CHANGED)
649 second
649 second
650 THIRD (CHANGED)
650 THIRD (CHANGED)
651 fourth
651 fourth
652 fifth
652 fifth
653 $ cat file.changed
653 $ cat file.changed
654 FIRST (CHANGED)
654 FIRST (CHANGED)
655 second
655 second
656 THIRD (CHANGED)
656 THIRD (CHANGED)
657 fourth
657 fourth
658 FIFTH (CHANGED)
658 FIFTH (CHANGED)
659
659
660 $ cd ..
660 $ cd ..
661
661
662 If we incrementally fix a merge commit, we should fix any lines that changed
662 If we incrementally fix a merge commit, we should fix any lines that changed
663 versus either parent. You could imagine only fixing the intersection or some
663 versus either parent. You could imagine only fixing the intersection or some
664 other subset, but this is necessary if either parent is being fixed. It
664 other subset, but this is necessary if either parent is being fixed. It
665 prevents us from forgetting fixes made in either parent.
665 prevents us from forgetting fixes made in either parent.
666
666
667 $ hg init incrementallyfixmergecommit
667 $ hg init incrementallyfixmergecommit
668 $ cd incrementallyfixmergecommit
668 $ cd incrementallyfixmergecommit
669
669
670 $ printf "a\nb\nc\n" > file.changed
670 $ printf "a\nb\nc\n" > file.changed
671 $ hg commit -Aqm "ancestor"
671 $ hg commit -Aqm "ancestor"
672
672
673 $ printf "aa\nb\nc\n" > file.changed
673 $ printf "aa\nb\nc\n" > file.changed
674 $ hg commit -m "change a"
674 $ hg commit -m "change a"
675
675
676 $ hg checkout '.^'
676 $ hg checkout '.^'
677 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
677 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
678 $ printf "a\nb\ncc\n" > file.changed
678 $ printf "a\nb\ncc\n" > file.changed
679 $ hg commit -m "change c"
679 $ hg commit -m "change c"
680 created new head
680 created new head
681
681
682 $ hg merge
682 $ hg merge
683 merging file.changed
683 merging file.changed
684 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
684 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
685 (branch merge, don't forget to commit)
685 (branch merge, don't forget to commit)
686 $ hg commit -m "merge"
686 $ hg commit -m "merge"
687 $ hg cat -r . file.changed
687 $ hg cat -r . file.changed
688 aa
688 aa
689 b
689 b
690 cc
690 cc
691
691
692 $ hg fix -r . --working-dir
692 $ hg fix -r . --working-dir
693 $ hg cat -r . file.changed
693 $ hg cat -r . file.changed
694 AA
694 AA
695 b
695 b
696 CC
696 CC
697
697
698 $ cd ..
698 $ cd ..
699
699
700 Abort fixing revisions if there is an unfinished operation. We don't want to
700 Abort fixing revisions if there is an unfinished operation. We don't want to
701 make things worse by editing files or stripping/obsoleting things. Also abort
701 make things worse by editing files or stripping/obsoleting things. Also abort
702 fixing the working directory if there are unresolved merge conflicts.
702 fixing the working directory if there are unresolved merge conflicts.
703
703
704 $ hg init abortunresolved
704 $ hg init abortunresolved
705 $ cd abortunresolved
705 $ cd abortunresolved
706
706
707 $ echo "foo1" > foo.whole
707 $ echo "foo1" > foo.whole
708 $ hg commit -Aqm "foo 1"
708 $ hg commit -Aqm "foo 1"
709
709
710 $ hg update null
710 $ hg update null
711 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
711 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
712 $ echo "foo2" > foo.whole
712 $ echo "foo2" > foo.whole
713 $ hg commit -Aqm "foo 2"
713 $ hg commit -Aqm "foo 2"
714
714
715 $ hg --config extensions.rebase= rebase -r 1 -d 0
715 $ hg --config extensions.rebase= rebase -r 1 -d 0
716 rebasing 1:c3b6dc0e177a "foo 2" (tip)
716 rebasing 1:c3b6dc0e177a "foo 2" (tip)
717 merging foo.whole
717 merging foo.whole
718 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
718 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
719 unresolved conflicts (see hg resolve, then hg rebase --continue)
719 unresolved conflicts (see hg resolve, then hg rebase --continue)
720 [1]
720 [1]
721
721
722 $ hg --config extensions.rebase= fix --working-dir
722 $ hg --config extensions.rebase= fix --working-dir
723 abort: unresolved conflicts
723 abort: unresolved conflicts
724 (use 'hg resolve')
724 (use 'hg resolve')
725 [255]
725 [255]
726
726
727 $ hg --config extensions.rebase= fix -r .
727 $ hg --config extensions.rebase= fix -r .
728 abort: rebase in progress
728 abort: rebase in progress
729 (use 'hg rebase --continue' or 'hg rebase --abort')
729 (use 'hg rebase --continue' or 'hg rebase --abort')
730 [255]
730 [255]
731
731
732 When fixing a file that was renamed, we should diff against the source of the
732 When fixing a file that was renamed, we should diff against the source of the
733 rename for incremental fixing and we should correctly reproduce the rename in
733 rename for incremental fixing and we should correctly reproduce the rename in
734 the replacement revision.
734 the replacement revision.
735
735
736 $ hg init fixrenamecommit
736 $ hg init fixrenamecommit
737 $ cd fixrenamecommit
737 $ cd fixrenamecommit
738
738
739 $ printf "a\nb\nc\n" > source.changed
739 $ printf "a\nb\nc\n" > source.changed
740 $ hg commit -Aqm "source revision"
740 $ hg commit -Aqm "source revision"
741 $ hg move source.changed dest.changed
741 $ hg move source.changed dest.changed
742 $ printf "a\nb\ncc\n" > dest.changed
742 $ printf "a\nb\ncc\n" > dest.changed
743 $ hg commit -m "dest revision"
743 $ hg commit -m "dest revision"
744
744
745 $ hg fix -r .
745 $ hg fix -r .
746 $ hg log -r tip --copies --template "{file_copies}\n"
746 $ hg log -r tip --copies --template "{file_copies}\n"
747 dest.changed (source.changed)
747 dest.changed (source.changed)
748 $ hg cat -r tip dest.changed
748 $ hg cat -r tip dest.changed
749 a
749 a
750 b
750 b
751 CC
751 CC
752
752
753 $ cd ..
753 $ cd ..
754
754
755 When fixing revisions that remove files we must ensure that the replacement
755 When fixing revisions that remove files we must ensure that the replacement
756 actually removes the file, whereas it could accidentally leave it unchanged or
756 actually removes the file, whereas it could accidentally leave it unchanged or
757 write an empty string to it.
757 write an empty string to it.
758
758
759 $ hg init fixremovedfile
759 $ hg init fixremovedfile
760 $ cd fixremovedfile
760 $ cd fixremovedfile
761
761
762 $ printf "foo\n" > foo.whole
762 $ printf "foo\n" > foo.whole
763 $ printf "bar\n" > bar.whole
763 $ printf "bar\n" > bar.whole
764 $ hg commit -Aqm "add files"
764 $ hg commit -Aqm "add files"
765 $ hg remove bar.whole
765 $ hg remove bar.whole
766 $ hg commit -m "remove file"
766 $ hg commit -m "remove file"
767 $ hg status --change .
767 $ hg status --change .
768 R bar.whole
768 R bar.whole
769 $ hg fix -r . foo.whole
769 $ hg fix -r . foo.whole
770 $ hg status --change tip
770 $ hg status --change tip
771 M foo.whole
771 M foo.whole
772 R bar.whole
772 R bar.whole
773
773
774 $ cd ..
774 $ cd ..
775
775
776 If fixing a revision finds no fixes to make, no replacement revision should be
776 If fixing a revision finds no fixes to make, no replacement revision should be
777 created.
777 created.
778
778
779 $ hg init nofixesneeded
779 $ hg init nofixesneeded
780 $ cd nofixesneeded
780 $ cd nofixesneeded
781
781
782 $ printf "FOO\n" > foo.whole
782 $ printf "FOO\n" > foo.whole
783 $ hg commit -Aqm "add file"
783 $ hg commit -Aqm "add file"
784 $ hg log --template '{rev}\n'
784 $ hg log --template '{rev}\n'
785 0
785 0
786 $ hg fix -r .
786 $ hg fix -r .
787 $ hg log --template '{rev}\n'
787 $ hg log --template '{rev}\n'
788 0
788 0
789
789
790 $ cd ..
790 $ cd ..
791
791
792 If fixing a commit reverts all the changes in the commit, we replace it with a
792 If fixing a commit reverts all the changes in the commit, we replace it with a
793 commit that changes no files.
793 commit that changes no files.
794
794
795 $ hg init nochangesleft
795 $ hg init nochangesleft
796 $ cd nochangesleft
796 $ cd nochangesleft
797
797
798 $ printf "FOO\n" > foo.whole
798 $ printf "FOO\n" > foo.whole
799 $ hg commit -Aqm "add file"
799 $ hg commit -Aqm "add file"
800 $ printf "foo\n" > foo.whole
800 $ printf "foo\n" > foo.whole
801 $ hg commit -m "edit file"
801 $ hg commit -m "edit file"
802 $ hg status --change .
802 $ hg status --change .
803 M foo.whole
803 M foo.whole
804 $ hg fix -r .
804 $ hg fix -r .
805 $ hg status --change tip
805 $ hg status --change tip
806
806
807 $ cd ..
807 $ cd ..
808
808
809 If we fix a parent and child revision together, the child revision must be
809 If we fix a parent and child revision together, the child revision must be
810 replaced if the parent is replaced, even if the diffs of the child needed no
810 replaced if the parent is replaced, even if the diffs of the child needed no
811 fixes. However, we're free to not replace revisions that need no fixes and have
811 fixes. However, we're free to not replace revisions that need no fixes and have
812 no ancestors that are replaced.
812 no ancestors that are replaced.
813
813
814 $ hg init mustreplacechild
814 $ hg init mustreplacechild
815 $ cd mustreplacechild
815 $ cd mustreplacechild
816
816
817 $ printf "FOO\n" > foo.whole
817 $ printf "FOO\n" > foo.whole
818 $ hg commit -Aqm "add foo"
818 $ hg commit -Aqm "add foo"
819 $ printf "foo\n" > foo.whole
819 $ printf "foo\n" > foo.whole
820 $ hg commit -m "edit foo"
820 $ hg commit -m "edit foo"
821 $ printf "BAR\n" > bar.whole
821 $ printf "BAR\n" > bar.whole
822 $ hg commit -Aqm "add bar"
822 $ hg commit -Aqm "add bar"
823
823
824 $ hg log --graph --template '{node|shortest} {files}'
824 $ hg log --graph --template '{node|shortest} {files}'
825 @ bc05 bar.whole
825 @ bc05 bar.whole
826 |
826 |
827 o 4fd2 foo.whole
827 o 4fd2 foo.whole
828 |
828 |
829 o f9ac foo.whole
829 o f9ac foo.whole
830
830
831 $ hg fix -r 0:2
831 $ hg fix -r 0:2
832 $ hg log --graph --template '{node|shortest} {files}'
832 $ hg log --graph --template '{node|shortest} {files}'
833 o 3801 bar.whole
833 o 3801 bar.whole
834 |
834 |
835 o 38cc
835 o 38cc
836 |
836 |
837 | @ bc05 bar.whole
837 | @ bc05 bar.whole
838 | |
838 | |
839 | x 4fd2 foo.whole
839 | x 4fd2 foo.whole
840 |/
840 |/
841 o f9ac foo.whole
841 o f9ac foo.whole
842
842
843
843
844 $ cd ..
844 $ cd ..
845
845
846 It's also possible that the child needs absolutely no changes, but we still
846 It's also possible that the child needs absolutely no changes, but we still
847 need to replace it to update its parent. If we skipped replacing the child
847 need to replace it to update its parent. If we skipped replacing the child
848 because it had no file content changes, it would become an orphan for no good
848 because it had no file content changes, it would become an orphan for no good
849 reason.
849 reason.
850
850
851 $ hg init mustreplacechildevenifnop
851 $ hg init mustreplacechildevenifnop
852 $ cd mustreplacechildevenifnop
852 $ cd mustreplacechildevenifnop
853
853
854 $ printf "Foo\n" > foo.whole
854 $ printf "Foo\n" > foo.whole
855 $ hg commit -Aqm "add a bad foo"
855 $ hg commit -Aqm "add a bad foo"
856 $ printf "FOO\n" > foo.whole
856 $ printf "FOO\n" > foo.whole
857 $ hg commit -m "add a good foo"
857 $ hg commit -m "add a good foo"
858 $ hg fix -r . -r '.^'
858 $ hg fix -r . -r '.^'
859 $ hg log --graph --template '{rev} {desc}'
859 $ hg log --graph --template '{rev} {desc}'
860 o 3 add a good foo
860 o 3 add a good foo
861 |
861 |
862 o 2 add a bad foo
862 o 2 add a bad foo
863
863
864 @ 1 add a good foo
864 @ 1 add a good foo
865 |
865 |
866 x 0 add a bad foo
866 x 0 add a bad foo
867
867
868
868
869 $ cd ..
869 $ cd ..
870
870
871 Similar to the case above, the child revision may become empty as a result of
871 Similar to the case above, the child revision may become empty as a result of
872 fixing its parent. We should still create an empty replacement child.
872 fixing its parent. We should still create an empty replacement child.
873 TODO: determine how this should interact with ui.allowemptycommit given that
873 TODO: determine how this should interact with ui.allowemptycommit given that
874 the empty replacement could have children.
874 the empty replacement could have children.
875
875
876 $ hg init mustreplacechildevenifempty
876 $ hg init mustreplacechildevenifempty
877 $ cd mustreplacechildevenifempty
877 $ cd mustreplacechildevenifempty
878
878
879 $ printf "foo\n" > foo.whole
879 $ printf "foo\n" > foo.whole
880 $ hg commit -Aqm "add foo"
880 $ hg commit -Aqm "add foo"
881 $ printf "Foo\n" > foo.whole
881 $ printf "Foo\n" > foo.whole
882 $ hg commit -m "edit foo"
882 $ hg commit -m "edit foo"
883 $ hg fix -r . -r '.^'
883 $ hg fix -r . -r '.^'
884 $ hg log --graph --template '{rev} {desc}\n' --stat
884 $ hg log --graph --template '{rev} {desc}\n' --stat
885 o 3 edit foo
885 o 3 edit foo
886 |
886 |
887 o 2 add foo
887 o 2 add foo
888 foo.whole | 1 +
888 foo.whole | 1 +
889 1 files changed, 1 insertions(+), 0 deletions(-)
889 1 files changed, 1 insertions(+), 0 deletions(-)
890
890
891 @ 1 edit foo
891 @ 1 edit foo
892 | foo.whole | 2 +-
892 | foo.whole | 2 +-
893 | 1 files changed, 1 insertions(+), 1 deletions(-)
893 | 1 files changed, 1 insertions(+), 1 deletions(-)
894 |
894 |
895 x 0 add foo
895 x 0 add foo
896 foo.whole | 1 +
896 foo.whole | 1 +
897 1 files changed, 1 insertions(+), 0 deletions(-)
897 1 files changed, 1 insertions(+), 0 deletions(-)
898
898
899
899
900 $ cd ..
900 $ cd ..
901
901
902 Fixing a secret commit should replace it with another secret commit.
902 Fixing a secret commit should replace it with another secret commit.
903
903
904 $ hg init fixsecretcommit
904 $ hg init fixsecretcommit
905 $ cd fixsecretcommit
905 $ cd fixsecretcommit
906
906
907 $ printf "foo\n" > foo.whole
907 $ printf "foo\n" > foo.whole
908 $ hg commit -Aqm "add foo" --secret
908 $ hg commit -Aqm "add foo" --secret
909 $ hg fix -r .
909 $ hg fix -r .
910 $ hg log --template '{rev} {phase}\n'
910 $ hg log --template '{rev} {phase}\n'
911 1 secret
911 1 secret
912 0 secret
912 0 secret
913
913
914 $ cd ..
914 $ cd ..
915
915
916 We should also preserve phase when fixing a draft commit while the user has
916 We should also preserve phase when fixing a draft commit while the user has
917 their default set to secret.
917 their default set to secret.
918
918
919 $ hg init respectphasesnewcommit
919 $ hg init respectphasesnewcommit
920 $ cd respectphasesnewcommit
920 $ cd respectphasesnewcommit
921
921
922 $ printf "foo\n" > foo.whole
922 $ printf "foo\n" > foo.whole
923 $ hg commit -Aqm "add foo"
923 $ hg commit -Aqm "add foo"
924 $ hg --config phases.newcommit=secret fix -r .
924 $ hg --config phases.newcommit=secret fix -r .
925 $ hg log --template '{rev} {phase}\n'
925 $ hg log --template '{rev} {phase}\n'
926 1 draft
926 1 draft
927 0 draft
927 0 draft
928
928
929 $ cd ..
929 $ cd ..
930
930
931 Debug output should show what fixer commands are being subprocessed, which is
931 Debug output should show what fixer commands are being subprocessed, which is
932 useful for anyone trying to set up a new config.
932 useful for anyone trying to set up a new config.
933
933
934 $ hg init debugoutput
934 $ hg init debugoutput
935 $ cd debugoutput
935 $ cd debugoutput
936
936
937 $ printf "foo\nbar\nbaz\n" > foo.changed
937 $ printf "foo\nbar\nbaz\n" > foo.changed
938 $ hg commit -Aqm "foo"
938 $ hg commit -Aqm "foo"
939 $ printf "Foo\nbar\nBaz\n" > foo.changed
939 $ printf "Foo\nbar\nBaz\n" > foo.changed
940 $ hg --debug fix --working-dir
940 $ hg --debug fix --working-dir
941 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
941 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
942
942
943 $ cd ..
943 $ cd ..
944
944
945 Fixing an obsolete revision can cause divergence, so we abort unless the user
945 Fixing an obsolete revision can cause divergence, so we abort unless the user
946 configures to allow it. This is not yet smart enough to know whether there is a
946 configures to allow it. This is not yet smart enough to know whether there is a
947 successor, but even then it is not likely intentional or idiomatic to fix an
947 successor, but even then it is not likely intentional or idiomatic to fix an
948 obsolete revision.
948 obsolete revision.
949
949
950 $ hg init abortobsoleterev
950 $ hg init abortobsoleterev
951 $ cd abortobsoleterev
951 $ cd abortobsoleterev
952
952
953 $ printf "foo\n" > foo.changed
953 $ printf "foo\n" > foo.changed
954 $ hg commit -Aqm "foo"
954 $ hg commit -Aqm "foo"
955 $ hg debugobsolete `hg parents --template '{node}'`
955 $ hg debugobsolete `hg parents --template '{node}'`
956 obsoleted 1 changesets
956 obsoleted 1 changesets
957 $ hg --hidden fix -r 0
957 $ hg --hidden fix -r 0
958 abort: fixing obsolete revision could cause divergence
958 abort: fixing obsolete revision could cause divergence
959 [255]
959 [255]
960
960
961 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
961 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
962 $ hg cat -r tip foo.changed
962 $ hg cat -r tip foo.changed
963 FOO
963 FOO
964
964
965 $ cd ..
965 $ cd ..
966
966
967 Test all of the available substitution values for fixer commands.
967 Test all of the available substitution values for fixer commands.
968
968
969 $ hg init substitution
969 $ hg init substitution
970 $ cd substitution
970 $ cd substitution
971
971
972 $ mkdir foo
972 $ mkdir foo
973 $ printf "hello\ngoodbye\n" > foo/bar
973 $ printf "hello\ngoodbye\n" > foo/bar
974 $ hg add
974 $ hg add
975 adding foo/bar
975 adding foo/bar
976 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
976 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
977 > --config "fix.fail:linerange='{first}' '{last}'" \
977 > --config "fix.fail:linerange='{first}' '{last}'" \
978 > --config "fix.fail:fileset=foo/bar" \
978 > --config "fix.fail:fileset=foo/bar" \
979 > fix --working-dir
979 > fix --working-dir
980 $ cat foo/bar
980 $ cat foo/bar
981 foo/bar
981 foo/bar
982 bar
982 bar
983 1
983 1
984 2
984 2
985
985
986 $ cd ..
986 $ cd ..
987
987
988 The --base flag should allow picking the revisions to diff against for changed
988 The --base flag should allow picking the revisions to diff against for changed
989 files and incremental line formatting.
989 files and incremental line formatting.
990
990
991 $ hg init baseflag
991 $ hg init baseflag
992 $ cd baseflag
992 $ cd baseflag
993
993
994 $ printf "one\ntwo\n" > foo.changed
994 $ printf "one\ntwo\n" > foo.changed
995 $ printf "bar\n" > bar.changed
995 $ printf "bar\n" > bar.changed
996 $ hg commit -Aqm "first"
996 $ hg commit -Aqm "first"
997 $ printf "one\nTwo\n" > foo.changed
997 $ printf "one\nTwo\n" > foo.changed
998 $ hg commit -m "second"
998 $ hg commit -m "second"
999 $ hg fix -w --base .
999 $ hg fix -w --base .
1000 $ hg status
1000 $ hg status
1001 $ hg fix -w --base null
1001 $ hg fix -w --base null
1002 $ cat foo.changed
1002 $ cat foo.changed
1003 ONE
1003 ONE
1004 TWO
1004 TWO
1005 $ cat bar.changed
1005 $ cat bar.changed
1006 BAR
1006 BAR
1007
1007
1008 $ cd ..
1008 $ cd ..
1009
1009
1010 If the user asks to fix the parent of another commit, they are asking to create
1010 If the user asks to fix the parent of another commit, they are asking to create
1011 an orphan. We must respect experimental.evolution.allowunstable.
1011 an orphan. We must respect experimental.evolution.allowunstable.
1012
1012
1013 $ hg init allowunstable
1013 $ hg init allowunstable
1014 $ cd allowunstable
1014 $ cd allowunstable
1015
1015
1016 $ printf "one\n" > foo.whole
1016 $ printf "one\n" > foo.whole
1017 $ hg commit -Aqm "first"
1017 $ hg commit -Aqm "first"
1018 $ printf "two\n" > foo.whole
1018 $ printf "two\n" > foo.whole
1019 $ hg commit -m "second"
1019 $ hg commit -m "second"
1020 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1020 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1021 abort: can only fix a changeset together with all its descendants
1021 abort: can only fix a changeset together with all its descendants
1022 [255]
1022 [255]
1023 $ hg fix -r '.^'
1023 $ hg fix -r '.^'
1024 1 new orphan changesets
1024 1 new orphan changesets
1025 $ hg cat -r 2 foo.whole
1025 $ hg cat -r 2 foo.whole
1026 ONE
1026 ONE
1027
1027
1028 $ cd ..
1028 $ cd ..
1029
1029
1030 The --base flag affects the set of files being fixed. So while the --whole flag
1031 makes the base irrelevant for changed line ranges, it still changes the
1032 meaning and effect of the command. In this example, no files or lines are fixed
1033 until we specify the base, but then we do fix unchanged lines.
1034
1035 $ hg init basewhole
1036 $ cd basewhole
1037 $ printf "foo1\n" > foo.changed
1038 $ hg commit -Aqm "first"
1039 $ printf "foo2\n" >> foo.changed
1040 $ printf "bar\n" > bar.changed
1041 $ hg commit -Aqm "second"
1042
1043 $ hg fix --working-dir --whole
1044 $ cat *.changed
1045 bar
1046 foo1
1047 foo2
1048
1049 $ hg fix --working-dir --base 0 --whole
1050 $ cat *.changed
1051 BAR
1052 FOO1
1053 FOO2
1054
1055 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now