Skip to content

Commit

Permalink
Add an autocracy
Browse files Browse the repository at this point in the history
At least one positive autocracy review on the most recent commit of a PR
is required for that PR to be accepted.
  • Loading branch information
PlasmaPower committed May 29, 2017
1 parent a16b42f commit fc3483d
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 33 deletions.
23 changes: 14 additions & 9 deletions cron/poll_pull_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ def poll_pull_requests():
__log.info("processing PR #%d", pr_num)

# gather all current votes
votes = gh.voting.get_votes(api, settings.URN, pr)
votes, autocracy_satisfied = gh.voting.get_votes(api, settings.URN, pr)

# is our PR approved or rejected?
vote_total, variance = gh.voting.get_vote_sum(api, votes)
threshold = gh.voting.get_approval_threshold(api, settings.URN)
is_approved = vote_total >= threshold
is_approved = vote_total >= threshold and autocracy_satisfied

# the PR is mitigated or the threshold is not reached ?
if variance >= threshold or not is_approved:
Expand All @@ -48,14 +48,15 @@ def poll_pull_requests():
__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)
api, settings.URN, pr, voting_window, votes, vote_total,
threshold, autocracy_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)
threshold, autocracy_satisfied)
# some error, like suddenly there's a merge conflict, or some
# new commits were introduced between findint this ready pr and
# merging it
Expand All @@ -66,7 +67,8 @@ def poll_pull_requests():
continue

gh.comments.leave_accept_comment(
api, settings.URN, pr_num, sha, votes, vote_total, threshold)
api, settings.URN, pr_num, sha, votes, vote_total,
threshold, autocracy_satisfied)
gh.prs.label_pr(api, settings.URN, pr_num, ["accepted"])

# chaosbot rewards merge owners with a follow
Expand All @@ -80,18 +82,21 @@ def poll_pull_requests():

if in_window:
gh.prs.post_rejected_status(
api, settings.URN, pr, voting_window, votes, vote_total, threshold)
api, settings.URN, pr, voting_window, votes, vote_total,
threshold, autocracy_satisfied)
__log.info("PR %d rejected, closing", pr_num)
gh.comments.leave_reject_comment(
api, settings.URN, pr_num, votes, vote_total, threshold)
api, settings.URN, pr_num, votes, vote_total, threshold, autocracy_satisfied)
gh.prs.label_pr(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)
api, settings.URN, pr, voting_window, votes, vote_total,
threshold, autocracy_satisfied)
else:
gh.prs.post_pending_status(
api, settings.URN, pr, voting_window, votes, vote_total, threshold)
api, settings.URN, pr, voting_window, votes, vote_total,
threshold, autocracy_satisfied)

# This sets up a voting record, with each user having a count of votes
# that they have cast.
Expand Down
8 changes: 8 additions & 0 deletions data/autocracy.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
PlasmaPower
hongaar
mark-i-m
rudehn
eukaryote31
phil-r
amoffat
xyproto
8 changes: 4 additions & 4 deletions github_api/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ def get_reactions_for_comment(api, urn, comment_id):
yield reaction


def leave_reject_comment(api, urn, pr, votes, total, threshold):
votes_summary = prs.formatted_votes_summary(votes, total, threshold)
def leave_reject_comment(api, urn, pr, votes, total, threshold, autocracy_satisfied):
votes_summary = prs.formatted_votes_summary(votes, total, threshold, autocracy_satisfied)
body = """
:no_good: PR rejected {summary}.
Expand All @@ -43,8 +43,8 @@ def leave_reject_comment(api, urn, pr, votes, total, threshold):
return leave_comment(api, urn, pr, body)


def leave_accept_comment(api, urn, pr, sha, votes, total, threshold):
votes_summary = prs.formatted_votes_summary(votes, total, threshold)
def leave_accept_comment(api, urn, pr, sha, votes, total, threshold, autocracy_satisfied):
votes_summary = prs.formatted_votes_summary(votes, total, threshold, autocracy_satisfied)
body = """
:ok_woman: PR passed {summary}.
Expand Down
39 changes: 24 additions & 15 deletions github_api/prs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
TRAVIS_CI_CONTEXT = "continuous-integration/travis-ci"


def merge_pr(api, urn, pr, votes, total, threshold):
def merge_pr(api, urn, pr, votes, total, threshold, autocracy_satisfied):
""" merge a pull request, if possible, and use a nice detailed merge commit
message """

Expand All @@ -26,7 +26,7 @@ def merge_pr(api, urn, pr, votes, total, threshold):
if record:
record = "Vote record:\n" + record

votes_summary = formatted_votes_summary(votes, total, threshold)
votes_summary = formatted_votes_summary(votes, total, threshold, autocracy_satisfied)

pr_url = "https://github.com/{urn}/pull/{pr}".format(urn=urn, pr=pr_num)

Expand Down Expand Up @@ -79,21 +79,28 @@ def merge_pr(api, urn, pr, votes, total, threshold):
raise


def formatted_votes_summary(votes, total, threshold):
def formatted_votes_summary(votes, total, threshold, autocracy_satisfied):
vfor = sum(v for v in votes.values() if v > 0)
vagainst = abs(sum(v for v in votes.values() if v < 0))
autocracy_str = "an" if autocracy_satisfied else "**NO**"

return ("with a vote of {vfor} for and {vagainst} against, with a weighted total \
of {total:.1f} and a threshold of {threshold:.1f}"
.strip().format(vfor=vfor, vagainst=vagainst, total=total, threshold=threshold))
return """
with a vote of {vfor} for and {vagainst} against, a weighted total of {total:.1f} \
and a threshold of {threshold:.1f}, and {autocracy} current autocracy review
""".strip().format(vfor=vfor, vagainst=vagainst, total=total, threshold=threshold,
autocracy=autocracy_str)


def formatted_votes_short_summary(votes, total, threshold):
def formatted_votes_short_summary(votes, total, threshold, autocracy_satisfied):
vfor = sum(v for v in votes.values() if v > 0)
vagainst = abs(sum(v for v in votes.values() if v < 0))
autocracy_str = "✓" if autocracy_satisfied else "✗"

return "vote: {vfor}-{vagainst}, weighted total: {total:.1f}, threshold: {threshold:.1f}" \
.strip().format(vfor=vfor, vagainst=vagainst, total=total, threshold=threshold)
return """
vote: {vfor}-{vagainst}, weighted total: {total:.1f}, threshold: {threshold:.1f}, \
autocracy: {autocracy}
""".strip().format(vfor=vfor, vagainst=vagainst, total=total, threshold=threshold,
autocracy=autocracy_str)


def label_pr(api, urn, pr_num, labels):
Expand Down Expand Up @@ -260,34 +267,36 @@ def get_reactions_for_pr(api, urn, pr):
yield reaction


def post_accepted_status(api, urn, pr, voting_window, votes, total, threshold):
def post_accepted_status(api, urn, pr, voting_window, votes, total, threshold,
autocracy_satisfied):
sha = pr["head"]["sha"]

remaining_seconds = voting_window_remaining_seconds(pr, voting_window)
remaining_human = misc.seconds_to_human(remaining_seconds)
votes_summary = formatted_votes_short_summary(votes, total, threshold)
votes_summary = formatted_votes_short_summary(votes, total, threshold, autocracy_satisfied)

post_status(api, urn, sha, "success",
"remaining: {time}, {summary}".format(time=remaining_human, summary=votes_summary))


def post_rejected_status(api, urn, pr, voting_window, votes, total, threshold):
def post_rejected_status(api, urn, pr, voting_window, votes, total, threshold,
autocracy_satisfied):
sha = pr["head"]["sha"]

remaining_seconds = voting_window_remaining_seconds(pr, voting_window)
remaining_human = misc.seconds_to_human(remaining_seconds)
votes_summary = formatted_votes_short_summary(votes, total, threshold)
votes_summary = formatted_votes_short_summary(votes, total, threshold, autocracy_satisfied)

post_status(api, urn, sha, "failure",
"remaining: {time}, {summary}".format(time=remaining_human, summary=votes_summary))


def post_pending_status(api, urn, pr, voting_window, votes, total, threshold):
def post_pending_status(api, urn, pr, voting_window, votes, total, threshold, autocracy_satisfied):
sha = pr["head"]["sha"]

remaining_seconds = voting_window_remaining_seconds(pr, voting_window)
remaining_human = misc.seconds_to_human(remaining_seconds)
votes_summary = formatted_votes_short_summary(votes, total, threshold)
votes_summary = formatted_votes_short_summary(votes, total, threshold, autocracy_satisfied)

post_status(api, urn, sha, "pending",
"remaining: {time}, {summary}".format(time=remaining_human, summary=votes_summary))
Expand Down
14 changes: 9 additions & 5 deletions github_api/voting.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def get_votes(api, urn, pr):
can't acquire approval votes, then change the pr """

votes = {}
autocracy_satisfied = False
pr_owner = pr["user"]["login"]
pr_num = pr["number"]

Expand All @@ -26,15 +27,17 @@ def get_votes(api, urn, pr):
votes[voter] = vote

# get all the pr-review-based votes
for vote_owner, vote in get_pr_review_votes(api, urn, pr_num):
for vote_owner, is_current, vote in get_pr_review_votes(api, urn, pr):
if vote and vote_owner != pr_owner:
votes[vote_owner] = vote
if vote > 0 and is_current and vote_owner.lower() in settings.AUTOCRACY_MEMBERS:
autocracy_satisfied = True

# by virtue of creating the PR, the owner defaults to a vote of 1
if votes.get(pr_owner) != -1:
votes[pr_owner] = 1

return votes
return votes, autocracy_satisfied


def get_pr_comment_votes_all(api, urn, pr_num):
Expand Down Expand Up @@ -94,15 +97,16 @@ def get_comment_reaction_votes(api, urn, comment_id):
yield reaction_owner, vote


def get_pr_review_votes(api, urn, pr_num):
def get_pr_review_votes(api, urn, pr):
""" votes made through
https://help.github.com/articles/about-pull-request-reviews/ """
for review in prs.get_pr_reviews(api, urn, pr_num):
for review in prs.get_pr_reviews(api, urn, pr["number"]):
state = review["state"]
if state in ("APPROVED", "DISMISSED"):
user = review["user"]["login"]
is_current = review["commit_id"] == pr["head"]["sha"]
vote = parse_review_for_vote(state)
yield user, vote
yield user, is_current, vote


def get_vote_weight(api, username):
Expand Down
4 changes: 4 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,7 @@
PR_STALE_HOURS = 36

API_COOLDOWN_RESET_PADDING = 30

# list of autocracy members
with open("data/autocracy.txt", "r") as f:
AUTOCRACY_MEMBERS = [line.strip().lower() for line in f.readlines()]

0 comments on commit fc3483d

Please sign in to comment.