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