##// END OF EJS Templates
fix: warn when a fixer doesn't have a configured command...
Martin von Zweigbergk -
r43494:d3d1a3af default
parent child Browse files
Show More
@@ -1,874 +1,878 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 procutil
137 137
138 138 from mercurial import (
139 139 cmdutil,
140 140 context,
141 141 copies,
142 142 error,
143 143 mdiff,
144 144 merge,
145 145 obsolete,
146 146 pycompat,
147 147 registrar,
148 148 scmutil,
149 149 util,
150 150 worker,
151 151 )
152 152
153 153 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
154 154 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
155 155 # be specifying the version(s) of Mercurial they are tested with, or
156 156 # leave the attribute unspecified.
157 157 testedwith = b'ships-with-hg-core'
158 158
159 159 cmdtable = {}
160 160 command = registrar.command(cmdtable)
161 161
162 162 configtable = {}
163 163 configitem = registrar.configitem(configtable)
164 164
165 165 # Register the suboptions allowed for each configured fixer, and default values.
166 166 FIXER_ATTRS = {
167 167 b'command': None,
168 168 b'linerange': None,
169 169 b'pattern': None,
170 170 b'priority': 0,
171 171 b'metadata': False,
172 172 b'skipclean': True,
173 173 b'enabled': True,
174 174 }
175 175
176 176 for key, default in FIXER_ATTRS.items():
177 177 configitem(b'fix', b'.*:%s$' % key, default=default, generic=True)
178 178
179 179 # A good default size allows most source code files to be fixed, but avoids
180 180 # letting fixer tools choke on huge inputs, which could be surprising to the
181 181 # user.
182 182 configitem(b'fix', b'maxfilesize', default=b'2MB')
183 183
184 184 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
185 185 # This helps users do shell scripts that stop when a fixer tool signals a
186 186 # problem.
187 187 configitem(b'fix', b'failure', default=b'continue')
188 188
189 189
190 190 def checktoolfailureaction(ui, message, hint=None):
191 191 """Abort with 'message' if fix.failure=abort"""
192 192 action = ui.config(b'fix', b'failure')
193 193 if action not in (b'continue', b'abort'):
194 194 raise error.Abort(
195 195 _(b'unknown fix.failure action: %s') % (action,),
196 196 hint=_(b'use "continue" or "abort"'),
197 197 )
198 198 if action == b'abort':
199 199 raise error.Abort(message, hint=hint)
200 200
201 201
202 202 allopt = (b'', b'all', False, _(b'fix all non-public non-obsolete revisions'))
203 203 baseopt = (
204 204 b'',
205 205 b'base',
206 206 [],
207 207 _(
208 208 b'revisions to diff against (overrides automatic '
209 209 b'selection, and applies to every revision being '
210 210 b'fixed)'
211 211 ),
212 212 _(b'REV'),
213 213 )
214 214 revopt = (b'r', b'rev', [], _(b'revisions to fix'), _(b'REV'))
215 215 wdiropt = (b'w', b'working-dir', False, _(b'fix the working directory'))
216 216 wholeopt = (b'', b'whole', False, _(b'always fix every line of a file'))
217 217 usage = _(b'[OPTION]... [FILE]...')
218 218
219 219
220 220 @command(
221 221 b'fix',
222 222 [allopt, baseopt, revopt, wdiropt, wholeopt],
223 223 usage,
224 224 helpcategory=command.CATEGORY_FILE_CONTENTS,
225 225 )
226 226 def fix(ui, repo, *pats, **opts):
227 227 """rewrite file content in changesets or working directory
228 228
229 229 Runs any configured tools to fix the content of files. Only affects files
230 230 with changes, unless file arguments are provided. Only affects changed lines
231 231 of files, unless the --whole flag is used. Some tools may always affect the
232 232 whole file regardless of --whole.
233 233
234 234 If revisions are specified with --rev, those revisions will be checked, and
235 235 they may be replaced with new revisions that have fixed file content. It is
236 236 desirable to specify all descendants of each specified revision, so that the
237 237 fixes propagate to the descendants. If all descendants are fixed at the same
238 238 time, no merging, rebasing, or evolution will be required.
239 239
240 240 If --working-dir is used, files with uncommitted changes in the working copy
241 241 will be fixed. If the checked-out revision is also fixed, the working
242 242 directory will update to the replacement revision.
243 243
244 244 When determining what lines of each file to fix at each revision, the whole
245 245 set of revisions being fixed is considered, so that fixes to earlier
246 246 revisions are not forgotten in later ones. The --base flag can be used to
247 247 override this default behavior, though it is not usually desirable to do so.
248 248 """
249 249 opts = pycompat.byteskwargs(opts)
250 250 if opts[b'all']:
251 251 if opts[b'rev']:
252 252 raise error.Abort(_(b'cannot specify both "--rev" and "--all"'))
253 253 opts[b'rev'] = [b'not public() and not obsolete()']
254 254 opts[b'working_dir'] = True
255 255 with repo.wlock(), repo.lock(), repo.transaction(b'fix'):
256 256 revstofix = getrevstofix(ui, repo, opts)
257 257 basectxs = getbasectxs(repo, opts, revstofix)
258 258 workqueue, numitems = getworkqueue(
259 259 ui, repo, pats, opts, revstofix, basectxs
260 260 )
261 261 fixers = getfixers(ui)
262 262
263 263 # There are no data dependencies between the workers fixing each file
264 264 # revision, so we can use all available parallelism.
265 265 def getfixes(items):
266 266 for rev, path in items:
267 267 ctx = repo[rev]
268 268 olddata = ctx[path].data()
269 269 metadata, newdata = fixfile(
270 270 ui, repo, opts, fixers, ctx, path, basectxs[rev]
271 271 )
272 272 # Don't waste memory/time passing unchanged content back, but
273 273 # produce one result per item either way.
274 274 yield (
275 275 rev,
276 276 path,
277 277 metadata,
278 278 newdata if newdata != olddata else None,
279 279 )
280 280
281 281 results = worker.worker(
282 282 ui, 1.0, getfixes, tuple(), workqueue, threadsafe=False
283 283 )
284 284
285 285 # We have to hold on to the data for each successor revision in memory
286 286 # until all its parents are committed. We ensure this by committing and
287 287 # freeing memory for the revisions in some topological order. This
288 288 # leaves a little bit of memory efficiency on the table, but also makes
289 289 # the tests deterministic. It might also be considered a feature since
290 290 # it makes the results more easily reproducible.
291 291 filedata = collections.defaultdict(dict)
292 292 aggregatemetadata = collections.defaultdict(list)
293 293 replacements = {}
294 294 wdirwritten = False
295 295 commitorder = sorted(revstofix, reverse=True)
296 296 with ui.makeprogress(
297 297 topic=_(b'fixing'), unit=_(b'files'), total=sum(numitems.values())
298 298 ) as progress:
299 299 for rev, path, filerevmetadata, newdata in results:
300 300 progress.increment(item=path)
301 301 for fixername, fixermetadata in filerevmetadata.items():
302 302 aggregatemetadata[fixername].append(fixermetadata)
303 303 if newdata is not None:
304 304 filedata[rev][path] = newdata
305 305 hookargs = {
306 306 b'rev': rev,
307 307 b'path': path,
308 308 b'metadata': filerevmetadata,
309 309 }
310 310 repo.hook(
311 311 b'postfixfile',
312 312 throw=False,
313 313 **pycompat.strkwargs(hookargs)
314 314 )
315 315 numitems[rev] -= 1
316 316 # Apply the fixes for this and any other revisions that are
317 317 # ready and sitting at the front of the queue. Using a loop here
318 318 # prevents the queue from being blocked by the first revision to
319 319 # be ready out of order.
320 320 while commitorder and not numitems[commitorder[-1]]:
321 321 rev = commitorder.pop()
322 322 ctx = repo[rev]
323 323 if rev == wdirrev:
324 324 writeworkingdir(repo, ctx, filedata[rev], replacements)
325 325 wdirwritten = bool(filedata[rev])
326 326 else:
327 327 replacerev(ui, repo, ctx, filedata[rev], replacements)
328 328 del filedata[rev]
329 329
330 330 cleanup(repo, replacements, wdirwritten)
331 331 hookargs = {
332 332 b'replacements': replacements,
333 333 b'wdirwritten': wdirwritten,
334 334 b'metadata': aggregatemetadata,
335 335 }
336 336 repo.hook(b'postfix', throw=True, **pycompat.strkwargs(hookargs))
337 337
338 338
339 339 def cleanup(repo, replacements, wdirwritten):
340 340 """Calls scmutil.cleanupnodes() with the given replacements.
341 341
342 342 "replacements" is a dict from nodeid to nodeid, with one key and one value
343 343 for every revision that was affected by fixing. This is slightly different
344 344 from cleanupnodes().
345 345
346 346 "wdirwritten" is a bool which tells whether the working copy was affected by
347 347 fixing, since it has no entry in "replacements".
348 348
349 349 Useful as a hook point for extending "hg fix" with output summarizing the
350 350 effects of the command, though we choose not to output anything here.
351 351 """
352 352 replacements = {
353 353 prec: [succ] for prec, succ in pycompat.iteritems(replacements)
354 354 }
355 355 scmutil.cleanupnodes(repo, replacements, b'fix', fixphase=True)
356 356
357 357
358 358 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
359 359 """"Constructs the list of files to be fixed at specific revisions
360 360
361 361 It is up to the caller how to consume the work items, and the only
362 362 dependence between them is that replacement revisions must be committed in
363 363 topological order. Each work item represents a file in the working copy or
364 364 in some revision that should be fixed and written back to the working copy
365 365 or into a replacement revision.
366 366
367 367 Work items for the same revision are grouped together, so that a worker
368 368 pool starting with the first N items in parallel is likely to finish the
369 369 first revision's work before other revisions. This can allow us to write
370 370 the result to disk and reduce memory footprint. At time of writing, the
371 371 partition strategy in worker.py seems favorable to this. We also sort the
372 372 items by ascending revision number to match the order in which we commit
373 373 the fixes later.
374 374 """
375 375 workqueue = []
376 376 numitems = collections.defaultdict(int)
377 377 maxfilesize = ui.configbytes(b'fix', b'maxfilesize')
378 378 for rev in sorted(revstofix):
379 379 fixctx = repo[rev]
380 380 match = scmutil.match(fixctx, pats, opts)
381 381 for path in sorted(
382 382 pathstofix(ui, repo, pats, opts, match, basectxs[rev], fixctx)
383 383 ):
384 384 fctx = fixctx[path]
385 385 if fctx.islink():
386 386 continue
387 387 if fctx.size() > maxfilesize:
388 388 ui.warn(
389 389 _(b'ignoring file larger than %s: %s\n')
390 390 % (util.bytecount(maxfilesize), path)
391 391 )
392 392 continue
393 393 workqueue.append((rev, path))
394 394 numitems[rev] += 1
395 395 return workqueue, numitems
396 396
397 397
398 398 def getrevstofix(ui, repo, opts):
399 399 """Returns the set of revision numbers that should be fixed"""
400 400 revs = set(scmutil.revrange(repo, opts[b'rev']))
401 401 for rev in revs:
402 402 checkfixablectx(ui, repo, repo[rev])
403 403 if revs:
404 404 cmdutil.checkunfinished(repo)
405 405 checknodescendants(repo, revs)
406 406 if opts.get(b'working_dir'):
407 407 revs.add(wdirrev)
408 408 if list(merge.mergestate.read(repo).unresolved()):
409 409 raise error.Abort(b'unresolved conflicts', hint=b"use 'hg resolve'")
410 410 if not revs:
411 411 raise error.Abort(
412 412 b'no changesets specified', hint=b'use --rev or --working-dir'
413 413 )
414 414 return revs
415 415
416 416
417 417 def checknodescendants(repo, revs):
418 418 if not obsolete.isenabled(repo, obsolete.allowunstableopt) and repo.revs(
419 419 b'(%ld::) - (%ld)', revs, revs
420 420 ):
421 421 raise error.Abort(
422 422 _(b'can only fix a changeset together with all its descendants')
423 423 )
424 424
425 425
426 426 def checkfixablectx(ui, repo, ctx):
427 427 """Aborts if the revision shouldn't be replaced with a fixed one."""
428 428 if not ctx.mutable():
429 429 raise error.Abort(
430 430 b'can\'t fix immutable changeset %s'
431 431 % (scmutil.formatchangeid(ctx),)
432 432 )
433 433 if ctx.obsolete():
434 434 # It would be better to actually check if the revision has a successor.
435 435 allowdivergence = ui.configbool(
436 436 b'experimental', b'evolution.allowdivergence'
437 437 )
438 438 if not allowdivergence:
439 439 raise error.Abort(
440 440 b'fixing obsolete revision could cause divergence'
441 441 )
442 442
443 443
444 444 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
445 445 """Returns the set of files that should be fixed in a context
446 446
447 447 The result depends on the base contexts; we include any file that has
448 448 changed relative to any of the base contexts. Base contexts should be
449 449 ancestors of the context being fixed.
450 450 """
451 451 files = set()
452 452 for basectx in basectxs:
453 453 stat = basectx.status(
454 454 fixctx, match=match, listclean=bool(pats), listunknown=bool(pats)
455 455 )
456 456 files.update(
457 457 set(
458 458 itertools.chain(
459 459 stat.added, stat.modified, stat.clean, stat.unknown
460 460 )
461 461 )
462 462 )
463 463 return files
464 464
465 465
466 466 def lineranges(opts, path, basectxs, fixctx, content2):
467 467 """Returns the set of line ranges that should be fixed in a file
468 468
469 469 Of the form [(10, 20), (30, 40)].
470 470
471 471 This depends on the given base contexts; we must consider lines that have
472 472 changed versus any of the base contexts, and whether the file has been
473 473 renamed versus any of them.
474 474
475 475 Another way to understand this is that we exclude line ranges that are
476 476 common to the file in all base contexts.
477 477 """
478 478 if opts.get(b'whole'):
479 479 # Return a range containing all lines. Rely on the diff implementation's
480 480 # idea of how many lines are in the file, instead of reimplementing it.
481 481 return difflineranges(b'', content2)
482 482
483 483 rangeslist = []
484 484 for basectx in basectxs:
485 485 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
486 486 if basepath in basectx:
487 487 content1 = basectx[basepath].data()
488 488 else:
489 489 content1 = b''
490 490 rangeslist.extend(difflineranges(content1, content2))
491 491 return unionranges(rangeslist)
492 492
493 493
494 494 def unionranges(rangeslist):
495 495 """Return the union of some closed intervals
496 496
497 497 >>> unionranges([])
498 498 []
499 499 >>> unionranges([(1, 100)])
500 500 [(1, 100)]
501 501 >>> unionranges([(1, 100), (1, 100)])
502 502 [(1, 100)]
503 503 >>> unionranges([(1, 100), (2, 100)])
504 504 [(1, 100)]
505 505 >>> unionranges([(1, 99), (1, 100)])
506 506 [(1, 100)]
507 507 >>> unionranges([(1, 100), (40, 60)])
508 508 [(1, 100)]
509 509 >>> unionranges([(1, 49), (50, 100)])
510 510 [(1, 100)]
511 511 >>> unionranges([(1, 48), (50, 100)])
512 512 [(1, 48), (50, 100)]
513 513 >>> unionranges([(1, 2), (3, 4), (5, 6)])
514 514 [(1, 6)]
515 515 """
516 516 rangeslist = sorted(set(rangeslist))
517 517 unioned = []
518 518 if rangeslist:
519 519 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
520 520 for a, b in rangeslist:
521 521 c, d = unioned[-1]
522 522 if a > d + 1:
523 523 unioned.append((a, b))
524 524 else:
525 525 unioned[-1] = (c, max(b, d))
526 526 return unioned
527 527
528 528
529 529 def difflineranges(content1, content2):
530 530 """Return list of line number ranges in content2 that differ from content1.
531 531
532 532 Line numbers are 1-based. The numbers are the first and last line contained
533 533 in the range. Single-line ranges have the same line number for the first and
534 534 last line. Excludes any empty ranges that result from lines that are only
535 535 present in content1. Relies on mdiff's idea of where the line endings are in
536 536 the string.
537 537
538 538 >>> from mercurial import pycompat
539 539 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
540 540 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
541 541 >>> difflineranges2(b'', b'')
542 542 []
543 543 >>> difflineranges2(b'a', b'')
544 544 []
545 545 >>> difflineranges2(b'', b'A')
546 546 [(1, 1)]
547 547 >>> difflineranges2(b'a', b'a')
548 548 []
549 549 >>> difflineranges2(b'a', b'A')
550 550 [(1, 1)]
551 551 >>> difflineranges2(b'ab', b'')
552 552 []
553 553 >>> difflineranges2(b'', b'AB')
554 554 [(1, 2)]
555 555 >>> difflineranges2(b'abc', b'ac')
556 556 []
557 557 >>> difflineranges2(b'ab', b'aCb')
558 558 [(2, 2)]
559 559 >>> difflineranges2(b'abc', b'aBc')
560 560 [(2, 2)]
561 561 >>> difflineranges2(b'ab', b'AB')
562 562 [(1, 2)]
563 563 >>> difflineranges2(b'abcde', b'aBcDe')
564 564 [(2, 2), (4, 4)]
565 565 >>> difflineranges2(b'abcde', b'aBCDe')
566 566 [(2, 4)]
567 567 """
568 568 ranges = []
569 569 for lines, kind in mdiff.allblocks(content1, content2):
570 570 firstline, lastline = lines[2:4]
571 571 if kind == b'!' and firstline != lastline:
572 572 ranges.append((firstline + 1, lastline))
573 573 return ranges
574 574
575 575
576 576 def getbasectxs(repo, opts, revstofix):
577 577 """Returns a map of the base contexts for each revision
578 578
579 579 The base contexts determine which lines are considered modified when we
580 580 attempt to fix just the modified lines in a file. It also determines which
581 581 files we attempt to fix, so it is important to compute this even when
582 582 --whole is used.
583 583 """
584 584 # The --base flag overrides the usual logic, and we give every revision
585 585 # exactly the set of baserevs that the user specified.
586 586 if opts.get(b'base'):
587 587 baserevs = set(scmutil.revrange(repo, opts.get(b'base')))
588 588 if not baserevs:
589 589 baserevs = {nullrev}
590 590 basectxs = {repo[rev] for rev in baserevs}
591 591 return {rev: basectxs for rev in revstofix}
592 592
593 593 # Proceed in topological order so that we can easily determine each
594 594 # revision's baserevs by looking at its parents and their baserevs.
595 595 basectxs = collections.defaultdict(set)
596 596 for rev in sorted(revstofix):
597 597 ctx = repo[rev]
598 598 for pctx in ctx.parents():
599 599 if pctx.rev() in basectxs:
600 600 basectxs[rev].update(basectxs[pctx.rev()])
601 601 else:
602 602 basectxs[rev].add(pctx)
603 603 return basectxs
604 604
605 605
606 606 def fixfile(ui, repo, opts, fixers, fixctx, path, basectxs):
607 607 """Run any configured fixers that should affect the file in this context
608 608
609 609 Returns the file content that results from applying the fixers in some order
610 610 starting with the file's content in the fixctx. Fixers that support line
611 611 ranges will affect lines that have changed relative to any of the basectxs
612 612 (i.e. they will only avoid lines that are common to all basectxs).
613 613
614 614 A fixer tool's stdout will become the file's new content if and only if it
615 615 exits with code zero. The fixer tool's working directory is the repository's
616 616 root.
617 617 """
618 618 metadata = {}
619 619 newdata = fixctx[path].data()
620 620 for fixername, fixer in pycompat.iteritems(fixers):
621 621 if fixer.affects(opts, fixctx, path):
622 622 ranges = lineranges(opts, path, basectxs, fixctx, newdata)
623 623 command = fixer.command(ui, path, ranges)
624 624 if command is None:
625 625 continue
626 626 ui.debug(b'subprocess: %s\n' % (command,))
627 627 proc = subprocess.Popen(
628 628 procutil.tonativestr(command),
629 629 shell=True,
630 630 cwd=procutil.tonativestr(repo.root),
631 631 stdin=subprocess.PIPE,
632 632 stdout=subprocess.PIPE,
633 633 stderr=subprocess.PIPE,
634 634 )
635 635 stdout, stderr = proc.communicate(newdata)
636 636 if stderr:
637 637 showstderr(ui, fixctx.rev(), fixername, stderr)
638 638 newerdata = stdout
639 639 if fixer.shouldoutputmetadata():
640 640 try:
641 641 metadatajson, newerdata = stdout.split(b'\0', 1)
642 642 metadata[fixername] = json.loads(metadatajson)
643 643 except ValueError:
644 644 ui.warn(
645 645 _(b'ignored invalid output from fixer tool: %s\n')
646 646 % (fixername,)
647 647 )
648 648 continue
649 649 else:
650 650 metadata[fixername] = None
651 651 if proc.returncode == 0:
652 652 newdata = newerdata
653 653 else:
654 654 if not stderr:
655 655 message = _(b'exited with status %d\n') % (proc.returncode,)
656 656 showstderr(ui, fixctx.rev(), fixername, message)
657 657 checktoolfailureaction(
658 658 ui,
659 659 _(b'no fixes will be applied'),
660 660 hint=_(
661 661 b'use --config fix.failure=continue to apply any '
662 662 b'successful fixes anyway'
663 663 ),
664 664 )
665 665 return metadata, newdata
666 666
667 667
668 668 def showstderr(ui, rev, fixername, stderr):
669 669 """Writes the lines of the stderr string as warnings on the ui
670 670
671 671 Uses the revision number and fixername to give more context to each line of
672 672 the error message. Doesn't include file names, since those take up a lot of
673 673 space and would tend to be included in the error message if they were
674 674 relevant.
675 675 """
676 676 for line in re.split(b'[\r\n]+', stderr):
677 677 if line:
678 678 ui.warn(b'[')
679 679 if rev is None:
680 680 ui.warn(_(b'wdir'), label=b'evolve.rev')
681 681 else:
682 682 ui.warn((str(rev)), label=b'evolve.rev')
683 683 ui.warn(b'] %s: %s\n' % (fixername, line))
684 684
685 685
686 686 def writeworkingdir(repo, ctx, filedata, replacements):
687 687 """Write new content to the working copy and check out the new p1 if any
688 688
689 689 We check out a new revision if and only if we fixed something in both the
690 690 working directory and its parent revision. This avoids the need for a full
691 691 update/merge, and means that the working directory simply isn't affected
692 692 unless the --working-dir flag is given.
693 693
694 694 Directly updates the dirstate for the affected files.
695 695 """
696 696 for path, data in pycompat.iteritems(filedata):
697 697 fctx = ctx[path]
698 698 fctx.write(data, fctx.flags())
699 699 if repo.dirstate[path] == b'n':
700 700 repo.dirstate.normallookup(path)
701 701
702 702 oldparentnodes = repo.dirstate.parents()
703 703 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
704 704 if newparentnodes != oldparentnodes:
705 705 repo.setparents(*newparentnodes)
706 706
707 707
708 708 def replacerev(ui, repo, ctx, filedata, replacements):
709 709 """Commit a new revision like the given one, but with file content changes
710 710
711 711 "ctx" is the original revision to be replaced by a modified one.
712 712
713 713 "filedata" is a dict that maps paths to their new file content. All other
714 714 paths will be recreated from the original revision without changes.
715 715 "filedata" may contain paths that didn't exist in the original revision;
716 716 they will be added.
717 717
718 718 "replacements" is a dict that maps a single node to a single node, and it is
719 719 updated to indicate the original revision is replaced by the newly created
720 720 one. No entry is added if the replacement's node already exists.
721 721
722 722 The new revision has the same parents as the old one, unless those parents
723 723 have already been replaced, in which case those replacements are the parents
724 724 of this new revision. Thus, if revisions are replaced in topological order,
725 725 there is no need to rebase them into the original topology later.
726 726 """
727 727
728 728 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
729 729 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
730 730 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
731 731 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
732 732
733 733 # We don't want to create a revision that has no changes from the original,
734 734 # but we should if the original revision's parent has been replaced.
735 735 # Otherwise, we would produce an orphan that needs no actual human
736 736 # intervention to evolve. We can't rely on commit() to avoid creating the
737 737 # un-needed revision because the extra field added below produces a new hash
738 738 # regardless of file content changes.
739 739 if (
740 740 not filedata
741 741 and p1ctx.node() not in replacements
742 742 and p2ctx.node() not in replacements
743 743 ):
744 744 return
745 745
746 746 def filectxfn(repo, memctx, path):
747 747 if path not in ctx:
748 748 return None
749 749 fctx = ctx[path]
750 750 copysource = fctx.copysource()
751 751 return context.memfilectx(
752 752 repo,
753 753 memctx,
754 754 path=fctx.path(),
755 755 data=filedata.get(path, fctx.data()),
756 756 islink=fctx.islink(),
757 757 isexec=fctx.isexec(),
758 758 copysource=copysource,
759 759 )
760 760
761 761 extra = ctx.extra().copy()
762 762 extra[b'fix_source'] = ctx.hex()
763 763
764 764 memctx = context.memctx(
765 765 repo,
766 766 parents=(newp1node, newp2node),
767 767 text=ctx.description(),
768 768 files=set(ctx.files()) | set(filedata.keys()),
769 769 filectxfn=filectxfn,
770 770 user=ctx.user(),
771 771 date=ctx.date(),
772 772 extra=extra,
773 773 branch=ctx.branch(),
774 774 editor=None,
775 775 )
776 776 sucnode = memctx.commit()
777 777 prenode = ctx.node()
778 778 if prenode == sucnode:
779 779 ui.debug(b'node %s already existed\n' % (ctx.hex()))
780 780 else:
781 781 replacements[ctx.node()] = sucnode
782 782
783 783
784 784 def getfixers(ui):
785 785 """Returns a map of configured fixer tools indexed by their names
786 786
787 787 Each value is a Fixer object with methods that implement the behavior of the
788 788 fixer's config suboptions. Does not validate the config values.
789 789 """
790 790 fixers = {}
791 791 for name in fixernames(ui):
792 792 enabled = ui.configbool(b'fix', name + b':enabled')
793 793 command = ui.config(b'fix', name + b':command')
794 794 pattern = ui.config(b'fix', name + b':pattern')
795 795 linerange = ui.config(b'fix', name + b':linerange')
796 796 priority = ui.configint(b'fix', name + b':priority')
797 797 metadata = ui.configbool(b'fix', name + b':metadata')
798 798 skipclean = ui.configbool(b'fix', name + b':skipclean')
799 799 # Don't use a fixer if it has no pattern configured. It would be
800 800 # dangerous to let it affect all files. It would be pointless to let it
801 801 # affect no files. There is no reasonable subset of files to use as the
802 802 # default.
803 if pattern is None:
803 if command is None:
804 ui.warn(
805 _(b'fixer tool has no command configuration: %s\n') % (name,)
806 )
807 elif pattern is None:
804 808 ui.warn(
805 809 _(b'fixer tool has no pattern configuration: %s\n') % (name,)
806 810 )
807 811 elif not enabled:
808 812 ui.debug(b'ignoring disabled fixer tool: %s\n' % (name,))
809 813 else:
810 814 fixers[name] = Fixer(
811 815 command, pattern, linerange, priority, metadata, skipclean
812 816 )
813 817 return collections.OrderedDict(
814 818 sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
815 819 )
816 820
817 821
818 822 def fixernames(ui):
819 823 """Returns the names of [fix] config options that have suboptions"""
820 824 names = set()
821 825 for k, v in ui.configitems(b'fix'):
822 826 if b':' in k:
823 827 names.add(k.split(b':', 1)[0])
824 828 return names
825 829
826 830
827 831 class Fixer(object):
828 832 """Wraps the raw config values for a fixer with methods"""
829 833
830 834 def __init__(
831 835 self, command, pattern, linerange, priority, metadata, skipclean
832 836 ):
833 837 self._command = command
834 838 self._pattern = pattern
835 839 self._linerange = linerange
836 840 self._priority = priority
837 841 self._metadata = metadata
838 842 self._skipclean = skipclean
839 843
840 844 def affects(self, opts, fixctx, path):
841 845 """Should this fixer run on the file at the given path and context?"""
842 846 return self._pattern is not None and scmutil.match(
843 847 fixctx, [self._pattern], opts
844 848 )(path)
845 849
846 850 def shouldoutputmetadata(self):
847 851 """Should the stdout of this fixer start with JSON and a null byte?"""
848 852 return self._metadata
849 853
850 854 def command(self, ui, path, ranges):
851 855 """A shell command to use to invoke this fixer on the given file/lines
852 856
853 857 May return None if there is no appropriate command to run for the given
854 858 parameters.
855 859 """
856 860 expand = cmdutil.rendercommandtemplate
857 861 parts = [
858 862 expand(
859 863 ui,
860 864 self._command,
861 865 {b'rootpath': path, b'basename': os.path.basename(path)},
862 866 )
863 867 ]
864 868 if self._linerange:
865 869 if self._skipclean and not ranges:
866 870 # No line ranges to fix, so don't run the fixer.
867 871 return None
868 872 for first, last in ranges:
869 873 parts.append(
870 874 expand(
871 875 ui, self._linerange, {b'first': first, b'last': last}
872 876 )
873 877 )
874 878 return b' '.join(parts)
@@ -1,1421 +1,1423 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 $ hg fix --debug --working-dir --config "fix.nocommand:pattern=foo.bar"
1342 fixer tool has no command configuration: nocommand
1341 1343
1342 1344 $ cd ..
1343 1345
1344 1346 Tools can be disabled. Disabled tools do nothing but print a debug message.
1345 1347
1346 1348 $ hg init disabled
1347 1349 $ cd disabled
1348 1350
1349 1351 $ printf "foo\n" > foo
1350 1352 $ hg add -q
1351 1353 $ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \
1352 1354 > --config "fix.disabled:pattern=foo" \
1353 1355 > --config "fix.disabled:enabled=false"
1354 1356 ignoring disabled fixer tool: disabled
1355 1357 $ cat foo
1356 1358 foo
1357 1359
1358 1360 $ cd ..
1359 1361
1360 1362 Test that we can configure a fixer to affect all files regardless of the cwd.
1361 1363 The way we invoke matching must not prohibit this.
1362 1364
1363 1365 $ hg init affectallfiles
1364 1366 $ cd affectallfiles
1365 1367
1366 1368 $ mkdir foo bar
1367 1369 $ printf "foo" > foo/file
1368 1370 $ printf "bar" > bar/file
1369 1371 $ printf "baz" > baz_file
1370 1372 $ hg add -q
1371 1373
1372 1374 $ cd bar
1373 1375 $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \
1374 1376 > --config "fix.cooltool:pattern=rootglob:**"
1375 1377 $ cd ..
1376 1378
1377 1379 $ cat foo/file
1378 1380 fixed
1379 1381 $ cat bar/file
1380 1382 fixed
1381 1383 $ cat baz_file
1382 1384 fixed
1383 1385
1384 1386 $ cd ..
1385 1387
1386 1388 Tools should be able to run on unchanged files, even if they set :linerange.
1387 1389 This includes a corner case where deleted chunks of a file are not considered
1388 1390 changes.
1389 1391
1390 1392 $ hg init skipclean
1391 1393 $ cd skipclean
1392 1394
1393 1395 $ printf "a\nb\nc\n" > foo
1394 1396 $ printf "a\nb\nc\n" > bar
1395 1397 $ printf "a\nb\nc\n" > baz
1396 1398 $ hg commit -Aqm "base"
1397 1399
1398 1400 $ printf "a\nc\n" > foo
1399 1401 $ printf "a\nx\nc\n" > baz
1400 1402
1401 1403 $ cat >> print.py <<EOF
1402 1404 > import sys
1403 1405 > for a in sys.argv[1:]:
1404 1406 > print(a)
1405 1407 > EOF
1406 1408
1407 1409 $ hg fix --working-dir foo bar baz \
1408 1410 > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \
1409 1411 > --config 'fix.changedlines:linerange="{first} through {last}"' \
1410 1412 > --config 'fix.changedlines:pattern=rootglob:**' \
1411 1413 > --config 'fix.changedlines:skipclean=false'
1412 1414
1413 1415 $ cat foo
1414 1416 Line ranges:
1415 1417 $ cat bar
1416 1418 Line ranges:
1417 1419 $ cat baz
1418 1420 Line ranges:
1419 1421 2 through 2
1420 1422
1421 1423 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now