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