Mercurial > public > mercurial-scm > hg
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 |