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