From 145a912193675d4a221b57bb5138705387288a71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 13:48:41 +0000 Subject: [PATCH 1/4] Initial plan From 4a2a7b979d15587c942be4ea61b26ab8ecbec45f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 14:00:48 +0000 Subject: [PATCH 2/4] refactor changelog current sentinel for entries layout Agent-Logs-Url: https://github.com/envoyproxy/toolshed/sessions/77330e05-f689-47e3-92fc-89612b89f5e5 Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- .../base/utils/abstract/project/changelog.py | 22 +++- .../tests/test_abstract_project_changelogs.py | 114 ++++++++++++++++-- .../envoy/code/check/abstract/changelog.py | 3 +- .../tests/test_abstract_changelog.py | 3 +- 4 files changed, 125 insertions(+), 17 deletions(-) diff --git a/py/envoy.base.utils/envoy/base/utils/abstract/project/changelog.py b/py/envoy.base.utils/envoy/base/utils/abstract/project/changelog.py index 2166f46bc..a0d280610 100644 --- a/py/envoy.base.utils/envoy/base/utils/abstract/project/changelog.py +++ b/py/envoy.base.utils/envoy/base/utils/abstract/project/changelog.py @@ -254,6 +254,16 @@ def changelog_class(self) -> type[interface.IChangelog]: @cached_property def changelog_paths(self) -> typing.ChangelogPathsDict: + if self._entries_layout: + historical_paths = self.project.path.glob(CHANGELOG_PATH_GLOB) + current_version = _version.Version( + self.project.version.base_version) + return { + **{ + self._version_from_path(path): path + for path + in historical_paths}, + current_version: self.current_dir_path} return { self._version_from_path(path): path for path @@ -263,7 +273,11 @@ def changelog_paths(self) -> typing.ChangelogPathsDict: def changelogs(self) -> typing.ChangelogsDict: return { k: self.changelog_class( - self.project, k, self.changelog_paths[k],) + self.project, + k, + (self.current_path + if self._entries_layout and self.project.is_current(k) + else self.changelog_paths[k]),) for k in reversed(sorted(self.changelog_paths.keys()))} @@ -299,9 +313,11 @@ async def is_pending(self) -> bool: @property def paths(self) -> tuple[pathlib.Path, ...]: + paths = self.project.path.glob(CHANGELOG_PATH_GLOB) return ( - *self.project.path.glob(CHANGELOG_PATH_GLOB), - self.current_path) + (*paths, self.current_dir_path) + if self._entries_layout + else (*paths, self.current_path)) @property def rel_current_path(self) -> pathlib.Path: diff --git a/py/envoy.base.utils/tests/test_abstract_project_changelogs.py b/py/envoy.base.utils/tests/test_abstract_project_changelogs.py index babc53d00..35c0917ef 100644 --- a/py/envoy.base.utils/tests/test_abstract_project_changelogs.py +++ b/py/envoy.base.utils/tests/test_abstract_project_changelogs.py @@ -88,22 +88,48 @@ def test_abstract_changelogs_dunder_iter(iters, patches): == clogs) -def test_abstract_changelogs_changelog_paths(iters, patches): - changelogs = DummyChangelogs("PROJECT") +@pytest.mark.parametrize("entries_layout", [True, False]) +def test_abstract_changelogs_changelog_paths(iters, patches, entries_layout): + project = MagicMock() + project.version.base_version = "1.2.3" + changelogs = DummyChangelogs(project) patched = patches( + ("AChangelogs._entries_layout", + dict(new_callable=PropertyMock)), + ("AChangelogs.current_dir_path", + dict(new_callable=PropertyMock)), ("AChangelogs.paths", dict(new_callable=PropertyMock)), "AChangelogs._version_from_path", prefix="envoy.base.utils.abstract.project.changelog") paths = iters(cb=lambda x: f"P{x}") - with patched as (m_paths, m_version): - m_paths.return_value = paths + with patched as (m_entries, m_current_dir_path, m_paths, m_version): + m_entries.return_value = entries_layout m_version.side_effect = lambda x: f"P{x}" - assert ( - changelogs.changelog_paths - == {f"P{p}": p - for p in paths}) + if entries_layout: + project.path.glob.return_value = paths + assert ( + changelogs.changelog_paths + == { + **{ + f"P{p}": p + for p + in paths}, + abstract.project.changelog._version.Version( + project.version.base_version): + m_current_dir_path.return_value}) + assert not m_paths.called + assert ( + project.path.glob.call_args + == [(abstract.project.changelog.CHANGELOG_PATH_GLOB, ), {}]) + else: + m_paths.return_value = paths + assert ( + changelogs.changelog_paths + == {f"P{p}": p + for p in paths}) + assert not project.path.glob.called assert ( m_version.call_args_list @@ -116,6 +142,8 @@ def test_abstract_changelogs_changelogs(iters, patches): patched = patches( "reversed", "sorted", + ("AChangelogs._entries_layout", + dict(new_callable=PropertyMock)), ("AChangelogs.changelog_class", dict(new_callable=PropertyMock)), ("AChangelogs.changelog_paths", @@ -123,8 +151,9 @@ def test_abstract_changelogs_changelogs(iters, patches): prefix="envoy.base.utils.abstract.project.changelog") paths = iters(cb=lambda x: f"P{x}") - with patched as (m_rev, m_sort, m_class, m_paths): + with patched as (m_rev, m_sort, m_entries, m_class, m_paths): m_rev.return_value = paths + m_entries.return_value = False assert ( changelogs.changelogs == {p: m_class.return_value.return_value @@ -150,6 +179,58 @@ def test_abstract_changelogs_changelogs(iters, patches): assert "changelogs" in changelogs.__dict__ +def test_abstract_changelogs_changelogs_entries_layout_current_path(patches): + project = MagicMock() + changelogs = DummyChangelogs(project) + current_version = abstract.project.changelog._version.Version("1.2.3") + historical_version = abstract.project.changelog._version.Version("1.2.2") + patched = patches( + "reversed", + "sorted", + ("AChangelogs._entries_layout", + dict(new_callable=PropertyMock)), + ("AChangelogs.changelog_class", + dict(new_callable=PropertyMock)), + ("AChangelogs.changelog_paths", + dict(new_callable=PropertyMock)), + ("AChangelogs.current_path", + dict(new_callable=PropertyMock)), + prefix="envoy.base.utils.abstract.project.changelog") + + with patched as ( + m_reversed, m_sorted, m_entries, m_class, m_paths, + m_current_path): + m_entries.return_value = True + project.is_current.side_effect = ( + lambda version: version == current_version) + m_paths.return_value = { + current_version: MagicMock(name="CURRENT_DIR_PATH"), + historical_version: MagicMock(name="HISTORICAL_PATH")} + m_reversed.return_value = [current_version, historical_version] + assert ( + changelogs.changelogs + == { + current_version: m_class.return_value.return_value, + historical_version: m_class.return_value.return_value}) + + assert ( + m_class.return_value.call_args_list + == [[(project, current_version, m_current_path.return_value), {}], + [(project, + historical_version, + m_paths.return_value[historical_version]), {}]]) + assert ( + project.is_current.call_args_list + == [[(current_version, ), {}], + [(historical_version, ), {}]]) + assert ( + m_sorted.call_args + == [(m_paths.return_value.keys(), ), {}]) + assert ( + m_reversed.call_args + == [(m_sorted.return_value, ), {}]) + + def test_abstract_changelogs_current(patches): changelogs = DummyChangelogs("PROJECT") patched = patches( @@ -276,20 +357,29 @@ async def test_abstract_changelogs_is_pending(patches, pending): assert "is_pending" not in changelogs.__dict__ -def test_abstract_changelogs_paths(iters, patches): +@pytest.mark.parametrize("entries_layout", [True, False]) +def test_abstract_changelogs_paths(iters, patches, entries_layout): project = MagicMock() changelogs = DummyChangelogs(project) patched = patches( + ("AChangelogs._entries_layout", + dict(new_callable=PropertyMock)), + ("AChangelogs.current_dir_path", + dict(new_callable=PropertyMock)), ("AChangelogs.current_path", dict(new_callable=PropertyMock)), prefix="envoy.base.utils.abstract.project.changelog") paths = iters() project.path.glob.return_value = paths - with patched as (m_path, ): + with patched as (m_entries, m_dir_path, m_path): + m_entries.return_value = entries_layout assert ( changelogs.paths - == (*paths, m_path.return_value)) + == (*paths, ( + m_dir_path.return_value + if entries_layout + else m_path.return_value))) assert ( project.path.glob.call_args == [(abstract.project.changelog.CHANGELOG_PATH_GLOB, ), {}]) diff --git a/py/envoy.code.check/envoy/code/check/abstract/changelog.py b/py/envoy.code.check/envoy/code/check/abstract/changelog.py index 165845b40..111088c07 100644 --- a/py/envoy.code.check/envoy/code/check/abstract/changelog.py +++ b/py/envoy.code.check/envoy/code/check/abstract/changelog.py @@ -340,7 +340,8 @@ def check_version(self) -> tuple[str, ...]: if self.duplicate_current: errors.append( "Duplicate current version file. " - "Only `current.yaml` should exist for the current version " + "A `changelogs/{version}.yaml` exists alongside the " + "in-flight current changelog " f"({self.project.version.base_version})") elif self.version_higher_than_current: errors.append( diff --git a/py/envoy.code.check/tests/test_abstract_changelog.py b/py/envoy.code.check/tests/test_abstract_changelog.py index 7c655632a..45e75462f 100644 --- a/py/envoy.code.check/tests/test_abstract_changelog.py +++ b/py/envoy.code.check/tests/test_abstract_changelog.py @@ -552,7 +552,8 @@ def test_changelogstatus_check_version( if duplicate_current: expected.append( "Duplicate current version file. " - "Only `current.yaml` should exist for the current version " + "A `changelogs/{version}.yaml` exists alongside the " + "in-flight current changelog " f"({m_project.return_value.version.base_version})") elif version_higher_than_current: expected.append( From 149ad70ff46ad89a73e768f6b5a87c43100f21f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 14:03:16 +0000 Subject: [PATCH 3/4] fix duplicate-current message interpolation Agent-Logs-Url: https://github.com/envoyproxy/toolshed/sessions/77330e05-f689-47e3-92fc-89612b89f5e5 Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- py/envoy.code.check/envoy/code/check/abstract/changelog.py | 5 +++-- py/envoy.code.check/tests/test_abstract_changelog.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/py/envoy.code.check/envoy/code/check/abstract/changelog.py b/py/envoy.code.check/envoy/code/check/abstract/changelog.py index 111088c07..005c59eae 100644 --- a/py/envoy.code.check/envoy/code/check/abstract/changelog.py +++ b/py/envoy.code.check/envoy/code/check/abstract/changelog.py @@ -340,8 +340,9 @@ def check_version(self) -> tuple[str, ...]: if self.duplicate_current: errors.append( "Duplicate current version file. " - "A `changelogs/{version}.yaml` exists alongside the " - "in-flight current changelog " + "A `changelogs/" + f"{self.project.version.base_version}.yaml` exists alongside " + "the in-flight current changelog " f"({self.project.version.base_version})") elif self.version_higher_than_current: errors.append( diff --git a/py/envoy.code.check/tests/test_abstract_changelog.py b/py/envoy.code.check/tests/test_abstract_changelog.py index 45e75462f..39cbb5013 100644 --- a/py/envoy.code.check/tests/test_abstract_changelog.py +++ b/py/envoy.code.check/tests/test_abstract_changelog.py @@ -552,7 +552,9 @@ def test_changelogstatus_check_version( if duplicate_current: expected.append( "Duplicate current version file. " - "A `changelogs/{version}.yaml` exists alongside the " + "A `changelogs/" + f"{m_project.return_value.version.base_version}.yaml` exists " + "alongside the " "in-flight current changelog " f"({m_project.return_value.version.base_version})") elif version_higher_than_current: From 25c8e55693aec154284667afeb7c92479db1282b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 14:05:48 +0000 Subject: [PATCH 4/4] polish duplicate-current wording for directory layout Agent-Logs-Url: https://github.com/envoyproxy/toolshed/sessions/77330e05-f689-47e3-92fc-89612b89f5e5 Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- py/envoy.code.check/envoy/code/check/abstract/changelog.py | 2 +- py/envoy.code.check/tests/test_abstract_changelog.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/py/envoy.code.check/envoy/code/check/abstract/changelog.py b/py/envoy.code.check/envoy/code/check/abstract/changelog.py index 005c59eae..72315b1fb 100644 --- a/py/envoy.code.check/envoy/code/check/abstract/changelog.py +++ b/py/envoy.code.check/envoy/code/check/abstract/changelog.py @@ -342,7 +342,7 @@ def check_version(self) -> tuple[str, ...]: "Duplicate current version file. " "A `changelogs/" f"{self.project.version.base_version}.yaml` exists alongside " - "the in-flight current changelog " + "`changelogs/current/` for the in-flight changelog " f"({self.project.version.base_version})") elif self.version_higher_than_current: errors.append( diff --git a/py/envoy.code.check/tests/test_abstract_changelog.py b/py/envoy.code.check/tests/test_abstract_changelog.py index 39cbb5013..70246d0a5 100644 --- a/py/envoy.code.check/tests/test_abstract_changelog.py +++ b/py/envoy.code.check/tests/test_abstract_changelog.py @@ -554,8 +554,8 @@ def test_changelogstatus_check_version( "Duplicate current version file. " "A `changelogs/" f"{m_project.return_value.version.base_version}.yaml` exists " - "alongside the " - "in-flight current changelog " + "alongside " + "`changelogs/current/` for the in-flight changelog " f"({m_project.return_value.version.base_version})") elif version_higher_than_current: expected.append(