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