def poll_pull_requests(api): __log.info("looking for PRs") db = DB.get_instance() try: db.query(""" CREATE TABLE IF NOT EXISTS meritocracy_mentioned ( id INTEGER PRIMARY KEY, commit_hash VARCHAR(40) ) """) except: __log.exception("Failed to create meritocracy mentioned DB table") # get voting window voting_window = gh.voting.get_initial_voting_window() # get all ready prs (disregarding of the voting window) prs = gh.prs.get_ready_prs(api, settings.URN, 0) # This sets up a voting record, with each user having a count of votes # that they have cast. try: fp = open('server/voters.json', 'x') fp.close() except: # file already exists, which is what we want pass with open('server/voters.json', 'r+') as fp: total_votes = {} fs = fp.read() if fs: total_votes = json.loads(fs) top_contributors = sorted(gh.repos.get_contributors(api, settings.URN), key=lambda user: user["total"], reverse=True) top_contributors = [item["author"]["login"].lower() for item in top_contributors] contributors = set(top_contributors) # store it while it's still a complete list top_contributors = top_contributors[:settings.MERITOCRACY_TOP_CONTRIBUTORS] top_contributors = set(top_contributors) top_voters = sorted(total_votes, key=total_votes.get, reverse=True) top_voters = set([user.lower() for user in top_voters[:settings.MERITOCRACY_TOP_VOTERS]]) meritocracy = top_voters | top_contributors __log.info("generated meritocracy: " + str(meritocracy)) with open('server/meritocracy.json', 'w') as mfp: json.dump(list(meritocracy), mfp) needs_update = False for pr in prs: pr_num = pr["number"] __log.info("processing PR #%d", pr_num) # gather all current votes votes, meritocracy_satisfied = gh.voting.get_votes(api, settings.URN, pr, meritocracy) # is our PR approved or rejected? vote_total, variance = gh.voting.get_vote_sum(api, votes, contributors) threshold = gh.voting.get_approval_threshold(api, settings.URN) is_approved = vote_total >= threshold and meritocracy_satisfied # the PR is mitigated or the threshold is not reached ? if variance >= threshold or not is_approved: voting_window = gh.voting.get_extended_voting_window(api, settings.URN) if vote_total >= threshold / 2: # check if we need to mention the meritocracy try: commit = pr["head"]["sha"] if not db.query("SELECT * FROM meritocracy_mentioned WHERE commit_hash=?", (commit,)): db.query("INSERT INTO meritocracy_mentioned (commit_hash) VALUES (?)", (commit,)) gh.comments.leave_meritocracy_comment(api, settings.URN, pr["number"], meritocracy) except: __log.exception("Failed to process meritocracy mention") # is our PR in voting window? in_window = gh.prs.is_pr_in_voting_window(api, pr, voting_window) if is_approved: __log.info("PR %d status: will be approved", pr_num) gh.prs.post_accepted_status( api, settings.URN, pr, voting_window, votes, vote_total, threshold, meritocracy_satisfied) if in_window: __log.info("PR %d approved for merging!", pr_num) try: sha = gh.prs.merge_pr(api, settings.URN, pr, votes, vote_total, threshold, meritocracy_satisfied) # some error, like suddenly there's a merge conflict, or some # new commits were introduced between finding this ready pr and # merging it except gh.exceptions.CouldntMerge: __log.info("couldn't merge PR %d for some reason, skipping", pr_num) gh.issues.label_issue(api, settings.URN, pr_num, ["can't merge"]) continue gh.comments.leave_accept_comment( api, settings.URN, pr_num, sha, votes, vote_total, threshold, meritocracy_satisfied) gh.issues.label_issue(api, settings.URN, pr_num, ["accepted"]) # chaosbot rewards merge owners with a follow pr_owner = pr["user"]["login"] gh.users.follow_user(api, pr_owner) needs_update = True else: __log.info("PR %d status: will be rejected", pr_num) if in_window: gh.prs.post_rejected_status( api, settings.URN, pr, voting_window, votes, vote_total, threshold, meritocracy_satisfied) __log.info("PR %d rejected, closing", pr_num) gh.comments.leave_reject_comment( api, settings.URN, pr_num, votes, vote_total, threshold, meritocracy_satisfied) gh.issues.label_issue(api, settings.URN, pr_num, ["rejected"]) gh.prs.close_pr(api, settings.URN, pr) elif vote_total < 0: gh.prs.post_rejected_status( api, settings.URN, pr, voting_window, votes, vote_total, threshold, meritocracy_satisfied) else: gh.prs.post_pending_status( api, settings.URN, pr, voting_window, votes, vote_total, threshold, meritocracy_satisfied) for user in votes: if user in total_votes: total_votes[user] += 1 else: total_votes[user] = 1 if fs: # prepare for overwriting fp.seek(0) fp.truncate() json.dump(total_votes, fp) # flush all buffers because we might restart, which could cause a crash os.fsync(fp) # we approved a PR, restart if needs_update: __log.info("updating code and requirements and restarting self") startup_path = join(THIS_DIR, "..", "startup.sh") # before we exec, we need to flush i/o buffers so we don't lose logs or voters sys.stdout.flush() sys.stderr.flush() os.execl(startup_path, startup_path) __log.info("Waiting %d seconds until next scheduled PR polling event", settings.PULL_REQUEST_POLLING_INTERVAL_SECONDS)