mirror of
https://gh.wpcy.net/https://github.com/WeblateOrg/weblate.git
synced 2026-04-24 19:02:34 +08:00
156 lines
4.1 KiB
Python
Executable file
156 lines
4.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
# Copyright © Michal Čihař <michal@weblate.org>
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Literal
|
|
|
|
from git import Repo
|
|
|
|
import weblate.utils.version
|
|
|
|
if TYPE_CHECKING:
|
|
from git import Commit
|
|
from git.diff import Diff
|
|
|
|
VERSION = weblate.utils.version.VERSION_BASE
|
|
ROOT_DIR = Path(__file__).parent.parent
|
|
|
|
CategoryType = Literal["code", "translations", "docs"]
|
|
|
|
# Ignore known bots
|
|
IGNORE_AUTHORS: tuple[str, ...] = (
|
|
"GitHub",
|
|
"Anonymous",
|
|
"Hosted Weblate",
|
|
"Copilot",
|
|
"root",
|
|
)
|
|
|
|
# GitHub sometimes uses username instead of full name
|
|
MAP_AUTHORS: dict[str, str] = {
|
|
"nijel": "Michal Čihař",
|
|
"gersona": "Gersona",
|
|
"gers": "Gersona",
|
|
"KarenKonou": "Karen Konou",
|
|
}
|
|
|
|
CATEGORIES: dict[CategoryType, str] = {
|
|
"code": "Code contributions",
|
|
"translations": "Translations contributions",
|
|
"docs": "Documentation contributions",
|
|
}
|
|
|
|
TEMPLATE = """
|
|
{label}
|
|
.. only:: not gettext
|
|
|
|
{contributors}"""
|
|
|
|
|
|
def get_file_category(filename: str) -> CategoryType:
|
|
if filename.endswith((".po", ".pot")):
|
|
return "translations"
|
|
if filename.endswith(".rst") or filename.startswith("docs/"):
|
|
return "docs"
|
|
return "code"
|
|
|
|
|
|
def get_diff_categories(diff: list[Diff]) -> set[CategoryType]:
|
|
categories = set()
|
|
for item in diff:
|
|
if item.a_path:
|
|
categories.add(get_file_category(item.a_path))
|
|
if item.b_path:
|
|
categories.add(get_file_category(item.b_path))
|
|
|
|
return categories
|
|
|
|
|
|
def is_valid_author(author: str) -> bool:
|
|
return (
|
|
"(bot)" not in author
|
|
and "[bot]" not in author
|
|
and "add-on" not in author
|
|
and not author.startswith(IGNORE_AUTHORS)
|
|
)
|
|
|
|
|
|
def get_commit_authors(commit: Commit) -> set[str]:
|
|
authors: list[str] = [str(commit.author)]
|
|
if commit.committer:
|
|
authors.append(str(commit.committer))
|
|
authors.extend(
|
|
line[15:].split("<")[0].strip()
|
|
for line in str(commit.message).splitlines()
|
|
if line.lower().startswith("co-authored-by:")
|
|
)
|
|
return {
|
|
MAP_AUTHORS.get(author, author) for author in authors if is_valid_author(author)
|
|
}
|
|
|
|
|
|
def get_contributors() -> dict[CategoryType, list[str]]:
|
|
contributors: dict[CategoryType, list[str]] = defaultdict(list)
|
|
repo = Repo.init(ROOT_DIR)
|
|
tags = sorted(
|
|
(tag for tag in repo.tags if tag.name.startswith("weblate-")),
|
|
key=lambda tag: tag.commit.committed_date,
|
|
)
|
|
recent_tag = tags[-1]
|
|
commits = list(repo.iter_commits(f"{recent_tag}..HEAD"))
|
|
previous = recent_tag.object.object
|
|
for commit in reversed(commits):
|
|
authors = get_commit_authors(commit)
|
|
if authors:
|
|
diff = previous.diff(commit.tree)
|
|
for category in get_diff_categories(diff):
|
|
for author in authors:
|
|
if author not in contributors[category]:
|
|
contributors[category].append(author)
|
|
previous = commit.tree
|
|
return contributors
|
|
|
|
|
|
def get_contributors_text() -> str:
|
|
all_contributors = get_contributors()
|
|
output = []
|
|
|
|
for category, label in CATEGORIES.items():
|
|
contributors = all_contributors[category]
|
|
if contributors:
|
|
output.append(
|
|
TEMPLATE.format(label=label, contributors=", ".join(contributors))
|
|
)
|
|
|
|
return "\n".join(output)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(
|
|
prog="list-contributors.py",
|
|
description="Lists contributors in a repository since last tag",
|
|
)
|
|
parser.add_argument(
|
|
"-u", "--update", action="store_true", help="Updates docs/changes.rst"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
text = get_contributors_text()
|
|
if args.update:
|
|
changelog_file = (
|
|
ROOT_DIR / "docs" / "changes" / "contributors" / f"{VERSION}.rst"
|
|
)
|
|
changelog_file.write_text(f"{text}\n")
|
|
else:
|
|
print(text)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|