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