Skip to content

Commit fe552da

Browse files
authored
Merge pull request #2 from tedivm/lib_group
Labels, Contributor Privileges (delay override), bugfixes
2 parents 47fbce5 + 8478ba9 commit fe552da

File tree

3 files changed

+111
-17
lines changed

3 files changed

+111
-17
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,22 @@ collaborators_only: false
3333
# When defined only process votes from these github users
3434
whitelist:
3535
- alice
36-
- bob
3736
- carol
3837

38+
# When defined votes from these users will be ignored
39+
blacklist:
40+
- bob
41+
- dan
42+
3943
# Number of hours after last action (commit or opening the pull request) before issue can be merged
4044
mergedelay: 24
4145

42-
# Number of votes at which the mergedelay gets ignored, assuming no negative votes.
46+
# Number of votes from contributors at which the mergedelay gets ignored, assuming no negative votes.
4347
delayoverride: 10
4448

49+
# When `delayoverride` is set this value is the minimum hours without changes before the PR will be merged
50+
mergedelaymin: 1
51+
4552
# Number of hours after last action (commit or opening the pull request) before issue is autoclosed
4653
timeout: 720
4754
```

gitconsensus/gitconsensus.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def merge(username, repository_name):
8585
repo = Repository(username, repository_name)
8686
requests = repo.getPullRequests()
8787
for request in requests:
88+
request.addInfoLabels()
8889
if request.validate():
8990
click.echo("Merging PR#%s" % (request.number,))
9091
request.vote_merge()
@@ -101,6 +102,7 @@ def close(username, repository_name):
101102
continue
102103
if request.shouldClose():
103104
click.echo("Closing PR#%s" % (request.number,))
105+
request.addInfoLabels()
104106
request.close()
105107

106108
if __name__ == '__main__':

gitconsensus/repository.py

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def getPullRequest(self, number):
7878
def isContributor(self, username):
7979
if not self.contributors:
8080
contributor_list = self.repository.iter_contributors()
81-
self.contributors = [contributor['login'] for contributor in contributor_list]
81+
self.contributors = [str(contributor) for contributor in contributor_list]
8282
return username in self.contributors
8383

8484
def isCollaborator(username):
@@ -94,6 +94,7 @@ class PullRequest:
9494
labels = False
9595
def __init__(self, repository, number):
9696
self.repository = repository
97+
self.consensus = repository.getConsensus()
9798
self.number = number
9899
self.pr = self.repository.client.pull_request(self.repository.user, self.repository.name, number)
99100

@@ -105,6 +106,11 @@ def __init__(self, repository, number):
105106
self.yes = []
106107
self.no = []
107108
self.abstain = []
109+
110+
self.contributors_yes = []
111+
self.contributors_no = []
112+
self.contributors_abstain = []
113+
108114
self.users = []
109115
self.doubles = []
110116
for reaction in reactions:
@@ -115,6 +121,10 @@ def __init__(self, repository, number):
115121
if username in self.doubles:
116122
continue
117123

124+
if 'blacklist' in self.repository.rules and self.repository.rules['blacklist']:
125+
if username in self.repository.blacklist:
126+
continue
127+
118128
if 'collaborators_only' in self.repository.rules and self.repository.rules['collaborators_only']:
119129
if not isCollaborator(username):
120130
continue
@@ -138,17 +148,38 @@ def __init__(self, repository, number):
138148
self.no.remove(username)
139149
if username in self.abstain:
140150
self.abstain.remove(username)
151+
if username in self.contributors_yes:
152+
self.contributors_yes.remove(username)
153+
if username in self.contributors_no:
154+
self.contributors_no.remove(username)
155+
if username in self.contributors_abstain:
156+
self.contributors_abstain.remove(username)
141157
continue
142158

143159
if content == '+1':
144-
self.yes.append(user['login'])
145160
self.users.append(user['login'])
161+
self.yes.append(user['login'])
162+
if self.repository.isContributor(user['login']):
163+
self.contributors_yes.append(user['login'])
146164
elif content == '-1':
147-
self.no.append(user['login'])
148165
self.users.append(user['login'])
166+
self.no.append(user['login'])
167+
if seld.repository.isContributor(user['login']):
168+
self.contributors_no.append(user['login'])
149169
elif content == 'confused':
150-
self.abstain.append(user['login'])
151170
self.users.append(user['login'])
171+
self.abstain.append(user['login'])
172+
if seld.repository.isContributor(user['login']):
173+
self.contributors_abstain.append(user['login'])
174+
175+
files = self.pr.iter_files()
176+
self.changes_consensus = False
177+
self.changes_license = False
178+
for changed_file in files:
179+
if changed_file.filename == '.gitconsensus.yaml':
180+
self.changes_consensus = True
181+
if changed_file.filename.lower().startswith('license'):
182+
self.changes_license = True
152183

153184
def hoursSinceLastCommit(self):
154185
commits = self.pr.iter_commits()
@@ -158,31 +189,35 @@ def hoursSinceLastCommit(self):
158189

159190
# 2017-08-19T23:29:31Z
160191
commit_date = datetime.datetime.strptime(commit_date_string, '%Y-%m-%dT%H:%M:%SZ')
161-
now = datetime.datetime.now()
192+
now = datetime.datetime.utcnow()
162193
delta = now - commit_date
163194
return delta.seconds / 3600
164195

165196
def hoursSincePullOpened(self):
166-
now = datetime.datetime.now()
197+
now = datetime.datetime.utcnow()
167198
delta = now - self.pr.created_at.replace(tzinfo=None)
168199
return delta.seconds / 3600
169200

170201
def hoursSinceLastUpdate(self):
171202
hoursOpen = self.hoursSincePullOpened()
172203
hoursSinceCommit = self.hoursSinceLastCommit()
173-
174204
if hoursOpen < hoursSinceCommit:
175205
return hoursOpen
176206
return hoursSinceCommit
177207

208+
def changesConsensus(self):
209+
return self.changes_consensus
210+
211+
def changesLicense(self):
212+
return self.changes_license
213+
178214
def getIssue(self):
179215
return self.repository.repository.issue(self.number)
180216

181217
def validate(self):
182218
if self.repository.rules == False:
183219
return False
184-
consenttest = self.repository.getConsensus()
185-
return consenttest.validate(self)
220+
return self.consensus.validate(self)
186221

187222
def shouldClose(self):
188223
if 'timeout' in self.repository.rules:
@@ -193,11 +228,13 @@ def shouldClose(self):
193228
def close(self):
194229
self.pr.close()
195230
self.addLabels(['gc-closed'])
231+
self.cleanInfoLabels()
196232
self.commentAction('closed')
197233

198234
def vote_merge(self):
199235
self.pr.merge('GitConsensus Merge')
200236
self.addLabels(['gc-merged'])
237+
self.cleanInfoLabels()
201238

202239
if 'extra_labels' in self.repository.rules and self.repository.rules['extra_labels']:
203240
self.addLabels([
@@ -208,18 +245,53 @@ def vote_merge(self):
208245
])
209246
self.commentAction('merged')
210247

248+
def addInfoLabels(self):
249+
labels = self.getLabelList()
250+
251+
licenseMessage = 'License Change'
252+
if self.changesLicense():
253+
self.addLabels([licenseMessage])
254+
else:
255+
self.removeLabels([licenseMessage])
256+
257+
consensusMessage = 'Consensus Change'
258+
if self.changesConsensus():
259+
self.addLabels([consensusMessage])
260+
else:
261+
self.removeLabels([consensusMessage])
262+
263+
hasQuorumMessage = 'Has Quorum'
264+
needsQuorumMessage = 'Needs Votes'
265+
if self.consensus.hasQuorum(self):
266+
self.addLabels([hasQuorumMessage])
267+
self.removeLabels([needsQuorumMessage])
268+
else:
269+
self.removeLabels([hasQuorumMessage])
270+
self.addLabels([needsQuorumMessage])
271+
272+
passingMessage = 'Passing'
273+
failingMessage = 'Failing'
274+
if self.consensus.hasVotes(self):
275+
self.addLabels([passingMessage])
276+
self.removeLabels([failingMessage])
277+
else:
278+
self.removeLabels([passingMessage])
279+
self.addLabels([failingMessage])
280+
281+
def cleanInfoLabels(self):
282+
self.removeLabels(['Failing', 'Passing', 'Needs Votes', 'Has Quorum'])
283+
211284
def commentAction(self, action):
212285
table = self.buildVoteTable()
213-
consensus = self.repository.getConsensus()
214286
message = message_template % (
215287
action,
216288
str(len(self.yes)),
217289
str(len(self.no)),
218290
str(len(self.abstain)),
219291
str(len(self.users)),
220292
table,
221-
consensus.hasQuorum(self),
222-
consensus.hasVotes(self)
293+
self.consensus.hasQuorum(self),
294+
self.consensus.hasVotes(self)
223295
)
224296

225297
if len(self.doubles) > 0:
@@ -251,11 +323,19 @@ def buildVoteTable(self):
251323
table = "%s\n%s" % (table, row)
252324
return table
253325

254-
255326
def addLabels(self, labels):
327+
existing = self.getLabelList()
256328
issue = self.getIssue()
257329
for label in labels:
258-
issue.add_labels(label)
330+
if label not in existing:
331+
issue.add_labels(label)
332+
333+
def removeLabels(self, labels):
334+
existing = self.getLabelList()
335+
issue = self.getIssue()
336+
for label in labels:
337+
if label in existing:
338+
issue.remove_label(label)
259339

260340
def addComment(self, comment_string):
261341
return self.getIssue().create_comment(comment_string)
@@ -320,8 +400,13 @@ def hasAged(self, pr):
320400
if hours >= self.rules['mergedelay']:
321401
return True
322402
if 'delayoverride' in self.rules and self.rules['delayoverride']:
403+
if pr.changesConsensus() or pr.changesLicense():
404+
return False
405+
if 'mergedelaymin' in self.rules and self.rules['mergedelaymin']:
406+
if hours < self.rules['mergedelaymin']:
407+
return False
323408
if len(pr.no) > 0:
324409
return False
325-
if len(pr.yes) >= self.rules['delayoverride']:
410+
if len(pr.contributors_yes) >= self.rules['delayoverride']:
326411
return True
327412
return False

0 commit comments

Comments
 (0)