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