Skip to content

Add ability to sync multiple files #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 12 additions & 18 deletions src/labels/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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,
Expand Down
25 changes: 24 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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"
4 changes: 4 additions & 0 deletions tests/other-sync.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[other]
color = "000000"
name = "other"
description = "Some other label"
28 changes: 28 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "[email protected]: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