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