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