From a8439e2d0747762a8086d53c7a2f4ba6894ca042 Mon Sep 17 00:00:00 2001 From: Qalipso Date: Sun, 26 Jul 2026 00:49:24 -0300 Subject: [PATCH 1/2] Fix unique_path counter for names ending in two or more digits unique_path parses a trailing counter so it can continue from it, but the pattern is `\.(\d)+$` -- a single-digit group repeated, not a multi-digit group. group(1) is therefore the last digit only, and the counter restarts from it: track.10.mp3 -> track.1.mp3 (expected track.11.mp3) track.12.mp3 -> track.3.mp3 track.123.mp3 -> track.4.mp3 The returned path still does not exist, so nothing is overwritten, but the new name sorts before the file it was derived from, and the scan restarts from a low number when the neighbours are already taken. Use `\.(\d+)$`. Existing coverage only exercised a single-digit counter (`x.1.mp3`), which is why this held. --- beets/util/__init__.py | 2 +- docs/changelog.rst | 4 ++++ test/test_util.py | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index fd998ddef..e12264b9d 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -644,7 +644,7 @@ def unique_path(path: AnyStr) -> AnyStr: byte_path = os.fsencode(path) base, ext = os.path.splitext(byte_path) - match = re.search(rb"\.(\d)+$", base) + match = re.search(rb"\.(\d+)$", base) if match: num = int(match.group(1)) base = base[: match.start()] diff --git a/docs/changelog.rst b/docs/changelog.rst index 75bb94499..2ca8ac6b0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -125,6 +125,10 @@ Bug fixes valid date/time string" error instead of crashing with an uncaught ``KeyError``. A ``|`` was being accepted as a relative-date unit due to a regular expression character-class typo. +- Deduplicating a file whose name already ends in a counter of two or more + digits no longer restarts the numbering: ``track.10.mp3`` now yields + ``track.11.mp3`` instead of ``track.1.mp3``. The counter was matched with + ``\.(\d)+$``, which captures only the final digit. .. For plugin developers diff --git a/test/test_util.py b/test/test_util.py index f2c9f5c4b..f08d9b47a 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -428,6 +428,11 @@ class UniquePathTest(BeetsTestCase): path = util.unique_path(self.base / "x.1.mp3") assert path == str(self.base / "x.3.mp3") + def test_conflicting_file_with_multi_digit_number_increases_number(self): + (self.base / "w.10.mp3").touch() + path = util.unique_path(self.base / "w.10.mp3") + assert path == str(self.base / "w.11.mp3") + class MkDirAllTest(BeetsTestCase): def test_mkdirall(self): From 5d2d3993a78a29937d345bdffd173b27b1ca4a4f Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Thu, 30 Jul 2026 12:53:21 +0200 Subject: [PATCH 2/2] Moved changelog entry to the current unreleased section. --- docs/changelog.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2ca8ac6b0..bfb97468d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -19,6 +19,10 @@ Bug fixes - A date range query written back to front (for example ``added:2024..2020``) no longer crashes with an uncaught ``ValueError``. The endpoints are now swapped, so such a range means the same as ``added:2020..2024``. +- Deduplicating a file whose name already ends in a counter of two or more + digits no longer restarts the numbering: ``track.10.mp3`` now yields + ``track.11.mp3`` instead of ``track.1.mp3``. The counter was matched with + ``\.(\d)+$``, which captures only the final digit. .. For plugin developers @@ -125,10 +129,6 @@ Bug fixes valid date/time string" error instead of crashing with an uncaught ``KeyError``. A ``|`` was being accepted as a relative-date unit due to a regular expression character-class typo. -- Deduplicating a file whose name already ends in a counter of two or more - digits no longer restarts the numbering: ``track.10.mp3`` now yields - ``track.11.mp3`` instead of ``track.1.mp3``. The counter was matched with - ``\.(\d)+$``, which captures only the final digit. .. For plugin developers