aboutsummaryrefslogtreecommitdiff
path: root/scripts/squash_typos.py
blob: e7ee2e24a8b9e5b0830ed2b04cfef54ecb44882d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/usr/bin/env python
"""

This script squashes a PR tagged with the "typo" label into a single, dedicated
"squash PR".

"""

import subprocess
import os
import re


def get_authors_and_emails_from_pr():
    """

    For a given PR number, returns all contributing authors and their emails
    for that PR. This includes co-authors, meaning that if two authors are
    credited for a single commit, which is possible with GitHub, then both will
    get credited.

    """

    # Get a list of all authors involved in the pull request (including co-authors).
    authors = subprocess.check_output(
        ["gh", "pr", "view", "--json", "commits", "--jq", ".[][].authors.[].name"],
        text=True,
    ).splitlines()

    # Get a list of emails of the aforementioned authors.
    emails = subprocess.check_output(
        ["gh", "pr", "view", "--json", "commits", "--jq", ".[][].authors.[].email"],
        text=True,
    ).splitlines()

    return [(author, mail) for author, mail in zip(authors, emails)]


def rebase_onto_pr(pr, squash_branch):
    """

    Add all commits from PR into current branch. This is done by rebasing
    current branch onto the PR.

    """

    # Check out the pull request.
    subprocess.call(["gh", "pr", "checkout", pr])

    pr_branch_name = subprocess.check_output(
        ["git", "branch", "--show-current"], text=True
    ).strip()

    # Change back to the original branch.
    subprocess.call(["git", "switch", squash_branch])

    # Rebase onto the pull request, aka include the commits in the pull
    # request in the current branch.
    subprocess.call(["git", "rebase", pr_branch_name])


def squash_all_commits():
    """

    Squash all commits into a single commit. Credit all authors by name and
    email.

    """

    authors_and_emails = get_authors_and_emails_from_pr()
    subprocess.call(["git", "reset", "--soft", f"{os.environ['GITHUB_BASE_REF']}"])

    authors_and_emails = sorted(set(authors_and_emails))
    commit_message_coauthors = "\n" + "\n".join(
        [f"Co-authored-by: {i[0]} <{i[1]}>" for i in authors_and_emails]
    )
    subprocess.call(
        ["git", "commit", "-m", "chore: typo fixes", "-m", commit_message_coauthors]
    )


def force_push(branch):
    gh_actor = os.environ["GITHUB_ACTOR"]
    gh_token = os.environ["GITHUB_TOKEN"]
    gh_repo = os.environ["GITHUB_REPOSITORY"]
    subprocess.call(
        [
            "git",
            "push",
            "--force",
            f"https://{gh_actor}:{gh_token}@github.com/{gh_repo}",
            branch,
        ]
    )


def main():
    squash_branch = "marvim/squash-typos"
    all_pr_urls = ""

    pr_number = re.sub(r"\D", "", os.environ["GITHUB_REF"])

    show_ref_output = subprocess.check_output(["git", "show-ref"], text=True).strip()

    if squash_branch in show_ref_output:
        subprocess.call(
            ["git", "checkout", "-b", squash_branch, f"origin/{squash_branch}"]
        )
        squash_branch_exists = True

        all_pr_urls += subprocess.check_output(
            ["gh", "pr", "view", "--json", "body", "--jq", ".body"], text=True
        )
    else:
        subprocess.call(["git", "checkout", "-b", squash_branch])
        squash_branch_exists = False

    all_pr_urls += subprocess.check_output(
        ["gh", "pr", "view", pr_number, "--json", "url", "--jq", ".url"], text=True
    ).strip()

    rebase_onto_pr(pr_number, squash_branch)
    force_push(squash_branch)

    subprocess.call(["gh", "pr", "close", pr_number])

    squash_all_commits()
    force_push(squash_branch)

    if not squash_branch_exists:
        subprocess.call(
            [
                "gh",
                "pr",
                "create",
                "--fill",
                "--head",
                squash_branch,
                "--title",
                "Dedicated PR for all typo fixes.",
            ]
        )

    subprocess.call(["gh", "pr", "edit", "--add-label", "typo", "--body", all_pr_urls])


if __name__ == "__main__":
    main()