From 6702173b71c4d0621299989bc4625df7da0a2651 Mon Sep 17 00:00:00 2001 From: Jan-Jaap Driessen Date: Fri, 3 Jul 2026 13:50:20 +0200 Subject: [PATCH] Allow a later source option to override an earlier one A duplicate key in a ``[sources]`` entry used to raise "Key '...' already in source info."; now the later value wins, so a buildout ``+=`` addition can refine a value from a shared or extended source definition, e.g. ``foo += branch=my-feature`` overriding a ``branch`` set upstream. Fixes https://github.com/fschulze/mr.developer/issues/125 --- CHANGES.rst | 7 ++++++- src/mr/developer/extension.py | 6 +++++- src/mr/developer/tests/test_extension.py | 13 +++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 10c0f2c..9495b02 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,12 @@ Changelog 2.0.5 (unreleased) ------------------ -- Nothing changed yet. +- Allow a later source option to override an earlier one instead of raising + ``ValueError: Key '...' already in source info.``. This lets a ``[sources]`` + entry refine a shared/extended definition, e.g. ``foo += branch=my-feature`` + overriding the ``branch`` set upstream. Fixes `#125 + `_. + [janjaapdriessen] 2.0.4 (2025-07-17) diff --git a/src/mr/developer/extension.py b/src/mr/developer/extension.py index 8e752fa..6c64ef8 100644 --- a/src/mr/developer/extension.py +++ b/src/mr/developer/extension.py @@ -115,7 +115,11 @@ def get_sources(self): if not key: raise ValueError("Option with no name '%s'." % option) if key in source: - raise ValueError("Key '%s' already in source info." % key) + # A later option overrides an earlier one, e.g. + # ``foo += branch=my-feature`` refining the ``branch`` set + # in a shared/extended source definition. + logger.info( + "Overriding '%s' for source '%s'." % (key, name)) if key == 'path': value = os.path.join(value, name) if not os.path.isabs(value): diff --git a/src/mr/developer/tests/test_extension.py b/src/mr/developer/tests/test_extension.py index 0ebdb80..f8a9b53 100644 --- a/src/mr/developer/tests/test_extension.py +++ b/src/mr/developer/tests/test_extension.py @@ -218,15 +218,16 @@ def testOptionParsingBeforeURL(self, buildout, extension): assert sources['pkg.foo']['rev'] == '>=456ad138' def testDuplicateOptionParsing(self, buildout, extension): + # A duplicate option is not an error: the later value wins, so a + # ``+=`` addition can override a value from a shared source definition + # (e.g. ``pkg.foo += branch=feature``). buildout['sources'].update({ 'pkg.foo': 'git dummy://foo/trunk rev=456ad138 rev=blubber', + 'pkg.bar': 'git dummy://bar branch=main branch=feature', }) - pytest.raises(ValueError, extension.get_sources) - - buildout['sources'].update({ - 'pkg.foo': 'git dummy://foo/trunk kind=svn', - }) - pytest.raises(ValueError, extension.get_sources) + sources = extension.get_sources() + assert sources['pkg.foo']['rev'] == 'blubber' + assert sources['pkg.bar']['branch'] == 'feature' def testInvalidOptionParsing(self, buildout, extension): buildout['sources'].update({