From 02c8b817e8349d72ce4bbdee95e08d717641ab49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20R=C3=A9gent?= Date: Tue, 2 Apr 2024 11:15:51 +0200 Subject: [PATCH 1/5] Add ability to sync multiple files --- README.md | 7 +++++-- src/labels/cli.py | 24 +++++++++++------------- tests/conftest.py | 25 ++++++++++++++++++++++++- tests/other-sync.toml | 4 ++++ tests/test_cli.py | 27 +++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 tests/other-sync.toml diff --git a/README.md b/README.md index 99120f2..078d196 100644 --- a/README.md +++ b/README.md @@ -150,8 +150,11 @@ This would NOT modify the following labels: - docs ``` -Running ``labels sync`` without the ``dryrun`` option also updates the labels -file, so that section names match the ``name`` parameter. +You can also synchronize your repository with labels defined in multiple files: + +```text +labels sync -o hackebrot -r pytest-emoji -f issue-labels.toml -f release-labels.toml +``` If **labels** encounters any errors while sending requests to the GitHub API, it will print information about the failure and continue with the next label diff --git a/src/labels/cli.py b/src/labels/cli.py index e5f0d69..5a68103 100644 --- a/src/labels/cli.py +++ b/src/labels/cli.py @@ -151,13 +151,18 @@ def fetch_cmd(context: LabelsContext, owner: str, repo: str, filename: str) -> N @click.option( "-f", "--filename", - help="Filename for labels", - default="labels.toml", + help="Filenames for labels", + default=["labels.toml"], type=click.Path(exists=True), required=True, + multiple=True, ) def sync_cmd( - context: LabelsContext, owner: str, repo: str, filename: str, dryrun: bool + context: LabelsContext, + owner: str, + repo: str, + filenames: typing.List[str], + dryrun: bool, ) -> None: """Sync labels with a GitHub repository. @@ -169,7 +174,9 @@ def sync_cmd( labels_to_create = {} labels_to_ignore = {} - local_labels = read_labels(filename) + local_labels = {} + for filename in filenames: + local_labels.update(read_labels(filename)) repository = Repository(owner, repo) @@ -244,15 +251,6 @@ def sync_cmd( if failures: sys.exit(1) - # Make sure to write the local labels file to update TOML sections - write_labels( - filename, - sorted( - local_labels.values(), - key=operator.attrgetter("name", "description", "color"), - ), - ) - def dryrun_echo( labels_to_delete: Labels_Dict, diff --git a/tests/conftest.py b/tests/conftest.py index 6f150c6..615c1fe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -290,7 +290,7 @@ def fixture_mock_delete_label( def fixture_mock_sync( base_url: str, repo_owner: str, repo_name: str, response_list_labels: ResponseLabels ) -> Generator: - with responses.RequestsMock() as rsps: + with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: # Response mock for when sync requests the existing remote labels rsps.add( responses.GET, @@ -317,6 +317,23 @@ def fixture_mock_sync( content_type="application/json", ) + # Response mock for when sync creates the "other" label + rsps.add( + responses.POST, + f"{base_url}/repos/{repo_owner}/{repo_name}/labels", + json={ + "id": 8080, + "node_id": "4848", + "url": f"{base_url}/repos/{repo_owner}/{repo_name}/labels/other", + "name": "other", + "description": "Some other label", + "color": "000000", + "default": False, + }, + status=201, + content_type="application/json", + ) + # Response mock for when sync edits the "bug" label rsps.add( responses.PATCH, @@ -455,3 +472,9 @@ def fixture_labels_file_load() -> str: def fixture_labels_file_sync(tmpdir: Any) -> str: """Return a filepath to an existing labels file for the sync test.""" return "tests/sync.toml" + + +@pytest.fixture(name="other_labels_file_sync") +def fixture_other_labels_file_sync() -> str: + """Return a filepath to another existing labels file for the sync test.""" + return "tests/other-sync.toml" diff --git a/tests/other-sync.toml b/tests/other-sync.toml new file mode 100644 index 0000000..e82b6e7 --- /dev/null +++ b/tests/other-sync.toml @@ -0,0 +1,4 @@ +[other] +color = "000000" +name = "other" +description = "Some other label" diff --git a/tests/test_cli.py b/tests/test_cli.py index cae7e59..734d6e2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -204,3 +204,30 @@ def test_sync_dryrun( " - docs\n" ) assert output in result.output + + +@pytest.mark.usefixtures("mock_sync", "mock_repo_info") +@pytest.mark.parametrize( + "repo_owner, repo_name, remote_url", + [("pytest-dev", "pytest", "git@github.com:hackebrot/pytest-emoji.git")], + ids=["override_owner_and_repo"], +) +def test_sync_multiple_files( + run_cli: typing.Callable, + repo_owner: str, + repo_name: str, + labels_file_sync: str, + other_labels_file_sync: str, +) -> None: + """Test that sync with multiple files works as designed.""" + result = run_cli( + f"-v sync -o {repo_owner} -r {repo_name} -f {labels_file_sync} -f {other_labels_file_sync}" + ) + assert result.exit_code == 0 + assert f"Requesting labels for {repo_owner}/{repo_name}" in result.output + assert f"Deleting label 'infra' for {repo_owner}/{repo_name}" in result.output + assert f"Editing label 'bug' for {repo_owner}/{repo_name}" in result.output + assert ( + f"Creating label 'dependencies' for {repo_owner}/{repo_name}" in result.output + ) + assert f"Creating label 'other' for {repo_owner}/{repo_name}" in result.output From 02274329534ae6056345a4c380f4052db68b3d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20R=C3=A9gent?= Date: Tue, 2 Apr 2024 18:13:52 +0200 Subject: [PATCH 2/5] Fix sync_multiple test id --- tests/test_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 734d6e2..32abfff 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -210,7 +210,7 @@ def test_sync_dryrun( @pytest.mark.parametrize( "repo_owner, repo_name, remote_url", [("pytest-dev", "pytest", "git@github.com:hackebrot/pytest-emoji.git")], - ids=["override_owner_and_repo"], + ids=["sync_multiple"], ) def test_sync_multiple_files( run_cli: typing.Callable, From 0312c99f191747982c59249bef37785cdfba3478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20R=C3=A9gent?= Date: Tue, 2 Apr 2024 18:19:00 +0200 Subject: [PATCH 3/5] Fix filename input in sync command --- src/labels/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/labels/cli.py b/src/labels/cli.py index 5a68103..635a811 100644 --- a/src/labels/cli.py +++ b/src/labels/cli.py @@ -161,7 +161,7 @@ def sync_cmd( context: LabelsContext, owner: str, repo: str, - filenames: typing.List[str], + filename: typing.List[str], dryrun: bool, ) -> None: """Sync labels with a GitHub repository. @@ -175,8 +175,8 @@ def sync_cmd( labels_to_ignore = {} local_labels = {} - for filename in filenames: - local_labels.update(read_labels(filename)) + for file in filename: + local_labels.update(read_labels(file)) repository = Repository(owner, repo) From b5cc1f5cf6547cc83ae623d65ee5b8651dab0992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20R=C3=A9gent?= Date: Tue, 2 Apr 2024 18:52:14 +0200 Subject: [PATCH 4/5] Removed the "local file update" from sync_cmd doc --- src/labels/cli.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/labels/cli.py b/src/labels/cli.py index 635a811..a4e8ec4 100644 --- a/src/labels/cli.py +++ b/src/labels/cli.py @@ -164,11 +164,7 @@ def sync_cmd( filename: typing.List[str], dryrun: bool, ) -> None: - """Sync labels with a GitHub repository. - - On success this will also update the local labels file, so that section - names match the `name` parameter. - """ + """Sync labels with a GitHub repository.""" labels_to_delete = {} labels_to_update = {} labels_to_create = {} From a29d119c7a8c1c105eb0bb73f09602ba818cde19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20R=C3=A9gent?= Date: Fri, 5 Apr 2024 14:14:21 +0200 Subject: [PATCH 5/5] Fix lint issue --- tests/test_cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 32abfff..52db0e9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -221,7 +221,8 @@ def test_sync_multiple_files( ) -> None: """Test that sync with multiple files works as designed.""" result = run_cli( - f"-v sync -o {repo_owner} -r {repo_name} -f {labels_file_sync} -f {other_labels_file_sync}" + f"-v sync -o {repo_owner} -r {repo_name}" + + f" -f {labels_file_sync} -f {other_labels_file_sync}" ) assert result.exit_code == 0 assert f"Requesting labels for {repo_owner}/{repo_name}" in result.output