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