Skip to content

Conversation

@gabe-push
Copy link
Contributor

Description

Fixes #6104.

When using the fromfilename and edit plugins together during import, aborted edit sessions could silently discard the temporary tags injected by fromfilename (e.g., track number and title derived from the filename). This happened when using eDit or edit Candidates and then cancelling: the edit plugin reverted objects by re-reading from disk, which does not contain the fromfilename-generated metadata.

This PR changes the edit plugin so that cancel and “continue Editing” both roll back objects to the original in-memory snapshot captured before opening the editor, instead of reloading from the files. This preserves temporary tags provided by other plugins (like fromfilename) across aborted edit sessions, while still only writing to disk when the user chooses Apply.

importer_edit is also updated to rely on this rollback behavior when edits are cancelled, rather than re-reading from disk, so interactive imports resume with the same in-memory metadata they started with.

To Do

  • Documentation
  • Changelog (Entry added under the Unreleased “Bug fixes” section.)
  • Tests (Existing edit plugin tests exercised and extended to cover the updated cancel/rollback behavior.)

@gabe-push gabe-push marked this pull request as ready for review December 3, 2025 01:27
@gabe-push gabe-push requested a review from a team as a code owner December 3, 2025 01:27
Copilot AI review requested due to automatic review settings December 3, 2025 01:27
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Double‑check the apply_data(objs, new_data, old_data) calls used for cancel / continue flows actually restore the original snapshot and don’t accidentally re‑apply edited values (the parameter order and behavior of apply_data are critical here).
  • Given that importer_edit now returns Action.RETAG in one branch and None in the other, verify that all callers correctly handle both return types and that the new None path really preserves the expected import state without triggering unintended follow‑up actions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Double‑check the `apply_data(objs, new_data, old_data)` calls used for cancel / continue flows actually restore the original snapshot and don’t accidentally re‑apply edited values (the parameter order and behavior of `apply_data` are critical here).
- Given that `importer_edit` now returns `Action.RETAG` in one branch and `None` in the other, verify that all callers correctly handle both return types and that the new `None` path really preserves the expected import state without triggering unintended follow‑up actions.

## Individual Comments

### Comment 1
<location> `beetsplug/edit.py:382-389` </location>
<code_context>
    def importer_edit(self, session, task):
        """Callback for invoking the functionality during an interactive
        import session on the *original* item tags.
        """
        # Assign negative temporary ids to Items that are not in the database
        # yet. By using negative values, no clash with items in the database
        # can occur.
        for i, obj in enumerate(task.items, start=1):
            # The importer may set the id to None when re-importing albums.
            if not obj._db or obj.id is None:
                obj.id = -i

        # Present the YAML to the user and let them change it.
        fields = self._get_fields(album=False, extra=[])
        success = self.edit_objects(task.items, fields)

        # Remove temporary ids.
        for obj in task.items:
            if obj.id < 0:
                obj.id = None

        # Save the new data.
        if success:
            # Return Action.RETAG, which makes the importer write the tags
            # to the files if needed without re-applying metadata.
            return Action.RETAG
        else:
            # Edit cancelled / no edits made. `edit_objects` has already
            # restored each object to its original in-memory state, so there
            # is nothing more to do here. Returning None lets the importer
            # resume the candidate prompt.
            return None

</code_context>

<issue_to_address>
**suggestion (code-quality):** Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))

```suggestion
        return Action.RETAG if success else None
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 380 to +389
# to the files if needed without re-applying metadata.
return Action.RETAG
else:
# Edit cancelled / no edits made. Revert changes.
for obj in task.items:
obj.read()
# Edit cancelled / no edits made. `edit_objects` has already
# restored each object to its original in-memory state, so there
# is nothing more to do here. Returning None lets the importer
# resume the candidate prompt.
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Replace if statement with if expression (assign-if-exp)

Suggested change
# to the files if needed without re-applying metadata.
return Action.RETAG
else:
# Edit cancelled / no edits made. Revert changes.
for obj in task.items:
obj.read()
# Edit cancelled / no edits made. `edit_objects` has already
# restored each object to its original in-memory state, so there
# is nothing more to do here. Returning None lets the importer
# resume the candidate prompt.
return None
return Action.RETAG if success else None

Copilot finished reviewing on behalf of gabe-push December 3, 2025 01:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug where temporary tags injected by the fromfilename plugin (and potentially other plugins) were silently discarded when cancelling edit sessions during interactive imports. The fix ensures that the edit plugin restores objects to their original in-memory state rather than reloading from disk, preserving plugin-provided metadata across cancelled edit sessions.

Key Changes:

  • Modified the cancel and "continue Editing" flows to restore the original in-memory state using apply_data(objs, new_data, old_data)
  • Updated importer_edit to return None instead of re-reading from disk when edits are cancelled, allowing the importer to resume with preserved metadata

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
docs/changelog.rst Documents the bug fix for preserving temporary tags from fromfilename plugin during cancelled edit sessions
beetsplug/edit.py Implements the fix by reverting to original in-memory state on cancel/continue-editing, and removes the disk reload logic from importer_edit

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@codecov
Copy link

codecov bot commented Dec 3, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.96%. Comparing base (2bd77b9) to head (e179281).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6200      +/-   ##
==========================================
+ Coverage   67.93%   67.96%   +0.02%     
==========================================
  Files         137      137              
  Lines       18677    18674       -3     
  Branches     3155     3152       -3     
==========================================
+ Hits        12688    12691       +3     
+ Misses       5324     5318       -6     
  Partials      665      665              
Files with missing lines Coverage Δ
beetsplug/edit.py 85.62% <100.00%> (+3.27%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bad interaction: fromfilename and edit plugins

1 participant