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