Skip to content
This repository was archived by the owner on Mar 20, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sudo: false
language: python
python:
- 3.3
- 3.4
- 3.5
install:
- pip install flake8
script:
- flake8 --ignore E501 homu
2 changes: 1 addition & 1 deletion homu/git_helper.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/usr/bin/env python3

import sys
import subprocess
import os

SSH_KEY_FILE = os.path.join(os.path.dirname(__file__), '../cache/key')


def main():
args = ['ssh', '-i', SSH_KEY_FILE] + sys.argv[1:]
os.execvp('ssh', args)
Expand Down
80 changes: 57 additions & 23 deletions homu/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
INTERRUPTED_BY_HOMU_FMT = 'Interrupted by Homu ({})'
INTERRUPTED_BY_HOMU_RE = re.compile(r'Interrupted by Homu \((.+?)\)')


@contextmanager
def buildbot_sess(repo_cfg):
sess = requests.Session()
Expand All @@ -43,10 +44,13 @@ def buildbot_sess(repo_cfg):
sess.get(repo_cfg['buildbot']['url'] + '/logout', allow_redirects=False)

db_query_lock = Lock()


def db_query(db, *args):
with db_query_lock:
db.execute(*args)


class PullReqState:
num = 0
priority = 0
Expand Down Expand Up @@ -214,17 +218,20 @@ def fake_merged(self, repo_cfg):
if self.get_repo().merge(self.base_ref, self.head_sha, msg):
self.rebased = True


def sha_cmp(short, full):
return len(short) >= 4 and short == full[:len(short)]


def sha_or_blank(sha):
return sha if re.match(r'^[0-9a-f]+$', sha) else ''


def parse_commands(body, username, repo_cfg, state, my_username, db, *, realtime=False, sha=''):
try_only = False
if username not in repo_cfg['reviewers'] and username != my_username:
if username == state.delegate:
pass # Allow users who have been delegated review powers
pass # Allow users who have been delegated review powers
elif username in repo_cfg.get('try_users', []):
try_only = True
else:
Expand All @@ -238,11 +245,12 @@ def parse_commands(body, username, repo_cfg, state, my_username, db, *, realtime

if word == 'r+' or word.startswith('r='):
if try_only:
if realtime: state.add_comment(':key: Insufficient privileges')
if realtime:
state.add_comment(':key: Insufficient privileges')
continue

if not sha and i+1 < len(words):
cur_sha = sha_or_blank(words[i+1])
if not sha and i + 1 < len(words):
cur_sha = sha_or_blank(words[i + 1])
else:
cur_sha = sha

Expand Down Expand Up @@ -277,42 +285,49 @@ def parse_commands(body, username, repo_cfg, state, my_username, db, *, realtime

elif word == 'r-':
if try_only:
if realtime: state.add_comment(':key: Insufficient privileges')
if realtime:
state.add_comment(':key: Insufficient privileges')
continue

state.approved_by = ''

state.save()

elif word.startswith('p='):
try: state.priority = int(word[len('p='):])
except ValueError: pass
try:
state.priority = int(word[len('p='):])
except ValueError:
pass

state.save()

elif word.startswith('delegate='):
if try_only:
if realtime: state.add_comment(':key: Insufficient privileges')
if realtime:
state.add_comment(':key: Insufficient privileges')
continue

state.delegate = word[len('delegate='):]
state.save()

if realtime: state.add_comment(':v: @{} can now approve this pull request'.format(state.delegate))
if realtime:
state.add_comment(':v: @{} can now approve this pull request'.format(state.delegate))

elif word == 'delegate-':
state.delegate = ''
state.save()

elif word == 'delegate+':
if try_only:
if realtime: state.add_comment(':key: Insufficient privileges')
if realtime:
state.add_comment(':key: Insufficient privileges')
continue

state.delegate = state.get_repo().pull_request(state.num).user.login
state.save()

if realtime: state.add_comment(':v: @{} can now approve this pull request'.format(state.delegate))
if realtime:
state.add_comment(':v: @{} can now approve this pull request'.format(state.delegate))

elif word == 'retry' and realtime:
state.set_status('')
Expand Down Expand Up @@ -343,7 +358,8 @@ def parse_commands(body, username, repo_cfg, state, my_username, db, *, realtime
mat = re.search('(?s)<div class="error">(.*?)</div>', res.text)
if mat:
err = mat.group(1).strip()
if not err: err = 'Unknown error'
if not err:
err = 'Unknown error'
else:
err = ''

Expand All @@ -366,6 +382,7 @@ def parse_commands(body, username, repo_cfg, state, my_username, db, *, realtime

return state_changed


def create_merge(state, repo_cfg, branch, git_cfg):
base_sha = state.get_repo().ref('heads/' + state.base_ref).object.sha

Expand Down Expand Up @@ -453,9 +470,11 @@ def create_merge(state, repo_cfg, branch, git_cfg):
force=True,
)

try: merge_commit = state.get_repo().merge(branch, state.head_sha, merge_msg)
try:
merge_commit = state.get_repo().merge(branch, state.head_sha, merge_msg)
except github3.models.GitHubError as e:
if e.code != 409: raise
if e.code != 409:
raise
else:
return merge_commit.sha if merge_commit else ''

Expand All @@ -466,6 +485,7 @@ def create_merge(state, repo_cfg, branch, git_cfg):

return ''


def start_build(state, repo_cfgs, buildbot_slots, logger, db, git_cfg):
if buildbot_slots[0]:
return True
Expand Down Expand Up @@ -539,6 +559,7 @@ def start_build(state, repo_cfgs, buildbot_slots, logger, db, git_cfg):

return True


def start_rebuild(state, repo_cfgs):
repo_cfg = repo_cfgs[state.repo_label]

Expand Down Expand Up @@ -603,12 +624,14 @@ def start_rebuild(state, repo_cfgs):

return True


def start_build_or_rebuild(state, repo_cfgs, *args):
if start_rebuild(state, repo_cfgs):
return True

return start_build(state, repo_cfgs, *args)


def process_queue(states, repos, repo_cfgs, logger, buildbot_slots, db, git_cfg):
for repo_label, repo in repos.items():
repo_states = sorted(states[repo_label].values())
Expand All @@ -634,6 +657,7 @@ def process_queue(states, repos, repo_cfgs, logger, buildbot_slots, db, git_cfg)
if start_build(state, repo_cfgs, buildbot_slots, logger, db, git_cfg):
return


def fetch_mergeability(mergeable_que):
re_pull_num = re.compile('(?i)merge (?:of|pull request) #([0-9]+)')

Expand All @@ -650,8 +674,10 @@ def fetch_mergeability(mergeable_que):
if cause:
mat = re_pull_num.search(cause['title'])

if mat: issue_or_commit = '#' + mat.group(1)
else: issue_or_commit = cause['sha'][:7]
if mat:
issue_or_commit = '#' + mat.group(1)
else:
issue_or_commit = cause['sha'][:7]
else:
issue_or_commit = ''

Expand All @@ -667,6 +693,7 @@ def fetch_mergeability(mergeable_que):
finally:
mergeable_que.task_done()


def synchronize(repo_label, repo_cfg, logger, gh, states, repos, db, mergeable_que, my_username, repo_labels):
logger.info('Synchronizing {}...'.format(repo_label))

Expand Down Expand Up @@ -727,15 +754,17 @@ def synchronize(repo_label, repo_cfg, logger, gh, states, repos, db, mergeable_q

logger.info('Done synchronizing {}!'.format(repo_label))


def arguments():
parser = argparse.ArgumentParser(description =
'A bot that integrates with GitHub and '
'your favorite continuous integration service')
parser = argparse.ArgumentParser(
description='A bot that integrates with GitHub and your favorite '
'continuous integration service')
parser.add_argument('-v', '--verbose',
action='store_true', help='Enable more verbose logging')

return parser.parse_args()


def main():
args = arguments()

Expand All @@ -752,7 +781,8 @@ def main():

gh = github3.login(token=cfg['github']['access_token'])
user = gh.user()
try: user_email = [x for x in gh.iter_emails() if x['primary']][0]['email']
try:
user_email = [x for x in gh.iter_emails() if x['primary']][0]['email']
except IndexError:
raise RuntimeError('Primary email not set, or "user" scope not granted')

Expand Down Expand Up @@ -858,8 +888,10 @@ def main():
for repo_label, num, builder, res, url, merge_sha in db.fetchall():
try:
state = states[repo_label][num]
if builder not in state.build_res: raise KeyError
if state.merge_sha != merge_sha: raise KeyError
if builder not in state.build_res:
raise KeyError
if state.merge_sha != merge_sha:
raise KeyError
except KeyError:
db_query(db, 'DELETE FROM build_res WHERE repo = ? AND num = ? AND builder = ?', [repo_label, num, builder])
continue
Expand All @@ -871,14 +903,16 @@ def main():

db_query(db, 'SELECT repo, num, mergeable FROM mergeable')
for repo_label, num, mergeable in db.fetchall():
try: state = states[repo_label][num]
try:
state = states[repo_label][num]
except KeyError:
db_query(db, 'DELETE FROM mergeable WHERE repo = ? AND num = ?', [repo_label, num])
continue

state.mergeable = bool(mergeable) if mergeable is not None else None

queue_handler_lock = Lock()

def queue_handler():
with queue_handler_lock:
return process_queue(states, repos, repo_cfgs, logger, buildbot_slots, db, git_cfg)
Expand Down
Loading