-
Notifications
You must be signed in to change notification settings - Fork 136
/
Copy pathbot.py
195 lines (173 loc) · 7.01 KB
/
bot.py
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import logging as log
import time
from collections import namedtuple
from tempfile import TemporaryDirectory
from . import batch_job
from . import git
from . import job
from . import merge_request as merge_request_module
from . import single_merge_job
from . import store
from .project import AccessLevel, Project
MergeRequest = merge_request_module.MergeRequest
class Bot:
def __init__(self, *, api, config):
self._api = api
self._config = config
user = config.user
opts = config.merge_opts
if not user.is_admin:
assert not opts.reapprove, (
"{0.username} is not an admin, can't impersonate!".format(user)
)
assert not opts.add_reviewers, (
"{0.username} is not an admin, can't lookup Reviewed-by: email addresses ".format(user)
)
def start(self):
with TemporaryDirectory() as root_dir:
repo_manager = store.RepoManager(
user=self.user,
root_dir=root_dir,
ssh_key_file=self._config.ssh_key_file,
timeout=self._config.git_timeout,
reference=self._config.git_reference_repo,
)
self._run(repo_manager)
@property
def user(self):
return self._config.user
@property
def api(self):
return self._api
def _run(self, repo_manager):
time_to_sleep_between_projects_in_secs = 1
min_time_to_sleep_after_iterating_all_projects_in_secs = 30
while True:
projects = self._get_projects()
self._process_projects(
repo_manager,
time_to_sleep_between_projects_in_secs,
projects,
)
big_sleep = max(0,
min_time_to_sleep_after_iterating_all_projects_in_secs -
time_to_sleep_between_projects_in_secs * len(projects))
log.info('Sleeping for %s seconds...', big_sleep)
time.sleep(big_sleep)
def _get_projects(self):
log.info('Finding out my current projects...')
my_projects = Project.fetch_all_mine(self._api)
project_regexp = self._config.project_regexp
filtered_projects = [p for p in my_projects if project_regexp.match(p.path_with_namespace)]
log.debug(
'Projects that match project_regexp: %s',
[p.path_with_namespace for p in filtered_projects]
)
filtered_out = set(my_projects) - set(filtered_projects)
if filtered_out:
log.debug(
'Projects that do not match project_regexp: %s',
[p.path_with_namespace for p in filtered_out]
)
return filtered_projects
def _process_projects(
self,
repo_manager,
time_to_sleep_between_projects_in_secs,
projects,
):
for project in projects:
project_name = project.path_with_namespace
if project.access_level < AccessLevel.reporter:
log.warning("Don't have enough permissions to browse merge requests in %s!", project_name)
continue
merge_requests = self._get_merge_requests(project, project_name)
self._process_merge_requests(repo_manager, project, merge_requests)
time.sleep(time_to_sleep_between_projects_in_secs)
def _get_merge_requests(self, project, project_name):
log.info('Fetching merge requests assigned to me in %s...', project_name)
my_merge_requests = MergeRequest.fetch_all_open_for_user(
project_id=project.id,
user_id=self.user.id,
api=self._api,
merge_order=self._config.merge_order,
)
branch_regexp = self._config.branch_regexp
filtered_mrs = [mr for mr in my_merge_requests
if branch_regexp.match(mr.target_branch)]
log.debug(
'MRs that match branch_regexp: %s',
[mr.web_url for mr in filtered_mrs]
)
filtered_out = set(my_merge_requests) - set(filtered_mrs)
if filtered_out:
log.debug(
'MRs that do not match branch_regexp: %s',
[mr.web_url for mr in filtered_out]
)
source_branch_regexp = self._config.source_branch_regexp
source_filtered_mrs = [mr for mr in filtered_mrs
if source_branch_regexp.match(mr.source_branch)]
log.debug(
'MRs that match source_branch_regexp: %s',
[mr.web_url for mr in source_filtered_mrs]
)
source_filtered_out = set(filtered_mrs) - set(source_filtered_mrs)
if filtered_out:
log.debug(
'MRs that do not match source_branch_regexp: %s',
[mr.web_url for mr in source_filtered_out]
)
return source_filtered_mrs
def _process_merge_requests(self, repo_manager, project, merge_requests):
if not merge_requests:
log.info('Nothing to merge at this point...')
return
try:
repo = repo_manager.repo_for_project(project)
except git.GitError:
log.exception("Couldn't initialize repository for project!")
raise
log.info('Got %s requests to merge;', len(merge_requests))
if self._config.batch and len(merge_requests) > 1:
log.info('Attempting to merge as many MRs as possible using BatchMergeJob...')
batch_merge_job = batch_job.BatchMergeJob(
api=self._api,
user=self.user,
project=project,
merge_requests=merge_requests,
repo=repo,
options=self._config.merge_opts,
)
try:
batch_merge_job.execute()
return
except batch_job.CannotBatch as err:
log.warning('BatchMergeJob aborted: %s', err)
except batch_job.CannotMerge as err:
log.warning('BatchMergeJob failed: %s', err)
return
except git.GitError as err:
log.exception('BatchMergeJob failed: %s', err)
log.info('Attempting to merge the oldest MR...')
merge_request = merge_requests[0]
merge_job = self._get_single_job(
project=project, merge_request=merge_request, repo=repo,
options=self._config.merge_opts,
)
merge_job.execute()
def _get_single_job(self, project, merge_request, repo, options):
return single_merge_job.SingleMergeJob(
api=self._api,
user=self.user,
project=project,
merge_request=merge_request,
repo=repo,
options=options,
)
class BotConfig(namedtuple('BotConfig',
'user ssh_key_file project_regexp merge_order merge_opts git_timeout ' +
'git_reference_repo branch_regexp source_branch_regexp batch')):
pass
MergeJobOptions = job.MergeJobOptions
Fusion = job.Fusion