Skip to content

Merge labels #19

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

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ You can make the following changes to labels for your repo:
labels file 🗑
- You can **edit** a label by changing the value for one or more parameters for
that label 🎨
- You can **merge** one label to another by setting the ``name`` of a label to
that of an existing label. The merged label will be deleted.
- You can **create** a new label by adding a new section with your desired
parameters 📝

Expand All @@ -137,15 +139,18 @@ labels sync -n -o hackebrot -r pytest-emoji
```

```text
This would delete the following labels:
- dependencies
This would merge the following labels:
- dependencies to dependency
This would update the following labels:
- bug
- good first issue
This would delete the following labels:
- dependencies
This would create the following labels:
- duplicate
This would NOT modify the following labels:
- code quality
- dependency
- docs
```

Expand Down
53 changes: 40 additions & 13 deletions src/labels/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,9 @@ def sync_cmd(
On success this will also update the local labels file, so that section
names match the `name` parameter.
"""
labels_to_delete = {}
labels_to_merge = {}
labels_to_update = {}
labels_to_delete = {}
labels_to_create = {}
labels_to_ignore = {}

Expand All @@ -187,7 +188,12 @@ def sync_cmd(
if local_label.params_dict == remote_label.params_dict:
labels_to_ignore[remote_name] = local_label
else:
labels_to_update[remote_name] = local_label
if ((remote_name != local_label.name)
and (local_label.name in remote_labels)):
# There is already a label with this name
labels_to_merge[remote_name] = local_label.name
else:
labels_to_update[remote_name] = local_label
else:
if remote_name == local_label.name:
labels_to_create[local_label.name] = local_label
Expand All @@ -198,7 +204,7 @@ def sync_cmd(
f'parameter: "{local_label.name}"',
err=True,
)
sys.exit(1)
labels_to_ignore[remote_name] = local_label

for remote_name, remote_label in remote_labels.items():
if remote_name in labels_to_update:
Expand All @@ -212,18 +218,25 @@ def sync_cmd(
if dryrun:
# Do not modify remote labels, but only print info
dryrun_echo(
labels_to_delete, labels_to_update, labels_to_create, labels_to_ignore
labels_to_merge,
labels_to_update,
labels_to_delete,
labels_to_create,
labels_to_ignore
)
sys.exit(0)
# sys.exit(0)
return

failures = []

for name in labels_to_delete.keys():
# Merge has to occur before update and delete
for old_label, new_label in labels_to_merge.items():
try:
context.client.delete_label(repository, name=name)
context.client.merge_label(
repository, old_label=old_label, new_label=new_label)
except LabelsException as exc:
click.echo(str(exc), err=True)
failures.append(name)
failures.append(old_label)

for name, label in labels_to_update.items():
try:
Expand All @@ -232,6 +245,13 @@ def sync_cmd(
click.echo(str(exc), err=True)
failures.append(name)

for name in labels_to_delete.keys():
try:
context.client.delete_label(repository, name=name)
except LabelsException as exc:
click.echo(str(exc), err=True)
failures.append(name)

for name, label in labels_to_create.items():
try:
context.client.create_label(repository, label=label)
Expand All @@ -253,23 +273,30 @@ def sync_cmd(


def dryrun_echo(
labels_to_delete: Labels_Dict,
labels_to_merge: dict,
labels_to_update: Labels_Dict,
labels_to_delete: Labels_Dict,
labels_to_create: Labels_Dict,
labels_to_ignore: Labels_Dict,
) -> None:
"""Print information about how labels would be updated on sync."""

if labels_to_delete:
click.echo(f"This would delete the following labels:")
for name in labels_to_delete:
click.echo(f" - {name}")
if labels_to_merge:
click.echo(f"This would merge the following labels:")
for name in labels_to_merge:
click.echo(f"""\
- {', '.join([f"'{old}' to '{new}'" for old, new in labels_to_merge.items()])}""")

if labels_to_update:
click.echo(f"This would update the following labels:")
for name in labels_to_update:
click.echo(f" - {name}")

if labels_to_delete:
click.echo(f"This would delete the following labels:")
for name in labels_to_delete:
click.echo(f" - {name}")

if labels_to_create:
click.echo(f"This would create the following labels:")
for name in labels_to_create:
Expand Down
70 changes: 70 additions & 0 deletions src/labels/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,76 @@ def edit_label(self, repo: Repository, *, name: str, label: Label) -> Label:

return Label(**response.json())

def merge_label(self, repo: Repository, *, old_label: str, new_label: str) -> None:
"""Merge a GitHub issue label to an existing label.

- Add the target label to all issues with the old label.
- The old label will be deleted while processing labels to delete.
"""
logger = logging.getLogger("labels")
logger.debug(f"Merging label '{old_label}' to '{new_label}' in {repo.owner}/{repo.name}") # noqa: E501

response = self.session.get(
f"{self.base_url}/search/issues?q=label:{old_label}+repo:{repo.owner}/{repo.name}", # noqa: E501
headers={"Accept": "application/vnd.github.symmetra-preview+json"},
)

if response.status_code != 200:
raise GitHubException(
f"Error retrieving issues for label {old_label} in {repo.owner}/{repo.name}: " # noqa: E501
f"{response.status_code} - "
f"{response.reason}"
)

json = response.json()

next_page = response.links.get('next', None)
while next_page:
logger.debug(f"Requesting {next_page}")
response = self.session.get(
next_page['url'],
headers={"Accept": "application/vnd.github.symmetra-preview+json"},
)

if response.status_code != 200:
raise GitHubException(
f"Error retrieving next page of issues for label {old_label}: "
f"{response.status_code} - "
f"{response.reason}"
)

json.extend(response.json())
next_page = response.links.get('next', None)

for issue in json['items']:
response = self.session.get(
f"{self.base_url}/repos/{repo.owner}/{repo.name}/issues/{issue['number']}/labels", # noqa: E501
headers={"Accept": "application/vnd.github.symmetra-preview+json"},
)

if response.status_code != 200:
raise GitHubException(
f"Error retrieving labels for {repo.owner}/{repo.name}/issue/{issue['number']}: " # noqa: E501
f"{response.status_code} - "
f"{response.reason}"
)

labels = [l['name'] for l in response.json()]

if new_label not in labels:
response = self.session.post(
f"{self.base_url}/repos/{repo.owner}/{repo.name}/issues/{issue['number']}/labels", # noqa: E501
headers={"Accept": "application/vnd.github.symmetra-preview+json"},
json={'labels': [f"{new_label}"]},
)
if response.status_code != 200:
raise GitHubException(
f"Error adding '{new_label}' for issue {repo.owner}/{repo.name}/issues/{issue['number']}: " # noqa: E501
f"{response.status_code} - "
f"{response.reason}"
)
logger.debug(f"Added label '{new_label}' to {repo.owner}/{repo.name}/issue/{issue['number']}") # noqa: E501

def delete_label(self, repo: Repository, *, name: str) -> None:
"""Delete a GitHub issue label.

Expand Down
4 changes: 2 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,10 @@ def test_sync_dryrun(
assert result.exit_code == 0

output = (
"This would delete the following labels:\n"
" - infra\n"
"This would update the following labels:\n"
" - bug\n"
"This would delete the following labels:\n"
" - infra\n"
"This would create the following labels:\n"
" - dependencies\n"
"This would NOT modify the following labels:\n"
Expand Down