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