Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 641 lines (562 sloc) 23.785 kB
2a7e00d @doubleotoo tmp
authored
1 # TODO: validate repo exists first
cf4e5d4 @doubleotoo Add quick example of listing pull requests (stolen from schacon/git-p…
authored
2 #
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
3 require 'grit'
4 require 'tmpdir'
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
5 require 'yaml'
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
6
cf4e5d4 @doubleotoo Add quick example of listing pull requests (stolen from schacon/git-p…
authored
7 require 'github_api'
8 require 'github_flow'
9
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
10 require 'utilities'
11
2a7e00d @doubleotoo tmp
authored
12 #GithubFlow::Models::Schema.new(adapter='sqlite3', database='tmp-demo.sqlite3', force=true)
13 #GithubFlow::Models::Schema.new
14 #GithubFlow::Models::Schema.new(adapter='sqlite3', database=':memory:', force=true, logger=nil)
15 GithubFlow::Models::Schema.new(adapter='sqlite3', database='tmp.sqlite3', force=true, logger=nil)
16
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
17 #repo = GithubFlow::Models::GithubRepo.create(:name => 'foo', :user => 'rose-compiler')
2a7e00d @doubleotoo tmp
authored
18 #if repo.valid?
19 # puts 'Valid!'
20 #else
21 # raise "Error: #{repo.errors.full_messages}"
22 #end
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
23 #
24 #
2a7e00d @doubleotoo tmp
authored
25
26 def pull_requests
27 GithubFlow::Models::GithubRepo.all.each do |repo|
28 page_no=1; @github.pull_requests.pull_requests(
29 repo.user, repo.repo, {:state => 'open'}).each_page do |page|
30 puts "Page #{page_no}"
31 page.each do |pull_request|
32 puts "#{pull_request.number} #{pull_request.state} #{pull_request.title}: #{pull_request.updated_at}"
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
33 puts @github.pull_requests.comments 'doubleotoo', 'foo', pull_request.number
2a7e00d @doubleotoo tmp
authored
34 end # page.each
35 page_no += 1
36 end # pull_requests.each_page
37 end # GithubRepo.all
38 end # pull_requests
39
40 # puts Github::Repos.actions
41
42 # get_updated_branches
43 #
44 # Polls GitHub repositories for new/updated branches (these are persisted).
45 #
46 # +github+ is a +Github+ object (default='Github.new')
47 #
48 # options::
49 #
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
50 # :repos is an array of source GithubFlow::Models::GithubRepo objects.
2a7e00d @doubleotoo tmp
authored
51 #
52 # Returns {
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
53 # Github::Repos => [ {:new => GithubRepoBranch, :old_sha => SHA1-string, ...],
2a7e00d @doubleotoo tmp
authored
54 # ...
55 # }.
56 #
57 def get_updated_branches(github = Github.new, user_options = {}, &block)
58 options = {
59 :repos => GithubFlow::Models::GithubRepo.all
60 }.merge(user_options).freeze
61 #-----------------------------------------------------------------------------
62 updated_repos = {} # { repo => [branch, ...] }
63
64 options[:repos].compact.each do |repo|
65 github.repos.branches(repo.user, repo.name).each_page do |page|
66 page.each do |remote_branch|
67
68 begin
69
70 db_branch = repo.branches.find_by_name!(remote_branch.name)
71 #puts "Branch already exists: #{db_branch}"
72
73 #---------------------------------------------------------------------
74 # Updated branch
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
75 if db_branch.sha != remote_branch.commit.sha
2a7e00d @doubleotoo tmp
authored
76 #puts "Branch updating: #{db_branch}"
77
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
78 old_sha = db_branch.sha
2a7e00d @doubleotoo tmp
authored
79 db_branch.sha = remote_branch.commit.sha
80 db_branch.save
81
82 if db_branch.valid?
83 #puts "Branch updated: #{db_branch}"
84 updated_repos[repo] ||= []
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
85 updated_repos[repo] << {
86 :new => db_branch,
87 :old_sha => old_sha
88 }
2a7e00d @doubleotoo tmp
authored
89 else
90 raise "Error: #{db_branch.errors.full_messages}"
91 end
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
92 else
93 #puts "Branch is up-to-date: #{db_branch}!"
2a7e00d @doubleotoo tmp
authored
94 end
95
96 rescue ActiveRecord::RecordNotFound
97
98 #---------------------------------------------------------------------
99 # New branch
100 db_branch = repo.branches.create(
101 :name => remote_branch.name,
102 :sha => remote_branch.commit.sha
103 )
104
105 if db_branch.valid?
106 #puts "Branch is NEW: #{db_branch}"
107 updated_repos[repo] ||= []
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
108 updated_repos[repo] << {
109 :new => db_branch,
110 :old_sha => nil
111 }
2a7e00d @doubleotoo tmp
authored
112 else
113 raise "Error: #{db_branch.errors.full_messages}"
114 end
115
116 end # begin..rescue
117 end # page.each |remote_branch|
118 end # list_branches.each_page
119 end
120
121 if block_given?
122 yield updated_repos
123 end
124
125 updated_repos
126 end # get_updated_branches
127
128 # get_commit
129 #
130 # +github+ is a +Github+ object (default='Github.new')
131 #
132 # options::
133 #
134 # :user is a GitHub username (string)
135 # :repo is a GitHub repository name (string)
136 # :sha is a Git Sha1 (string)
137 #
138 def get_commit(github = Github.new, user_options = {}, &block)
139 options = {}.merge(user_options).freeze
140 raise 'Missing required option :user' if not options.has_key?(:user)
141 raise 'Missing required option :repo' if not options.has_key?(:repo)
142 raise 'Missing required option :sha' if not options.has_key?(:sha)
143 #-----------------------------------------------------------------------------
6ca26f8 @doubleotoo tmp2
authored
144 if github.repos.get_repo(options[:user], options[:repo]).nil?
2a7e00d @doubleotoo tmp
authored
145 raise "repository #{options[:user]}/#{options[:repo]} does not exist."
146 else
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
147 commit = nil
148
cf4e5d4 @doubleotoo Add quick example of listing pull requests (stolen from schacon/git-p…
authored
149 GithubFlow.log "Searching #{options[:user]}/#{options[:repo]} for #{options[:sha]}"
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
150
151 # TODO: GitHub APIv3 bug? returns commit if it exists in a forked repo...
152 # TODO: sent email to github support
153 #commit = github.repos.commit(options[:user], options[:repo], options[:sha])
154
155 # TODO: cloning the entire repository each time is slow...
cf4e5d4 @doubleotoo Add quick example of listing pull requests (stolen from schacon/git-p…
authored
156 # TODO: we may already have a clone of this repository...
4d3001d @doubleotoo Create new pull requests for each branch update.
authored
157 repo_path = "https://github.com/#{options[:user]}/#{options[:repo]}.git"
158
159 GithubFlow.log "$ git clone #{repo_path}"
160
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
161 Dir.mktmpdir do |tmp_git_path|
162 git = Grit::Git.new(tmp_git_path)
163 git.clone({
164 :quiet => false,
165 :verbose => true,
166 :progress => true,
167 :branch => 'master'
168 },
4d3001d @doubleotoo Create new pull requests for each branch update.
authored
169 repo_path,
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
170 tmp_git_path)
171
172 grit = Grit::Repo.new(tmp_git_path)
173 if grit.git.branch( {:contains => options[:sha]} ).empty?
174 commit = nil
175 else
176 commit = github.repos.commit(options[:user], options[:repo], options[:sha])
177 end
178 end
179
2a7e00d @doubleotoo tmp
authored
180 if block_given?
181 yield commit
182 end
183 return commit
184 end
185 end # get_commit
186
187 # get_updated_branches_relative_to_repo
188 #
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
189 # Polls GitHub repositories for new/updated branches that have new commits.
190 # (Branches are persisted.)
2a7e00d @doubleotoo tmp
authored
191 #
192 # +github+ is a +Github+ object (default='Github.new')
193 #
194 # options::
195 #
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
196 # :base_user is a GitHub username (string)
197 # :base_repo is a GitHub repository name to check commits against.
6ca26f8 @doubleotoo tmp2
authored
198 # :repos is an array of source GithubRepo objects.
2a7e00d @doubleotoo tmp
authored
199 #
200 # Returns {
201 # Github::Repos => [GithubRepoBranch, ...],
202 # ...
203 # }.
204 #
205 def get_updated_branches_relative_to_repo(github = Github.new, user_options = {}, &block)
206 options = {
6ca26f8 @doubleotoo tmp2
authored
207 :repos => GithubFlow::Models::GithubRepo.all
2a7e00d @doubleotoo tmp
authored
208 }.merge(user_options).freeze
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
209 raise 'Missing required option :base_user' if not options.has_key?(:base_user)
210 raise 'Missing required option :base_repo' if not options.has_key?(:base_repo)
2a7e00d @doubleotoo tmp
authored
211 #-----------------------------------------------------------------------------
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
212 updated_branches = {} # { repo => [branch, ...] }
2a7e00d @doubleotoo tmp
authored
213
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
214 get_updated_branches(github, options) do |updated_repos|
215 updated_repos.each do |updated_repo, updated_db_branches|
216 updated_db_branches.each do |updated_db_branch_hash|
217 updated_db_branch = updated_db_branch_hash[:new]
218 old_sha = updated_db_branch_hash[:old_sha]
1b04f6a @doubleotoo Use Grit to check forked-commit in base repository.
authored
219 if get_commit(github,
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
220 :user => options[:base_user],
221 :repo => options[:base_repo],
222 :sha => updated_db_branch.sha).nil?
223 # new commit not in :base_repo
224 updated_branches[updated_repo] ||= []
225 updated_branches[updated_repo] << updated_db_branch_hash
226 GithubFlow.log "#{updated_db_branch.sha} does NOT exist in #{options[:base_user]}/#{options[:base_repo]}"
2a7e00d @doubleotoo tmp
authored
227 else
228 # old commit already exists in repository
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
229 GithubFlow.log"#{updated_db_branch.sha} EXISTS in #{options[:base_user]}/#{options[:base_repo]}"
2a7e00d @doubleotoo tmp
authored
230 end
231 end
232 end
233 end
234
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
235 if block_given?
236 yield updated_branches
237 end
238 updated_branches
2a7e00d @doubleotoo tmp
authored
239 end # get_updated_branches_relative_to_repo
240
f053cdf @doubleotoo Add some TODO comments.
authored
241 # create_pull_requests_for_updated_branches
242 #
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
243 # TODO: add labels 'pull-request', 'test-request'
f053cdf @doubleotoo Add some TODO comments.
authored
244 # TODO: check if an identical pull_request was created before, but is now 'closed'. Instead
245 # of creating a new one, it would probably make more sense to 'reopen' the old one.
246 # This way, we can retain any previous "history" related to the pull_request, e.g.
247 # pull_request comments.
248 #
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
249 def create_pull_requests_for_updated_branches(github = Github.new, user_options ={}, &block)
250 options = {
251 :repos => GithubFlow::Models::GithubRepo.all,
252 :base_branch => 'master'
253 }.merge(user_options).freeze
254 raise 'Missing required option :base_user' if not options.has_key?(:base_user)
255 raise 'Missing required option :base_repo' if not options.has_key?(:base_repo)
256 #-----------------------------------------------------------------------------
257
258 updated_repo_branches = get_updated_branches_relative_to_repo(github,
259 :base_user => options[:base_user],
260 :base_repo => options[:base_repo])
261 updated_repo_branches.each do |updated_repo, updated_branches|
262 updated_branches.each do |updated_branch_hash|
263 updated_branch = updated_branch_hash[:new]
264 old_sha = updated_branch_hash[:old_sha]
265
266 # TODO: add ass method option
267 next if not updated_branch.name.match(/-rc$/)
268
7360e27 @doubleotoo Minor refactoring.
authored
269 GithubFlow.log 'Creating pull request: ' +
270 "[from: #{updated_repo.path}:#{updated_branch.name} (#{updated_branch.sha})] " +
271 "[into: #{options[:base_user]}/#{options[:base_repo]}:#{options[:base_branch]}]"
4d3001d @doubleotoo Create new pull requests for each branch update.
authored
272
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
273 # TODO: between [rose-compiler:rose, user:branch]
02ef044 @doubleotoo Automatically add code reviewers PER FILE.
authored
274 file_reviewers = {}
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
275 Dir.mktmpdir do |tmp_git_path|
276 git = Grit::Git.new(tmp_git_path)
277 git.clone({
278 :quiet => false,
279 :verbose => true,
280 :progress => true,
281 :branch => 'master'
282 },
283 "http://github.com/#{updated_repo.path}.git",
284 tmp_git_path)
285
286 grit = Grit::Repo.new(tmp_git_path)
287 new_commits = grit.commits_between('origin/master', updated_branch.sha)
288 review_commits = new_commits.select { |commit| ignore_commit(grit, commit) == false }
289 ignored_commits = new_commits - review_commits
290
291 GithubFlow.log "New commits: #{new_commits.size} #{new_commits}"
292 GithubFlow.log "Ignored commits: #{ignored_commits.size} #{ignored_commits}"
293
02ef044 @doubleotoo Automatically add code reviewers PER FILE.
authored
294 file_reviewers = get_reviewers_by_file(grit, 'AUTHORS.yml', review_commits)
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
295
02ef044 @doubleotoo Automatically add code reviewers PER FILE.
authored
296 # Only need to validate each unique user once.
297 reviewers = []
298 file_reviewers.each do |f, r|
299 reviewers << r
300 end
301 reviewers = reviewers.flatten.compact.uniq
302
303 validate_reviewers(github, reviewers)
304 end # Dir.mktmpdir
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
305
306 # Don't allow self-reviews. Someone else has to review your work!
02ef044 @doubleotoo Automatically add code reviewers PER FILE.
authored
307 file_reviewers = file_reviewers.each do |file, reviewers|
308 reviewers = reviewers.collect do |reviewer|
309 if reviewer['github-user'] == updated_repo.user
310 nil
311 else
312 reviewer
313 end
314 end.compact
315 file_reviewers[file] = reviewers
316 end
317
318 GithubFlow.log "Reviewers for new pull request: #{file_reviewers}"
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
319
02ef044 @doubleotoo Automatically add code reviewers PER FILE.
authored
320 #---------------------------------
321 # Pull request description body
322 #---------------------------------
18acc39 @doubleotoo Update pull_request description to have nice links to file diffs.
authored
323 body = "(Automatically generated pull-request.)\n"
02ef044 @doubleotoo Automatically add code reviewers PER FILE.
authored
324 file_reviewers.each {|file, reviewers|
325 body << reviewers.collect {|r| "\n@#{r['github-user']} "}.join + ": please code review #{file}."
326 }
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
327
4d3001d @doubleotoo Create new pull requests for each branch update.
authored
328 begin
18acc39 @doubleotoo Update pull_request description to have nice links to file diffs.
authored
329 pull_request = github.pull_requests.create_request(
4d3001d @doubleotoo Create new pull requests for each branch update.
authored
330 options[:base_user],
331 options[:base_repo],
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
332 'title' => "Merge #{updated_repo.user}:#{updated_branch.name} (#{updated_branch.sha[0,8]})",
02ef044 @doubleotoo Automatically add code reviewers PER FILE.
authored
333 'body' => body,
4d3001d @doubleotoo Create new pull requests for each branch update.
authored
334 'head' => "#{updated_repo.user}:#{updated_branch.sha}",
335 'base' => "#{options[:base_branch]}")
18acc39 @doubleotoo Update pull_request description to have nice links to file diffs.
authored
336 GithubFlow.log "Created GitHub::PullRequest: #{pull_request.to_json}"
4d3001d @doubleotoo Create new pull requests for each branch update.
authored
337
338 updated_repo.pull_requests.create!(
18acc39 @doubleotoo Update pull_request description to have nice links to file diffs.
authored
339 :issue_number => pull_request.number,
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
340 :base_github_repo_path => "#{options[:base_user]}/#{options[:base_repo]}",
341 :base_sha => options[:base_branch],
4d3001d @doubleotoo Create new pull requests for each branch update.
authored
342 :head_sha => updated_branch.sha)
343 GithubFlow.log 'Persisted GitHubFlow::PullRequest'
344 rescue Github::Error::UnprocessableEntity
345 # TODO: existing pull request
346 # TODO: ...check if it's being tested already. If so, create a new request.
347 # TODO: should have been caught above
348 # e = GithubFlow::Error::PullRequestExistsError.new($!.response_message)
349 # if e.matches?
350 # raise e
351 # else
352 # raise "Unknown Github::Error: #{$!}"
353 # end
354 GithubFlow.log "Github Error"
355 end # begin..rescue
356
18acc39 @doubleotoo Update pull_request description to have nice links to file diffs.
authored
357 #-------------------------------------------------------------------------
358 # Update pull request
359 #
360 # (This extra step is simply a convenience for code reviewers.)
361 #
362 # Update the pull request's description with links to each file's diff-url.
363 # This allows a code-reviewer to simply click the link to jump to the diff.
364 #
365 # Example::
366 #
367 # Markdown:
368 # @doubleotoo: please code review \
369 # [src/README](https://github.com/doubleotoo/foo/pull/40/files#diff-0).
370 #
371 # Visible HTML:
372 # @doubleotoo: please code review src/README.
373 #
374 #
375 # First, we compute the "diff number" (i.e. ../files#diff-<number>) for
376 # each file.
377 #
378 # Note: This is currently quite hackish. There's no json API that maps
379 # a file to a diff number. So our best bet is to grab the array of
380 # pull_request files and then hope that a file's index in the array is
381 # it's diff number.
382 # I suppose we could just link to the diff page instead...
383 #
384 #
385 # If this update step fails, the pull_request will have a description,
386 # requesting developers to code review files--there just won't be any
387 # nice HTML links to the diff page.
388 #
389 #-------------------------------------------------------------------------
390 pull_request_file_diff_number = {}
391 i=0; github.pull_requests.files(options[:base_user],
392 options[:base_repo],
393 pull_request.number).each_page do |page|
394 page.each do |file|
395 pull_request_file_diff_number[file.filename] = i
396 i += 1
397 end
398 end
399
400 #---------------------------------
401 # Pull request description body
402 #
403 # + With diff-links for files
404 #---------------------------------
405 body = "(Automatically generated pull-request.)\n"
406 file_reviewers.each {|file, reviewers|
407 diff_number = pull_request_file_diff_number[file]
408 diff_url = "#{pull_request.html_url}/files#diff-#{diff_number}"
409 diff_link = "[#{file}](#{diff_url})" # markdown [name](anchor)
410 GithubFlow.log "diff_link='#{diff_link}' for #{file}"
411 body << reviewers.collect {|r| "\n@#{r['github-user']} "}.join + ": please code review #{diff_link}."
412
413 raise "diff_number is nil for #{file}!" if diff_number.nil?
414 }
415
416 github.pull_requests.update_request(options[:base_user],
417 options[:base_repo],
418 pull_request.number,
419 'body' => body)
420 GithubFlow.log "Updated GitHub::PullRequest with diff-links for files: #{pull_request.to_json}"
4d3001d @doubleotoo Create new pull requests for each branch update.
authored
421 end # updated_branches.each
422 end # updated_repo_branches.each
423 end # create_pull_requests_for_updated_branches
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
424
7665029 @doubleotoo Determine which pull requests have been reviewed.
authored
425 # list_pull_requests
426 #
427 def list_pull_requests(github = Github.new, user_options ={}, &block)
428 options = {
429 :repos => GithubFlow::Models::GithubRepo.all
430 }.merge(user_options).freeze
431 raise 'Missing required option :base_user' if not options.has_key?(:base_user)
432 raise 'Missing required option :base_repo' if not options.has_key?(:base_repo)
433 #-----------------------------------------------------------------------------
cf4e5d4 @doubleotoo Add quick example of listing pull requests (stolen from schacon/git-p…
authored
434 # puts GitPulls.start('list')
7665029 @doubleotoo Determine which pull requests have been reviewed.
authored
435 pulls = github.pull_requests.requests(options[:base_user], options[:base_repo])
cf4e5d4 @doubleotoo Add quick example of listing pull requests (stolen from schacon/git-p…
authored
436 pulls.reverse.each do |p|
437 puts "Number : #{p.number}"
438 puts "Label : #{p.head}"
439 puts "Created : #{p.created_at}"
440 puts "Votes : #{p.votes}"
441 puts "Comments : #{p.comments}"
442 puts
443 puts "Title : #{p.title}"
444 puts "Body :"
445 puts
446 puts p.body
447 puts
448 puts '------------'
7665029 @doubleotoo Determine which pull requests have been reviewed.
authored
449 github.issues.comments(options[:base_user], options[:base_repo], p.number).each_page do |page|
8ff13bb @doubleotoo Add file-based mechanism to automatically add reviewers to a pull req…
authored
450 page.each do |c|
451 puts "ID : #{c.id}"
452 puts "Author: #{c.user.login}"
453 puts "Created : #{c.created_at}"
454 puts
455 puts "Body :"
456 puts
457 puts c.body
458 puts
459 puts '------------'
460 end
461 end
cf4e5d4 @doubleotoo Add quick example of listing pull requests (stolen from schacon/git-p…
authored
462 puts
463 end
7665029 @doubleotoo Determine which pull requests have been reviewed.
authored
464 end # list_pull_requests
465
466 # get_reviewed_pull_requests
467 #
468 # TODO: regular expressions need to be made more robust
f053cdf @doubleotoo Add some TODO comments.
authored
469 # TODO: (policy) if a pull_request wasn't automatically created, there won't be
470 # any @reviewers in the pull_request description. In general,
471 # this means that a user manually submitted a pull_request. In
472 # this case, how do we want to validate @user reviewed XXX
473 # comments?
474 #
475 # * No "@reviewer review request" lines in the description,
476 # then => accept any @reviewed comment line by:
477 #
478 # 1. repository collaborators or "Admin" users that we
479 # track in some other data store.
7665029 @doubleotoo Determine which pull requests have been reviewed.
authored
480 #
481 def get_reviewed_pull_requests(github = Github.new, user_options ={}, &block)
482 options = {
483 :repos => GithubFlow::Models::GithubRepo.all
484 }.merge(user_options).freeze
485 raise 'Missing required option :base_user' if not options.has_key?(:base_user)
486 raise 'Missing required option :base_repo' if not options.has_key?(:base_repo)
487 #-----------------------------------------------------------------------------
488 # puts GitPulls.start('list')
489 pulls = github.pull_requests.requests(options[:base_user], options[:base_repo])
490 pulls.reverse.each do |p|
491 GithubFlow.log "Checking if pull request " +
492 "'#{options[:base_user]}/#{options[:base_repo]}##{p.number}' " +
493 "has been code reviewed."
494
495 # Extract all the automatically generated lines pertaining to code review.
496 review_lines = []
497 file_reviewers = {}
498 request_description = p.body
499 request_description.each_line do |line|
500 # TODO: make the message a configuration
501 if match = line.match(/^@(?<user>.+): please code review (?<file>.+).*/)
502 user = match['user'].strip
503 file = match['file'].strip
504 # file could contain Markdown HTML links: [name](anchor)
505 if match = file.match(/\[(?<file>.+)\]\((?<anchor>.+)\)/)
506 file = match['file'].strip
507 anchor = match['anchor'].strip
508 end
509
510 review_lines << line
511
512 file_reviewers[file] ||= []
513 file_reviewers[file] << user
514 file_reviewers[file] = file_reviewers[file].compact # just as a pre-caution...
515 end
516 end
517
518 GithubFlow.log "Code review request lines for pull request " +
519 "'#{options[:base_user]}/#{options[:base_repo]}##{p.number}': #{review_lines}"
520
521 file_reviewers.each do |file, reviewers|
522 GithubFlow.log "Pull request " +
523 "'#{options[:base_user]}/#{options[:base_repo]}##{p.number}' " +
524 "requests '#{reviewers}' to code review file='#{file}'"
525 end
526
527 github.issues.comments('doubleotoo', 'foo', p.number).each_page do |page|
528 page.each do |c|
529 c_id = c.id
530 c_author = c.user.login
531 c_body = c.body
532
533 if match = c_body.match(/@(?<user>#{options[:base_user]}) reviewed (?<file>.+)./)
534 user = match['user'].strip
535 file = match['file'].strip
536
f053cdf @doubleotoo Add some TODO comments.
authored
537 GithubFlow.log "Detected code reviewed line for file='#{file}' in " +
7665029 @doubleotoo Determine which pull requests have been reviewed.
authored
538 "comment id='#{c_id}' authored by '#{c_author}' for " +
539 "pull request '#{options[:base_user]}/#{options[:base_repo]}##{p.number}'"
540
541 # file could contain Markdown HTML links: [name](anchor)
542 if match = file.match(/\[(?<file>.+)\]\((?<anchor>.+)\)/)
543 file = match['file'].strip
544 anchor = match['anchor'].strip
545 end
546
547 # This code reviewer is one of the people in the code review request list.
548 reviewers = file_reviewers[file]
549 if reviewers.nil?
550 raise "No reviewers for '#{file}' (#{file_reviewers})"
551 else
552 if reviewers.include?(c_author)
553 GithubFlow.log "User '#{c_author}' has code reviewed file='#{file}' for " +
554 "pull request '#{options[:base_user]}/#{options[:base_repo]}##{p.number}' " +
555 "as requested."
556
557 file_reviewers.delete(file) # file has been reviewed
558 else
559 GithubFlow.log "User '#{c_author}' was not requested to code review file='#{file}' " +
560 "for pull request '#{options[:base_user]}/#{options[:base_repo]}##{p.number}'"
561 end
562 end
563 end
564 end
565 end # github.issues.comments.each_page
566
567 outstanding_file_reviews = file_reviewers
568
569 if outstanding_file_reviews.empty?
570 GithubFlow.log "Pull request " +
571 "'#{options[:base_user]}/#{options[:base_repo]}##{p.number}' " +
572 "has been code reviewed. Ready for testing!"
573 else
574 GithubFlow.log "Pull request " +
575 "'#{options[:base_user]}/#{options[:base_repo]}##{p.number}' " +
576 "has ('#{outstanding_file_reviews.size}') outstanding code review requests."
577
578 outstanding_file_reviews.each do |file, reviewers|
579 GithubFlow.log "Pull request " +
580 "'#{options[:base_user]}/#{options[:base_repo]}##{p.number}' " +
581 "still requires '#{reviewers}' to code review file='#{file}'"
582 end
583 end
584 end
585 end # get_reviewed_pull_requests
586
587 #-------------------------------------------------------------------------------
588 # Main
589 #-------------------------------------------------------------------------------
590
591 # TODO: validate that tested commits are current pull_request's commits
592 #
593 # 1. In the DB, we have [pull_request.head_sha], so we can just make sure
594 # that [doubleotoo/foo/pulls/39].head.ref == [pull_request.head_sha].
595 #
596 # X. Check that each commit [doubleotoo/foo/pulls/39/commits] was
597 # created before [doubleotoo/foo/pulls/39].created_at. However,
598 # in this case, a user can forge GIT_COMMIT_DATE or GIT_AUTHOR_DATE.
599
600
601 # TODO: test multiple reviewers per file
602
f053cdf @doubleotoo Add some TODO comments.
authored
603
604 # TODO: web interface:
605 #
606 # FORKS:
607 #
608 # * We should poll all forks of the base repository. In the interface, the
609 # user can 'whitelist' forks that they want to automatically generate
610 # pull_requests for. If a new fork is detected, we should alert the
611 # user in the interface (maybe a pretty START).
612 #
613 # * Embed fork graphs (as a first step) to alert base_repo_owner of
614 # activity that they might be interested in...if there's a lot going
615 # on. They may want to start generated pull_requests for that repository.
616
7665029 @doubleotoo Determine which pull requests have been reviewed.
authored
617 begin
618 GithubFlow.debug = true
619 #Grit.debug = true
620
621 @github = Github.new(:basic_auth => 'doubleotoo:x')
622
623 create_pull_requests_for_updated_branches(
624 @github,
625 :base_user => 'doubleotoo',
626 :base_repo => 'foo',
627 :base_branch => 'master')
628
629 # list_pull_requests(@github,
630 # :base_user => 'doubleotoo',
631 # :base_repo => 'foo')
632
633 get_reviewed_pull_requests(@github,
634 :base_user => 'doubleotoo',
635 :base_repo => 'foo')
41e8bdc @doubleotoo Minor formatting adjustments.
authored
636
b67b4a5 @doubleotoo Add initial Pull Request generation.
authored
637 rescue Github::Error::GithubError
638 puts "Github API error response message:\n#{$!.response_message}"
639 end
2a7e00d @doubleotoo tmp
authored
640
Something went wrong with that request. Please try again.