##// END OF EJS Templates
fix: don't include obsolete descendants with -s...
Martin von Zweigbergk -
r46400:7f07e634 default draft
parent child Browse files
Show More
@@ -1,937 +1,937 b''
1 1 # fix - rewrite file content in changesets and working copy
2 2 #
3 3 # Copyright 2018 Google LLC.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """rewrite file content in changesets or working copy (EXPERIMENTAL)
8 8
9 9 Provides a command that runs configured tools on the contents of modified files,
10 10 writing back any fixes to the working copy or replacing changesets.
11 11
12 12 Here is an example configuration that causes :hg:`fix` to apply automatic
13 13 formatting fixes to modified lines in C++ code::
14 14
15 15 [fix]
16 16 clang-format:command=clang-format --assume-filename={rootpath}
17 17 clang-format:linerange=--lines={first}:{last}
18 18 clang-format:pattern=set:**.cpp or **.hpp
19 19
20 20 The :command suboption forms the first part of the shell command that will be
21 21 used to fix a file. The content of the file is passed on standard input, and the
22 22 fixed file content is expected on standard output. Any output on standard error
23 23 will be displayed as a warning. If the exit status is not zero, the file will
24 24 not be affected. A placeholder warning is displayed if there is a non-zero exit
25 25 status but no standard error output. Some values may be substituted into the
26 26 command::
27 27
28 28 {rootpath} The path of the file being fixed, relative to the repo root
29 29 {basename} The name of the file being fixed, without the directory path
30 30
31 31 If the :linerange suboption is set, the tool will only be run if there are
32 32 changed lines in a file. The value of this suboption is appended to the shell
33 33 command once for every range of changed lines in the file. Some values may be
34 34 substituted into the command::
35 35
36 36 {first} The 1-based line number of the first line in the modified range
37 37 {last} The 1-based line number of the last line in the modified range
38 38
39 39 Deleted sections of a file will be ignored by :linerange, because there is no
40 40 corresponding line range in the version being fixed.
41 41
42 42 By default, tools that set :linerange will only be executed if there is at least
43 43 one changed line range. This is meant to prevent accidents like running a code
44 44 formatter in such a way that it unexpectedly reformats the whole file. If such a
45 45 tool needs to operate on unchanged files, it should set the :skipclean suboption
46 46 to false.
47 47
48 48 The :pattern suboption determines which files will be passed through each
49 49 configured tool. See :hg:`help patterns` for possible values. However, all
50 50 patterns are relative to the repo root, even if that text says they are relative
51 51 to the current working directory. If there are file arguments to :hg:`fix`, the
52 52 intersection of these patterns is used.
53 53
54 54 There is also a configurable limit for the maximum size of file that will be
55 55 processed by :hg:`fix`::
56 56
57 57 [fix]
58 58 maxfilesize = 2MB
59 59
60 60 Normally, execution of configured tools will continue after a failure (indicated
61 61 by a non-zero exit status). It can also be configured to abort after the first
62 62 such failure, so that no files will be affected if any tool fails. This abort
63 63 will also cause :hg:`fix` to exit with a non-zero status::
64 64
65 65 [fix]
66 66 failure = abort
67 67
68 68 When multiple tools are configured to affect a file, they execute in an order
69 69 defined by the :priority suboption. The priority suboption has a default value
70 70 of zero for each tool. Tools are executed in order of descending priority. The
71 71 execution order of tools with equal priority is unspecified. For example, you
72 72 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
73 73 in a text file by ensuring that 'sort' runs before 'head'::
74 74
75 75 [fix]
76 76 sort:command = sort -n
77 77 head:command = head -n 10
78 78 sort:pattern = numbers.txt
79 79 head:pattern = numbers.txt
80 80 sort:priority = 2
81 81 head:priority = 1
82 82
83 83 To account for changes made by each tool, the line numbers used for incremental
84 84 formatting are recomputed before executing the next tool. So, each tool may see
85 85 different values for the arguments added by the :linerange suboption.
86 86
87 87 Each fixer tool is allowed to return some metadata in addition to the fixed file
88 88 content. The metadata must be placed before the file content on stdout,
89 89 separated from the file content by a zero byte. The metadata is parsed as a JSON
90 90 value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer tool
91 91 is expected to produce this metadata encoding if and only if the :metadata
92 92 suboption is true::
93 93
94 94 [fix]
95 95 tool:command = tool --prepend-json-metadata
96 96 tool:metadata = true
97 97
98 98 The metadata values are passed to hooks, which can be used to print summaries or
99 99 perform other post-fixing work. The supported hooks are::
100 100
101 101 "postfixfile"
102 102 Run once for each file in each revision where any fixer tools made changes
103 103 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
104 104 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
105 105 tools that affected the file. Fixer tools that didn't affect the file have a
106 106 value of None. Only fixer tools that executed are present in the metadata.
107 107
108 108 "postfix"
109 109 Run once after all files and revisions have been handled. Provides
110 110 "$HG_REPLACEMENTS" with information about what revisions were created and
111 111 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
112 112 files in the working copy were updated. Provides a list "$HG_METADATA"
113 113 mapping fixer tool names to lists of metadata values returned from
114 114 executions that modified a file. This aggregates the same metadata
115 115 previously passed to the "postfixfile" hook.
116 116
117 117 Fixer tools are run in the repository's root directory. This allows them to read
118 118 configuration files from the working copy, or even write to the working copy.
119 119 The working copy is not updated to match the revision being fixed. In fact,
120 120 several revisions may be fixed in parallel. Writes to the working copy are not
121 121 amended into the revision being fixed; fixer tools should always write fixed
122 122 file content back to stdout as documented above.
123 123 """
124 124
125 125 from __future__ import absolute_import
126 126
127 127 import collections
128 128 import itertools
129 129 import os
130 130 import re
131 131 import subprocess
132 132
133 133 from mercurial.i18n import _
134 134 from mercurial.node import nullrev
135 135 from mercurial.node import wdirrev
136 136
137 137 from mercurial.utils import procutil
138 138
139 139 from mercurial import (
140 140 cmdutil,
141 141 context,
142 142 copies,
143 143 error,
144 144 match as matchmod,
145 145 mdiff,
146 146 merge,
147 147 mergestate as mergestatemod,
148 148 pycompat,
149 149 registrar,
150 150 rewriteutil,
151 151 scmutil,
152 152 util,
153 153 worker,
154 154 )
155 155
156 156 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
157 157 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
158 158 # be specifying the version(s) of Mercurial they are tested with, or
159 159 # leave the attribute unspecified.
160 160 testedwith = b'ships-with-hg-core'
161 161
162 162 cmdtable = {}
163 163 command = registrar.command(cmdtable)
164 164
165 165 configtable = {}
166 166 configitem = registrar.configitem(configtable)
167 167
168 168 # Register the suboptions allowed for each configured fixer, and default values.
169 169 FIXER_ATTRS = {
170 170 b'command': None,
171 171 b'linerange': None,
172 172 b'pattern': None,
173 173 b'priority': 0,
174 174 b'metadata': False,
175 175 b'skipclean': True,
176 176 b'enabled': True,
177 177 }
178 178
179 179 for key, default in FIXER_ATTRS.items():
180 180 configitem(b'fix', b'.*:%s$' % key, default=default, generic=True)
181 181
182 182 # A good default size allows most source code files to be fixed, but avoids
183 183 # letting fixer tools choke on huge inputs, which could be surprising to the
184 184 # user.
185 185 configitem(b'fix', b'maxfilesize', default=b'2MB')
186 186
187 187 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
188 188 # This helps users do shell scripts that stop when a fixer tool signals a
189 189 # problem.
190 190 configitem(b'fix', b'failure', default=b'continue')
191 191
192 192
193 193 def checktoolfailureaction(ui, message, hint=None):
194 194 """Abort with 'message' if fix.failure=abort"""
195 195 action = ui.config(b'fix', b'failure')
196 196 if action not in (b'continue', b'abort'):
197 197 raise error.Abort(
198 198 _(b'unknown fix.failure action: %s') % (action,),
199 199 hint=_(b'use "continue" or "abort"'),
200 200 )
201 201 if action == b'abort':
202 202 raise error.Abort(message, hint=hint)
203 203
204 204
205 205 allopt = (b'', b'all', False, _(b'fix all non-public non-obsolete revisions'))
206 206 baseopt = (
207 207 b'',
208 208 b'base',
209 209 [],
210 210 _(
211 211 b'revisions to diff against (overrides automatic '
212 212 b'selection, and applies to every revision being '
213 213 b'fixed)'
214 214 ),
215 215 _(b'REV'),
216 216 )
217 217 revopt = (b'r', b'rev', [], _(b'revisions to fix (ADVANCED)'), _(b'REV'))
218 218 sourceopt = (
219 219 b's',
220 220 b'source',
221 221 [],
222 222 _(b'fix the specified revisions and their descendants'),
223 223 _(b'REV'),
224 224 )
225 225 wdiropt = (b'w', b'working-dir', False, _(b'fix the working directory'))
226 226 wholeopt = (b'', b'whole', False, _(b'always fix every line of a file'))
227 227 usage = _(b'[OPTION]... [FILE]...')
228 228
229 229
230 230 @command(
231 231 b'fix',
232 232 [allopt, baseopt, revopt, sourceopt, wdiropt, wholeopt],
233 233 usage,
234 234 helpcategory=command.CATEGORY_FILE_CONTENTS,
235 235 )
236 236 def fix(ui, repo, *pats, **opts):
237 237 """rewrite file content in changesets or working directory
238 238
239 239 Runs any configured tools to fix the content of files. Only affects files
240 240 with changes, unless file arguments are provided. Only affects changed lines
241 241 of files, unless the --whole flag is used. Some tools may always affect the
242 242 whole file regardless of --whole.
243 243
244 244 If --working-dir is used, files with uncommitted changes in the working copy
245 245 will be fixed. Note that no backup are made.
246 246
247 247 If revisions are specified with --source, those revisions and their
248 248 descendants will be checked, and they may be replaced with new revisions
249 249 that have fixed file content. By automatically including the descendants,
250 250 no merging, rebasing, or evolution will be required. If an ancestor of the
251 251 working copy is included, then the working copy itself will also be fixed,
252 252 and the working copy will be updated to the fixed parent.
253 253
254 254 When determining what lines of each file to fix at each revision, the whole
255 255 set of revisions being fixed is considered, so that fixes to earlier
256 256 revisions are not forgotten in later ones. The --base flag can be used to
257 257 override this default behavior, though it is not usually desirable to do so.
258 258 """
259 259 opts = pycompat.byteskwargs(opts)
260 260 cmdutil.check_at_most_one_arg(opts, b'all', b'source', b'rev')
261 261 cmdutil.check_incompatible_arguments(
262 262 opts, b'working_dir', [b'all', b'source']
263 263 )
264 264
265 265 with repo.wlock(), repo.lock(), repo.transaction(b'fix'):
266 266 revstofix = getrevstofix(ui, repo, opts)
267 267 basectxs = getbasectxs(repo, opts, revstofix)
268 268 workqueue, numitems = getworkqueue(
269 269 ui, repo, pats, opts, revstofix, basectxs
270 270 )
271 271 basepaths = getbasepaths(repo, opts, workqueue, basectxs)
272 272 fixers = getfixers(ui)
273 273
274 274 # Rather than letting each worker independently fetch the files
275 275 # (which also would add complications for shared/keepalive
276 276 # connections), prefetch them all first.
277 277 _prefetchfiles(repo, workqueue, basepaths)
278 278
279 279 # There are no data dependencies between the workers fixing each file
280 280 # revision, so we can use all available parallelism.
281 281 def getfixes(items):
282 282 for rev, path in items:
283 283 ctx = repo[rev]
284 284 olddata = ctx[path].data()
285 285 metadata, newdata = fixfile(
286 286 ui, repo, opts, fixers, ctx, path, basepaths, basectxs[rev]
287 287 )
288 288 # Don't waste memory/time passing unchanged content back, but
289 289 # produce one result per item either way.
290 290 yield (
291 291 rev,
292 292 path,
293 293 metadata,
294 294 newdata if newdata != olddata else None,
295 295 )
296 296
297 297 results = worker.worker(
298 298 ui, 1.0, getfixes, tuple(), workqueue, threadsafe=False
299 299 )
300 300
301 301 # We have to hold on to the data for each successor revision in memory
302 302 # until all its parents are committed. We ensure this by committing and
303 303 # freeing memory for the revisions in some topological order. This
304 304 # leaves a little bit of memory efficiency on the table, but also makes
305 305 # the tests deterministic. It might also be considered a feature since
306 306 # it makes the results more easily reproducible.
307 307 filedata = collections.defaultdict(dict)
308 308 aggregatemetadata = collections.defaultdict(list)
309 309 replacements = {}
310 310 wdirwritten = False
311 311 commitorder = sorted(revstofix, reverse=True)
312 312 with ui.makeprogress(
313 313 topic=_(b'fixing'), unit=_(b'files'), total=sum(numitems.values())
314 314 ) as progress:
315 315 for rev, path, filerevmetadata, newdata in results:
316 316 progress.increment(item=path)
317 317 for fixername, fixermetadata in filerevmetadata.items():
318 318 aggregatemetadata[fixername].append(fixermetadata)
319 319 if newdata is not None:
320 320 filedata[rev][path] = newdata
321 321 hookargs = {
322 322 b'rev': rev,
323 323 b'path': path,
324 324 b'metadata': filerevmetadata,
325 325 }
326 326 repo.hook(
327 327 b'postfixfile',
328 328 throw=False,
329 329 **pycompat.strkwargs(hookargs)
330 330 )
331 331 numitems[rev] -= 1
332 332 # Apply the fixes for this and any other revisions that are
333 333 # ready and sitting at the front of the queue. Using a loop here
334 334 # prevents the queue from being blocked by the first revision to
335 335 # be ready out of order.
336 336 while commitorder and not numitems[commitorder[-1]]:
337 337 rev = commitorder.pop()
338 338 ctx = repo[rev]
339 339 if rev == wdirrev:
340 340 writeworkingdir(repo, ctx, filedata[rev], replacements)
341 341 wdirwritten = bool(filedata[rev])
342 342 else:
343 343 replacerev(ui, repo, ctx, filedata[rev], replacements)
344 344 del filedata[rev]
345 345
346 346 cleanup(repo, replacements, wdirwritten)
347 347 hookargs = {
348 348 b'replacements': replacements,
349 349 b'wdirwritten': wdirwritten,
350 350 b'metadata': aggregatemetadata,
351 351 }
352 352 repo.hook(b'postfix', throw=True, **pycompat.strkwargs(hookargs))
353 353
354 354
355 355 def cleanup(repo, replacements, wdirwritten):
356 356 """Calls scmutil.cleanupnodes() with the given replacements.
357 357
358 358 "replacements" is a dict from nodeid to nodeid, with one key and one value
359 359 for every revision that was affected by fixing. This is slightly different
360 360 from cleanupnodes().
361 361
362 362 "wdirwritten" is a bool which tells whether the working copy was affected by
363 363 fixing, since it has no entry in "replacements".
364 364
365 365 Useful as a hook point for extending "hg fix" with output summarizing the
366 366 effects of the command, though we choose not to output anything here.
367 367 """
368 368 replacements = {
369 369 prec: [succ] for prec, succ in pycompat.iteritems(replacements)
370 370 }
371 371 scmutil.cleanupnodes(repo, replacements, b'fix', fixphase=True)
372 372
373 373
374 374 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
375 375 """"Constructs the list of files to be fixed at specific revisions
376 376
377 377 It is up to the caller how to consume the work items, and the only
378 378 dependence between them is that replacement revisions must be committed in
379 379 topological order. Each work item represents a file in the working copy or
380 380 in some revision that should be fixed and written back to the working copy
381 381 or into a replacement revision.
382 382
383 383 Work items for the same revision are grouped together, so that a worker
384 384 pool starting with the first N items in parallel is likely to finish the
385 385 first revision's work before other revisions. This can allow us to write
386 386 the result to disk and reduce memory footprint. At time of writing, the
387 387 partition strategy in worker.py seems favorable to this. We also sort the
388 388 items by ascending revision number to match the order in which we commit
389 389 the fixes later.
390 390 """
391 391 workqueue = []
392 392 numitems = collections.defaultdict(int)
393 393 maxfilesize = ui.configbytes(b'fix', b'maxfilesize')
394 394 for rev in sorted(revstofix):
395 395 fixctx = repo[rev]
396 396 match = scmutil.match(fixctx, pats, opts)
397 397 for path in sorted(
398 398 pathstofix(ui, repo, pats, opts, match, basectxs[rev], fixctx)
399 399 ):
400 400 fctx = fixctx[path]
401 401 if fctx.islink():
402 402 continue
403 403 if fctx.size() > maxfilesize:
404 404 ui.warn(
405 405 _(b'ignoring file larger than %s: %s\n')
406 406 % (util.bytecount(maxfilesize), path)
407 407 )
408 408 continue
409 409 workqueue.append((rev, path))
410 410 numitems[rev] += 1
411 411 return workqueue, numitems
412 412
413 413
414 414 def getrevstofix(ui, repo, opts):
415 415 """Returns the set of revision numbers that should be fixed"""
416 416 if opts[b'all']:
417 417 revs = repo.revs(b'(not public() and not obsolete()) or wdir()')
418 418 elif opts[b'source']:
419 419 source_revs = scmutil.revrange(repo, opts[b'source'])
420 revs = set(repo.revs(b'%ld::', source_revs))
420 revs = set(repo.revs(b'(%ld::) - obsolete()', source_revs))
421 421 if wdirrev in source_revs:
422 422 # `wdir()::` is currently empty, so manually add wdir
423 423 revs.add(wdirrev)
424 424 if repo[b'.'].rev() in revs:
425 425 revs.add(wdirrev)
426 426 else:
427 427 revs = set(scmutil.revrange(repo, opts[b'rev']))
428 428 if opts.get(b'working_dir'):
429 429 revs.add(wdirrev)
430 430 for rev in revs:
431 431 checkfixablectx(ui, repo, repo[rev])
432 432 # Allow fixing only wdir() even if there's an unfinished operation
433 433 if not (len(revs) == 1 and wdirrev in revs):
434 434 cmdutil.checkunfinished(repo)
435 435 rewriteutil.precheck(repo, revs, b'fix')
436 436 if wdirrev in revs and list(
437 437 mergestatemod.mergestate.read(repo).unresolved()
438 438 ):
439 439 raise error.Abort(b'unresolved conflicts', hint=b"use 'hg resolve'")
440 440 if not revs:
441 441 raise error.Abort(
442 442 b'no changesets specified', hint=b'use --source or --working-dir'
443 443 )
444 444 return revs
445 445
446 446
447 447 def checkfixablectx(ui, repo, ctx):
448 448 """Aborts if the revision shouldn't be replaced with a fixed one."""
449 449 if ctx.obsolete():
450 450 # It would be better to actually check if the revision has a successor.
451 451 allowdivergence = ui.configbool(
452 452 b'experimental', b'evolution.allowdivergence'
453 453 )
454 454 if not allowdivergence:
455 455 raise error.Abort(
456 456 b'fixing obsolete revision could cause divergence'
457 457 )
458 458
459 459
460 460 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
461 461 """Returns the set of files that should be fixed in a context
462 462
463 463 The result depends on the base contexts; we include any file that has
464 464 changed relative to any of the base contexts. Base contexts should be
465 465 ancestors of the context being fixed.
466 466 """
467 467 files = set()
468 468 for basectx in basectxs:
469 469 stat = basectx.status(
470 470 fixctx, match=match, listclean=bool(pats), listunknown=bool(pats)
471 471 )
472 472 files.update(
473 473 set(
474 474 itertools.chain(
475 475 stat.added, stat.modified, stat.clean, stat.unknown
476 476 )
477 477 )
478 478 )
479 479 return files
480 480
481 481
482 482 def lineranges(opts, path, basepaths, basectxs, fixctx, content2):
483 483 """Returns the set of line ranges that should be fixed in a file
484 484
485 485 Of the form [(10, 20), (30, 40)].
486 486
487 487 This depends on the given base contexts; we must consider lines that have
488 488 changed versus any of the base contexts, and whether the file has been
489 489 renamed versus any of them.
490 490
491 491 Another way to understand this is that we exclude line ranges that are
492 492 common to the file in all base contexts.
493 493 """
494 494 if opts.get(b'whole'):
495 495 # Return a range containing all lines. Rely on the diff implementation's
496 496 # idea of how many lines are in the file, instead of reimplementing it.
497 497 return difflineranges(b'', content2)
498 498
499 499 rangeslist = []
500 500 for basectx in basectxs:
501 501 basepath = basepaths.get((basectx.rev(), fixctx.rev(), path), path)
502 502
503 503 if basepath in basectx:
504 504 content1 = basectx[basepath].data()
505 505 else:
506 506 content1 = b''
507 507 rangeslist.extend(difflineranges(content1, content2))
508 508 return unionranges(rangeslist)
509 509
510 510
511 511 def getbasepaths(repo, opts, workqueue, basectxs):
512 512 if opts.get(b'whole'):
513 513 # Base paths will never be fetched for line range determination.
514 514 return {}
515 515
516 516 basepaths = {}
517 517 for rev, path in workqueue:
518 518 fixctx = repo[rev]
519 519 for basectx in basectxs[rev]:
520 520 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
521 521 if basepath in basectx:
522 522 basepaths[(basectx.rev(), fixctx.rev(), path)] = basepath
523 523 return basepaths
524 524
525 525
526 526 def unionranges(rangeslist):
527 527 """Return the union of some closed intervals
528 528
529 529 >>> unionranges([])
530 530 []
531 531 >>> unionranges([(1, 100)])
532 532 [(1, 100)]
533 533 >>> unionranges([(1, 100), (1, 100)])
534 534 [(1, 100)]
535 535 >>> unionranges([(1, 100), (2, 100)])
536 536 [(1, 100)]
537 537 >>> unionranges([(1, 99), (1, 100)])
538 538 [(1, 100)]
539 539 >>> unionranges([(1, 100), (40, 60)])
540 540 [(1, 100)]
541 541 >>> unionranges([(1, 49), (50, 100)])
542 542 [(1, 100)]
543 543 >>> unionranges([(1, 48), (50, 100)])
544 544 [(1, 48), (50, 100)]
545 545 >>> unionranges([(1, 2), (3, 4), (5, 6)])
546 546 [(1, 6)]
547 547 """
548 548 rangeslist = sorted(set(rangeslist))
549 549 unioned = []
550 550 if rangeslist:
551 551 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
552 552 for a, b in rangeslist:
553 553 c, d = unioned[-1]
554 554 if a > d + 1:
555 555 unioned.append((a, b))
556 556 else:
557 557 unioned[-1] = (c, max(b, d))
558 558 return unioned
559 559
560 560
561 561 def difflineranges(content1, content2):
562 562 """Return list of line number ranges in content2 that differ from content1.
563 563
564 564 Line numbers are 1-based. The numbers are the first and last line contained
565 565 in the range. Single-line ranges have the same line number for the first and
566 566 last line. Excludes any empty ranges that result from lines that are only
567 567 present in content1. Relies on mdiff's idea of where the line endings are in
568 568 the string.
569 569
570 570 >>> from mercurial import pycompat
571 571 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
572 572 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
573 573 >>> difflineranges2(b'', b'')
574 574 []
575 575 >>> difflineranges2(b'a', b'')
576 576 []
577 577 >>> difflineranges2(b'', b'A')
578 578 [(1, 1)]
579 579 >>> difflineranges2(b'a', b'a')
580 580 []
581 581 >>> difflineranges2(b'a', b'A')
582 582 [(1, 1)]
583 583 >>> difflineranges2(b'ab', b'')
584 584 []
585 585 >>> difflineranges2(b'', b'AB')
586 586 [(1, 2)]
587 587 >>> difflineranges2(b'abc', b'ac')
588 588 []
589 589 >>> difflineranges2(b'ab', b'aCb')
590 590 [(2, 2)]
591 591 >>> difflineranges2(b'abc', b'aBc')
592 592 [(2, 2)]
593 593 >>> difflineranges2(b'ab', b'AB')
594 594 [(1, 2)]
595 595 >>> difflineranges2(b'abcde', b'aBcDe')
596 596 [(2, 2), (4, 4)]
597 597 >>> difflineranges2(b'abcde', b'aBCDe')
598 598 [(2, 4)]
599 599 """
600 600 ranges = []
601 601 for lines, kind in mdiff.allblocks(content1, content2):
602 602 firstline, lastline = lines[2:4]
603 603 if kind == b'!' and firstline != lastline:
604 604 ranges.append((firstline + 1, lastline))
605 605 return ranges
606 606
607 607
608 608 def getbasectxs(repo, opts, revstofix):
609 609 """Returns a map of the base contexts for each revision
610 610
611 611 The base contexts determine which lines are considered modified when we
612 612 attempt to fix just the modified lines in a file. It also determines which
613 613 files we attempt to fix, so it is important to compute this even when
614 614 --whole is used.
615 615 """
616 616 # The --base flag overrides the usual logic, and we give every revision
617 617 # exactly the set of baserevs that the user specified.
618 618 if opts.get(b'base'):
619 619 baserevs = set(scmutil.revrange(repo, opts.get(b'base')))
620 620 if not baserevs:
621 621 baserevs = {nullrev}
622 622 basectxs = {repo[rev] for rev in baserevs}
623 623 return {rev: basectxs for rev in revstofix}
624 624
625 625 # Proceed in topological order so that we can easily determine each
626 626 # revision's baserevs by looking at its parents and their baserevs.
627 627 basectxs = collections.defaultdict(set)
628 628 for rev in sorted(revstofix):
629 629 ctx = repo[rev]
630 630 for pctx in ctx.parents():
631 631 if pctx.rev() in basectxs:
632 632 basectxs[rev].update(basectxs[pctx.rev()])
633 633 else:
634 634 basectxs[rev].add(pctx)
635 635 return basectxs
636 636
637 637
638 638 def _prefetchfiles(repo, workqueue, basepaths):
639 639 toprefetch = set()
640 640
641 641 # Prefetch the files that will be fixed.
642 642 for rev, path in workqueue:
643 643 if rev == wdirrev:
644 644 continue
645 645 toprefetch.add((rev, path))
646 646
647 647 # Prefetch the base contents for lineranges().
648 648 for (baserev, fixrev, path), basepath in basepaths.items():
649 649 toprefetch.add((baserev, basepath))
650 650
651 651 if toprefetch:
652 652 scmutil.prefetchfiles(
653 653 repo,
654 654 [
655 655 (rev, scmutil.matchfiles(repo, [path]))
656 656 for rev, path in toprefetch
657 657 ],
658 658 )
659 659
660 660
661 661 def fixfile(ui, repo, opts, fixers, fixctx, path, basepaths, basectxs):
662 662 """Run any configured fixers that should affect the file in this context
663 663
664 664 Returns the file content that results from applying the fixers in some order
665 665 starting with the file's content in the fixctx. Fixers that support line
666 666 ranges will affect lines that have changed relative to any of the basectxs
667 667 (i.e. they will only avoid lines that are common to all basectxs).
668 668
669 669 A fixer tool's stdout will become the file's new content if and only if it
670 670 exits with code zero. The fixer tool's working directory is the repository's
671 671 root.
672 672 """
673 673 metadata = {}
674 674 newdata = fixctx[path].data()
675 675 for fixername, fixer in pycompat.iteritems(fixers):
676 676 if fixer.affects(opts, fixctx, path):
677 677 ranges = lineranges(
678 678 opts, path, basepaths, basectxs, fixctx, newdata
679 679 )
680 680 command = fixer.command(ui, path, ranges)
681 681 if command is None:
682 682 continue
683 683 ui.debug(b'subprocess: %s\n' % (command,))
684 684 proc = subprocess.Popen(
685 685 procutil.tonativestr(command),
686 686 shell=True,
687 687 cwd=procutil.tonativestr(repo.root),
688 688 stdin=subprocess.PIPE,
689 689 stdout=subprocess.PIPE,
690 690 stderr=subprocess.PIPE,
691 691 )
692 692 stdout, stderr = proc.communicate(newdata)
693 693 if stderr:
694 694 showstderr(ui, fixctx.rev(), fixername, stderr)
695 695 newerdata = stdout
696 696 if fixer.shouldoutputmetadata():
697 697 try:
698 698 metadatajson, newerdata = stdout.split(b'\0', 1)
699 699 metadata[fixername] = pycompat.json_loads(metadatajson)
700 700 except ValueError:
701 701 ui.warn(
702 702 _(b'ignored invalid output from fixer tool: %s\n')
703 703 % (fixername,)
704 704 )
705 705 continue
706 706 else:
707 707 metadata[fixername] = None
708 708 if proc.returncode == 0:
709 709 newdata = newerdata
710 710 else:
711 711 if not stderr:
712 712 message = _(b'exited with status %d\n') % (proc.returncode,)
713 713 showstderr(ui, fixctx.rev(), fixername, message)
714 714 checktoolfailureaction(
715 715 ui,
716 716 _(b'no fixes will be applied'),
717 717 hint=_(
718 718 b'use --config fix.failure=continue to apply any '
719 719 b'successful fixes anyway'
720 720 ),
721 721 )
722 722 return metadata, newdata
723 723
724 724
725 725 def showstderr(ui, rev, fixername, stderr):
726 726 """Writes the lines of the stderr string as warnings on the ui
727 727
728 728 Uses the revision number and fixername to give more context to each line of
729 729 the error message. Doesn't include file names, since those take up a lot of
730 730 space and would tend to be included in the error message if they were
731 731 relevant.
732 732 """
733 733 for line in re.split(b'[\r\n]+', stderr):
734 734 if line:
735 735 ui.warn(b'[')
736 736 if rev is None:
737 737 ui.warn(_(b'wdir'), label=b'evolve.rev')
738 738 else:
739 739 ui.warn(b'%d' % rev, label=b'evolve.rev')
740 740 ui.warn(b'] %s: %s\n' % (fixername, line))
741 741
742 742
743 743 def writeworkingdir(repo, ctx, filedata, replacements):
744 744 """Write new content to the working copy and check out the new p1 if any
745 745
746 746 We check out a new revision if and only if we fixed something in both the
747 747 working directory and its parent revision. This avoids the need for a full
748 748 update/merge, and means that the working directory simply isn't affected
749 749 unless the --working-dir flag is given.
750 750
751 751 Directly updates the dirstate for the affected files.
752 752 """
753 753 for path, data in pycompat.iteritems(filedata):
754 754 fctx = ctx[path]
755 755 fctx.write(data, fctx.flags())
756 756 if repo.dirstate[path] == b'n':
757 757 repo.dirstate.normallookup(path)
758 758
759 759 oldparentnodes = repo.dirstate.parents()
760 760 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
761 761 if newparentnodes != oldparentnodes:
762 762 repo.setparents(*newparentnodes)
763 763
764 764
765 765 def replacerev(ui, repo, ctx, filedata, replacements):
766 766 """Commit a new revision like the given one, but with file content changes
767 767
768 768 "ctx" is the original revision to be replaced by a modified one.
769 769
770 770 "filedata" is a dict that maps paths to their new file content. All other
771 771 paths will be recreated from the original revision without changes.
772 772 "filedata" may contain paths that didn't exist in the original revision;
773 773 they will be added.
774 774
775 775 "replacements" is a dict that maps a single node to a single node, and it is
776 776 updated to indicate the original revision is replaced by the newly created
777 777 one. No entry is added if the replacement's node already exists.
778 778
779 779 The new revision has the same parents as the old one, unless those parents
780 780 have already been replaced, in which case those replacements are the parents
781 781 of this new revision. Thus, if revisions are replaced in topological order,
782 782 there is no need to rebase them into the original topology later.
783 783 """
784 784
785 785 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
786 786 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
787 787 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
788 788 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
789 789
790 790 # We don't want to create a revision that has no changes from the original,
791 791 # but we should if the original revision's parent has been replaced.
792 792 # Otherwise, we would produce an orphan that needs no actual human
793 793 # intervention to evolve. We can't rely on commit() to avoid creating the
794 794 # un-needed revision because the extra field added below produces a new hash
795 795 # regardless of file content changes.
796 796 if (
797 797 not filedata
798 798 and p1ctx.node() not in replacements
799 799 and p2ctx.node() not in replacements
800 800 ):
801 801 return
802 802
803 803 extra = ctx.extra().copy()
804 804 extra[b'fix_source'] = ctx.hex()
805 805
806 806 wctx = context.overlayworkingctx(repo)
807 807 wctx.setbase(repo[newp1node])
808 808 merge.revert_to(ctx, wc=wctx)
809 809 copies.graftcopies(wctx, ctx, ctx.p1())
810 810
811 811 for path in filedata.keys():
812 812 fctx = ctx[path]
813 813 copysource = fctx.copysource()
814 814 wctx.write(path, filedata[path], flags=fctx.flags())
815 815 if copysource:
816 816 wctx.markcopied(path, copysource)
817 817
818 818 desc = rewriteutil.update_hash_refs(
819 819 repo,
820 820 ctx.description(),
821 821 {oldnode: [newnode] for oldnode, newnode in replacements.items()},
822 822 )
823 823
824 824 memctx = wctx.tomemctx(
825 825 text=desc,
826 826 branch=ctx.branch(),
827 827 extra=extra,
828 828 date=ctx.date(),
829 829 parents=(newp1node, newp2node),
830 830 user=ctx.user(),
831 831 )
832 832
833 833 sucnode = memctx.commit()
834 834 prenode = ctx.node()
835 835 if prenode == sucnode:
836 836 ui.debug(b'node %s already existed\n' % (ctx.hex()))
837 837 else:
838 838 replacements[ctx.node()] = sucnode
839 839
840 840
841 841 def getfixers(ui):
842 842 """Returns a map of configured fixer tools indexed by their names
843 843
844 844 Each value is a Fixer object with methods that implement the behavior of the
845 845 fixer's config suboptions. Does not validate the config values.
846 846 """
847 847 fixers = {}
848 848 for name in fixernames(ui):
849 849 enabled = ui.configbool(b'fix', name + b':enabled')
850 850 command = ui.config(b'fix', name + b':command')
851 851 pattern = ui.config(b'fix', name + b':pattern')
852 852 linerange = ui.config(b'fix', name + b':linerange')
853 853 priority = ui.configint(b'fix', name + b':priority')
854 854 metadata = ui.configbool(b'fix', name + b':metadata')
855 855 skipclean = ui.configbool(b'fix', name + b':skipclean')
856 856 # Don't use a fixer if it has no pattern configured. It would be
857 857 # dangerous to let it affect all files. It would be pointless to let it
858 858 # affect no files. There is no reasonable subset of files to use as the
859 859 # default.
860 860 if command is None:
861 861 ui.warn(
862 862 _(b'fixer tool has no command configuration: %s\n') % (name,)
863 863 )
864 864 elif pattern is None:
865 865 ui.warn(
866 866 _(b'fixer tool has no pattern configuration: %s\n') % (name,)
867 867 )
868 868 elif not enabled:
869 869 ui.debug(b'ignoring disabled fixer tool: %s\n' % (name,))
870 870 else:
871 871 fixers[name] = Fixer(
872 872 command, pattern, linerange, priority, metadata, skipclean
873 873 )
874 874 return collections.OrderedDict(
875 875 sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
876 876 )
877 877
878 878
879 879 def fixernames(ui):
880 880 """Returns the names of [fix] config options that have suboptions"""
881 881 names = set()
882 882 for k, v in ui.configitems(b'fix'):
883 883 if b':' in k:
884 884 names.add(k.split(b':', 1)[0])
885 885 return names
886 886
887 887
888 888 class Fixer(object):
889 889 """Wraps the raw config values for a fixer with methods"""
890 890
891 891 def __init__(
892 892 self, command, pattern, linerange, priority, metadata, skipclean
893 893 ):
894 894 self._command = command
895 895 self._pattern = pattern
896 896 self._linerange = linerange
897 897 self._priority = priority
898 898 self._metadata = metadata
899 899 self._skipclean = skipclean
900 900
901 901 def affects(self, opts, fixctx, path):
902 902 """Should this fixer run on the file at the given path and context?"""
903 903 repo = fixctx.repo()
904 904 matcher = matchmod.match(
905 905 repo.root, repo.root, [self._pattern], ctx=fixctx
906 906 )
907 907 return matcher(path)
908 908
909 909 def shouldoutputmetadata(self):
910 910 """Should the stdout of this fixer start with JSON and a null byte?"""
911 911 return self._metadata
912 912
913 913 def command(self, ui, path, ranges):
914 914 """A shell command to use to invoke this fixer on the given file/lines
915 915
916 916 May return None if there is no appropriate command to run for the given
917 917 parameters.
918 918 """
919 919 expand = cmdutil.rendercommandtemplate
920 920 parts = [
921 921 expand(
922 922 ui,
923 923 self._command,
924 924 {b'rootpath': path, b'basename': os.path.basename(path)},
925 925 )
926 926 ]
927 927 if self._linerange:
928 928 if self._skipclean and not ranges:
929 929 # No line ranges to fix, so don't run the fixer.
930 930 return None
931 931 for first, last in ranges:
932 932 parts.append(
933 933 expand(
934 934 ui, self._linerange, {b'first': first, b'last': last}
935 935 )
936 936 )
937 937 return b' '.join(parts)
@@ -1,556 +1,553 b''
1 1 A script that implements uppercasing all letters in a file.
2 2
3 3 $ UPPERCASEPY="$TESTTMP/uppercase.py"
4 4 $ cat > $UPPERCASEPY <<EOF
5 5 > import sys
6 6 > from mercurial.utils.procutil import setbinary
7 7 > setbinary(sys.stdin)
8 8 > setbinary(sys.stdout)
9 9 > sys.stdout.write(sys.stdin.read().upper())
10 10 > EOF
11 11 $ TESTLINES="foo\nbar\nbaz\n"
12 12 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY
13 13 FOO
14 14 BAR
15 15 BAZ
16 16
17 17 Tests for the fix extension's behavior around non-trivial history topologies.
18 18 Looks for correct incremental fixing and reproduction of parent/child
19 19 relationships. We indicate fixed file content by uppercasing it.
20 20
21 21 $ cat >> $HGRCPATH <<EOF
22 22 > [extensions]
23 23 > fix =
24 24 > strip =
25 25 > debugdrawdag=$TESTDIR/drawdag.py
26 26 > [fix]
27 27 > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY
28 28 > uppercase-whole-file:pattern=set:**
29 29 > EOF
30 30
31 31 This tests the only behavior that should really be affected by obsolescence, so
32 32 we'll test it with evolution off and on. This only changes the revision
33 33 numbers, if all is well.
34 34
35 35 #testcases obsstore-off obsstore-on
36 36 #if obsstore-on
37 37 $ cat >> $HGRCPATH <<EOF
38 38 > [experimental]
39 39 > evolution.createmarkers=True
40 40 > evolution.allowunstable=True
41 41 > EOF
42 42 #endif
43 43
44 44 Setting up the test topology. Scroll down to see the graph produced. We make it
45 45 clear which files were modified in each revision. It's enough to test at the
46 46 file granularity, because that demonstrates which baserevs were diffed against.
47 47 The computation of changed lines is orthogonal and tested separately.
48 48
49 49 $ hg init repo
50 50 $ cd repo
51 51
52 52 $ printf "aaaa\n" > a
53 53 $ hg commit -Am "change A"
54 54 adding a
55 55 $ printf "bbbb\n" > b
56 56 $ hg commit -Am "change B"
57 57 adding b
58 58 $ printf "cccc\n" > c
59 59 $ hg commit -Am "change C"
60 60 adding c
61 61 $ hg checkout 0
62 62 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
63 63 $ printf "dddd\n" > d
64 64 $ hg commit -Am "change D"
65 65 adding d
66 66 created new head
67 67 $ hg merge -r 2
68 68 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 69 (branch merge, don't forget to commit)
70 70 $ printf "eeee\n" > e
71 71 $ hg commit -Am "change E"
72 72 adding e
73 73 $ hg checkout 0
74 74 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
75 75 $ printf "ffff\n" > f
76 76 $ hg commit -Am "change F"
77 77 adding f
78 78 created new head
79 79 $ hg checkout 0
80 80 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
81 81 $ printf "gggg\n" > g
82 82 $ hg commit -Am "change G"
83 83 adding g
84 84 created new head
85 85 $ hg merge -r 5
86 86 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 87 (branch merge, don't forget to commit)
88 88 $ printf "hhhh\n" > h
89 89 $ hg commit -Am "change H (child of b53d63e816fb and 0e49f92ee6e9)"
90 90 adding h
91 91 $ hg merge -r 4
92 92 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 93 (branch merge, don't forget to commit)
94 94 $ printf "iiii\n" > i
95 95 $ hg commit -Am "change I"
96 96 adding i
97 97 $ hg checkout 2
98 98 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
99 99 $ printf "jjjj\n" > j
100 100 $ hg commit -Am "change J (child of 7f371349286e)"
101 101 adding j
102 102 created new head
103 103 $ hg checkout 7
104 104 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
105 105 $ printf "kkkk\n" > k
106 106 $ hg add
107 107 adding k
108 108
109 109 $ hg log --graph --template '{rev}:{node|short} {desc}\n'
110 110 o 9:884041ccc490 change J (child of 7f371349286e)
111 111 |
112 112 | o 8:b7c772105fd2 change I
113 113 | |\
114 114 | | @ 7:4e7b9312dad2 change H (child of b53d63e816fb and 0e49f92ee6e9)
115 115 | | |\
116 116 | | | o 6:0e49f92ee6e9 change G
117 117 | | | |
118 118 | | o | 5:b53d63e816fb change F
119 119 | | |/
120 120 | o | 4:ddad58af5e51 change E
121 121 |/| |
122 122 | o | 3:c015ebfd2bfe change D
123 123 | |/
124 124 o | 2:7f371349286e change C
125 125 | |
126 126 o | 1:388fdd33fea0 change B
127 127 |/
128 128 o 0:a55a84d97a24 change A
129 129
130 130
131 131 Fix all but the root revision and its four children.
132 132
133 133 $ hg fix -r '2|4|7|8|9' --working-dir
134 134 saved backup bundle to * (glob) (obsstore-off !)
135 135
136 136 The five revisions remain, but the other revisions were fixed and replaced. All
137 137 parent pointers have been accurately set to reproduce the previous topology
138 138 (though it is rendered in a slightly different order now).
139 139
140 140 #if obsstore-on
141 141 $ hg log --graph --template '{rev}:{node|short} {desc}\n'
142 142 o 14:d8d0e7974598 change J (child of 89de0da1d5da)
143 143 |
144 144 | o 13:4fc0b354461e change I
145 145 | |\
146 146 | | @ 12:1c45f3923443 change H (child of b53d63e816fb and 0e49f92ee6e9)
147 147 | | |\
148 148 | o | | 11:d75754455722 change E
149 149 |/| | |
150 150 o | | | 10:89de0da1d5da change C
151 151 | | | |
152 152 | | | o 6:0e49f92ee6e9 change G
153 153 | | | |
154 154 | | o | 5:b53d63e816fb change F
155 155 | | |/
156 156 | o / 3:c015ebfd2bfe change D
157 157 | |/
158 158 o / 1:388fdd33fea0 change B
159 159 |/
160 160 o 0:a55a84d97a24 change A
161 161
162 162 $ C=10
163 163 $ E=11
164 164 $ H=12
165 165 $ I=13
166 166 $ J=14
167 167 #else
168 168 $ hg log --graph --template '{rev}:{node|short} {desc}\n'
169 169 o 9:d8d0e7974598 change J (child of 89de0da1d5da)
170 170 |
171 171 | o 8:4fc0b354461e change I
172 172 | |\
173 173 | | @ 7:1c45f3923443 change H (child of b53d63e816fb and 0e49f92ee6e9)
174 174 | | |\
175 175 | o | | 6:d75754455722 change E
176 176 |/| | |
177 177 o | | | 5:89de0da1d5da change C
178 178 | | | |
179 179 | | | o 4:0e49f92ee6e9 change G
180 180 | | | |
181 181 | | o | 3:b53d63e816fb change F
182 182 | | |/
183 183 | o / 2:c015ebfd2bfe change D
184 184 | |/
185 185 o / 1:388fdd33fea0 change B
186 186 |/
187 187 o 0:a55a84d97a24 change A
188 188
189 189 $ C=5
190 190 $ E=6
191 191 $ H=7
192 192 $ I=8
193 193 $ J=9
194 194 #endif
195 195
196 196 Change C is a root of the set being fixed, so all we fix is what has changed
197 197 since its parent. That parent, change B, is its baserev.
198 198
199 199 $ hg cat -r $C 'set:**'
200 200 aaaa
201 201 bbbb
202 202 CCCC
203 203
204 204 Change E is a merge with only one parent being fixed. Its baserevs are the
205 205 unfixed parent plus the baserevs of the other parent. This evaluates to changes
206 206 B and D. We now have to decide what it means to incrementally fix a merge
207 207 commit. We choose to fix anything that has changed versus any baserev. Only the
208 208 undisturbed content of the common ancestor, change A, is unfixed.
209 209
210 210 $ hg cat -r $E 'set:**'
211 211 aaaa
212 212 BBBB
213 213 CCCC
214 214 DDDD
215 215 EEEE
216 216
217 217 Change H is a merge with neither parent being fixed. This is essentially
218 218 equivalent to the previous case because there is still only one baserev for
219 219 each parent of the merge.
220 220
221 221 $ hg cat -r $H 'set:**'
222 222 aaaa
223 223 FFFF
224 224 GGGG
225 225 HHHH
226 226
227 227 Change I is a merge that has four baserevs; two from each parent. We handle
228 228 multiple baserevs in the same way regardless of how many came from each parent.
229 229 So, fixing change H will fix any files that were not exactly the same in each
230 230 baserev.
231 231
232 232 $ hg cat -r $I 'set:**'
233 233 aaaa
234 234 BBBB
235 235 CCCC
236 236 DDDD
237 237 EEEE
238 238 FFFF
239 239 GGGG
240 240 HHHH
241 241 IIII
242 242
243 243 Change J is a simple case with one baserev, but its baserev is not its parent,
244 244 change C. Its baserev is its grandparent, change B.
245 245
246 246 $ hg cat -r $J 'set:**'
247 247 aaaa
248 248 bbbb
249 249 CCCC
250 250 JJJJ
251 251
252 252 The working copy was dirty, so it is treated much like a revision. The baserevs
253 253 for the working copy are inherited from its parent, change H, because it is
254 254 also being fixed.
255 255
256 256 $ cat *
257 257 aaaa
258 258 FFFF
259 259 GGGG
260 260 HHHH
261 261 KKKK
262 262
263 263 Change A was never a baserev because none of its children were to be fixed.
264 264
265 265 $ cd ..
266 266
267 267
268 268 Test the --source option. We only do this with obsstore on to avoid duplicating
269 269 test code. We rely on the other tests to prove that obsolescence is not an
270 270 important factor here.
271 271
272 272 #if obsstore-on
273 273 $ hg init source-arg
274 274 $ cd source-arg
275 275 $ printf "aaaa\n" > a
276 276 $ hg commit -Am "change A"
277 277 adding a
278 278 $ printf "bbbb\n" > b
279 279 $ hg commit -Am "change B"
280 280 adding b
281 281 $ printf "cccc\n" > c
282 282 $ hg commit -Am "change C"
283 283 adding c
284 284 $ hg checkout 0
285 285 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
286 286 $ printf "dddd\n" > d
287 287 $ hg commit -Am "change D"
288 288 adding d
289 289 created new head
290 290 $ hg log --graph --template '{rev} {desc}\n'
291 291 @ 3 change D
292 292 |
293 293 | o 2 change C
294 294 | |
295 295 | o 1 change B
296 296 |/
297 297 o 0 change A
298 298
299 299
300 300 Test passing 'wdir()' to --source
301 301 $ printf "xxxx\n" > x
302 302 $ hg add x
303 303 $ hg fix -s 'wdir()'
304 304 $ cat *
305 305 aaaa
306 306 dddd
307 307 XXXX
308 308
309 309 Test passing '.' to --source
310 310 $ printf "xxxx\n" > x
311 311 $ hg fix -s .
312 312 $ hg log --graph --template '{rev} {desc}\n'
313 313 @ 4 change D
314 314 |
315 315 | o 2 change C
316 316 | |
317 317 | o 1 change B
318 318 |/
319 319 o 0 change A
320 320
321 321 $ cat *
322 322 aaaa
323 323 DDDD
324 324 XXXX
325 325 $ hg strip -qf 4
326 326 $ hg co -q 3
327 327
328 328 Test passing other branch to --source
329 329 $ printf "xxxx\n" > x
330 330 $ hg add x
331 331 $ hg fix -s 2
332 332 $ hg log --graph --template '{rev} {desc}\n'
333 333 o 4 change C
334 334 |
335 335 | @ 3 change D
336 336 | |
337 337 o | 1 change B
338 338 |/
339 339 o 0 change A
340 340
341 341 $ hg cat -r 4 b c
342 342 bbbb
343 343 CCCC
344 344 $ cat *
345 345 aaaa
346 346 dddd
347 347 xxxx
348 348 $ hg strip -qf 4
349 349
350 350 Test passing multiple revisions to --source
351 351 $ hg fix -s '2 + .'
352 352 $ hg log --graph --template '{rev} {desc}\n'
353 353 @ 5 change D
354 354 |
355 355 | o 4 change C
356 356 | |
357 357 | o 1 change B
358 358 |/
359 359 o 0 change A
360 360
361 361 $ hg cat -r 4 b c
362 362 bbbb
363 363 CCCC
364 364 $ cat *
365 365 aaaa
366 366 DDDD
367 367 XXXX
368 368
369 369 $ cd ..
370 370
371 371 $ hg init exclude-obsolete
372 372 $ cd exclude-obsolete
373 373 $ hg debugdrawdag <<'EOS'
374 374 > E C # prune: C
375 375 > | |
376 376 > D B # prune: B, D
377 377 > |/
378 378 > A
379 379 > EOS
380 380 1 new orphan changesets
381 381 $ hg log --graph --template '{rev} {desc}\n'
382 382 * 4 E
383 383 |
384 384 | x 3 C
385 385 | |
386 386 x | 2 D
387 387 | |
388 388 | x 1 B
389 389 |/
390 390 o 0 A
391 391
392 392 $ hg fix -s A
393 abort: fixing obsolete revision could cause divergence
394 [255]
395 393 $ hg fix -s B
396 abort: fixing obsolete revision could cause divergence
394 abort: no changesets specified
395 (use --source or --working-dir)
397 396 [255]
398 397 $ hg fix -s D
399 abort: fixing obsolete revision could cause divergence
400 [255]
401 398 $ hg fix -s E
402 399 $ cd ..
403 400
404 401 #endif
405 402
406 403 The --all flag should fix anything that wouldn't cause a problem if you fixed
407 404 it, including the working copy. Obsolete revisions are not fixed because that
408 405 could cause divergence. Public revisions would cause an abort because they are
409 406 immutable. We can fix orphans because their successors are still just orphans
410 407 of the original obsolete parent. When obsolesence is off, we're just fixing and
411 408 replacing anything that isn't public.
412 409
413 410 $ hg init fixall
414 411 $ cd fixall
415 412 $ hg fix --all --working-dir
416 413 abort: cannot specify both --working-dir and --all
417 414 [255]
418 415
419 416 #if obsstore-on
420 417 $ printf "one\n" > foo.whole
421 418 $ hg commit -Aqm "first"
422 419 $ hg phase --public
423 420 $ hg tag --local root
424 421 $ printf "two\n" > foo.whole
425 422 $ hg commit -m "second"
426 423 $ printf "three\n" > foo.whole
427 424 $ hg commit -m "third" --secret
428 425 $ hg tag --local secret
429 426 $ hg checkout root
430 427 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
431 428 $ printf "four\n" > foo.whole
432 429 $ hg commit -m "fourth"
433 430 created new head
434 431 $ printf "five\n" > foo.whole
435 432 $ hg commit -m "fifth"
436 433 $ hg tag --local replaced
437 434 $ printf "six\n" > foo.whole
438 435 $ hg commit -m "sixth"
439 436 $ hg checkout replaced
440 437 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
441 438 $ printf "seven\n" > foo.whole
442 439 $ hg commit --amend
443 440 1 new orphan changesets
444 441 $ hg checkout secret
445 442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
446 443 $ printf "uncommitted\n" > foo.whole
447 444
448 445 $ hg log --graph --template '{rev} {desc} {phase}\n'
449 446 o 6 fifth draft
450 447 |
451 448 | * 5 sixth draft
452 449 | |
453 450 | x 4 fifth draft
454 451 |/
455 452 o 3 fourth draft
456 453 |
457 454 | @ 2 third secret
458 455 | |
459 456 | o 1 second draft
460 457 |/
461 458 o 0 first public
462 459
463 460
464 461 $ hg fix --all
465 462
466 463 $ hg log --graph --template '{rev} {desc}\n' -r 'sort(all(), topo)' --hidden
467 464 o 11 fifth
468 465 |
469 466 o 9 fourth
470 467 |
471 468 | @ 8 third
472 469 | |
473 470 | o 7 second
474 471 |/
475 472 | * 10 sixth
476 473 | |
477 474 | | x 5 sixth
478 475 | |/
479 476 | x 4 fifth
480 477 | |
481 478 | | x 6 fifth
482 479 | |/
483 480 | x 3 fourth
484 481 |/
485 482 | x 2 third
486 483 | |
487 484 | x 1 second
488 485 |/
489 486 o 0 first
490 487
491 488
492 489 $ hg cat -r 7 foo.whole
493 490 TWO
494 491 $ hg cat -r 8 foo.whole
495 492 THREE
496 493 $ hg cat -r 9 foo.whole
497 494 FOUR
498 495 $ hg cat -r 10 foo.whole
499 496 SIX
500 497 $ hg cat -r 11 foo.whole
501 498 SEVEN
502 499 $ cat foo.whole
503 500 UNCOMMITTED
504 501 #else
505 502 $ printf "one\n" > foo.whole
506 503 $ hg commit -Aqm "first"
507 504 $ hg phase --public
508 505 $ hg tag --local root
509 506 $ printf "two\n" > foo.whole
510 507 $ hg commit -m "second"
511 508 $ printf "three\n" > foo.whole
512 509 $ hg commit -m "third" --secret
513 510 $ hg tag --local secret
514 511 $ hg checkout root
515 512 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
516 513 $ printf "four\n" > foo.whole
517 514 $ hg commit -m "fourth"
518 515 created new head
519 516 $ printf "uncommitted\n" > foo.whole
520 517
521 518 $ hg log --graph --template '{rev} {desc} {phase}\n'
522 519 @ 3 fourth draft
523 520 |
524 521 | o 2 third secret
525 522 | |
526 523 | o 1 second draft
527 524 |/
528 525 o 0 first public
529 526
530 527
531 528 $ hg fix --all
532 529 saved backup bundle to * (glob)
533 530
534 531 $ hg log --graph --template '{rev} {desc} {phase}\n'
535 532 @ 3 fourth draft
536 533 |
537 534 | o 2 third secret
538 535 | |
539 536 | o 1 second draft
540 537 |/
541 538 o 0 first public
542 539
543 540 $ hg cat -r 0 foo.whole
544 541 one
545 542 $ hg cat -r 1 foo.whole
546 543 TWO
547 544 $ hg cat -r 2 foo.whole
548 545 THREE
549 546 $ hg cat -r 3 foo.whole
550 547 FOUR
551 548 $ cat foo.whole
552 549 UNCOMMITTED
553 550 #endif
554 551
555 552 $ cd ..
556 553
General Comments 0
You need to be logged in to leave comments. Login now