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