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