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..a4e8ec4 100644 --- a/src/labels/cli.py +++ b/src/labels/cli.py @@ -151,25 +151,28 @@ 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, + 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 = {} labels_to_ignore = {} - local_labels = read_labels(filename) + local_labels = {} + for file in filename: + local_labels.update(read_labels(file)) repository = Repository(owner, repo) @@ -244,15 +247,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..52db0e9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -204,3 +204,31 @@ 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=["sync_multiple"], +) +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" -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