ghtorrent 0.4 → 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/CHANGELOG +24 -0
  2. data/Gemfile +17 -0
  3. data/Gemfile.lock +40 -0
  4. data/README.md +23 -22
  5. data/bin/ght-data-retrieval +66 -24
  6. data/bin/ght-load +41 -19
  7. data/bin/ght-mirror-events +13 -16
  8. data/bin/ght-rm-dupl +119 -77
  9. data/lib/ghtorrent.rb +14 -4
  10. data/lib/ghtorrent/adapters/base_adapter.rb +17 -5
  11. data/lib/ghtorrent/adapters/mongo_persister.rb +122 -56
  12. data/lib/ghtorrent/api_client.rb +151 -16
  13. data/lib/ghtorrent/bson_orderedhash.rb +23 -0
  14. data/lib/ghtorrent/cache.rb +97 -0
  15. data/lib/ghtorrent/command.rb +43 -25
  16. data/lib/ghtorrent/gh_torrent_exception.rb +6 -0
  17. data/lib/ghtorrent/ghtorrent.rb +615 -164
  18. data/lib/ghtorrent/hash.rb +11 -0
  19. data/lib/ghtorrent/logging.rb +11 -7
  20. data/lib/ghtorrent/migrations/001_init_schema.rb +3 -3
  21. data/lib/ghtorrent/migrations/002_add_external_ref_ids.rb +2 -0
  22. data/lib/ghtorrent/migrations/003_add_orgs.rb +4 -1
  23. data/lib/ghtorrent/migrations/004_add_commit_comments.rb +4 -2
  24. data/lib/ghtorrent/migrations/005_add_repo_collaborators.rb +2 -0
  25. data/lib/ghtorrent/migrations/006_add_watchers.rb +2 -0
  26. data/lib/ghtorrent/migrations/007_add_pull_requests.rb +64 -0
  27. data/lib/ghtorrent/migrations/008_add_project_unq.rb +23 -0
  28. data/lib/ghtorrent/migrations/009_add_project_commit.rb +27 -0
  29. data/lib/ghtorrent/migrations/010_add_forks.rb +28 -0
  30. data/lib/ghtorrent/migrations/mysql_defaults.rb +6 -0
  31. data/lib/ghtorrent/persister.rb +3 -0
  32. data/lib/ghtorrent/retriever.rb +298 -102
  33. data/lib/ghtorrent/settings.rb +20 -1
  34. data/lib/ghtorrent/time.rb +5 -0
  35. data/lib/ghtorrent/utils.rb +22 -4
  36. data/lib/version.rb +5 -0
  37. metadata +173 -145
  38. data/lib/ghtorrent/call_stack.rb +0 -91
@@ -0,0 +1,11 @@
1
+ class Hash
2
+ def merge_recursive(o, overwrite = true)
3
+ merge(o) do |_,x,y|
4
+ if x.respond_to?(:merge_recursive) && y.is_a?(Hash)
5
+ x.merge_recursive(y)
6
+ else
7
+ if overwrite then y else [*x,*y] end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -21,21 +21,25 @@ module GHTorrent
21
21
 
22
22
  # Log a message at the given level.
23
23
  def log(level, msg)
24
- return unless @logger
24
+
25
25
  case level
26
26
  when :fatal then
27
- @logger.fatal msg
27
+ logger.fatal msg
28
28
  when :error then
29
- @logger.error msg
29
+ logger.error msg
30
30
  when :warn then
31
- @logger.warn msg
31
+ logger.warn msg
32
32
  when :info then
33
- @logger.info msg
33
+ logger.info msg
34
34
  when :debug then
35
- @logger.debug msg
35
+ logger.debug msg
36
36
  else
37
- @logger.debug msg
37
+ logger.debug msg
38
38
  end
39
39
  end
40
+
41
+ def logger
42
+ raise Exception.new("Unimplemented")
43
+ end
40
44
  end
41
45
  end
@@ -1,5 +1,7 @@
1
1
  require 'sequel'
2
2
 
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
3
5
  Sequel.migration do
4
6
  up do
5
7
  puts("Creating table users")
@@ -9,9 +11,7 @@ Sequel.migration do
9
11
  String :name
10
12
  String :company, :null => true
11
13
  String :location, :null => true
12
- String :email, :null => true, :unique => true
13
- TrueClass :hireable, :null => true
14
- String :bio, :null => true
14
+ String :email, :null => true
15
15
  DateTime :created_at, :null => false, :default=>Sequel::CURRENT_TIMESTAMP
16
16
  end
17
17
 
@@ -1,5 +1,7 @@
1
1
  require 'sequel'
2
2
 
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
3
5
  Sequel.migration do
4
6
  up do
5
7
  alter_table :users do
@@ -1,12 +1,15 @@
1
1
  require 'sequel'
2
2
 
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
3
5
  Sequel.migration do
4
6
  up do
5
7
 
6
8
  puts("Adding organization descriminator field to table users")
7
9
 
8
10
  alter_table :users do
9
- add_column :type, "enum('USR', 'ORG')", :null => false
11
+ add_column :type, String, :null => false, :default => 'USR'
12
+ add_constraint(:type_allowed_values, :type => %w[USR ORG])
10
13
  end
11
14
 
12
15
  puts("Updating users with default values")
@@ -1,5 +1,7 @@
1
1
  require 'sequel'
2
2
 
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
3
5
  Sequel.migration do
4
6
  up do
5
7
 
@@ -9,7 +11,7 @@ Sequel.migration do
9
11
  primary_key :id
10
12
  foreign_key :commit_id, :commits, :null => false
11
13
  foreign_key :user_id, :users, :null => false
12
- String :body
14
+ String :body, :size => 256
13
15
  Integer :line, :null => true
14
16
  Integer :position, :null => true
15
17
  Integer :comment_id, :null => false, :unique => true
@@ -24,4 +26,4 @@ Sequel.migration do
24
26
  drop_table :commit_comments
25
27
 
26
28
  end
27
- end
29
+ end
@@ -1,5 +1,7 @@
1
1
  require 'sequel'
2
2
 
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
3
5
  Sequel.migration do
4
6
  up do
5
7
 
@@ -1,5 +1,7 @@
1
1
  require 'sequel'
2
2
 
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
3
5
  Sequel.migration do
4
6
  up do
5
7
 
@@ -0,0 +1,64 @@
1
+ require 'sequel'
2
+
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
5
+ Sequel.migration do
6
+ up do
7
+
8
+ puts("Adding table pull requests")
9
+
10
+ create_table :pull_requests do
11
+ primary_key :id
12
+ foreign_key :head_repo_id, :projects
13
+ foreign_key :base_repo_id, :projects, :null => false
14
+ foreign_key :head_commit_id, :commits
15
+ foreign_key :base_commit_id, :commits, :null => false
16
+ foreign_key :user_id, :users, :null => false
17
+ Integer :pullreq_id, :null => false
18
+ TrueClass :intra_branch, :null => false
19
+ unique([:pullreq_id, :base_repo_id])
20
+ end
21
+
22
+ puts("Adding table pull request history")
23
+
24
+ create_table :pull_request_history do
25
+ primary_key :id
26
+ foreign_key :pull_request_id, :pull_requests, :null => false
27
+ DateTime :created_at, :null => false, :default=>Sequel::CURRENT_TIMESTAMP
28
+ String :ext_ref_id, :null => false, :size => 24, :default => "0"
29
+ String :action, :null => false
30
+ check(:action=>%w[opened closed merged synchronize reopened])
31
+ end
32
+
33
+ puts("Adding table pull request commits")
34
+
35
+ create_table :pull_request_commits do
36
+ foreign_key :pull_request_id, :pull_requests, :null => false
37
+ foreign_key :commit_id, :commits, :null => false
38
+ primary_key [:pull_request_id, :commit_id]
39
+ end
40
+
41
+ puts("Adding table pull request comments")
42
+
43
+ create_table :pull_request_comments do
44
+ foreign_key :pull_request_id, :pull_requests, :null => false
45
+ foreign_key :user_id, :users, :null => false
46
+ Long :comment_id, :null => false
47
+ Integer :position, :null => true
48
+ String :body, :size => 256
49
+ foreign_key :commit_id, :commits, :null => false
50
+ DateTime :created_at, :null => false, :default=>Sequel::CURRENT_TIMESTAMP
51
+ String :ext_ref_id, :null => false, :size => 24, :default => "0"
52
+ end
53
+
54
+ end
55
+
56
+ down do
57
+
58
+ drop_table :pull_requests
59
+ drop_table :pull_request_history
60
+ drop_table :pull_request_commits
61
+ drop_table :pull_request_comments
62
+
63
+ end
64
+ end
@@ -0,0 +1,23 @@
1
+ require 'sequel'
2
+
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
5
+ Sequel.migration do
6
+ up do
7
+
8
+ puts("Adding unique(name, owner) constraint to table projects")
9
+
10
+ alter_table :projects do
11
+ add_unique_constraint([:name, :owner_id])
12
+ end
13
+ end
14
+
15
+ down do
16
+
17
+ puts("Removing unique(name, owner) constraint from table projects")
18
+
19
+ alter_table :projects do
20
+ drop_unique_constraint([:name, :owner_id])
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ require 'sequel'
2
+
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
5
+ Sequel.migration do
6
+ up do
7
+
8
+ puts("Create table project_commits")
9
+ create_table :project_commits do
10
+ foreign_key :project_id, :projects
11
+ foreign_key :commit_id, :commits
12
+ primary_key [:project_id, :commit_id]
13
+ end
14
+
15
+ puts("Migrating data from commits to project_commits")
16
+ transaction(:rollback => :reraise, :isolation => :committed) do
17
+ self[:project_commits].insert([:project_id, :commit_id],
18
+ self[:commits].select(:project_id, :id))
19
+ end
20
+
21
+ end
22
+
23
+ down do
24
+
25
+ drop_table :project_commits
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ require 'sequel'
2
+
3
+ require 'ghtorrent/migrations/mysql_defaults'
4
+
5
+ Sequel.migration do
6
+ up do
7
+
8
+ puts("Adding table forks")
9
+
10
+ create_table :forks do
11
+ foreign_key :forked_project_id, :projects, :null => false
12
+ foreign_key :forked_from_id, :projects, :null => false
13
+ Integer :fork_id, :null => false, :unique => true
14
+ DateTime :created_at, :null => false,
15
+ :default => Sequel::CURRENT_TIMESTAMP
16
+ String :ext_ref_id, :null => false, :size => 24, :default => "0"
17
+ primary_key([:forked_project_id, :forked_from_id])
18
+ end
19
+
20
+ end
21
+
22
+ down do
23
+
24
+ puts("Droping table forks")
25
+ drop_table :forks
26
+
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ require 'sequel'
2
+
3
+ Sequel::MySQL.default_engine = 'InnoDB' if defined?(Sequel::MySQL)
4
+ Sequel::MySQL.default_charset = 'utf8' if defined?(Sequel::MySQL)
5
+ Sequel::MySQL.default_collate = 'utf8_general_ci' if defined?(Sequel::MySQL)
6
+
@@ -1,3 +1,6 @@
1
+ require 'ghtorrent/adapters/mongo_persister'
2
+ require 'ghtorrent/adapters/noop_persister'
3
+
1
4
  module GHTorrent
2
5
 
3
6
  #
@@ -1,29 +1,37 @@
1
1
  require 'uri'
2
2
 
3
+ require 'ghtorrent/api_client'
4
+ require 'ghtorrent/settings'
5
+ require 'ghtorrent/utils'
6
+ require 'ghtorrent/gh_torrent_exception'
7
+
3
8
  module GHTorrent
4
9
  module Retriever
5
10
 
11
+ include GHTorrent::Settings
6
12
  include GHTorrent::Utils
7
13
  include GHTorrent::APIClient
8
14
 
9
- def initialize(settings)
10
- super(settings)
11
- @settings = settings
12
- @uniq = config(:uniq_id)
15
+ def ext_uniq
16
+ raise Exception("Unimplemented")
17
+ end
18
+
19
+ def persister
20
+ raise Exception("Unimplemented")
13
21
  end
14
22
 
15
23
  def retrieve_user_byusername(user)
16
- stored_user = @persister.find(:users, {'login' => user})
24
+ stored_user = persister.find(:users, {'login' => user})
17
25
  if stored_user.empty?
18
26
  url = ghurl "users/#{user}"
19
27
  u = api_request(url)
20
28
 
21
29
  if u.empty?
22
- throw GHTorrentException.new("Cannot find user #{user}")
30
+ return
23
31
  end
24
32
 
25
- unq = @persister.store(:users, u)
26
- u[@uniq] = unq
33
+ unq = persister.store(:users, u)
34
+ u[ext_uniq] = unq
27
35
  what = user_type(u['type'])
28
36
  info "Retriever: New #{what} #{user}"
29
37
  u
@@ -41,62 +49,79 @@ module GHTorrent
41
49
  url = ghurl("legacy/user/email/#{URI.escape(email)}")
42
50
  r = api_request(url)
43
51
 
44
- return nil if r.empty?
45
- r
52
+ unless r.empty? or r['user']['login'].nil?
53
+ info "Retriever: User #{r['user']['login']} retrieved by email #{email}"
54
+ retrieve_user_byusername(r['user']['login'])
55
+ else
56
+ if r.empty?
57
+ nil
58
+ else
59
+ u = r['user']
60
+ unq = persister.store(:users, u)
61
+ u[ext_uniq] = unq
62
+ what = user_type(u['type'])
63
+ info "Retriever: New #{what} #{user}"
64
+ u
65
+ end
66
+ end
46
67
  end
47
68
 
48
- def retrieve_user_followers(user)
49
- stored_followers = @persister.find(:followers, {'follows' => user})
69
+ def retrieve_user_follower(followed, follower)
70
+ stored_item = persister.find(:followers, {'follows' => followed,
71
+ 'login' => follower})
72
+
73
+ if stored_item.empty?
74
+ retrieve_user_followers(followed).find{|x| x['login'] == follower}
75
+ else
76
+ stored_item.first
77
+ end
78
+ end
50
79
 
80
+ def retrieve_user_followers(user)
51
81
  followers = paged_api_request(ghurl "users/#{user}/followers")
52
82
  followers.each do |x|
53
83
  x['follows'] = user
54
84
 
55
- exists = !stored_followers.find { |f|
56
- f['follows'] == user && f['login'] == x['login']
57
- }.nil?
85
+ exists = !persister.find(:followers, {'follows' => user,
86
+ 'login' => x['login']}).empty?
58
87
 
59
88
  if not exists
60
- @persister.store(:followers, x)
89
+ persister.store(:followers, x)
61
90
  info "Retriever: Added follower #{user} -> #{x['login']}"
62
91
  else
63
92
  debug "Retriever: Follower #{user} -> #{x['login']} exists"
64
93
  end
65
94
  end
66
95
 
67
- @persister.find(:followers, {'follows' => user})
96
+ persister.find(:followers, {'follows' => user})
68
97
  end
69
98
 
70
99
  # Retrieve a single commit from a repo
71
100
  def retrieve_commit(repo, sha, user)
72
- commit = @persister.find(:commits, {'sha' => "#{sha}"})
101
+ commit = persister.find(:commits, {'sha' => "#{sha}"})
73
102
 
74
103
  if commit.empty?
75
104
  url = ghurl "repos/#{user}/#{repo}/commits/#{sha}"
76
105
  c = api_request(url)
77
106
 
78
107
  if c.empty?
79
- throw GHTorrentException.new("Cannot find commit #{user}/#{repo}/#{sha}")
108
+ return
80
109
  end
81
110
 
82
- unq = @persister.store(:commits, c)
83
- info "Retriever: New commit #{repo} -> #{sha}"
84
- c[@uniq] = unq
111
+ unq = persister.store(:commits, c)
112
+ info "Retriever: New commit #{user}/#{repo} -> #{sha}"
113
+ c[ext_uniq] = unq
85
114
  c
86
115
  else
87
- debug "Retriever: Already got commit #{repo} -> #{sha}"
116
+ debug "Retriever: Already got commit #{user}/#{repo} -> #{sha}"
88
117
  commit.first
89
118
  end
90
119
  end
91
120
 
92
- # Retrieve all project commits or 500 (whatever comes first),
121
+ # Retrieve up to 30 * +:mirror_commit_pages_new_repo+ commits
93
122
  # starting from the provided +sha+
94
123
  def retrieve_commits(repo, sha, user)
95
- last_sha = if sha.nil?
96
- "master"
97
- else
98
- sha
99
- end
124
+ last_sha = if sha.nil? then "master" else sha end
100
125
 
101
126
  url = ghurl "repos/#{user}/#{repo}/commits?last_sha=#{last_sha}"
102
127
  commits = paged_api_request(url, config(:mirror_commit_pages_new_repo))
@@ -108,19 +133,19 @@ module GHTorrent
108
133
 
109
134
 
110
135
  def retrieve_repo(user, repo)
111
- stored_repo = @persister.find(:repos, {'owner.login' => user,
136
+ stored_repo = persister.find(:repos, {'owner.login' => user,
112
137
  'name' => repo })
113
138
  if stored_repo.empty?
114
139
  url = ghurl "repos/#{user}/#{repo}"
115
140
  r = api_request(url)
116
141
 
117
142
  if r.empty?
118
- throw GHTorrentException.new("Cannot find repo #{user}/#{repo}")
143
+ return
119
144
  end
120
145
 
121
- unq = @persister.store(:repos, r)
146
+ unq = persister.store(:repos, r)
122
147
  info "Retriever: New repo #{user} -> #{repo}"
123
- r[@uniq] = unq
148
+ r[ext_uniq] = unq
124
149
  r
125
150
  else
126
151
  debug "Retriever: Already got repo #{user} -> #{repo}"
@@ -142,51 +167,49 @@ module GHTorrent
142
167
 
143
168
  # Retrieve organization members
144
169
  def retrieve_org_members(org)
145
- url = ghurl "orgs/#{org}/members"
146
- stored_org_members = @persister.find(:org_members, {'org' => org})
170
+ stored_org_members = persister.find(:org_members, {'org' => org})
147
171
 
148
172
  org_members = paged_api_request(ghurl "orgs/#{org}/members")
149
173
  org_members.each do |x|
150
174
  x['org'] = org
151
175
 
152
176
  exists = !stored_org_members.find { |f|
153
- f['org'] == user && f['login'] == x['login']
177
+ f['org'] == org && f['login'] == x['login']
154
178
  }.nil?
155
179
 
156
180
  if not exists
157
- @persister.store(:org_members, x)
158
- info "Retriever: Added member #{org} -> #{x['login']}"
181
+ persister.store(:org_members, x)
182
+ info "Retriever: Added org member #{org} -> #{x['login']}"
159
183
  else
160
- debug "Retriever: Member #{org} -> #{x['login']} exists"
184
+ debug "Retriever: Org Member #{org} -> #{x['login']} exists"
161
185
  end
162
186
  end
163
187
 
164
- @persister.find(:org_members, {'org' => org}).map{|o| retrieve_org(o['login'])}
188
+ persister.find(:org_members, {'org' => org}).map{|o| retrieve_org(o['login'])}
165
189
  end
166
190
 
167
191
  # Retrieve all comments for a single commit
168
192
  def retrieve_commit_comments(user, repo, sha)
169
- stored_comments = @persister.find(:commit_comments, {'commit_id' => sha})
170
193
  retrieved_comments = paged_api_request(ghurl "repos/#{user}/#{repo}/commits/#{sha}/comments")
171
194
 
172
- retrieved_comments.each{ |x|
195
+ retrieved_comments.each { |x|
173
196
  x['repo'] = repo
174
197
  x['user'] = user
175
198
  x['commit_id'] = sha
176
199
 
177
- if @persister.find(:commit_comments, {'repo' => repo,
200
+ if persister.find(:commit_comments, {'repo' => repo,
178
201
  'user' => user,
179
202
  'id' => x['id']}).empty?
180
- @persister.store(:commit_comments, x)
203
+ persister.store(:commit_comments, x)
181
204
  end
182
205
  }
183
- @persister.find(:commit_comments, {'commit_id' => sha})#.map{|x| x[@uniq] = x['_id']; x}
184
- end
206
+ persister.find(:commit_comments, {'commit_id' => sha})
207
+ end
185
208
 
186
- # Retrieve a single comment
209
+ # Retrieve a single comment
187
210
  def retrieve_commit_comment(user, repo, id)
188
211
 
189
- comment = @persister.find(:commit_comments, {'repo' => repo,
212
+ comment = persister.find(:commit_comments, {'repo' => repo,
190
213
  'user' => user,
191
214
  'id' => id}).first
192
215
  if comment.nil?
@@ -199,10 +222,10 @@ module GHTorrent
199
222
 
200
223
  r['repo'] = repo
201
224
  r['user'] = user
202
- @persister.store(:commit_comments, r)
225
+ persister.store(:commit_comments, r)
203
226
  info "Retriever: Added commit comment #{r['commit_id']} -> #{r['id']}"
204
- r[@uniq] = r['_id']
205
- r
227
+ persister.find(:commit_comments, {'repo' => repo, 'user' => user,
228
+ 'id' => id}).first
206
229
  else
207
230
  debug "Retriever: Commit comment #{comment['commit_id']} -> #{comment['id']} exists"
208
231
  comment
@@ -211,91 +234,264 @@ module GHTorrent
211
234
 
212
235
  # Retrieve all collaborators for a repository
213
236
  def retrieve_repo_collaborators(user, repo)
237
+ repo_bound_items(user, repo, :repo_collaborators,
238
+ "repos/#{user}/#{repo}/collaborators",
239
+ {'repo' => repo, 'owner' => user},
240
+ 'login')
241
+ end
242
+
243
+ # Retrieve a single repository collaborator
244
+ def retrieve_repo_collaborator(user, repo, new_member)
245
+ repo_bound_item(user, repo, new_member, :repo_collaborators,
246
+ "repos/#{user}/#{repo}/collaborators",
247
+ {'repo' => repo, 'owner' => user},
248
+ 'login')
249
+ end
250
+
251
+ # Retrieve all watchers for a repository
252
+ def retrieve_watchers(user, repo)
253
+ repo_bound_items(user, repo, :watchers,
254
+ "repos/#{user}/#{repo}/watchers",
255
+ {'repo' => repo, 'owner' => user},
256
+ 'login')
257
+ end
258
+
259
+ # Retrieve a single watcher for a repositry
260
+ def retrieve_watcher(user, repo, watcher)
261
+ repo_bound_item(user, repo, watcher, :watchers,
262
+ "repos/#{user}/#{repo}/watchers",
263
+ {'repo' => repo, 'owner' => user},
264
+ 'login')
265
+ end
214
266
 
215
- url = ghurl "repos/#{user}/#{repo}/collaborators"
216
- stored_collaborators = @persister.find(:repo_collaborators,
217
- {'repo' => repo, 'owner' => user})
267
+ def retrieve_pull_requests(user, repo)
268
+ open = "repos/#{user}/#{repo}/pulls"
269
+ closed = "repos/#{user}/#{repo}/pulls?state=closed"
270
+ repo_bound_items(user, repo, :pull_requests,
271
+ [open, closed],
272
+ {'repo' => repo, 'owner' => user},
273
+ 'number')
274
+ end
275
+
276
+ def retrieve_pull_request(user, repo, pullreq_id)
277
+ open = "repos/#{user}/#{repo}/pulls"
278
+ closed = "repos/#{user}/#{repo}/pulls?state=closed"
279
+ repo_bound_item(user, repo, pullreq_id, :pull_requests,
280
+ [open, closed],
281
+ {'repo' => repo, 'owner' => user,
282
+ 'number' => pullreq_id},
283
+ 'number')
284
+ end
285
+
286
+ def retrieve_forks(user, repo)
287
+ repo_bound_items(user, repo, :forks,
288
+ "repos/#{user}/#{repo}/forks",
289
+ {'repo' => repo, 'owner' => user},
290
+ 'id')
291
+ end
292
+
293
+ def retrieve_fork(user, repo, fork_id)
294
+ repo_bound_item(user, repo, fork_id, :forks,
295
+ "repos/#{user}/#{repo}/forks",
296
+ {'repo' => repo, 'owner' => user},
297
+ 'id')
298
+ end
218
299
 
219
- collaborators = paged_api_request url
220
- collaborators.each do |x|
300
+ def retrieve_pull_req_commits(user, repo, pullreq_id)
301
+ is_intra_branch = Proc.new do |req|
302
+ req['head']['repo'].nil?
303
+ end
304
+
305
+ pull_req = retrieve_pull_request(user, repo, pullreq_id)
306
+
307
+ unless is_intra_branch.call(pull_req)
308
+ head_user = pull_req['head']['repo']['owner']['login']
309
+ head_repo = pull_req['head']['repo']['name']
310
+
311
+ commits = paged_api_request(ghurl "repos/#{user}/#{repo}/pulls/#{pullreq_id}/commits")
312
+ commits.map { |x|
313
+ retrieve_commit(head_repo, x['sha'], head_user)
314
+ }
315
+ else
316
+ commits = paged_api_request(ghurl "repos/#{user}/#{repo}/pulls/#{pullreq_id}/commits")
317
+ commits.map { |x|
318
+ retrieve_commit(repo, x['sha'], user)
319
+ }
320
+ end
321
+ end
322
+
323
+ def retrieve_pull_req_comments(owner, repo, pullreq_id)
324
+ review_comments_url = ghurl "repos/#{owner}/#{repo}/pulls/#{pullreq_id}/comments"
325
+
326
+ url = review_comments_url
327
+ retrieved_comments = paged_api_request url
328
+
329
+ retrieved_comments.each { |x|
330
+ x['owner'] = owner
221
331
  x['repo'] = repo
222
- x['owner'] = user
332
+ x['issue_id'] = pullreq_id
223
333
 
224
- exists = !stored_collaborators.find { |f|
225
- f['login'] == x['login']
226
- }.nil?
334
+ if persister.find(:pull_request_comments, {'owner' => owner,
335
+ 'repo' => repo,
336
+ 'pullreq_id' => pullreq_id,
337
+ 'id' => x['id']}).empty?
338
+ persister.store(:pull_request_comments, x)
339
+ end
340
+ }
227
341
 
228
- if not exists
229
- @persister.store(:repo_collaborators, x)
230
- info "Retriever: Added collaborator #{repo} -> #{x['login']}"
231
- else
232
- debug "Retriever: Collaborator #{repo} -> #{x['login']} exists"
342
+ persister.find(:pull_request_comments, {'owner' => owner, 'repo' => repo,
343
+ 'pullreq_id' => pullreq_id})
344
+ end
345
+
346
+ def retrieve_pull_req_comment(owner, repo, pullreq_id, comment_id)
347
+ comment = persister.find(:pull_request_comments, {'repo' => repo,
348
+ 'owner' => owner,
349
+ 'pullreq_id' => pullreq_id,
350
+ 'id' => comment_id}).first
351
+ if comment.nil?
352
+ r = api_request(ghurl "repos/#{owner}/#{repo}/pulls/comments/#{comment_id}")
353
+
354
+ if r.empty?
355
+ debug "Retriever: Pullreq comment #{owner}/#{repo} #{pullreq_id}->#{comment_id} deleted"
356
+ return
233
357
  end
358
+
359
+ r['repo'] = repo
360
+ r['owner'] = owner
361
+ r['pullreq_id'] = pullreq_id
362
+ persister.store(:pull_request_comments, r)
363
+ info "Retriever: Added pullreq comment #{owner}/#{repo} #{pullreq_id}->#{comment_id}"
364
+ persister.find(:pull_request_comments, {'repo' => repo, 'owner' => owner,
365
+ 'pullreq_id' => pullreq_id,
366
+ 'id' => comment_id}).first
367
+ else
368
+ debug "Retriever: Pullreq comment #{owner}/#{repo} #{pullreq_id}->#{comment_id} exists"
369
+ comment
234
370
  end
371
+ end
235
372
 
236
- @persister.find(:repo_collaborators, {'repo' => repo, 'owner' => user})
373
+ def retrieve_issues(user, repo)
374
+ repo_bound_items(user, repo, :issues,
375
+ "repos/#{user}/#{repo}/issues",
376
+ {'repo' => repo, 'owner' => user},
377
+ 'id')
237
378
  end
238
379
 
239
- # Retrieve a single repository collaborator
240
- def retrieve_repo_collaborator(user, repo, new_member)
380
+ def retrieve_issue(user, repo, issue_id)
381
+ repo_bound_item(user, repo, issue_id, :issues,
382
+ "repos/#{user}/#{repo}/issues/#{issue_id}",
383
+ {'repo' => repo, 'owner' => user},
384
+ 'id')
385
+ end
386
+
387
+ def retrieve_issue_comments(owner, repo, issue_id)
388
+ url = ghurl "repos/#{owner}/#{repo}/issues/#{issue_id}/comments"
389
+ retrieved_comments = paged_api_request url
241
390
 
242
- collaborator = @persister.find(:repo_collaborators,
243
- {'repo' => repo,
244
- 'owner' => user,
245
- 'login' => new_member})
391
+ retrieved_comments.each { |x|
392
+ x['owner'] = owner
393
+ x['repo'] = repo
394
+ x['issue_id'] = issue_id
395
+
396
+ if persister.find(:issue_comments, {'owner' => owner,
397
+ 'repo' => repo,
398
+ 'issue_id' => issue_id,
399
+ 'id' => x['id']}).empty?
400
+ persister.store(:issue_comments, x)
401
+ end
402
+ }
403
+ persister.find(:issue_comments, {'owner' => owner, 'repo' => repo,
404
+ 'issue_id' => issue_id})
405
+ end
406
+
407
+ def retrieve_issue_comment(owner, repo, issue_id, comment_id)
408
+ comment = persister.find(:issue_comments, {'repo' => repo,
409
+ 'owner' => owner,
410
+ 'issue_id' => issue_id,
411
+ 'id' => comment_id}).first
412
+ if comment.nil?
413
+ r = api_request(ghurl "repos/#{owner}/#{repo}/issues/#{issue_id}/comments/#{comment_id}")
414
+
415
+ if r.empty?
416
+ debug "Retriever: Issue comment #{owner}/#{repo} #{issue_id}->#{comment_id} deleted"
417
+ return
418
+ end
246
419
 
247
- if collaborator.empty?
248
- retrieve_repo_collaborators(user, repo).find{|x| x['login'] == new_member}
420
+ r['repo'] = repo
421
+ r['owner'] = owner
422
+ x['issue_id'] = issue_id
423
+ persister.store(:issue_comments, r)
424
+ info "Retriever: Added issue comment #{owner}/#{repo} #{issue_id}->#{comment_id}"
425
+ persister.find(:issue_comments, {'repo' => repo, 'owner' => owner,
426
+ 'issue_id' => issue_id,
427
+ 'id' => comment_id}).first
428
+ r
249
429
  else
250
- collaborator.first
430
+ debug "Retriever: Commit comment #{owner}/#{repo} #{issue_id}->#{comment_id} exists"
431
+ comment
251
432
  end
252
433
  end
253
434
 
254
- # Retrieve all watchers for a repository
255
- def retrieve_watchers(user, repo)
256
- stored_watchers = @persister.find(:watchers,
257
- {'repo' => repo, 'owner' => user})
435
+ # Get current Github events
436
+ def get_events
437
+ api_request "https://api.github.com/events"
438
+ end
439
+
440
+ private
441
+
442
+ def repo_bound_items(user, repo, entity, urls, selector, descriminator,
443
+ item_id = nil)
258
444
 
259
- watchers = paged_api_request(ghurl "repos/#{user}/#{repo}/watchers")
260
- watchers.each do |x|
445
+ items = if urls.class == Array
446
+ urls.map { |url| paged_api_request(ghurl url) }.flatten
447
+ else
448
+ paged_api_request(ghurl urls)
449
+ end
450
+
451
+ items.each do |x|
261
452
  x['repo'] = repo
262
453
  x['owner'] = user
263
454
 
264
- exists = !stored_watchers.find { |f|
265
- f['login'] == x['login']
266
- }.nil?
455
+ exists = !repo_bound_instance(entity, selector,
456
+ descriminator, x[descriminator]).empty?
267
457
 
268
458
  if not exists
269
- @persister.store(:watchers, x)
270
- info "Retriever: Added watcher #{repo} -> #{x['login']}"
459
+ persister.store(entity, x)
460
+ info "Retriever: Added #{entity} #{user}/#{repo} -> #{x[descriminator]}"
271
461
  else
272
- debug "Retriever: Watcher #{repo} -> #{x['login']} exists"
462
+ debug "Retriever: #{entity} #{user}/#{repo} -> #{x[descriminator]} exists"
273
463
  end
274
464
  end
275
465
 
276
- @persister.find(:watchers, {'repo' => repo, 'owner' => user})
466
+ if item_id.nil?
467
+ persister.find(entity, selector)
468
+ else
469
+ repo_bound_instance(entity, selector, descriminator, item_id)
470
+ end
277
471
  end
278
472
 
279
- # Retrieve a single watcher for a repositry
280
- def retrieve_watcher(user, repo, watcher)
281
- stored_watcher = @persister.find(:watchers,
282
- {'repo' => repo,
283
- 'owner' => user,
284
- 'login' => watcher})
473
+ def repo_bound_item(user, repo, item_id, entity, url, selector, descriminator)
474
+ stored_item = repo_bound_instance(entity, selector, descriminator, item_id)
285
475
 
286
- if stored_watcher.empty?
287
- retrieve_watchers(user, repo).find{|x| x['login'] == watcher}
476
+ if stored_item.empty?
477
+ repo_bound_items(user, repo, entity, url, selector, descriminator,
478
+ item_id).first
288
479
  else
289
- stored_watcher.first
480
+ stored_item.first
290
481
  end
291
482
  end
292
483
 
293
- # Get current Github events
294
- def get_events
295
- api_request "https://api.github.com/events"
296
- end
484
+ def repo_bound_instance(entity, selector, descriminator, item_id)
297
485
 
298
- private
486
+ id = if item_id.to_i.to_s != item_id
487
+ item_id # item_id is string
488
+ else
489
+ item_id.to_i # convert to int
490
+ end
491
+
492
+ instance_selector = selector.merge({descriminator => id})
493
+ persister.find(entity, instance_selector)
494
+ end
299
495
 
300
496
  def ghurl(path)
301
497
  config(:mirror_urlbase) + path