hubba 0.7.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +5 -4
- data/README.md +146 -146
- data/Rakefile +30 -30
- data/lib/hubba/config.rb +51 -51
- data/lib/hubba/github.rb +210 -210
- data/lib/hubba/reposet.rb +83 -83
- data/lib/hubba/stats.rb +284 -246
- data/lib/hubba/update.rb +46 -43
- data/lib/hubba/update_traffic.rb +51 -51
- data/lib/hubba/version.rb +18 -18
- data/lib/hubba.rb +46 -46
- data/test/helper.rb +7 -7
- data/test/test_config.rb +31 -31
- metadata +8 -9
data/lib/hubba/github.rb
CHANGED
@@ -1,210 +1,210 @@
|
|
1
|
-
module Hubba
|
2
|
-
|
3
|
-
|
4
|
-
class Github
|
5
|
-
BASE_URL = 'https://api.github.com'
|
6
|
-
|
7
|
-
###############
|
8
|
-
## (nested) classes for "wrapped" response (parsed json body)
|
9
|
-
class Resource
|
10
|
-
attr_reader :data
|
11
|
-
def initialize( data )
|
12
|
-
@data = data
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
class Repos < Resource
|
17
|
-
def names
|
18
|
-
## sort by name
|
19
|
-
data.map { |item| item['name'] }.sort
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class Orgs < Resource
|
24
|
-
def logins
|
25
|
-
## sort by name
|
26
|
-
data.map { |item| item['login'] }.sort
|
27
|
-
end
|
28
|
-
alias_method :names, :logins ## add name alias - why? why not?
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
def initialize( token: nil,
|
34
|
-
user: nil,
|
35
|
-
password: nil )
|
36
|
-
@token = nil
|
37
|
-
@user = nil
|
38
|
-
@password = nil
|
39
|
-
|
40
|
-
if token ## 1a) give preference to passed in token
|
41
|
-
@token = token
|
42
|
-
elsif user && password ## 1b) or passed in user/password credentials
|
43
|
-
@user = user
|
44
|
-
@password = password
|
45
|
-
elsif Hubba.config.token ## 2a) followed by configured (or env) token
|
46
|
-
@token = Hubba.config.token
|
47
|
-
elsif Hubba.config.user && Hubba.config.password ## 2b)
|
48
|
-
@user = Hubba.config.user
|
49
|
-
@password = Hubba.config.password
|
50
|
-
else ## 3)
|
51
|
-
## no token or credentials passed in or configured
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
def user( name )
|
58
|
-
Resource.new( get "/users/#{name}" )
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
def user_repos( name )
|
63
|
-
Repos.new( get "/users/#{name}/repos" ) ## add ?per_page=100 - why? why not?
|
64
|
-
end
|
65
|
-
|
66
|
-
|
67
|
-
##
|
68
|
-
# note: pagination
|
69
|
-
# requests that return multiple items will be paginated to 30 items by default.
|
70
|
-
# You can specify further pages with the ?page parameter.
|
71
|
-
# For some resources, you can also set a custom page size up to 100
|
72
|
-
# with the ?per_page=100 parameter
|
73
|
-
|
74
|
-
def user_orgs( name )
|
75
|
-
Orgs.new( get "/users/#{name}/orgs?per_page=100" )
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
def org( name )
|
81
|
-
Resource.new( get "/orgs/#{name}" )
|
82
|
-
end
|
83
|
-
|
84
|
-
def org_repos( name )
|
85
|
-
Repos.new( get "/orgs/#{name}/repos?per_page=100" )
|
86
|
-
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
def repo( full_name ) ## full_name (handle) e.g. henrythemes/jekyll-starter-theme
|
91
|
-
Resource.new( get "/repos/#{full_name}" )
|
92
|
-
end
|
93
|
-
|
94
|
-
def repo_languages( full_name )
|
95
|
-
Resource.new( get "/repos/#{full_name}/languages" )
|
96
|
-
end
|
97
|
-
|
98
|
-
def repo_topics( full_name )
|
99
|
-
## note: requires "api preview" accept headers (overwrites default v3+json)
|
100
|
-
## e.g. application/vnd.github.mercy-preview+json
|
101
|
-
Resource.new( get( "/repos/#{full_name}/topics", preview: 'mercy' ) )
|
102
|
-
end
|
103
|
-
|
104
|
-
|
105
|
-
def repo_commits( full_name )
|
106
|
-
Resource.new( get "/repos/#{full_name}/commits" )
|
107
|
-
end
|
108
|
-
|
109
|
-
|
110
|
-
def repo_traffic_clones( full_name )
|
111
|
-
# Get repository clones
|
112
|
-
# Get the total number of clones and breakdown per day or week
|
113
|
-
# for the last 14 days.
|
114
|
-
# Timestamps are aligned to UTC midnight of the beginning of the day or week.
|
115
|
-
# Week begins on Monday.
|
116
|
-
Resource.new( get "/repos/#{full_name}/traffic/clones" )
|
117
|
-
end
|
118
|
-
|
119
|
-
def repo_traffic_views( full_name )
|
120
|
-
# Get page views
|
121
|
-
# Get the total number of views and breakdown per day or week
|
122
|
-
# for the last 14 days.
|
123
|
-
# Timestamps are aligned to UTC midnight of the beginning of the day or week.
|
124
|
-
# Week begins on Monday.
|
125
|
-
Resource.new( get "/repos/#{full_name}/traffic/views" )
|
126
|
-
end
|
127
|
-
|
128
|
-
|
129
|
-
def repo_traffic_popular_paths( full_name )
|
130
|
-
# Get top referral paths
|
131
|
-
# Get the top 10 popular contents over the last 14 days.
|
132
|
-
Resource.new( get "/repos/#{full_name}/traffic/popular/paths" )
|
133
|
-
end
|
134
|
-
|
135
|
-
def repo_traffic_popular_referrers( full_name )
|
136
|
-
# Get top referral sources
|
137
|
-
# Get the top 10 referrers over the last 14 days.
|
138
|
-
Resource.new( get "/repos/#{full_name}/traffic/popular/referrers" )
|
139
|
-
end
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
private
|
145
|
-
def get( request_uri, preview: nil )
|
146
|
-
|
147
|
-
puts "GET #{request_uri}"
|
148
|
-
|
149
|
-
## note: request_uri ALWAYS starts with leading /, thus use + for now!!!
|
150
|
-
# e.g. /users/geraldb
|
151
|
-
# /users/geraldb/repos
|
152
|
-
url = BASE_URL + request_uri
|
153
|
-
|
154
|
-
|
155
|
-
headers = {}
|
156
|
-
## add default headers if nothing (custom) set / passed-in
|
157
|
-
headers['User-Agent'] = "ruby/hubba v#{VERSION}" ## required by GitHub API
|
158
|
-
headers['Accept'] = if preview # e.g. mercy or ???
|
159
|
-
"application/vnd.github.#{preview}-preview+json"
|
160
|
-
else
|
161
|
-
'application/vnd.github.v3+json' ## default - recommend by GitHub API
|
162
|
-
end
|
163
|
-
|
164
|
-
auth = []
|
165
|
-
## check if credentials (user/password) present - if yes, use basic auth
|
166
|
-
if @token
|
167
|
-
puts " using (personal access) token - starting with: #{@token[0..6]}**********"
|
168
|
-
headers['Authorization'] = "token #{@token}"
|
169
|
-
## token works like:
|
170
|
-
## curl -H 'Authorization: token my_access_token' https://api.github.com/user/repos
|
171
|
-
elsif @user && @password
|
172
|
-
puts " using basic auth - user: #{@user}, password: ***"
|
173
|
-
## use credential auth "tuple" (that is, array with two string items) for now
|
174
|
-
## or use Webclient::HttpBasicAuth or something - why? why not?
|
175
|
-
auth = [@user, @password]
|
176
|
-
# req.basic_auth( @user, @password )
|
177
|
-
else
|
178
|
-
puts " using no credentials (no token, no user/password)"
|
179
|
-
end
|
180
|
-
|
181
|
-
res = Webclient.get( url,
|
182
|
-
headers: headers,
|
183
|
-
auth: auth )
|
184
|
-
|
185
|
-
# Get specific header
|
186
|
-
# response["content-type"]
|
187
|
-
# => "text/html; charset=UTF-8"
|
188
|
-
|
189
|
-
# Iterate all response headers.
|
190
|
-
# puts "HTTP HEADERS:"
|
191
|
-
# res.headers.each do |key, value|
|
192
|
-
# puts " #{key}: >#{value}<"
|
193
|
-
# end
|
194
|
-
# puts
|
195
|
-
|
196
|
-
# => "location => http://www.google.com/"
|
197
|
-
# => "content-type => text/html; charset=UTF-8"
|
198
|
-
# ...
|
199
|
-
|
200
|
-
if res.status.ok?
|
201
|
-
res.json
|
202
|
-
else
|
203
|
-
puts "!! HTTP ERROR: #{res.status.code} #{res.status.message}:"
|
204
|
-
pp res.raw
|
205
|
-
exit 1
|
206
|
-
end
|
207
|
-
end # method get
|
208
|
-
|
209
|
-
end # class Github
|
210
|
-
end # module Hubba
|
1
|
+
module Hubba
|
2
|
+
|
3
|
+
|
4
|
+
class Github
|
5
|
+
BASE_URL = 'https://api.github.com'
|
6
|
+
|
7
|
+
###############
|
8
|
+
## (nested) classes for "wrapped" response (parsed json body)
|
9
|
+
class Resource
|
10
|
+
attr_reader :data
|
11
|
+
def initialize( data )
|
12
|
+
@data = data
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Repos < Resource
|
17
|
+
def names
|
18
|
+
## sort by name
|
19
|
+
data.map { |item| item['name'] }.sort
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Orgs < Resource
|
24
|
+
def logins
|
25
|
+
## sort by name
|
26
|
+
data.map { |item| item['login'] }.sort
|
27
|
+
end
|
28
|
+
alias_method :names, :logins ## add name alias - why? why not?
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
def initialize( token: nil,
|
34
|
+
user: nil,
|
35
|
+
password: nil )
|
36
|
+
@token = nil
|
37
|
+
@user = nil
|
38
|
+
@password = nil
|
39
|
+
|
40
|
+
if token ## 1a) give preference to passed in token
|
41
|
+
@token = token
|
42
|
+
elsif user && password ## 1b) or passed in user/password credentials
|
43
|
+
@user = user
|
44
|
+
@password = password
|
45
|
+
elsif Hubba.config.token ## 2a) followed by configured (or env) token
|
46
|
+
@token = Hubba.config.token
|
47
|
+
elsif Hubba.config.user && Hubba.config.password ## 2b)
|
48
|
+
@user = Hubba.config.user
|
49
|
+
@password = Hubba.config.password
|
50
|
+
else ## 3)
|
51
|
+
## no token or credentials passed in or configured
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
def user( name )
|
58
|
+
Resource.new( get "/users/#{name}" )
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def user_repos( name )
|
63
|
+
Repos.new( get "/users/#{name}/repos" ) ## add ?per_page=100 - why? why not?
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
##
|
68
|
+
# note: pagination
|
69
|
+
# requests that return multiple items will be paginated to 30 items by default.
|
70
|
+
# You can specify further pages with the ?page parameter.
|
71
|
+
# For some resources, you can also set a custom page size up to 100
|
72
|
+
# with the ?per_page=100 parameter
|
73
|
+
|
74
|
+
def user_orgs( name )
|
75
|
+
Orgs.new( get "/users/#{name}/orgs?per_page=100" )
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
def org( name )
|
81
|
+
Resource.new( get "/orgs/#{name}" )
|
82
|
+
end
|
83
|
+
|
84
|
+
def org_repos( name )
|
85
|
+
Repos.new( get "/orgs/#{name}/repos?per_page=100" )
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
def repo( full_name ) ## full_name (handle) e.g. henrythemes/jekyll-starter-theme
|
91
|
+
Resource.new( get "/repos/#{full_name}" )
|
92
|
+
end
|
93
|
+
|
94
|
+
def repo_languages( full_name )
|
95
|
+
Resource.new( get "/repos/#{full_name}/languages" )
|
96
|
+
end
|
97
|
+
|
98
|
+
def repo_topics( full_name )
|
99
|
+
## note: requires "api preview" accept headers (overwrites default v3+json)
|
100
|
+
## e.g. application/vnd.github.mercy-preview+json
|
101
|
+
Resource.new( get( "/repos/#{full_name}/topics", preview: 'mercy' ) )
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def repo_commits( full_name )
|
106
|
+
Resource.new( get "/repos/#{full_name}/commits" )
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def repo_traffic_clones( full_name )
|
111
|
+
# Get repository clones
|
112
|
+
# Get the total number of clones and breakdown per day or week
|
113
|
+
# for the last 14 days.
|
114
|
+
# Timestamps are aligned to UTC midnight of the beginning of the day or week.
|
115
|
+
# Week begins on Monday.
|
116
|
+
Resource.new( get "/repos/#{full_name}/traffic/clones" )
|
117
|
+
end
|
118
|
+
|
119
|
+
def repo_traffic_views( full_name )
|
120
|
+
# Get page views
|
121
|
+
# Get the total number of views and breakdown per day or week
|
122
|
+
# for the last 14 days.
|
123
|
+
# Timestamps are aligned to UTC midnight of the beginning of the day or week.
|
124
|
+
# Week begins on Monday.
|
125
|
+
Resource.new( get "/repos/#{full_name}/traffic/views" )
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
def repo_traffic_popular_paths( full_name )
|
130
|
+
# Get top referral paths
|
131
|
+
# Get the top 10 popular contents over the last 14 days.
|
132
|
+
Resource.new( get "/repos/#{full_name}/traffic/popular/paths" )
|
133
|
+
end
|
134
|
+
|
135
|
+
def repo_traffic_popular_referrers( full_name )
|
136
|
+
# Get top referral sources
|
137
|
+
# Get the top 10 referrers over the last 14 days.
|
138
|
+
Resource.new( get "/repos/#{full_name}/traffic/popular/referrers" )
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
private
|
145
|
+
def get( request_uri, preview: nil )
|
146
|
+
|
147
|
+
puts "GET #{request_uri}"
|
148
|
+
|
149
|
+
## note: request_uri ALWAYS starts with leading /, thus use + for now!!!
|
150
|
+
# e.g. /users/geraldb
|
151
|
+
# /users/geraldb/repos
|
152
|
+
url = BASE_URL + request_uri
|
153
|
+
|
154
|
+
|
155
|
+
headers = {}
|
156
|
+
## add default headers if nothing (custom) set / passed-in
|
157
|
+
headers['User-Agent'] = "ruby/hubba v#{VERSION}" ## required by GitHub API
|
158
|
+
headers['Accept'] = if preview # e.g. mercy or ???
|
159
|
+
"application/vnd.github.#{preview}-preview+json"
|
160
|
+
else
|
161
|
+
'application/vnd.github.v3+json' ## default - recommend by GitHub API
|
162
|
+
end
|
163
|
+
|
164
|
+
auth = []
|
165
|
+
## check if credentials (user/password) present - if yes, use basic auth
|
166
|
+
if @token
|
167
|
+
puts " using (personal access) token - starting with: #{@token[0..6]}**********"
|
168
|
+
headers['Authorization'] = "token #{@token}"
|
169
|
+
## token works like:
|
170
|
+
## curl -H 'Authorization: token my_access_token' https://api.github.com/user/repos
|
171
|
+
elsif @user && @password
|
172
|
+
puts " using basic auth - user: #{@user}, password: ***"
|
173
|
+
## use credential auth "tuple" (that is, array with two string items) for now
|
174
|
+
## or use Webclient::HttpBasicAuth or something - why? why not?
|
175
|
+
auth = [@user, @password]
|
176
|
+
# req.basic_auth( @user, @password )
|
177
|
+
else
|
178
|
+
puts " using no credentials (no token, no user/password)"
|
179
|
+
end
|
180
|
+
|
181
|
+
res = Webclient.get( url,
|
182
|
+
headers: headers,
|
183
|
+
auth: auth )
|
184
|
+
|
185
|
+
# Get specific header
|
186
|
+
# response["content-type"]
|
187
|
+
# => "text/html; charset=UTF-8"
|
188
|
+
|
189
|
+
# Iterate all response headers.
|
190
|
+
# puts "HTTP HEADERS:"
|
191
|
+
# res.headers.each do |key, value|
|
192
|
+
# puts " #{key}: >#{value}<"
|
193
|
+
# end
|
194
|
+
# puts
|
195
|
+
|
196
|
+
# => "location => http://www.google.com/"
|
197
|
+
# => "content-type => text/html; charset=UTF-8"
|
198
|
+
# ...
|
199
|
+
|
200
|
+
if res.status.ok?
|
201
|
+
res.json
|
202
|
+
else
|
203
|
+
puts "!! HTTP ERROR: #{res.status.code} #{res.status.message}:"
|
204
|
+
pp res.raw
|
205
|
+
exit 1
|
206
|
+
end
|
207
|
+
end # method get
|
208
|
+
|
209
|
+
end # class Github
|
210
|
+
end # module Hubba
|
data/lib/hubba/reposet.rb
CHANGED
@@ -1,83 +1,83 @@
|
|
1
|
-
module Hubba
|
2
|
-
|
3
|
-
|
4
|
-
## orgs - include repos form org(anizations) too
|
5
|
-
## cache - save json response to cache_dir - change to/use debug/tmp_dir? - why? why not?
|
6
|
-
def self.reposet( *users, orgs: true,
|
7
|
-
cache: false )
|
8
|
-
# users = [users] if users.is_a?( String ) ### wrap in array if single user
|
9
|
-
|
10
|
-
gh = Github.new
|
11
|
-
|
12
|
-
forks = []
|
13
|
-
|
14
|
-
h = {}
|
15
|
-
users.each do |user|
|
16
|
-
res = gh.user_repos( user )
|
17
|
-
save_json( "#{config.cache_dir}/users~#{user}~repos.json", res.data ) if cache
|
18
|
-
|
19
|
-
repos = []
|
20
|
-
####
|
21
|
-
# check for forked repos (auto-exclude personal by default)
|
22
|
-
# note: forked repos in orgs get NOT auto-excluded!!!
|
23
|
-
res.data.each do |repo|
|
24
|
-
fork = repo['fork']
|
25
|
-
if fork
|
26
|
-
print "FORK "
|
27
|
-
forks << "#{repo['full_name']} (AUTO-EXCLUDED)"
|
28
|
-
else
|
29
|
-
print " "
|
30
|
-
repos << repo['name']
|
31
|
-
end
|
32
|
-
print repo['full_name']
|
33
|
-
print "\n"
|
34
|
-
end
|
35
|
-
|
36
|
-
|
37
|
-
h[ "#{user} (#{repos.size})" ] = repos.sort
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
|
-
## all repos from orgs
|
42
|
-
## note: for now only use first (primary user) - why? why not?
|
43
|
-
if orgs
|
44
|
-
user = users[0]
|
45
|
-
res = gh.user_orgs( user )
|
46
|
-
save_json( "#{config.cache_dir}/users~#{user}~orgs.json", res.data ) if cache
|
47
|
-
|
48
|
-
|
49
|
-
logins = res.logins.each do |login|
|
50
|
-
## next if ['xxx'].include?( login ) ## add orgs here to skip
|
51
|
-
|
52
|
-
res = gh.org_repos( login )
|
53
|
-
save_json( "#{config.cache_dir}/orgs~#{login}~repos.json", res.data ) if cache
|
54
|
-
|
55
|
-
repos = []
|
56
|
-
res.data.each do |repo|
|
57
|
-
fork = repo['fork']
|
58
|
-
if fork
|
59
|
-
print "FORK "
|
60
|
-
forks << repo['full_name']
|
61
|
-
repos << repo['name']
|
62
|
-
else
|
63
|
-
print " "
|
64
|
-
repos << repo['name']
|
65
|
-
end
|
66
|
-
print repo['full_name']
|
67
|
-
print "\n"
|
68
|
-
end
|
69
|
-
|
70
|
-
h[ "#{login} (#{repos.size})" ] = repos.sort
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
if forks.size > 0
|
75
|
-
puts
|
76
|
-
puts "#{forks.size} fork(s):"
|
77
|
-
puts forks
|
78
|
-
end
|
79
|
-
|
80
|
-
h
|
81
|
-
end ## method reposet
|
82
|
-
|
83
|
-
end # module Hubba
|
1
|
+
module Hubba
|
2
|
+
|
3
|
+
|
4
|
+
## orgs - include repos form org(anizations) too
|
5
|
+
## cache - save json response to cache_dir - change to/use debug/tmp_dir? - why? why not?
|
6
|
+
def self.reposet( *users, orgs: true,
|
7
|
+
cache: false )
|
8
|
+
# users = [users] if users.is_a?( String ) ### wrap in array if single user
|
9
|
+
|
10
|
+
gh = Github.new
|
11
|
+
|
12
|
+
forks = []
|
13
|
+
|
14
|
+
h = {}
|
15
|
+
users.each do |user|
|
16
|
+
res = gh.user_repos( user )
|
17
|
+
save_json( "#{config.cache_dir}/users~#{user}~repos.json", res.data ) if cache
|
18
|
+
|
19
|
+
repos = []
|
20
|
+
####
|
21
|
+
# check for forked repos (auto-exclude personal by default)
|
22
|
+
# note: forked repos in orgs get NOT auto-excluded!!!
|
23
|
+
res.data.each do |repo|
|
24
|
+
fork = repo['fork']
|
25
|
+
if fork
|
26
|
+
print "FORK "
|
27
|
+
forks << "#{repo['full_name']} (AUTO-EXCLUDED)"
|
28
|
+
else
|
29
|
+
print " "
|
30
|
+
repos << repo['name']
|
31
|
+
end
|
32
|
+
print repo['full_name']
|
33
|
+
print "\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
h[ "#{user} (#{repos.size})" ] = repos.sort
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
## all repos from orgs
|
42
|
+
## note: for now only use first (primary user) - why? why not?
|
43
|
+
if orgs
|
44
|
+
user = users[0]
|
45
|
+
res = gh.user_orgs( user )
|
46
|
+
save_json( "#{config.cache_dir}/users~#{user}~orgs.json", res.data ) if cache
|
47
|
+
|
48
|
+
|
49
|
+
logins = res.logins.each do |login|
|
50
|
+
## next if ['xxx'].include?( login ) ## add orgs here to skip
|
51
|
+
|
52
|
+
res = gh.org_repos( login )
|
53
|
+
save_json( "#{config.cache_dir}/orgs~#{login}~repos.json", res.data ) if cache
|
54
|
+
|
55
|
+
repos = []
|
56
|
+
res.data.each do |repo|
|
57
|
+
fork = repo['fork']
|
58
|
+
if fork
|
59
|
+
print "FORK "
|
60
|
+
forks << repo['full_name']
|
61
|
+
repos << repo['name']
|
62
|
+
else
|
63
|
+
print " "
|
64
|
+
repos << repo['name']
|
65
|
+
end
|
66
|
+
print repo['full_name']
|
67
|
+
print "\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
h[ "#{login} (#{repos.size})" ] = repos.sort
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
if forks.size > 0
|
75
|
+
puts
|
76
|
+
puts "#{forks.size} fork(s):"
|
77
|
+
puts forks
|
78
|
+
end
|
79
|
+
|
80
|
+
h
|
81
|
+
end ## method reposet
|
82
|
+
|
83
|
+
end # module Hubba
|