comparison mercurial/cmdutil.py @ 47565:00ae1fb6c459

cmdutil: fix newandmodified file accounting for --interactive commits `originalchunks` is a misleading name, because it only contains header objects, which are flattened to selected hunks by the filter function. As such, `chunks not in originalchunks` is always True and misleading, because hunk objects never compare equal to header objects. This change fixes the internal naming and removes the useless parameter from the method. This change also fixes issue6533, by considering the filtered headers, rather than the hunks, when determining new and modified files. If a file is renamed + edited, and the edited hunks are deselected (but the file is not), the filtered chunks will contain a header for the file (because it's .special()) but but no hunks. Differential Revision: https://phab.mercurial-scm.org/D10936
author Daniel Ploch <dploch@google.com>
date Fri, 02 Jul 2021 11:44:13 -0700
parents 7f7457f84311
children 791e2333d3d3
comparison
equal deleted inserted replaced
47564:57bdecf4322c 47565:00ae1fb6c459
344 def ishunk(x): 344 def ishunk(x):
345 hunkclasses = (crecordmod.uihunk, patch.recordhunk) 345 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
346 return isinstance(x, hunkclasses) 346 return isinstance(x, hunkclasses)
347 347
348 348
349 def newandmodified(chunks, originalchunks): 349 def isheader(x):
350 headerclasses = (crecordmod.uiheader, patch.header)
351 return isinstance(x, headerclasses)
352
353
354 def newandmodified(chunks):
350 newlyaddedandmodifiedfiles = set() 355 newlyaddedandmodifiedfiles = set()
351 alsorestore = set() 356 alsorestore = set()
352 for chunk in chunks: 357 for chunk in chunks:
353 if ( 358 if isheader(chunk) and chunk.isnewfile():
354 ishunk(chunk) 359 newlyaddedandmodifiedfiles.add(chunk.filename())
355 and chunk.header.isnewfile() 360 alsorestore.update(set(chunk.files()) - {chunk.filename()})
356 and chunk not in originalchunks
357 ):
358 newlyaddedandmodifiedfiles.add(chunk.header.filename())
359 alsorestore.update(
360 set(chunk.header.files()) - {chunk.header.filename()}
361 )
362 return newlyaddedandmodifiedfiles, alsorestore 361 return newlyaddedandmodifiedfiles, alsorestore
363 362
364 363
365 def parsealiases(cmd): 364 def parsealiases(cmd):
366 base_aliases = cmd.split(b"|") 365 base_aliases = cmd.split(b"|")
515 ) 514 )
516 diffopts.nodates = True 515 diffopts.nodates = True
517 diffopts.git = True 516 diffopts.git = True
518 diffopts.showfunc = True 517 diffopts.showfunc = True
519 originaldiff = patch.diff(repo, changes=status, opts=diffopts) 518 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
520 originalchunks = patch.parsepatch(originaldiff) 519 original_headers = patch.parsepatch(originaldiff)
521 match = scmutil.match(repo[None], pats) 520 match = scmutil.match(repo[None], pats)
522 521
523 # 1. filter patch, since we are intending to apply subset of it 522 # 1. filter patch, since we are intending to apply subset of it
524 try: 523 try:
525 chunks, newopts = filterfn(ui, originalchunks, match) 524 chunks, newopts = filterfn(ui, original_headers, match)
526 except error.PatchError as err: 525 except error.PatchError as err:
527 raise error.InputError(_(b'error parsing patch: %s') % err) 526 raise error.InputError(_(b'error parsing patch: %s') % err)
528 opts.update(newopts) 527 opts.update(newopts)
529 528
530 # We need to keep a backup of files that have been newly added and 529 # We need to keep a backup of files that have been newly added and
531 # modified during the recording process because there is a previous 530 # modified during the recording process because there is a previous
532 # version without the edit in the workdir. We also will need to restore 531 # version without the edit in the workdir. We also will need to restore
533 # files that were the sources of renames so that the patch application 532 # files that were the sources of renames so that the patch application
534 # works. 533 # works.
535 newlyaddedandmodifiedfiles, alsorestore = newandmodified( 534 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks)
536 chunks, originalchunks
537 )
538 contenders = set() 535 contenders = set()
539 for h in chunks: 536 for h in chunks:
540 try: 537 if isheader(h):
541 contenders.update(set(h.files())) 538 contenders.update(set(h.files()))
542 except AttributeError:
543 pass
544 539
545 changed = status.modified + status.added + status.removed 540 changed = status.modified + status.added + status.removed
546 newfiles = [f for f in changed if f in contenders] 541 newfiles = [f for f in changed if f in contenders]
547 if not newfiles: 542 if not newfiles:
548 ui.status(_(b'no changes to record\n')) 543 ui.status(_(b'no changes to record\n'))
3628 3623
3629 if operation == b'apply': 3624 if operation == b'apply':
3630 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts) 3625 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3631 else: 3626 else:
3632 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts) 3627 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3633 originalchunks = patch.parsepatch(diff) 3628 original_headers = patch.parsepatch(diff)
3634 3629
3635 try: 3630 try:
3636 3631
3637 chunks, opts = recordfilter( 3632 chunks, opts = recordfilter(
3638 repo.ui, originalchunks, match, operation=operation 3633 repo.ui, original_headers, match, operation=operation
3639 ) 3634 )
3640 if operation == b'discard': 3635 if operation == b'discard':
3641 chunks = patch.reversehunks(chunks) 3636 chunks = patch.reversehunks(chunks)
3642 3637
3643 except error.PatchError as err: 3638 except error.PatchError as err:
3646 # FIXME: when doing an interactive revert of a copy, there's no way of 3641 # FIXME: when doing an interactive revert of a copy, there's no way of
3647 # performing a partial revert of the added file, the only option is 3642 # performing a partial revert of the added file, the only option is
3648 # "remove added file <name> (Yn)?", so we don't need to worry about the 3643 # "remove added file <name> (Yn)?", so we don't need to worry about the
3649 # alsorestore value. Ideally we'd be able to partially revert 3644 # alsorestore value. Ideally we'd be able to partially revert
3650 # copied/renamed files. 3645 # copied/renamed files.
3651 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified( 3646 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(chunks)
3652 chunks, originalchunks
3653 )
3654 if tobackup is None: 3647 if tobackup is None:
3655 tobackup = set() 3648 tobackup = set()
3656 # Apply changes 3649 # Apply changes
3657 fp = stringio() 3650 fp = stringio()
3658 # chunks are serialized per file, but files aren't sorted 3651 # chunks are serialized per file, but files aren't sorted