Skip to content

Commit 6448488

Browse files
authored
Merge pull request #11 from arnica-ext/master
Added PR reviews and branch protection
2 parents 65f60a0 + c7db4c7 commit 6448488

File tree

5 files changed

+85
-7
lines changed

5 files changed

+85
-7
lines changed

_config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# This is the configuration file for the GitHub Pages.
2+
# Do not confuse this file with the config.yaml file (without the '_' prefix), which configures GitGoat's behavior.
13
plugins:
24
- jekyll-relative-links
35

config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ repo_names:
2323

2424
## Specific configurations per repo (optional).
2525
### "branch_protection" means the policy is enabled in the main branch
26+
### "branch_protection_restirctions" define who can push to the protected branch. A team name is specified by the prefix, e.g. the team "Lavender-push" should be included as "push".
2627
### "actions_enabled" means that the repo has GitHub Action enabled
2728
### "allowed_actions" defines the scope of the allowed actions. The options are "all", "selected" or "local_only". It is applicable only if "actions_enabled" is true.
2829
### "verified_allowed_actions" defined if verified actions (not neccesarily owned by GitHub) are allowed. It is applicable only if "actions_enabled" is true.
@@ -33,6 +34,9 @@ repo_configs:
3334
allowed_actions: all
3435
Lavender:
3536
branch_protection: True
37+
branch_protection_restirctions:
38+
users: []
39+
teams: ['push']
3640
actions_enabled: True
3741
allowed_actions: selected
3842
verified_allowed_actions: False
@@ -43,6 +47,9 @@ repo_configs:
4347
verified_allowed_actions: True
4448
Calendula:
4549
branch_protection: True
50+
branch_protection_restirctions:
51+
users: []
52+
teams: ['push']
4653
actions_enabled: False
4754
Tarragon:
4855
branch_protection: False

run.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from src.members import Membership
99
from src.pull_request import PullRequest
1010
from src.direct_permissions import DirectPermission
11+
from src.branch import Branch
1112

1213
async def mock(config_file: str, orgs: list = []):
1314
config = Config() if config_file is None else Config(config_file)
@@ -26,6 +27,8 @@ async def mock(config_file: str, orgs: list = []):
2627
await create_teams(config, org)
2728
logging.info('----- Granting Direct Permissions -----')
2829
await add_direct_permissions(config, org)
30+
logging.info('----- Configuring Branch Protection -----')
31+
await configure_branch_protection(config, org)
2932
logging.info('----- Creating Commits and Pull Requests -----')
3033
await create_commits(config, org)
3134
logging.info('----- Merging Pull Requests -----')
@@ -89,6 +92,16 @@ async def setup_actions(config, org):
8992
if config.repo_configs[actions_enabled_repo]['allowed_actions'] == 'selected':
9093
await a.enable_selected_actions_in_repo(actions_enabled_repo, verified_allowed=config.repo_configs[actions_enabled_repo]['verified_allowed_actions'])
9194

95+
async def configure_branch_protection(config, org):
96+
b = Branch(org, config.filename)
97+
for repo_name in tqdm(config.repo_names, desc='Branch Protection'):
98+
if 'branch_protection_restirctions' in config.repo_configs[repo_name]:
99+
users = config.repo_configs[repo_name]['branch_protection_restirctions']['users']
100+
teams = []
101+
for team_postfix in config.repo_configs[repo_name]['branch_protection_restirctions']['teams']:
102+
teams.append(f'{repo_name}-{team_postfix}')
103+
await b.set_branch_protection(repo_name, 'main', users, teams)
104+
92105
async def create_commits(config, org):
93106
r = Repository(org, config.filename)
94107
pr = PullRequest(org, config.filename)
@@ -97,23 +110,41 @@ async def create_commits(config, org):
97110
token = member['token'] if 'ghp_' in member['token'] else 'ghp_' + member['token']
98111
repo = await r.clone(commit_details['repo'], member['login'], token, member['email'], commit_details['branch'])
99112
c = Commit(repo)
100-
c.generate_commits(200, commit_details['days'])
113+
c.generate_commits(50, commit_details['days'])
101114
if commit_details['create_pr']:
102115
await pr.create_pull_request(token, commit_details['repo'], commit_details['branch'])
103116
#logging.info(f'Created a PR by {member["login"]} from branch {commit_details["branch"]}')
104117

105118
async def merge_pull_requests(config, org):
106119
pr = PullRequest(org, config.filename)
120+
reviewed_prs = {}
107121
for member in tqdm(config.members, desc=f'Member PRs'):
108122
for repo in config.repo_names:
109-
if f'{repo}-triage' in member['member_of_groups'] or f'{repo}-maintain' in member['member_of_groups']:
123+
reviewed_prs[repo] = []
124+
if is_member_allowed_to_merge(config, member, repo):
110125
token = member['token'] if 'ghp_' in member['token'] else 'ghp_' + member['token']
111126
prs = await pr.get_pull_requests(token,repo)
112127
for p in prs:
113-
merged = await pr.merge(token, repo, p)
114-
if not merged:
115-
logging.warning(f'Did NOT merge the PR id {p} in repository {repo} by {member["login"]}')
128+
if prs[p] != member['login']:
129+
await pr.review(token, repo, p)
130+
reviewed_prs[repo].append(p)
131+
if p in reviewed_prs[repo]:
132+
merged = await pr.merge(token, repo, p)
133+
if not merged:
134+
logging.warning(f'Did NOT merge the PR id {p} in repository {repo} by {member["login"]}')
116135

136+
def is_member_allowed_to_merge(config, member, repo):
137+
if 'branch_protection_restirctions' in config.repo_configs[repo]:
138+
if member['login'] in config.repo_configs[repo]['branch_protection_restirctions']['users']:
139+
return True
140+
for team in config.repo_configs[repo]['branch_protection_restirctions']['teams']:
141+
for group_membership in member['member_of_groups']:
142+
if group_membership == f'{repo}-{team}':
143+
return True
144+
else:
145+
if f'{repo}-maintain' in member['member_of_groups'] or f'{repo}-push' in member['member_of_groups']:
146+
return True
147+
return False
117148

118149
if __name__ == '__main__':
119150
try:

src/branch.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ class Branch:
44

55
def __init__(self, organization, config_file = None):
66
self.endpoint = f'/repos/{organization}/[REPO]/git/refs'
7+
self.branch_protection_endpoint = f'/repos/{organization}/[REPO]/branches/[BRANCH]/protection'
78
self.config_file = config_file
89

910
async def get_main(self, pat, repository):
@@ -21,4 +22,31 @@ async def create_branch(self, pat, repository, branch_name, source_branch_sha):
2122
}
2223
resp = await conn.post(endpoint, json_data=data)
2324
return resp
25+
26+
async def set_branch_protection(self, repository, branch_name, restricted_users = [], restricted_teams = []):
27+
conn = ConnectionHandler(config_file=self.config_file)
28+
endpoint = self.branch_protection_endpoint.replace('[REPO]',repository).replace('[BRANCH]', branch_name)
29+
payload = {
30+
'required_status_checks': {
31+
'strict': False,
32+
'contexts': []
33+
},
34+
'enforce_admins': False,
35+
'require_code_owner_reviews': False,
36+
'required_pull_request_reviews': {
37+
'required_approving_review_count': 1
38+
},
39+
'restrictions': {
40+
'users': restricted_users,
41+
'teams': restricted_teams
42+
},
43+
'allow_force_pushes': True,
44+
'allow_deletions': True,
45+
'bypass_pull_request_allowances': {
46+
'users': restricted_users,
47+
'teams': restricted_teams
48+
}
49+
}
50+
resp = await conn.put(endpoint, payload)
51+
return resp
2452

src/pull_request.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ def __init__(self, organization, config_file = None):
1111
async def get_pull_requests(self, pat, repository):
1212
conn = ConnectionHandler(pat, self.config_file)
1313
endpoint = self.endpoint.replace('[REPO]', repository)
14-
pr_ids = []
14+
pr_ids = {}
1515
resp = await conn.get(endpoint)
1616
for pr in resp:
1717
if pr['state'] == 'open':
18-
pr_ids.append(pr['number'])
18+
pr_ids[pr['number']] = pr['user']['login']
1919
return pr_ids
2020

2121
async def create_pull_request(self, pat, repository, head_branch):
@@ -30,6 +30,16 @@ async def create_pull_request(self, pat, repository, head_branch):
3030
resp = await conn.post(endpoint, json_data=data)
3131
return resp['number']
3232

33+
async def review(self, pat, repository, pull_request_number):
34+
conn = ConnectionHandler(pat, self.config_file)
35+
endpoint = self.endpoint.replace('[REPO]', repository) + f'/{str(pull_request_number)}/reviews'
36+
data = {
37+
'event': 'APPROVE',
38+
'body:': self.fake.lexify(text='GitGoat automated PR review ????????')
39+
}
40+
resp = await conn.post(endpoint, data)
41+
return resp
42+
3343
async def merge(self, pat, repository, pull_request_number):
3444
conn = ConnectionHandler(pat, self.config_file)
3545
endpoint = self.endpoint.replace('[REPO]', repository) + f'/{str(pull_request_number)}/merge'

0 commit comments

Comments
 (0)