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