We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Change "master" branch to "main"
We should update all refs to the new default branch.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `scripts/release.py`
Content:
```
1 import argparse
2 import atexit
3 import collections
4 import json
5 import re
6 import sys
7 from pathlib import Path
8 
9 import github
10 import pygit2
11 from packaging import version
12 
13 
14 class GithubUserResolver:
15     def __init__(self, email2commit, token):
16         self.__cache_file = Path(__file__).parent / "gh-users-cache.json"
17         self.__cache = (
18             json.loads(self.__cache_file.read_text())
19             if self.__cache_file.exists()
20             else {}
21         )
22         # filter unknown users hoping we'd be able to find them this time
23         self.__cache = {key: value for key, value in self.__cache.items() if value}
24         # using anonymous access if token not specified
25         self.__github = github.Github(token or None)
26         self.__modin_repo = self.__github.get_repo("modin-project/modin")
27         self.__email2commit = email2commit
28         atexit.register(self.__save)
29 
30     def __search_commits(self, term):
31         if commit := self.__email2commit.get(term):
32             gh_commit = self.__modin_repo.get_commit(str(commit))
33             return gh_commit.author.login
34         return None
35 
36     @staticmethod
37     def __is_email(term):
38         return re.match(r".*@.*\..*", term)
39 
40     def __search_github(self, term):
41         search = f"in:email {term}" if self.__is_email(term) else f"fullname:{term}"
42         match = [user.login for user in self.__github.search_users(search)]
43         return match[0] if len(match) == 1 else None
44 
45     def __try_user(self, term):
46         if self.__is_email(term):
47             return None
48         try:
49             return self.__github.get_user(term).login
50         except github.GithubException as ex:
51             if ex.status != 404:
52                 raise
53             return None
54 
55     def __resolve_single(self, term):
56         return (
57             self.__search_commits(term)
58             or self.__search_github(term)
59             or self.__try_user(term)
60         )
61 
62     def __resolve_cache(self, name, email):
63         return self.__cache.get(f"{name} <{email}>", None)
64 
65     def __register(self, name, email, match):
66         self.__cache[f"{name} <{email}>"] = match
67 
68     def resolve(self, people):
69         logins, unknowns = set(), set()
70 
71         for name, email in people:
72             if match := self.__resolve_cache(name, email):
73                 logins.add(match)
74             elif match := self.__resolve_single(email):
75                 self.__register(name, email, match)
76                 logins.add(match)
77             else:
78                 if match := self.__resolve_single(name):
79                     logins.add(match)
80                 else:
81                     unknowns.add((name, email))
82                 self.__register(name, email, match)
83 
84         return logins, unknowns
85 
86     def resolve_by_reviews(self, unknowns, email2pr):
87         logins, new_unknowns = set(), set()
88         for name, email in unknowns:
89             commit = self.__modin_repo.get_commit(str(email2pr[email]))
90             found = set()
91             for pull in commit.get_pulls():
92                 for review in pull.get_reviews():
93                     user = review.user
94                     if user.name == name and (not user.email or user.email == email):
95                         found.add(user.login)
96 
97             if len(found) == 1:
98                 self.__register(name, email, list(found)[0])
99                 logins |= found
100             else:
101                 new_unknowns.add((name, email))
102 
103         return logins, new_unknowns
104 
105     def __save(self):
106         self.__cache_file.write_text(json.dumps(self.__cache, indent=4, sort_keys=True))
107 
108 
109 class GitWrapper:
110     def __init__(self):
111         self.repo = pygit2.Repository(Path(__file__).parent)
112 
113     def is_on_master(self):
114         return self.repo.references["refs/heads/master"] == self.repo.head
115 
116     @staticmethod
117     def __get_tag_version(entry):
118         try:
119             return version.parse(entry.lstrip("refs/tags/"))
120         except version.InvalidVersion as ex:
121             return f'<bad version "{entry}": {ex}>'
122 
123     def get_previous_release(self, rel_type):
124         tags = [
125             (entry, self.__get_tag_version(entry))
126             for entry in self.repo.references
127             if entry.startswith("refs/tags/")
128         ]
129         # filter away legacy versions (which aren't following the proper naming schema);
130         # also skip pre-releases
131         tags = [
132             (entry, ver)
133             for entry, ver in tags
134             if isinstance(ver, version.Version) and not ver.pre
135         ]
136         if rel_type == "minor":
137             # leave only minor releases
138             tags = [(entry, ver) for entry, ver in tags if ver.micro == 0]
139         else:
140             assert rel_type == "patch"
141         prev_ref, prev_ver = max(tags, key=lambda pair: pair[1])
142         return prev_ref, self.repo.references[prev_ref].peel(), prev_ver
143 
144     def get_commits_upto(self, stop_commit):
145         history = []
146         for obj in self.repo.walk(self.repo.head.target):
147             if obj.id == stop_commit.id:
148                 break
149             history.append(obj)
150         else:
151             raise ValueError("Current HEAD is not derived from previous release")
152         return history
153 
154     def ensure_title_link(self, obj: pygit2.Commit):
155         title = obj.message.splitlines()[0]
156         if not re.match(r".*\(#(\d+)\)$", title):
157             title += f" ({obj.short_id})"
158         return title
159 
160 
161 def make_notes(args):
162     wrapper = GitWrapper()
163     release_type = "minor" if wrapper.is_on_master() else "patch"
164     sys.stderr.write(f"Detected release type: {release_type}\n")
165 
166     prev_ref, prev_commit, prev_ver = wrapper.get_previous_release(release_type)
167     sys.stderr.write(f"Previous {release_type} release: {prev_ref}\n")
168 
169     next_major, next_minor, next_patch = prev_ver.release
170     if release_type == "minor":
171         next_minor += 1
172     elif release_type == "patch":
173         next_patch += 1
174     else:
175         raise ValueError(f"Unexpected release type: {release_type}")
176     next_ver = version.Version(f"{next_major}.{next_minor}.{next_patch}")
177 
178     sys.stderr.write(f"Computing release notes for {prev_ver} -> {next_ver}...\n")
179     try:
180         history = wrapper.get_commits_upto(prev_commit)
181     except ValueError as ex:
182         sys.stderr.write(
183             f"{ex}: did you forget to checkout correct branch or pull tags?"
184         )
185         return 1
186     if not history:
187         sys.stderr.write(f"No commits since {prev_ver} found, nothing to generate!\n")
188         return 1
189 
190     titles = collections.defaultdict(list)
191     people = set()
192     email2commit, email2pr = {}, {}
193     for obj in history:
194         title = obj.message.splitlines()[0]
195         titles[title.split("-")[0]].append(obj)
196         new_people = set(
197             re.findall(
198                 r"(?:(?:Signed-off-by|Co-authored-by):\s*)([\w\s,]+?)\s*<([^>]+)>",
199                 obj.message,
200             )
201         )
202         for _, email in new_people:
203             email2pr[email] = obj.id
204         people |= new_people
205         email2commit[obj.author.email] = obj.id
206     sys.stderr.write(f"Found {len(history)} commit(s) since {prev_ref}\n")
207 
208     sys.stderr.write("Resolving contributors...\n")
209     user_resolver = GithubUserResolver(email2commit, args.token)
210     logins, unknowns = user_resolver.resolve(people)
211     new_logins, unknowns = user_resolver.resolve_by_reviews(unknowns, email2pr)
212     logins |= new_logins
213     sys.stderr.write(f"Found {len(logins)} GitHub usernames.\n")
214     if unknowns:
215         sys.stderr.write(
216             f"Warning! Failed to resolve {len(unknowns)} usernames, please resolve them manually!\n"
217         )
218 
219     sections = [
220         ("Stability and Bugfixes", "FIX"),
221         ("Performance enhancements", "PERF"),
222         ("Refactor Codebase", "REFACTOR"),
223         ("Update testing suite", "TEST"),
224         ("Documentation improvements", "DOCS"),
225         ("New Features", "FEAT"),
226     ]
227 
228     notes = rf"""Modin {next_ver}
229 
230 <Please fill in short release summary>
231 
232 Key Features and Updates Since {prev_ver}
233 -------------------------------{'-' * len(str(prev_ver))}
234 """
235 
236     def _add_section(section, prs):
237         nonlocal notes
238         if prs:
239             notes += f"* {section}\n"
240             notes += "\n".join(
241                 [
242                     f"  * {wrapper.ensure_title_link(obj)}"
243                     for obj in sorted(prs, key=lambda obj: obj.message)
244                 ]
245             )
246             notes += "\n"
247 
248     for section, key in sections:
249         _add_section(section, titles.pop(key, None))
250 
251     uncategorized = sum(titles.values(), [])
252     _add_section("Uncategorized improvements", uncategorized)
253 
254     notes += r"""
255 Contributors
256 ------------
257 """
258     notes += "\n".join(f"@{login}" for login in sorted(logins)) + "\n"
259     notes += (
260         "\n".join(
261             f"<unknown-login> {name} <{email}>" for name, email in sorted(unknowns)
262         )
263         + "\n"
264     )
265 
266     sys.stdout.write(notes)
267 
268 
269 def main():
270     parse = argparse.ArgumentParser()
271     parse.add_argument(
272         "--token",
273         type=str,
274         default="",
275         help="GitHub token for queries (optional, bumps up rate limit)",
276     )
277     parse.set_defaults(func=lambda _: parse.print_usage())
278     subparsers = parse.add_subparsers()
279 
280     notes = subparsers.add_parser("notes", help="Generate release notes")
281     notes.set_defaults(func=make_notes)
282 
283     args = parse.parse_args()
284     sys.exit(args.func(args))
285 
286 
287 if __name__ == "__main__":
288     main()
289
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/scripts/release.py b/scripts/release.py
--- a/scripts/release.py
+++ b/scripts/release.py
@@ -110,8 +110,8 @@
     def __init__(self):
         self.repo = pygit2.Repository(Path(__file__).parent)
 
-    def is_on_master(self):
-        return self.repo.references["refs/heads/master"] == self.repo.head
+    def is_on_main(self):
+        return self.repo.references["refs/heads/main"] == self.repo.head
 
     @staticmethod
     def __get_tag_version(entry):
@@ -160,7 +160,7 @@
 
 def make_notes(args):
     wrapper = GitWrapper()
-    release_type = "minor" if wrapper.is_on_master() else "patch"
+    release_type = "minor" if wrapper.is_on_main() else "patch"
     sys.stderr.write(f"Detected release type: {release_type}\n")
 
     prev_ref, prev_commit, prev_ver = wrapper.get_previous_release(release_type)

{"golden_diff": "diff --git a/scripts/release.py b/scripts/release.py\n--- a/scripts/release.py\n+++ b/scripts/release.py\n@@ -110,8 +110,8 @@\n     def __init__(self):\n         self.repo = pygit2.Repository(Path(__file__).parent)\n \n-    def is_on_master(self):\n-        return self.repo.references[\"refs/heads/master\"] == self.repo.head\n+    def is_on_main(self):\n+        return self.repo.references[\"refs/heads/main\"] == self.repo.head\n \n     @staticmethod\n     def __get_tag_version(entry):\n@@ -160,7 +160,7 @@\n \n def make_notes(args):\n     wrapper = GitWrapper()\n-    release_type = \"minor\" if wrapper.is_on_master() else \"patch\"\n+    release_type = \"minor\" if wrapper.is_on_main() else \"patch\"\n     sys.stderr.write(f\"Detected release type: {release_type}\\n\")\n \n     prev_ref, prev_commit, prev_ver = wrapper.get_previous_release(release_type)\n", "issue": "Change \"master\" branch to \"main\"\nWe should update all refs to the new default branch.\n", "before_files": [{"content": "import argparse\nimport atexit\nimport collections\nimport json\nimport re\nimport sys\nfrom pathlib import Path\n\nimport github\nimport pygit2\nfrom packaging import version\n\n\nclass GithubUserResolver:\n    def __init__(self, email2commit, token):\n        self.__cache_file = Path(__file__).parent / \"gh-users-cache.json\"\n        self.__cache = (\n            json.loads(self.__cache_file.read_text())\n            if self.__cache_file.exists()\n            else {}\n        )\n        # filter unknown users hoping we'd be able to find them this time\n        self.__cache = {key: value for key, value in self.__cache.items() if value}\n        # using anonymous access if token not specified\n        self.__github = github.Github(token or None)\n        self.__modin_repo = self.__github.get_repo(\"modin-project/modin\")\n        self.__email2commit = email2commit\n        atexit.register(self.__save)\n\n    def __search_commits(self, term):\n        if commit := self.__email2commit.get(term):\n            gh_commit = self.__modin_repo.get_commit(str(commit))\n            return gh_commit.author.login\n        return None\n\n    @staticmethod\n    def __is_email(term):\n        return re.match(r\".*@.*\\..*\", term)\n\n    def __search_github(self, term):\n        search = f\"in:email {term}\" if self.__is_email(term) else f\"fullname:{term}\"\n        match = [user.login for user in self.__github.search_users(search)]\n        return match[0] if len(match) == 1 else None\n\n    def __try_user(self, term):\n        if self.__is_email(term):\n            return None\n        try:\n            return self.__github.get_user(term).login\n        except github.GithubException as ex:\n            if ex.status != 404:\n                raise\n            return None\n\n    def __resolve_single(self, term):\n        return (\n            self.__search_commits(term)\n            or self.__search_github(term)\n            or self.__try_user(term)\n        )\n\n    def __resolve_cache(self, name, email):\n        return self.__cache.get(f\"{name} <{email}>\", None)\n\n    def __register(self, name, email, match):\n        self.__cache[f\"{name} <{email}>\"] = match\n\n    def resolve(self, people):\n        logins, unknowns = set(), set()\n\n        for name, email in people:\n            if match := self.__resolve_cache(name, email):\n                logins.add(match)\n            elif match := self.__resolve_single(email):\n                self.__register(name, email, match)\n                logins.add(match)\n            else:\n                if match := self.__resolve_single(name):\n                    logins.add(match)\n                else:\n                    unknowns.add((name, email))\n                self.__register(name, email, match)\n\n        return logins, unknowns\n\n    def resolve_by_reviews(self, unknowns, email2pr):\n        logins, new_unknowns = set(), set()\n        for name, email in unknowns:\n            commit = self.__modin_repo.get_commit(str(email2pr[email]))\n            found = set()\n            for pull in commit.get_pulls():\n                for review in pull.get_reviews():\n                    user = review.user\n                    if user.name == name and (not user.email or user.email == email):\n                        found.add(user.login)\n\n            if len(found) == 1:\n                self.__register(name, email, list(found)[0])\n                logins |= found\n            else:\n                new_unknowns.add((name, email))\n\n        return logins, new_unknowns\n\n    def __save(self):\n        self.__cache_file.write_text(json.dumps(self.__cache, indent=4, sort_keys=True))\n\n\nclass GitWrapper:\n    def __init__(self):\n        self.repo = pygit2.Repository(Path(__file__).parent)\n\n    def is_on_master(self):\n        return self.repo.references[\"refs/heads/master\"] == self.repo.head\n\n    @staticmethod\n    def __get_tag_version(entry):\n        try:\n            return version.parse(entry.lstrip(\"refs/tags/\"))\n        except version.InvalidVersion as ex:\n            return f'<bad version \"{entry}\": {ex}>'\n\n    def get_previous_release(self, rel_type):\n        tags = [\n            (entry, self.__get_tag_version(entry))\n            for entry in self.repo.references\n            if entry.startswith(\"refs/tags/\")\n        ]\n        # filter away legacy versions (which aren't following the proper naming schema);\n        # also skip pre-releases\n        tags = [\n            (entry, ver)\n            for entry, ver in tags\n            if isinstance(ver, version.Version) and not ver.pre\n        ]\n        if rel_type == \"minor\":\n            # leave only minor releases\n            tags = [(entry, ver) for entry, ver in tags if ver.micro == 0]\n        else:\n            assert rel_type == \"patch\"\n        prev_ref, prev_ver = max(tags, key=lambda pair: pair[1])\n        return prev_ref, self.repo.references[prev_ref].peel(), prev_ver\n\n    def get_commits_upto(self, stop_commit):\n        history = []\n        for obj in self.repo.walk(self.repo.head.target):\n            if obj.id == stop_commit.id:\n                break\n            history.append(obj)\n        else:\n            raise ValueError(\"Current HEAD is not derived from previous release\")\n        return history\n\n    def ensure_title_link(self, obj: pygit2.Commit):\n        title = obj.message.splitlines()[0]\n        if not re.match(r\".*\\(#(\\d+)\\)$\", title):\n            title += f\" ({obj.short_id})\"\n        return title\n\n\ndef make_notes(args):\n    wrapper = GitWrapper()\n    release_type = \"minor\" if wrapper.is_on_master() else \"patch\"\n    sys.stderr.write(f\"Detected release type: {release_type}\\n\")\n\n    prev_ref, prev_commit, prev_ver = wrapper.get_previous_release(release_type)\n    sys.stderr.write(f\"Previous {release_type} release: {prev_ref}\\n\")\n\n    next_major, next_minor, next_patch = prev_ver.release\n    if release_type == \"minor\":\n        next_minor += 1\n    elif release_type == \"patch\":\n        next_patch += 1\n    else:\n        raise ValueError(f\"Unexpected release type: {release_type}\")\n    next_ver = version.Version(f\"{next_major}.{next_minor}.{next_patch}\")\n\n    sys.stderr.write(f\"Computing release notes for {prev_ver} -> {next_ver}...\\n\")\n    try:\n        history = wrapper.get_commits_upto(prev_commit)\n    except ValueError as ex:\n        sys.stderr.write(\n            f\"{ex}: did you forget to checkout correct branch or pull tags?\"\n        )\n        return 1\n    if not history:\n        sys.stderr.write(f\"No commits since {prev_ver} found, nothing to generate!\\n\")\n        return 1\n\n    titles = collections.defaultdict(list)\n    people = set()\n    email2commit, email2pr = {}, {}\n    for obj in history:\n        title = obj.message.splitlines()[0]\n        titles[title.split(\"-\")[0]].append(obj)\n        new_people = set(\n            re.findall(\n                r\"(?:(?:Signed-off-by|Co-authored-by):\\s*)([\\w\\s,]+?)\\s*<([^>]+)>\",\n                obj.message,\n            )\n        )\n        for _, email in new_people:\n            email2pr[email] = obj.id\n        people |= new_people\n        email2commit[obj.author.email] = obj.id\n    sys.stderr.write(f\"Found {len(history)} commit(s) since {prev_ref}\\n\")\n\n    sys.stderr.write(\"Resolving contributors...\\n\")\n    user_resolver = GithubUserResolver(email2commit, args.token)\n    logins, unknowns = user_resolver.resolve(people)\n    new_logins, unknowns = user_resolver.resolve_by_reviews(unknowns, email2pr)\n    logins |= new_logins\n    sys.stderr.write(f\"Found {len(logins)} GitHub usernames.\\n\")\n    if unknowns:\n        sys.stderr.write(\n            f\"Warning! Failed to resolve {len(unknowns)} usernames, please resolve them manually!\\n\"\n        )\n\n    sections = [\n        (\"Stability and Bugfixes\", \"FIX\"),\n        (\"Performance enhancements\", \"PERF\"),\n        (\"Refactor Codebase\", \"REFACTOR\"),\n        (\"Update testing suite\", \"TEST\"),\n        (\"Documentation improvements\", \"DOCS\"),\n        (\"New Features\", \"FEAT\"),\n    ]\n\n    notes = rf\"\"\"Modin {next_ver}\n\n<Please fill in short release summary>\n\nKey Features and Updates Since {prev_ver}\n-------------------------------{'-' * len(str(prev_ver))}\n\"\"\"\n\n    def _add_section(section, prs):\n        nonlocal notes\n        if prs:\n            notes += f\"* {section}\\n\"\n            notes += \"\\n\".join(\n                [\n                    f\"  * {wrapper.ensure_title_link(obj)}\"\n                    for obj in sorted(prs, key=lambda obj: obj.message)\n                ]\n            )\n            notes += \"\\n\"\n\n    for section, key in sections:\n        _add_section(section, titles.pop(key, None))\n\n    uncategorized = sum(titles.values(), [])\n    _add_section(\"Uncategorized improvements\", uncategorized)\n\n    notes += r\"\"\"\nContributors\n------------\n\"\"\"\n    notes += \"\\n\".join(f\"@{login}\" for login in sorted(logins)) + \"\\n\"\n    notes += (\n        \"\\n\".join(\n            f\"<unknown-login> {name} <{email}>\" for name, email in sorted(unknowns)\n        )\n        + \"\\n\"\n    )\n\n    sys.stdout.write(notes)\n\n\ndef main():\n    parse = argparse.ArgumentParser()\n    parse.add_argument(\n        \"--token\",\n        type=str,\n        default=\"\",\n        help=\"GitHub token for queries (optional, bumps up rate limit)\",\n    )\n    parse.set_defaults(func=lambda _: parse.print_usage())\n    subparsers = parse.add_subparsers()\n\n    notes = subparsers.add_parser(\"notes\", help=\"Generate release notes\")\n    notes.set_defaults(func=make_notes)\n\n    args = parse.parse_args()\n    sys.exit(args.func(args))\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "scripts/release.py"}], "after_files": [{"content": "import argparse\nimport atexit\nimport collections\nimport json\nimport re\nimport sys\nfrom pathlib import Path\n\nimport github\nimport pygit2\nfrom packaging import version\n\n\nclass GithubUserResolver:\n    def __init__(self, email2commit, token):\n        self.__cache_file = Path(__file__).parent / \"gh-users-cache.json\"\n        self.__cache = (\n            json.loads(self.__cache_file.read_text())\n            if self.__cache_file.exists()\n            else {}\n        )\n        # filter unknown users hoping we'd be able to find them this time\n        self.__cache = {key: value for key, value in self.__cache.items() if value}\n        # using anonymous access if token not specified\n        self.__github = github.Github(token or None)\n        self.__modin_repo = self.__github.get_repo(\"modin-project/modin\")\n        self.__email2commit = email2commit\n        atexit.register(self.__save)\n\n    def __search_commits(self, term):\n        if commit := self.__email2commit.get(term):\n            gh_commit = self.__modin_repo.get_commit(str(commit))\n            return gh_commit.author.login\n        return None\n\n    @staticmethod\n    def __is_email(term):\n        return re.match(r\".*@.*\\..*\", term)\n\n    def __search_github(self, term):\n        search = f\"in:email {term}\" if self.__is_email(term) else f\"fullname:{term}\"\n        match = [user.login for user in self.__github.search_users(search)]\n        return match[0] if len(match) == 1 else None\n\n    def __try_user(self, term):\n        if self.__is_email(term):\n            return None\n        try:\n            return self.__github.get_user(term).login\n        except github.GithubException as ex:\n            if ex.status != 404:\n                raise\n            return None\n\n    def __resolve_single(self, term):\n        return (\n            self.__search_commits(term)\n            or self.__search_github(term)\n            or self.__try_user(term)\n        )\n\n    def __resolve_cache(self, name, email):\n        return self.__cache.get(f\"{name} <{email}>\", None)\n\n    def __register(self, name, email, match):\n        self.__cache[f\"{name} <{email}>\"] = match\n\n    def resolve(self, people):\n        logins, unknowns = set(), set()\n\n        for name, email in people:\n            if match := self.__resolve_cache(name, email):\n                logins.add(match)\n            elif match := self.__resolve_single(email):\n                self.__register(name, email, match)\n                logins.add(match)\n            else:\n                if match := self.__resolve_single(name):\n                    logins.add(match)\n                else:\n                    unknowns.add((name, email))\n                self.__register(name, email, match)\n\n        return logins, unknowns\n\n    def resolve_by_reviews(self, unknowns, email2pr):\n        logins, new_unknowns = set(), set()\n        for name, email in unknowns:\n            commit = self.__modin_repo.get_commit(str(email2pr[email]))\n            found = set()\n            for pull in commit.get_pulls():\n                for review in pull.get_reviews():\n                    user = review.user\n                    if user.name == name and (not user.email or user.email == email):\n                        found.add(user.login)\n\n            if len(found) == 1:\n                self.__register(name, email, list(found)[0])\n                logins |= found\n            else:\n                new_unknowns.add((name, email))\n\n        return logins, new_unknowns\n\n    def __save(self):\n        self.__cache_file.write_text(json.dumps(self.__cache, indent=4, sort_keys=True))\n\n\nclass GitWrapper:\n    def __init__(self):\n        self.repo = pygit2.Repository(Path(__file__).parent)\n\n    def is_on_main(self):\n        return self.repo.references[\"refs/heads/main\"] == self.repo.head\n\n    @staticmethod\n    def __get_tag_version(entry):\n        try:\n            return version.parse(entry.lstrip(\"refs/tags/\"))\n        except version.InvalidVersion as ex:\n            return f'<bad version \"{entry}\": {ex}>'\n\n    def get_previous_release(self, rel_type):\n        tags = [\n            (entry, self.__get_tag_version(entry))\n            for entry in self.repo.references\n            if entry.startswith(\"refs/tags/\")\n        ]\n        # filter away legacy versions (which aren't following the proper naming schema);\n        # also skip pre-releases\n        tags = [\n            (entry, ver)\n            for entry, ver in tags\n            if isinstance(ver, version.Version) and not ver.pre\n        ]\n        if rel_type == \"minor\":\n            # leave only minor releases\n            tags = [(entry, ver) for entry, ver in tags if ver.micro == 0]\n        else:\n            assert rel_type == \"patch\"\n        prev_ref, prev_ver = max(tags, key=lambda pair: pair[1])\n        return prev_ref, self.repo.references[prev_ref].peel(), prev_ver\n\n    def get_commits_upto(self, stop_commit):\n        history = []\n        for obj in self.repo.walk(self.repo.head.target):\n            if obj.id == stop_commit.id:\n                break\n            history.append(obj)\n        else:\n            raise ValueError(\"Current HEAD is not derived from previous release\")\n        return history\n\n    def ensure_title_link(self, obj: pygit2.Commit):\n        title = obj.message.splitlines()[0]\n        if not re.match(r\".*\\(#(\\d+)\\)$\", title):\n            title += f\" ({obj.short_id})\"\n        return title\n\n\ndef make_notes(args):\n    wrapper = GitWrapper()\n    release_type = \"minor\" if wrapper.is_on_main() else \"patch\"\n    sys.stderr.write(f\"Detected release type: {release_type}\\n\")\n\n    prev_ref, prev_commit, prev_ver = wrapper.get_previous_release(release_type)\n    sys.stderr.write(f\"Previous {release_type} release: {prev_ref}\\n\")\n\n    next_major, next_minor, next_patch = prev_ver.release\n    if release_type == \"minor\":\n        next_minor += 1\n    elif release_type == \"patch\":\n        next_patch += 1\n    else:\n        raise ValueError(f\"Unexpected release type: {release_type}\")\n    next_ver = version.Version(f\"{next_major}.{next_minor}.{next_patch}\")\n\n    sys.stderr.write(f\"Computing release notes for {prev_ver} -> {next_ver}...\\n\")\n    try:\n        history = wrapper.get_commits_upto(prev_commit)\n    except ValueError as ex:\n        sys.stderr.write(\n            f\"{ex}: did you forget to checkout correct branch or pull tags?\"\n        )\n        return 1\n    if not history:\n        sys.stderr.write(f\"No commits since {prev_ver} found, nothing to generate!\\n\")\n        return 1\n\n    titles = collections.defaultdict(list)\n    people = set()\n    email2commit, email2pr = {}, {}\n    for obj in history:\n        title = obj.message.splitlines()[0]\n        titles[title.split(\"-\")[0]].append(obj)\n        new_people = set(\n            re.findall(\n                r\"(?:(?:Signed-off-by|Co-authored-by):\\s*)([\\w\\s,]+?)\\s*<([^>]+)>\",\n                obj.message,\n            )\n        )\n        for _, email in new_people:\n            email2pr[email] = obj.id\n        people |= new_people\n        email2commit[obj.author.email] = obj.id\n    sys.stderr.write(f\"Found {len(history)} commit(s) since {prev_ref}\\n\")\n\n    sys.stderr.write(\"Resolving contributors...\\n\")\n    user_resolver = GithubUserResolver(email2commit, args.token)\n    logins, unknowns = user_resolver.resolve(people)\n    new_logins, unknowns = user_resolver.resolve_by_reviews(unknowns, email2pr)\n    logins |= new_logins\n    sys.stderr.write(f\"Found {len(logins)} GitHub usernames.\\n\")\n    if unknowns:\n        sys.stderr.write(\n            f\"Warning! Failed to resolve {len(unknowns)} usernames, please resolve them manually!\\n\"\n        )\n\n    sections = [\n        (\"Stability and Bugfixes\", \"FIX\"),\n        (\"Performance enhancements\", \"PERF\"),\n        (\"Refactor Codebase\", \"REFACTOR\"),\n        (\"Update testing suite\", \"TEST\"),\n        (\"Documentation improvements\", \"DOCS\"),\n        (\"New Features\", \"FEAT\"),\n    ]\n\n    notes = rf\"\"\"Modin {next_ver}\n\n<Please fill in short release summary>\n\nKey Features and Updates Since {prev_ver}\n-------------------------------{'-' * len(str(prev_ver))}\n\"\"\"\n\n    def _add_section(section, prs):\n        nonlocal notes\n        if prs:\n            notes += f\"* {section}\\n\"\n            notes += \"\\n\".join(\n                [\n                    f\"  * {wrapper.ensure_title_link(obj)}\"\n                    for obj in sorted(prs, key=lambda obj: obj.message)\n                ]\n            )\n            notes += \"\\n\"\n\n    for section, key in sections:\n        _add_section(section, titles.pop(key, None))\n\n    uncategorized = sum(titles.values(), [])\n    _add_section(\"Uncategorized improvements\", uncategorized)\n\n    notes += r\"\"\"\nContributors\n------------\n\"\"\"\n    notes += \"\\n\".join(f\"@{login}\" for login in sorted(logins)) + \"\\n\"\n    notes += (\n        \"\\n\".join(\n            f\"<unknown-login> {name} <{email}>\" for name, email in sorted(unknowns)\n        )\n        + \"\\n\"\n    )\n\n    sys.stdout.write(notes)\n\n\ndef main():\n    parse = argparse.ArgumentParser()\n    parse.add_argument(\n        \"--token\",\n        type=str,\n        default=\"\",\n        help=\"GitHub token for queries (optional, bumps up rate limit)\",\n    )\n    parse.set_defaults(func=lambda _: parse.print_usage())\n    subparsers = parse.add_subparsers()\n\n    notes = subparsers.add_parser(\"notes\", help=\"Generate release notes\")\n    notes.set_defaults(func=make_notes)\n\n    args = parse.parse_args()\n    sys.exit(args.func(args))\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "scripts/release.py"}]}