• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

conda-forge / conda-smithy / 10861974093

14 Sep 2024 11:15AM CUT coverage: 71.361%. Remained the same
10861974093

push

github

web-flow
Merge pull request #2060 from h-vetinari/win310

remove python 3.10 pin in `run_win_build.bat` template

1452 of 2149 branches covered (67.57%)

3182 of 4459 relevant lines covered (71.36%)

0.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

14.69
/conda_smithy/github.py
1
import os
1✔
2
from random import choice
1✔
3

4
import github
1✔
5
from git import Repo
1✔
6
from github import Github
1✔
7
from github.GithubException import GithubException
1✔
8
from github.Organization import Organization
1✔
9
from github.Team import Team
1✔
10

11
from conda_smithy.configure_feedstock import _load_forge_config
1✔
12
from conda_smithy.utils import (
1✔
13
    _get_metadata_from_feedstock_dir,
14
    get_feedstock_name_from_meta,
15
)
16

17

18
def gh_token():
1✔
19
    try:
×
20
        with open(os.path.expanduser("~/.conda-smithy/github.token")) as fh:
×
21
            token = fh.read().strip()
×
22
        if not token:
×
23
            raise ValueError()
×
24
    except (OSError, ValueError):
×
25
        msg = (
×
26
            "No github token. Go to https://github.com/settings/tokens/new and generate\n"
27
            "a token with repo access. Put it in ~/.conda-smithy/github.token"
28
        )
29
        raise RuntimeError(msg)
×
30
    return token
×
31

32

33
def create_team(org, name, description, repo_names=[]):
1✔
34
    # PyGithub creates secret teams, and has no way of turning that off! :(
35
    post_parameters = {
×
36
        "name": name,
37
        "description": description,
38
        "privacy": "closed",
39
        "permission": "push",
40
        "repo_names": repo_names,
41
    }
42
    headers, data = org._requester.requestJsonAndCheck(
×
43
        "POST", org.url + "/teams", input=post_parameters
44
    )
45
    return Team(org._requester, headers, data, completed=True)
×
46

47

48
def add_membership(team, member):
1✔
49
    headers, data = team._requester.requestJsonAndCheck(
×
50
        "PUT", team.url + "/memberships/" + member
51
    )
52
    return (headers, data)
×
53

54

55
def remove_membership(team, member):
1✔
56
    headers, data = team._requester.requestJsonAndCheck(
×
57
        "DELETE", team.url + "/memberships/" + member
58
    )
59
    return (headers, data)
×
60

61

62
def has_in_members(team, member):
1✔
63
    status, headers, data = team._requester.requestJson(
×
64
        "GET", team.url + "/members/" + member
65
    )
66
    return status == 204
×
67

68

69
def get_cached_team(org, team_name, description=""):
1✔
70
    cached_file = os.path.expanduser(
×
71
        f"~/.conda-smithy/{org.login}-{team_name}-team"
72
    )
73
    try:
×
74
        with open(cached_file) as fh:
×
75
            team_id = int(fh.read().strip())
×
76
            return org.get_team(team_id)
×
77
    except OSError:
×
78
        pass
×
79

80
    try:
×
81
        repo = org.get_repo(f"{team_name}-feedstock")
×
82
        team = next(
×
83
            (team for team in repo.get_teams() if team.name == team_name), None
84
        )
85
        if team:
×
86
            return team
×
87
    except GithubException:
×
88
        pass
×
89

90
    team = next(
×
91
        (team for team in org.get_teams() if team.name == team_name), None
92
    )
93
    if not team:
×
94
        if description:
×
95
            team = create_team(org, team_name, description, [])
×
96
        else:
97
            raise RuntimeError(f"Couldn't find team {team_name}")
×
98

99
    with open(cached_file, "w") as fh:
×
100
        fh.write(str(team.id))
×
101

102
    return team
×
103

104

105
def _conda_forge_specific_repo_setup(gh_repo):
1✔
106
    branch = gh_repo.get_branch(gh_repo.default_branch)
×
107
    branch.edit_protection(
×
108
        enforce_admins=True,
109
        allow_force_pushes=False,
110
        allow_deletions=False,
111
    )
112

113

114
def create_github_repo(args):
1✔
115
    token = gh_token()
×
116

117
    # Load the conda-forge config and read metadata from the feedstock recipe
118
    forge_config = _load_forge_config(args.feedstock_directory, None)
×
119
    metadata = _get_metadata_from_feedstock_dir(
×
120
        args.feedstock_directory, forge_config
121
    )
122

123
    feedstock_name = get_feedstock_name_from_meta(metadata)
×
124

125
    gh = Github(token)
×
126
    user_or_org = None
×
127
    is_conda_forge = False
×
128
    if args.user is not None:
×
129
        pass
130
        # User has been defined, and organization has not.
131
        user_or_org = gh.get_user()
×
132
    else:
133
        # Use the organization provided.
134
        user_or_org = gh.get_organization(args.organization)
×
135
        if args.organization == "conda-forge":
×
136
            is_conda_forge = True
×
137

138
    repo_name = f"{feedstock_name}-feedstock"
×
139
    try:
×
140
        gh_repo = user_or_org.create_repo(
×
141
            repo_name,
142
            has_wiki=False,
143
            private=args.private,
144
            description=f"A conda-smithy repository for {feedstock_name}.",
145
        )
146

147
        if is_conda_forge:
×
148
            _conda_forge_specific_repo_setup(gh_repo)
×
149

150
        print(f"Created {gh_repo.full_name} on github")
×
151
    except GithubException as gh_except:
×
152
        if (
×
153
            gh_except.data.get("errors", [{}])[0].get("message", "")
154
            != "name already exists on this account"
155
        ):
156
            raise
×
157
        gh_repo = user_or_org.get_repo(repo_name)
×
158
        print("Github repository already exists.")
×
159

160
    # Now add this new repo as a remote on the local clone.
161
    repo = Repo(args.feedstock_directory)
×
162
    remote_name = args.remote_name.strip()
×
163
    if remote_name:
×
164
        if remote_name in [remote.name for remote in repo.remotes]:
×
165
            existing_remote = repo.remotes[remote_name]
×
166
            if existing_remote.url != gh_repo.ssh_url:
×
167
                print(
×
168
                    f"Remote {remote_name} already exists, and doesn't point to {gh_repo.ssh_url} "
169
                    f"(it points to {existing_remote.url})."
170
                )
171
        else:
172
            repo.create_remote(remote_name, gh_repo.ssh_url)
×
173

174
    if args.extra_admin_users is not None:
×
175
        for user in args.extra_admin_users:
×
176
            gh_repo.add_to_collaborators(user, "admin")
×
177

178
    if args.add_teams:
×
179
        if isinstance(user_or_org, Organization):
×
180
            configure_github_team(
×
181
                metadata, gh_repo, user_or_org, feedstock_name
182
            )
183

184

185
def accept_all_repository_invitations(gh):
1✔
186
    user = gh.get_user()
×
187
    invitations = github.PaginatedList.PaginatedList(
×
188
        github.Invitation.Invitation,
189
        user._requester,
190
        user.url + "/repository_invitations",
191
        None,
192
    )
193
    for invite in invitations:
×
194
        invite._requester.requestJsonAndCheck("PATCH", invite.url)
×
195

196

197
def remove_from_project(gh, org, project):
1✔
198
    user = gh.get_user()
×
199
    repo = gh.get_repo(f"{org}/{project}")
×
200
    repo.remove_from_collaborators(user.login)
×
201

202

203
def configure_github_team(meta, gh_repo, org, feedstock_name, remove=True):
1✔
204
    # Add a team for this repo and add the maintainers to it.
205
    superlative = [
×
206
        "awesome",
207
        "slick",
208
        "formidable",
209
        "awe-inspiring",
210
        "breathtaking",
211
        "magnificent",
212
        "wonderous",
213
        "stunning",
214
        "astonishing",
215
        "superb",
216
        "splendid",
217
        "impressive",
218
        "unbeatable",
219
        "excellent",
220
        "top",
221
        "outstanding",
222
        "exalted",
223
        "standout",
224
        "smashing",
225
    ]
226

227
    maintainers = set(meta.meta.get("extra", {}).get("recipe-maintainers", []))
×
228
    maintainers = set(maintainer.lower() for maintainer in maintainers)
×
229
    maintainer_teams = set(m for m in maintainers if "/" in m)
×
230
    maintainers = set(m for m in maintainers if "/" not in m)
×
231

232
    # Try to get team or create it if it doesn't exist.
233
    team_name = feedstock_name
×
234
    current_maintainer_teams = list(gh_repo.get_teams())
×
235
    fs_team = next(
×
236
        (team for team in current_maintainer_teams if team.name == team_name),
237
        None,
238
    )
239
    current_maintainers = set()
×
240
    if not fs_team:
×
241
        fs_team = create_team(
×
242
            org,
243
            team_name,
244
            f"The {choice(superlative)} {team_name} contributors!",
245
        )
246
        fs_team.add_to_repos(gh_repo)
×
247

248
    current_maintainers = set([e.login.lower() for e in fs_team.get_members()])
×
249

250
    # Get the all-members team
251
    description = f"All of the awesome {org.login} contributors!"
×
252
    all_members_team = get_cached_team(org, "all-members", description)
×
253
    new_org_members = set()
×
254

255
    # Add only the new maintainers to the team.
256
    # Also add the new maintainers to all-members if not already included.
257
    for new_maintainer in maintainers - current_maintainers:
×
258
        add_membership(fs_team, new_maintainer)
×
259

260
        if not has_in_members(all_members_team, new_maintainer):
×
261
            add_membership(all_members_team, new_maintainer)
×
262
            new_org_members.add(new_maintainer)
×
263

264
    # Remove any maintainers that need to be removed (unlikely here).
265
    if remove:
×
266
        for old_maintainer in current_maintainers - maintainers:
×
267
            remove_membership(fs_team, old_maintainer)
×
268

269
    # Add any new maintainer teams
270
    maintainer_teams = set(
×
271
        m.split("/")[1]
272
        for m in maintainer_teams
273
        if m.startswith(str(org.login))
274
    )
275
    current_maintainer_team_objs = {
×
276
        team.slug: team for team in current_maintainer_teams
277
    }
278
    current_maintainer_teams = set(
×
279
        [team.slug for team in current_maintainer_teams]
280
    )
281
    for new_team in maintainer_teams - current_maintainer_teams:
×
282
        team = org.get_team_by_slug(new_team)
×
283
        team.add_to_repos(gh_repo)
×
284

285
    # remove any old teams
286
    if remove:
×
287
        for old_team in current_maintainer_teams - maintainer_teams:
×
288
            team = current_maintainer_team_objs.get(
×
289
                old_team, org.get_team_by_slug(old_team)
290
            )
291
            if team.name == fs_team.name:
×
292
                continue
293
            team.remove_from_repos(gh_repo)
×
294

295
    return maintainers, current_maintainers, new_org_members
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc