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