ghedsh 1.1.40 → 2.3.8
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.
- checksums.yaml +4 -4
- data/.editorconfig +12 -0
- data/.github/ISSUE_TEMPLATE.md +31 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +30 -0
- data/.gitignore +12 -1
- data/.rspec +3 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/Gemfile +5 -0
- data/LICENSE +165 -0
- data/README.md +6 -93
- data/Rakefile +3 -9
- data/bin/ghedsh +2 -1
- data/file_templates/add_members_template.json +12 -0
- data/file_templates/create_teams_template.json +19 -0
- data/file_templates/invite_outside_collabs.json +12 -0
- data/file_templates/remove_members_template.json +12 -0
- data/ghedsh.gemspec +3 -2
- data/lib/actions/orgs.rb +636 -842
- data/lib/actions/system.rb +212 -278
- data/lib/actions/teams.rb +15 -229
- data/lib/actions/user.rb +304 -12
- data/lib/commands.rb +465 -0
- data/lib/common.rb +15 -0
- data/lib/context.rb +42 -0
- data/lib/helpers.rb +147 -0
- data/lib/interface.rb +71 -733
- data/lib/plugin_loader.rb +43 -0
- data/lib/version.rb +1 -1
- data/spec/cli_spec.rb +30 -0
- data/spec/spec_helper.rb +106 -0
- metadata +38 -10
- data/docs/Javier-clemente-MemoriaTFG-ghedsh.pdf +0 -0
- data/lib/actions/help.rb +0 -357
- data/lib/actions/repo.rb +0 -832
- data/spec/spec.rb +0 -1
data/lib/actions/orgs.rb
CHANGED
@@ -1,955 +1,749 @@
|
|
1
|
-
require 'readline'
|
2
|
-
require 'octokit'
|
3
1
|
require 'json'
|
4
2
|
require 'csv'
|
3
|
+
require 'fileutils'
|
5
4
|
require 'require_all'
|
6
|
-
|
7
|
-
require '
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
5
|
+
require 'rainbow'
|
6
|
+
require 'tty-spinner'
|
7
|
+
require 'tty-prompt'
|
8
|
+
require_relative '../helpers'
|
9
|
+
|
10
|
+
# Class containing actions available inside an organization
|
11
|
+
#
|
12
|
+
# @see http://octokit.github.io/octokit.rb/Octokit/Client/Organizations.html
|
13
|
+
class Organization
|
14
|
+
# CLI prompt, info about the current (CLI) scope
|
15
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
16
|
+
def self.shell_prompt(config)
|
17
|
+
if config['Repo'].nil?
|
18
|
+
Rainbow("#{config['User']}> ").aqua << Rainbow("#{config['Org']}> ").magenta
|
19
|
+
else
|
20
|
+
Rainbow("#{config['User']}> ").aqua << Rainbow("#{config['Org']}> ").magenta << Rainbow("#{config['Repo']}> ").color(236, 151, 21)
|
21
|
+
end
|
23
22
|
end
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
# Builds final cd syntax. Transforms user's CLI input to valid Ruby expression doing some parsing.
|
25
|
+
# @param type [String] scope of cd command
|
26
|
+
# @param name [String, Regexp] name of repo, org, team etc. inside current organization
|
27
|
+
# @return syntax_map [String] Valid Ruby expression to perform eval.
|
28
|
+
def build_cd_syntax(type, name)
|
29
|
+
syntax_map = { 'repo' => "Organization.new.cd('repo', #{name}, client, env)",
|
30
|
+
'team' => "Organization.new.cd('team', #{name}, client, env)" }
|
31
|
+
unless syntax_map.key?(type)
|
32
|
+
raise Rainbow("cd #{type} currently not supported.").color(ERROR_CODE)
|
33
|
+
end
|
34
|
+
syntax_map[type]
|
29
35
|
end
|
30
36
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
puts "\n"
|
55
|
-
end
|
56
|
-
print "\n"
|
57
|
-
end
|
58
|
-
end
|
37
|
+
# Open info on default browser.
|
38
|
+
#
|
39
|
+
# If 'params' is String or Regexp user selects referred organization members
|
40
|
+
#
|
41
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
42
|
+
# @params [String, Regexp]
|
43
|
+
# @param client [Object] Octokit client object
|
44
|
+
# @example open user profile URL with String provided
|
45
|
+
# User > Org > open 'some_user'
|
46
|
+
# @example open user URL with Regexp provided
|
47
|
+
# User > Org > open /pattern/
|
48
|
+
def open_info(config, params, client)
|
49
|
+
unless params.nil?
|
50
|
+
# looking for org member by regexp
|
51
|
+
pattern = build_regexp_from_string(params)
|
52
|
+
member_url = select_member(config, pattern, client)
|
53
|
+
open_url(member_url.to_s) unless member_url.nil?
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
if config['Repo'].nil?
|
58
|
+
open_url(config['org_url'].to_s)
|
59
59
|
else
|
60
|
-
|
61
|
-
list["orgs"].push({"name"=>config["Org"],"assigs"=>[]})
|
62
|
-
Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
|
60
|
+
open_url(config['repo_url'].to_s)
|
63
61
|
end
|
62
|
+
rescue StandardError => exception
|
63
|
+
puts Rainbow(exception.message.to_s).color(ERROR_CODE)
|
64
|
+
puts
|
64
65
|
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
67
|
+
# Display organization repos
|
68
|
+
#
|
69
|
+
# @param client [Object] Octokit client object
|
70
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
71
|
+
# @param params [Regexp] regexp to check repo name
|
72
|
+
def show_repos(client, config, params)
|
73
|
+
spinner = custom_spinner("Fetching #{config['Org']} repositories :spinner ...")
|
74
|
+
spinner.auto_spin
|
75
|
+
org_repos = []
|
76
|
+
client.organization_repositories(config['Org'].to_s).each do |repo|
|
77
|
+
org_repos << repo[:name]
|
78
|
+
end
|
79
|
+
org_repos.sort_by!(&:downcase)
|
80
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
81
|
+
if params.nil?
|
82
|
+
item_counter = 0
|
83
|
+
org_repos.each do |repo_name|
|
84
|
+
puts repo_name
|
85
|
+
item_counter += 1
|
86
|
+
end
|
87
|
+
puts "\n#{item_counter} #{config['Org']} repositories listed."
|
88
|
+
else
|
89
|
+
pattern = build_regexp_from_string(params)
|
90
|
+
occurrences = show_matching_items(org_repos, pattern)
|
91
|
+
puts Rainbow("No repository inside #{config['Org']} matched \/#{pattern.source}\/").color(INFO_CODE) if occurrences.zero?
|
92
|
+
puts "\n#{occurrences} #{config['Org']} repositories listed."
|
80
93
|
end
|
81
|
-
|
82
|
-
when op=="1" || op=="2"
|
94
|
+
end
|
83
95
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
puts "\e[31m Already exists a repository with that name in #{config["Org"]}\e[0m"
|
125
|
-
else
|
126
|
-
reponame="#{config["Org"]}/#{reponame}"
|
127
|
-
ex2=true
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
when op=="3" then reponame=""
|
133
|
-
end
|
134
|
-
return reponame
|
96
|
+
# Set organization repos privacy to private. You need a paid plan to set private repos inside an organization
|
97
|
+
#
|
98
|
+
# @param client [Object] Octokit client object
|
99
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
100
|
+
# @param params [Regexp] regexp to change privacy of matching repos.
|
101
|
+
def change_to_private_repo(client, config, params)
|
102
|
+
pattern = build_regexp_from_string(params)
|
103
|
+
spinner = custom_spinner('Setting private repos :spinner ...')
|
104
|
+
spinner.auto_spin
|
105
|
+
repos = []
|
106
|
+
client.organization_repositories(config['Org'].to_s).each do |repo|
|
107
|
+
repos.push(repo[:name]) if pattern.match(repo[:name])
|
108
|
+
end
|
109
|
+
repos.each do |i|
|
110
|
+
client.set_private("#{config['Org']}/#{i}")
|
111
|
+
end
|
112
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
113
|
+
rescue StandardError => exception
|
114
|
+
puts Rainbow(exception.message.to_s).color(ERROR_CODE)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Set organization repos privacy to public.
|
118
|
+
#
|
119
|
+
# @param client [Object] Octokit client object
|
120
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
121
|
+
# @param params [Regexp] regexp to change privacy of matching repos.
|
122
|
+
def change_to_public_repo(client, config, params)
|
123
|
+
pattern = build_regexp_from_string(params)
|
124
|
+
spinner = custom_spinner('Setting public repos :spinner ...')
|
125
|
+
spinner.auto_spin
|
126
|
+
repos = []
|
127
|
+
client.organization_repositories(config['Org'].to_s).each do |repo|
|
128
|
+
repos.push(repo[:name]) if pattern.match(repo[:name])
|
129
|
+
end
|
130
|
+
repos.each do |i|
|
131
|
+
client.set_public("#{config['Org']}/#{i}")
|
132
|
+
end
|
133
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
134
|
+
rescue StandardError => exception
|
135
|
+
puts Rainbow(exception.message.to_s).color(ERROR_CODE)
|
135
136
|
end
|
136
137
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
138
|
+
# Create a 'super repo' containing assignment repositories as submodules
|
139
|
+
#
|
140
|
+
# @param [Object] client Octokit client object
|
141
|
+
# @param [Hash] config user configuration tracking current org, repo, etc.
|
142
|
+
# @param [Array<String>] params 'super repo's' name and Regexp to match submodules
|
143
|
+
# params[0] is the evaluation repository's name
|
144
|
+
# params[1] Regexp of submodules
|
145
|
+
def new_eval(client, config, params)
|
146
|
+
options = { private: true, organization: config['Org'].to_s, description: 'Evaluation repository created using ghedsh.' }
|
147
|
+
repo_name = params[0]
|
148
|
+
submodules = []
|
149
|
+
evaluation_repo = client.create_repository(repo_name, options)
|
150
|
+
pattern = build_regexp_from_string(params[1])
|
151
|
+
client.organization_repositories(config['Org'].to_s).each do |repo|
|
152
|
+
submodules << repo[:ssh_url] if pattern.match(repo[:name]) && (repo[:name] != repo_name)
|
153
|
+
end
|
154
|
+
unless submodules.empty?
|
155
|
+
local_repo_path = "#{Dir.pwd}/#{repo_name}"
|
156
|
+
FileUtils.mkdir_p(local_repo_path)
|
157
|
+
FileUtils.cd(local_repo_path) do
|
158
|
+
system('git init')
|
159
|
+
submodules.each do |i|
|
160
|
+
system("git submodule add #{i}")
|
161
|
+
end
|
162
|
+
system('git add .')
|
163
|
+
system('git commit -m "First commit"')
|
164
|
+
system("git remote add origin #{evaluation_repo[:ssh_url]}")
|
165
|
+
system('git push -u origin master')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
puts Rainbow("No submodule found with /#{pattern.source}/") if submodules.empty?
|
169
|
+
rescue StandardError => e
|
170
|
+
puts Rainbow(e.message.to_s).color(ERROR_CODE)
|
171
|
+
end
|
143
172
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
173
|
+
def repo_has_submodules?(client, config)
|
174
|
+
repo_content = []
|
175
|
+
client.contents("#{config['Org']}/#{config['Repo']}").each do |i|
|
176
|
+
repo_content << i[:name]
|
148
177
|
end
|
178
|
+
unless repo_content.include?('.gitmodules')
|
179
|
+
puts Rainbow('Current repo does not include .gitmodules and command will not work.').color(WARNING_CODE)
|
180
|
+
return false
|
181
|
+
end
|
182
|
+
true
|
183
|
+
end
|
149
184
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
name="Group-#{time.ctime}"
|
162
|
-
name=name.split(" ").join("-")
|
163
|
-
end
|
164
|
-
puts "\nTeams currently available:\n\n"
|
165
|
-
teamlist.each do |aux|
|
166
|
-
puts "\t#{aux[0]}"
|
167
|
-
end
|
168
|
-
begin
|
169
|
-
puts "\n1)Put a list of Teams"
|
170
|
-
puts "2)Take all the Teams available"
|
171
|
-
puts "3)Take the teams from a file\n"
|
172
|
-
puts "4)Add the teams to the group later"
|
173
|
-
print "option => "
|
174
|
-
op2=gets.chomp
|
175
|
-
end while (op2!="1" && op2!="2" && op2!="3" && op2!="4")
|
176
|
-
refuse=false #para no añadirlo cuando se niege en la repeticion de la busqueda de fichero
|
177
|
-
if op2=="1"
|
178
|
-
puts "\nPut a list of Teams (Separeted with space): "
|
179
|
-
list=gets.chomp
|
180
|
-
list=list.split(" ")
|
181
|
-
end
|
182
|
-
if op2=="2"
|
183
|
-
puts "All the teams have been taken"
|
184
|
-
list=teamlist.keys
|
185
|
-
end
|
186
|
-
if op2=="3"
|
187
|
-
begin
|
188
|
-
ex=2
|
189
|
-
puts "Put the name of the file: "
|
190
|
-
file=gets.chomp
|
191
|
-
list=sys.loadfile(file)
|
192
|
-
if list==nil
|
193
|
-
puts "The file doesn't exist or It's empty. Do you like to try again? (y/n):"
|
194
|
-
op3=gets.chomp
|
195
|
-
if op3 == "n" or op3 == "N"
|
196
|
-
ex=1
|
197
|
-
refuse=true
|
198
|
-
end
|
199
|
-
else
|
200
|
-
ex=1
|
201
|
-
end
|
202
|
-
end while ex!=1
|
203
|
-
end
|
204
|
-
if op!="4" and refuse==false
|
205
|
-
team.new_group(client,config,name,list)
|
206
|
-
end
|
207
|
-
groupsadd.push(name)
|
208
|
-
else
|
209
|
-
groupsadd=[]
|
185
|
+
# Evaluate a bash command in each submodule
|
186
|
+
#
|
187
|
+
# @param [Object] client Octokit client object
|
188
|
+
# @param [Hash] config user configuration tracking current org, repo, etc.
|
189
|
+
# @param [Array<String>] params bash command
|
190
|
+
def foreach_eval(client, config, params)
|
191
|
+
if config['Repo']
|
192
|
+
return unless repo_has_submodules?(client, config)
|
193
|
+
command = params.join(' ')
|
194
|
+
FileUtils.cd(Dir.pwd) do
|
195
|
+
system("git submodule foreach '#{command} || :'")
|
210
196
|
end
|
211
|
-
|
212
197
|
else
|
213
|
-
|
214
|
-
|
215
|
-
if groupslist.detect{|aux| aux==item}==nil
|
216
|
-
groupsadd.delete(item)
|
217
|
-
end
|
218
|
-
end
|
198
|
+
puts Rainbow('Please change to an organization repository to run this command.').color(INFO_CODE)
|
199
|
+
return
|
219
200
|
end
|
220
|
-
|
201
|
+
rescue StandardError => e
|
202
|
+
puts Rainbow(e.message.to_s).color(ERROR_CODE)
|
221
203
|
end
|
222
204
|
|
223
|
-
def
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
puts "2)Take all the list"
|
230
|
-
puts "3)Take the students from a file\n"
|
231
|
-
puts "4)Add the students later"
|
232
|
-
print "option => "
|
233
|
-
op2=gets.chomp
|
234
|
-
end while (op2!="1" && op2!="2" && op2!="3" && op2!="4")
|
235
|
-
if op2=="1"
|
236
|
-
puts members
|
237
|
-
puts "\nPut a list of students (Separeted with space): "
|
238
|
-
list=gets.chomp
|
239
|
-
list=list.split(" ")
|
240
|
-
end
|
241
|
-
if op2=="2"
|
242
|
-
puts "All the students have been taken"
|
243
|
-
list=members
|
244
|
-
end
|
245
|
-
if op2=="3"
|
246
|
-
begin
|
247
|
-
ex=2
|
248
|
-
puts "Put the name of the file: "
|
249
|
-
file=gets.chomp
|
250
|
-
list=sys.loadfile(file)
|
251
|
-
if list==nil
|
252
|
-
puts "The file doesn't exist or It's empty. Do you like to try again? (y/n):"
|
253
|
-
op3=gets.chomp
|
254
|
-
if op3 == "n" or op3 == "N"
|
255
|
-
ex=1
|
256
|
-
refuse=true
|
257
|
-
end
|
258
|
-
else
|
259
|
-
ex=1
|
260
|
-
end
|
261
|
-
end while ex!=1
|
262
|
-
end
|
263
|
-
if op2!=4 and refuse==false
|
264
|
-
if op2!=2
|
265
|
-
if list!=nil
|
266
|
-
list.each do |i|
|
267
|
-
if !members.include?(i)
|
268
|
-
list.delete(i)
|
269
|
-
end
|
270
|
-
end
|
271
|
-
end
|
205
|
+
def foreach_try(client, config, params)
|
206
|
+
if config['Repo']
|
207
|
+
return unless repo_has_submodules?(client, config)
|
208
|
+
command = params.join(' ')
|
209
|
+
FileUtils.cd(Dir.pwd) do
|
210
|
+
system("git submodule foreach '#{command}'")
|
272
211
|
end
|
273
|
-
return list
|
274
212
|
else
|
275
|
-
|
213
|
+
puts Rainbow('Please change to an organization repository to run this command.').color(INFO_CODE)
|
214
|
+
return
|
276
215
|
end
|
216
|
+
rescue StandardError => e
|
217
|
+
puts Rainbow(e.message.to_s).color(ERROR_CODE)
|
277
218
|
end
|
278
219
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
print "option => "
|
295
|
-
op=gets.chomp
|
296
|
-
end while op!="1" && op!="2" && op!="3"
|
297
|
-
|
298
|
-
if op!="3"
|
299
|
-
reponame=self.assignment_repository(client,config,name)
|
300
|
-
if op=="2"
|
301
|
-
groupsadd=self.assignment_groups(client,config)
|
302
|
-
peopleadd=[]
|
303
|
-
end
|
304
|
-
if op=="1"
|
305
|
-
peopleadd=self.assignment_people(client,config)
|
306
|
-
groupsadd=[]
|
307
|
-
end
|
308
|
-
begin
|
309
|
-
list["orgs"][list["orgs"].index{|aux| aux["name"]==config["Org"]}]["assigs"].push({"name_assig"=>name,"teams"=>[],"people"=>peopleadd,"groups"=>groupsadd,"repo"=>reponame})
|
310
|
-
rescue Exception => e
|
311
|
-
puts e
|
312
|
-
end
|
313
|
-
Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
|
314
|
-
end
|
220
|
+
# Display files and directories within a repository
|
221
|
+
#
|
222
|
+
# @param client [Object] Octokit client object
|
223
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
224
|
+
# @params params [String] Subdirectory within a repo, if not provided shows root directory
|
225
|
+
def show_files(client, config, params)
|
226
|
+
if config['Repo']
|
227
|
+
options = { path: '' }
|
228
|
+
options[:path] = params[0] unless params.empty?
|
229
|
+
file_names_and_types = []
|
230
|
+
client.contents("#{config['Org']}/#{config['Repo']}", options).each do |i|
|
231
|
+
file_names_and_types << "#{i[:name]} (#{i[:type]})"
|
232
|
+
end
|
233
|
+
file_names_and_types.sort_by!(&:downcase)
|
234
|
+
puts file_names_and_types
|
315
235
|
else
|
316
|
-
puts
|
236
|
+
puts Rainbow('Please change to organization repository to see its files.').color(INFO_CODE)
|
317
237
|
end
|
238
|
+
rescue StandardError => e
|
239
|
+
puts Rainbow(e.message.to_s).color(ERROR_CODE)
|
318
240
|
end
|
319
241
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
242
|
+
# Display organization teams
|
243
|
+
#
|
244
|
+
# @param client [Object] Octokit client object
|
245
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
246
|
+
# @param params [Regexp] regexp to check team name
|
247
|
+
def show_teams(client, config, params)
|
248
|
+
org_teams = []
|
249
|
+
spinner = custom_spinner("Fetching #{config['Org']} teams :spinner ...")
|
250
|
+
spinner.auto_spin
|
251
|
+
client.organization_teams(config['Org'].to_s).each do |team|
|
252
|
+
org_teams << team[:name]
|
253
|
+
end
|
254
|
+
org_teams.sort_by!(&:downcase)
|
255
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
256
|
+
if params.nil?
|
257
|
+
org_teams.each do |name|
|
258
|
+
puts name
|
333
259
|
end
|
334
260
|
else
|
335
|
-
|
336
|
-
|
261
|
+
pattern = build_regexp_from_string(params)
|
262
|
+
occurrences = show_matching_items(org_teams, pattern)
|
263
|
+
puts Rainbow("No team inside #{config['Org']} matched \/#{pattern.source}\/").color(INFO_CODE) if occurrences.zero?
|
337
264
|
end
|
338
|
-
return assiglist
|
339
|
-
end
|
340
|
-
|
341
|
-
def get_single_assig(config,wanted)
|
342
|
-
list=self.load_assig()
|
343
|
-
as=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
|
344
|
-
as=as["assigs"].detect{|aux| aux["name_assig"]==wanted}
|
345
|
-
return as
|
346
265
|
end
|
347
266
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
end
|
267
|
+
# Clone a repository, if repo_name is Regexp it will clone all org repositories matching pattern
|
268
|
+
# if repo_name is String searches for that repo and clones it
|
269
|
+
#
|
270
|
+
# @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
|
271
|
+
# @param repo_name [Regexp, String] pattern or name of repo
|
272
|
+
# @param custom_path [String] if is not provided default path is HOME/ghedsh-cloned else is some
|
273
|
+
# path under HOME (already existing or not)
|
274
|
+
def clone_repository(enviroment, repo_name, custom_path)
|
275
|
+
client = enviroment.client
|
276
|
+
config = enviroment.config
|
277
|
+
repos_to_clone = []
|
278
|
+
if repo_name.include?('/')
|
279
|
+
pattern = build_regexp_from_string(repo_name)
|
280
|
+
client.organization_repositories(config['Org'].to_s).each do |repo|
|
281
|
+
repos_to_clone << { name: repo[:name], ssh_url: repo[:clone_url] } if pattern.match(repo[:name])
|
282
|
+
end
|
283
|
+
puts Rainbow("No repository matched \/#{pattern.source}\/").color(INFO_CODE) if repos_to_clone.empty?
|
284
|
+
else
|
285
|
+
repo = client.repository("#{config['Org']}/#{repo_name}")
|
286
|
+
repos_to_clone << { name: repo[:name], ssh_url: repo[:clone_url] }
|
369
287
|
end
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
puts "
|
288
|
+
unless repos_to_clone.empty?
|
289
|
+
perform_git_clone(repos_to_clone, custom_path)
|
290
|
+
if custom_path.nil?
|
291
|
+
puts Rainbow("Cloned into #{Dir.pwd}").color(INFO_CODE).underline
|
292
|
+
else
|
293
|
+
puts Rainbow("Cloned into #{Dir.home}#{custom_path}").color(INFO_CODE).underline
|
374
294
|
end
|
375
|
-
puts
|
295
|
+
puts
|
376
296
|
end
|
297
|
+
rescue StandardError => exception
|
298
|
+
puts Rainbow(exception.message.to_s).color(ERROR_CODE)
|
299
|
+
puts
|
377
300
|
end
|
378
301
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
point=1
|
389
|
-
assig.each{|key, value| if key.include?("repo") then repolist.push(value) end}
|
390
|
-
|
391
|
-
if repolist!=[]
|
392
|
-
repolist.each do |repo|
|
393
|
-
sys.create_temp("#{ENV['HOME']}/.ghedsh/temp")
|
394
|
-
puts repo
|
395
|
-
if repolist.size>1
|
396
|
-
if point>1 || assig.has_key?("sufix1")
|
397
|
-
sufix="sufix#{point}"
|
398
|
-
sufix="-#{assig["#{sufix}"]}"
|
399
|
-
else
|
400
|
-
sufix=""
|
401
|
-
end
|
402
|
-
else
|
403
|
-
sufix=""
|
404
|
-
end
|
405
|
-
point=point+1
|
406
|
-
if !client.repository?(repo)
|
407
|
-
aux=repo.split("/")
|
408
|
-
aux=aux[1]
|
409
|
-
r.create_repository(client,config,aux,false,ORGS)
|
410
|
-
end
|
411
|
-
system("git clone #{web2}#{repo}.git #{ENV['HOME']}/.ghedsh/temp/#{repo}")
|
412
|
-
if assig["groups"]!=[]
|
413
|
-
assig["groups"].each do |i|
|
414
|
-
|
415
|
-
teamsforgroup=team.get_single_group(config,i)
|
416
|
-
if teamsforgroup!=nil
|
417
|
-
teamsforgroup.each do |y|
|
418
|
-
config["TeamID"]=teamlist["#{y}"]
|
419
|
-
config["Team"]=y
|
420
|
-
puts y
|
421
|
-
puts sufix
|
422
|
-
r.create_repository(client,config,"#{assig["name_assig"]}#{sufix}-#{y}",true,TEAM)
|
423
|
-
system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} remote rm origin")
|
424
|
-
system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} remote add origin #{web2}#{config["Org"]}/#{assig["name_assig"]}#{sufix}-#{y}.git")
|
425
|
-
|
426
|
-
system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} push origin --all")
|
427
|
-
end
|
428
|
-
end
|
429
|
-
end
|
430
|
-
end
|
431
|
-
if assig["people"]!=[] and assig["people"]!=nil
|
432
|
-
assig["people"].each do |i|
|
433
|
-
r.create_repository(client,config,"#{assig["name_assig"]}#{sufix}-#{i}",true,ORGS)
|
434
|
-
system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} remote rm origin")
|
435
|
-
system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} remote add origin #{web2}#{config["Org"]}/#{assig["name_assig"]}#{sufix}-#{i}.git")
|
436
|
-
system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} push origin --all")
|
437
|
-
r.add_collaborator(client,"#{config["Org"]}/#{assig["name_assig"]}#{sufix}-#{i}",i)
|
438
|
-
end
|
439
|
-
end
|
440
|
-
end
|
302
|
+
# Open default browser ready to create an issue with GitHub form.
|
303
|
+
#
|
304
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
305
|
+
# @example create issue
|
306
|
+
# User > Org > Repo > new_issue
|
307
|
+
def create_issue(config)
|
308
|
+
if config['Repo']
|
309
|
+
issue_creation_url = "https://github.com/#{config['Org']}/#{config['Repo']}/issues/new"
|
310
|
+
open_url(issue_creation_url)
|
441
311
|
else
|
442
|
-
puts
|
312
|
+
puts Rainbow('Change to repo in order to create an issue.').color(INFO_CODE)
|
443
313
|
end
|
444
314
|
end
|
445
315
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
if
|
316
|
+
# Add members typing them separately
|
317
|
+
#
|
318
|
+
# @param client [Object] Octokit client object
|
319
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
320
|
+
# @example add two members to current org (separated with commas)
|
321
|
+
# User > Org > invite_member member1, member2
|
322
|
+
# @example add two members to current org (separated with blanks)
|
323
|
+
# User > Org > invite_member member1 member2
|
324
|
+
# @example add members to current org (commas and blanks combined)
|
325
|
+
# User > Org > invite_member member1, member2 member4, member5
|
326
|
+
def add_members(client, config, members)
|
327
|
+
if members.empty?
|
328
|
+
puts Rainbow('Please type each member you would like to add.').color(INFO_CODE)
|
329
|
+
else
|
330
|
+
people = split_members(members)
|
331
|
+
spinner = custom_spinner('Adding member(s) :spinner ...')
|
458
332
|
people.each do |i|
|
459
|
-
|
460
|
-
|
461
|
-
else
|
462
|
-
puts "#{i} is already in this assignment."
|
463
|
-
end
|
333
|
+
options = { role: 'member', user: i.to_s }
|
334
|
+
client.update_organization_membership(config['Org'].to_s, options)
|
464
335
|
end
|
465
|
-
|
336
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
466
337
|
end
|
338
|
+
rescue StandardError => exception
|
339
|
+
puts Rainbow(exception.message.to_s).color(ERROR_CODE)
|
467
340
|
end
|
468
341
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
342
|
+
# Add members from file (JSON). File must be located somewhere in HOME.
|
343
|
+
#
|
344
|
+
# @param client [Object] Octokit client object
|
345
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
346
|
+
def add_members_from_file(client, config, path)
|
347
|
+
file_path = "#{Dir.home}#{path}"
|
348
|
+
members_json = File.read(file_path)
|
349
|
+
members_file = JSON.parse(members_json)
|
350
|
+
spinner = custom_spinner('Adding members from file :spinner ...')
|
351
|
+
members_file['members'].each do |member|
|
352
|
+
options = { role: 'member', user: member['id'].to_s }
|
353
|
+
client.update_organization_membership(config['Org'].to_s, options)
|
354
|
+
end
|
355
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
356
|
+
rescue Errno::ENOENT
|
357
|
+
puts Rainbow('Could not open file, check file location.').color(ERROR_CODE)
|
358
|
+
rescue StandardError => exception
|
359
|
+
puts Rainbow(exception.message.to_s).color(ERROR_CODE)
|
479
360
|
end
|
480
361
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
#
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
if
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
end
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
puts fields
|
514
|
-
puts
|
515
|
-
mem.each do |i|
|
516
|
-
aux=Hash.new
|
517
|
-
fields.each do |j|
|
518
|
-
if i[j]!=nil
|
519
|
-
if GITHUB_LIST.include?(j.gsub("\"", "").downcase.strip)
|
520
|
-
data=i[j]
|
521
|
-
data=data.gsub("\"", "")
|
522
|
-
aux["github"]=data
|
523
|
-
j="github"
|
524
|
-
else
|
525
|
-
if MAIL_LIST.include?(j.gsub("\"", "").downcase.strip)
|
526
|
-
aux["email"]=i[j].gsub("\"", "").strip
|
527
|
-
indexname=j
|
528
|
-
j="email"
|
529
|
-
change=true
|
530
|
-
else
|
531
|
-
data=i[j].gsub("\"", "")
|
532
|
-
aux[j.gsub("\"", "").downcase.strip]=data.strip
|
533
|
-
end
|
534
|
-
end
|
535
|
-
else
|
536
|
-
data=i[j].gsub("\"", "")
|
537
|
-
aux[j.gsub("\"", "").downcase.strip]=data.strip
|
538
|
-
end
|
539
|
-
end
|
540
|
-
users.push(aux)
|
541
|
-
end
|
542
|
-
## Aqui empiezan las diferenciaa
|
543
|
-
if relation==true
|
544
|
-
if change==true
|
545
|
-
fields[fields.index(indexname)]="email"
|
546
|
-
end
|
547
|
-
fields=users[0].keys
|
548
|
-
# if users.keys.include?("github") and users.keys.include?("email") and users.keys.size==2
|
549
|
-
if fields.include?("github") and fields.include?("email") and fields.size==2
|
550
|
-
users.each do |i|
|
551
|
-
if members.include?(i["github"].delete("\""))
|
552
|
-
here=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["github"]==i["github"]} #miro si ya esta registrado
|
553
|
-
if here==nil
|
554
|
-
list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"]<<i
|
555
|
-
puts "#{i["github"]} information correctly added"
|
556
|
-
else #si ya esta registrado...
|
557
|
-
puts "#{i["github"]} is already registered in this organization"
|
558
|
-
end
|
559
|
-
else
|
560
|
-
puts "#{i["github"]} is not registered in this organization"
|
561
|
-
end
|
562
|
-
end
|
563
|
-
else
|
564
|
-
puts "No relationship found between github users and emails."
|
565
|
-
return nil
|
566
|
-
end
|
567
|
-
else #insercion normal, relacion ya hecha
|
568
|
-
users.each do |i|
|
569
|
-
here=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["email"]==i["email"]}
|
570
|
-
if here!=nil
|
571
|
-
i.each do |j|
|
572
|
-
list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["email"]==i["email"]}["#{j[0]}"]=j[1]
|
573
|
-
end
|
574
|
-
else
|
575
|
-
puts "No relation found of #{i["email"]} in #{config["Org"]}"
|
576
|
-
end
|
362
|
+
# Removes a group of members form current organization. It is possible to specify them
|
363
|
+
# in a file or with Regexp to match GitHub IDs.
|
364
|
+
#
|
365
|
+
# @param client [Object] Octokit client object
|
366
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
367
|
+
# @param param [String, Regexp] if string must be file path to remove memebers from file
|
368
|
+
# if Regexp will remove matching GitHub IDs from organization
|
369
|
+
def delete_member(client, config, param)
|
370
|
+
permissions = client.organization_membership(config['Org'], opts = { user: client.login })
|
371
|
+
unless permissions[:role] == 'admin'
|
372
|
+
puts Rainbow("You must have Admin permissions on #{config['Org']} to run this command.").underline.color(WARNING_CODE)
|
373
|
+
return
|
374
|
+
end
|
375
|
+
if is_file?("#{Dir.home}#{param}")
|
376
|
+
puts 'Removing member/members from file.'
|
377
|
+
file_path = "#{Dir.home}#{param}"
|
378
|
+
remove_json = File.read(file_path)
|
379
|
+
remove_member_file = JSON.parse(remove_json)
|
380
|
+
remove_member_file['remove'].each do |member|
|
381
|
+
client.remove_organization_member(congif['Org'], member['id'].to_s)
|
382
|
+
end
|
383
|
+
elsif eval(param).is_a?(Regexp)
|
384
|
+
members_to_remove = []
|
385
|
+
pattern = build_regexp_from_string(param)
|
386
|
+
client.organization_members(config['Org'].to_s).each do |member|
|
387
|
+
members_to_remove.push(member[:login]) if pattern.match(member[:login])
|
388
|
+
end
|
389
|
+
if members_to_remove.empty?
|
390
|
+
puts Rainbow("No members to remove matched with \/#{pattern.source}\/").color(WARNING_CODE)
|
391
|
+
else
|
392
|
+
members_to_remove.each do |i|
|
393
|
+
client.remove_organization_member(congif['Org'], i.to_s)
|
577
394
|
end
|
578
395
|
end
|
579
|
-
#tocho
|
580
|
-
Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
|
581
|
-
else
|
582
|
-
print "\n#{file} file not found.\n\n"
|
583
396
|
end
|
397
|
+
rescue SyntaxError => e
|
398
|
+
puts Rainbow('Parameter is not a file and there was a Syntax Error building Regexp.').color(ERROR_CODE)
|
399
|
+
rescue StandardError => e
|
400
|
+
puts Rainbow(e.message.to_s).color(ERROR_CODE)
|
584
401
|
end
|
585
402
|
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
403
|
+
# Invite as members all outside collaborators from organization.
|
404
|
+
# This action needs admin permissions on the organization.
|
405
|
+
# This method checks first if parameter is an existing file, then checks Regexp, else invites all
|
406
|
+
# outside collaborators of current organization.
|
407
|
+
#
|
408
|
+
# @param client [Object] Octokit client object
|
409
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
410
|
+
# @param param [String, Regexp] file path or Regexp to invite outside collabs
|
411
|
+
def invite_all_outside_collaborators(client, config, param)
|
412
|
+
permissions = client.organization_membership(config['Org'], opts = { user: client.login })
|
413
|
+
unless permissions[:role] == 'admin'
|
414
|
+
puts Rainbow("You must have Admin permissions on #{config['Org']} to run this command.").underline.color(WARNING_CODE)
|
415
|
+
return
|
416
|
+
end
|
417
|
+
outside_collaborators = []
|
418
|
+
spinner = custom_spinner('Sending invitations :spinner ...')
|
419
|
+
if is_file?("#{Dir.home}#{param}")
|
420
|
+
puts 'Adding outside collaborators from file'
|
421
|
+
file_path = "#{Dir.home}#{param}"
|
422
|
+
collab_json = File.read(file_path)
|
423
|
+
collab_file = JSON.parse(collab_json)
|
424
|
+
collab_file['collabs'].each do |collab|
|
425
|
+
outside_collaborators.push(collab['id'])
|
426
|
+
end
|
427
|
+
elsif eval(param).is_a?(Regexp)
|
428
|
+
pattern = build_regexp_from_string(param)
|
429
|
+
client.outside_collaborators(config['Org']).each do |i|
|
430
|
+
outside_collaborators.push(i[:login]) if pattern.match(i[:login])
|
431
|
+
end
|
591
432
|
else
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
puts
|
433
|
+
begin
|
434
|
+
client.outside_collaborators(config['Org']).each do |i|
|
435
|
+
outside_collaborators.push(i[:login])
|
436
|
+
end
|
437
|
+
rescue StandardError => exception
|
438
|
+
puts Rainbow('If you entered file path, please ensure that is the correct path.').color(ERROR_CODE)
|
598
439
|
end
|
599
440
|
end
|
441
|
+
outside_collaborators.each do |j|
|
442
|
+
options = { role: 'member', user: j.to_s }
|
443
|
+
client.update_organization_membership(config['Org'], options)
|
444
|
+
end
|
445
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
446
|
+
rescue SyntaxError => e
|
447
|
+
puts Rainbow('Parameter is not a file and there was a Syntax Error building Regexp.').color(ERROR_CODE)
|
448
|
+
rescue StandardError => exception
|
449
|
+
puts Rainbow(exception.message.to_s).color(ERROR_CODE)
|
600
450
|
end
|
601
451
|
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
list=Sys.new.search_rexp_peoplehash(list["users"],exp)
|
612
|
-
|
613
|
-
if list!=[]
|
614
|
-
fields=list[0].keys
|
615
|
-
list.each do |i|
|
616
|
-
puts "\n\e[31m#{i["github"]}\e[0m"
|
617
|
-
fields.each do |j|
|
618
|
-
puts "#{j.capitalize}:\t #{i[j]}"
|
619
|
-
end
|
620
|
-
puts
|
621
|
-
end
|
622
|
-
end
|
623
|
-
else
|
624
|
-
puts "Extended information has not been added yet"
|
625
|
-
end
|
452
|
+
# Open default browser and shows open issues
|
453
|
+
#
|
454
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
455
|
+
# @example open issue's list
|
456
|
+
# User > Org > Repo > issues
|
457
|
+
def show_issues(config)
|
458
|
+
if config['Repo']
|
459
|
+
issues_url = "https://github.com/#{config['Org']}/#{config['Repo']}/issues"
|
460
|
+
open_url(issues_url)
|
626
461
|
else
|
627
|
-
|
628
|
-
Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
|
629
|
-
puts "Extended information has not been added yet"
|
462
|
+
puts Rainbow('Change to repo in order to view all issues').color(INFO_CODE)
|
630
463
|
end
|
631
464
|
end
|
632
465
|
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
466
|
+
# Display organization people. It shows all members and if the authenticated user is org admin
|
467
|
+
# also displays outside collaborators.
|
468
|
+
#
|
469
|
+
# @param client [Object] Octokit client object
|
470
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
471
|
+
# @param params [Regexp] if provided, it must be a Regexp
|
472
|
+
# @example Display organization people
|
473
|
+
# User > Org > people
|
474
|
+
# @example Display people matching Regexp
|
475
|
+
# User > Org > people /alu/
|
476
|
+
def show_people(client, config, params)
|
477
|
+
spinner = custom_spinner("Fetching #{config['Org']} people :spinner ...")
|
478
|
+
spinner.auto_spin
|
479
|
+
org_members = []
|
480
|
+
client.organization_members(config['Org'].to_s).each do |member|
|
481
|
+
org_members << [member[:login], 'member']
|
482
|
+
end
|
483
|
+
membership = {}
|
484
|
+
client.organization_membership(config['Org'].to_s).each do |key, value|
|
485
|
+
membership[key] = value
|
486
|
+
end
|
487
|
+
if membership[:role] == 'admin'
|
488
|
+
client.outside_collaborators(config['Org'].to_s).each do |collab|
|
489
|
+
org_members << [collab[:login], 'outside collaborator']
|
490
|
+
end
|
491
|
+
end
|
492
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
493
|
+
if params.nil?
|
494
|
+
table = Terminal::Table.new headings: ['Github ID', 'Role'], rows: org_members
|
495
|
+
puts table
|
643
496
|
else
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].each do |i|
|
648
|
-
puts "\n\e[31m#{i["github"]}\e[0m"
|
649
|
-
fields.each do |j|
|
650
|
-
puts "#{j.capitalize}:\t #{i[j]}"
|
651
|
-
end
|
652
|
-
peopleinfolist<<i["github"]
|
653
|
-
end
|
654
|
-
return peopleinfolist
|
655
|
-
else
|
656
|
-
if user.include?("@")
|
657
|
-
inuser=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["email"]==user}
|
658
|
-
else
|
659
|
-
inuser=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["github"]==user}
|
660
|
-
end
|
661
|
-
if inuser==nil
|
662
|
-
puts "Not extended information has been added of that user."
|
663
|
-
else
|
664
|
-
fields=inuser.keys
|
665
|
-
puts "\n\e[31m#{inuser["github"]}\e[0m"
|
666
|
-
fields.each do |j|
|
667
|
-
puts "#{j.capitalize}:\t #{inuser[j]}"
|
668
|
-
end
|
669
|
-
puts
|
670
|
-
end
|
671
|
-
end
|
672
|
-
else
|
673
|
-
puts "Extended information has not been added yet"
|
497
|
+
unless params.include?('/')
|
498
|
+
raise Rainbow('Parameter must be a Regexp. Example: /pattern/').color(ERROR_CODE)
|
499
|
+
return
|
674
500
|
end
|
501
|
+
pattern = build_regexp_from_string(params)
|
502
|
+
occurrences = build_item_table(org_members, pattern)
|
503
|
+
puts Rainbow("No member inside #{config['Org']} matched \/#{pattern.source}\/").color(INFO_CODE) if occurrences.zero?
|
675
504
|
end
|
676
505
|
end
|
677
506
|
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
507
|
+
# perform cd to repo scope, if 'name' is a Regexp, matches are stored and then we let the user select one
|
508
|
+
# if is not a Regexp, check that the string provided is a vale repository name
|
509
|
+
#
|
510
|
+
# @param name [String, Regexp] repo name
|
511
|
+
# @param client [Object] Octokit client object
|
512
|
+
# @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
|
513
|
+
# @return [ShellContext] changed context is returned if there is not an error during process.
|
514
|
+
def cd_repo(name, client, enviroment)
|
515
|
+
if name.class == Regexp
|
516
|
+
pattern = Regexp.new(name.source, name.options)
|
517
|
+
org_repos = []
|
518
|
+
org_repos_url = {}
|
519
|
+
spinner = custom_spinner("Matching #{enviroment.config['Org']} repositories :spinner ...")
|
520
|
+
spinner.auto_spin
|
521
|
+
client.organization_repositories(enviroment.config['Org'].to_s).each do |org_repo|
|
522
|
+
if pattern.match(org_repo[:name].to_s)
|
523
|
+
org_repos << org_repo[:name]
|
524
|
+
org_repos_url[org_repo[:name].to_s] = org_repo[:html_url]
|
525
|
+
end
|
526
|
+
end
|
527
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
528
|
+
if org_repos.empty?
|
529
|
+
puts Rainbow("No repository matched with #{name.source} inside organization #{enviroment.config['Org']}").color(WARNING_CODE)
|
530
|
+
puts
|
531
|
+
return
|
689
532
|
else
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
fields=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.keys
|
696
|
-
reponumber=1
|
697
|
-
ex=0
|
698
|
-
while ex==0
|
699
|
-
if reponumber==1
|
700
|
-
if fields.include?("repo")
|
701
|
-
reponumber=reponumber+1
|
702
|
-
else
|
703
|
-
ex=1
|
704
|
-
end
|
705
|
-
else
|
706
|
-
if fields.include?("repo#{reponumber}")
|
707
|
-
reponumber=reponumber+1
|
708
|
-
else
|
709
|
-
ex=1
|
710
|
-
end
|
711
|
-
end
|
533
|
+
prompt = TTY::Prompt.new
|
534
|
+
answer = prompt.select('Select desired organization repository', org_repos)
|
535
|
+
enviroment.config['Repo'] = answer
|
536
|
+
enviroment.config['repo_url'] = org_repos_url[answer]
|
537
|
+
enviroment.deep = Organization
|
712
538
|
end
|
713
|
-
end
|
714
|
-
|
715
|
-
if notexist==true
|
716
|
-
puts "Doesn't exist that repository"
|
717
539
|
else
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
end
|
724
|
-
|
725
|
-
if reponame!="" and notexist==false
|
726
|
-
if reponumber.to_i==1
|
727
|
-
list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["repo"]=reponame
|
728
|
-
Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
|
540
|
+
if client.repository?("#{enviroment.config['Org']}/#{name}")
|
541
|
+
org_repo_url = 'https://github.com/' << enviroment.config['Org'].to_s << '/' << name.to_s
|
542
|
+
enviroment.config['Repo'] = name
|
543
|
+
enviroment.config['repo_url'] = org_repo_url
|
544
|
+
enviroment.deep = Organization
|
729
545
|
else
|
730
|
-
|
731
|
-
|
732
|
-
Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
|
546
|
+
puts Rainbow("Maybe #{name} is not an organizaton or currently does not exist.").color(WARNING_CODE)
|
547
|
+
return
|
733
548
|
end
|
734
549
|
end
|
550
|
+
enviroment
|
735
551
|
end
|
736
552
|
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
553
|
+
# perform cd to team scope, first we retrieve all user's orgs, then we check 'name'
|
554
|
+
# if its a Regexp then matches are displayed on screen and user selects one (if no match warning
|
555
|
+
# is shown and we return nil)
|
556
|
+
# if 'name' is not a Regexp then it must be the full team's name so we check that is inside org_teams
|
557
|
+
# Last option is showing a warning and return nil (so we dont push to the stack_context)
|
558
|
+
#
|
559
|
+
# @param name [String, Regexp] team name
|
560
|
+
# @param client [Object] Octokit client object
|
561
|
+
# @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
|
562
|
+
# @return [ShellContext] changed context is returned if there is not an error during process.
|
563
|
+
def cd_team(name, client, enviroment)
|
564
|
+
org_teams = []
|
565
|
+
org_teams_id = {}
|
566
|
+
spinner = custom_spinner("Fetching #{enviroment.config['Org']} teams :spinner ...")
|
567
|
+
spinner.auto_spin
|
568
|
+
client.organization_teams(enviroment.config['Org'].to_s).each do |team|
|
569
|
+
org_teams << team[:name]
|
570
|
+
org_teams_id[team[:name].to_s] = team[:id]
|
571
|
+
end
|
572
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
573
|
+
if name.class == Regexp
|
574
|
+
pattern = Regexp.new(name.source, name.options)
|
575
|
+
name_matches = []
|
576
|
+
org_teams.each do |team_name|
|
577
|
+
name_matches << team_name if pattern.match(team_name.to_s)
|
578
|
+
end
|
579
|
+
if name_matches.empty?
|
580
|
+
puts Rainbow("No team matched with \/#{name.source}\/ inside organization #{enviroment.config['Org']}").color(WARNING_CODE)
|
581
|
+
puts
|
582
|
+
return
|
745
583
|
else
|
746
|
-
|
584
|
+
prompt = TTY::Prompt.new
|
585
|
+
answer = prompt.select('Select desired organization', name_matches)
|
586
|
+
enviroment.config['Team'] = answer
|
587
|
+
enviroment.config['TeamID'] = org_teams_id[answer]
|
588
|
+
enviroment.config['team_url'] = 'https://github.com/orgs/' << enviroment.config['Org'] << '/teams/' << enviroment.config['Team']
|
589
|
+
enviroment.deep = Team
|
747
590
|
end
|
748
591
|
else
|
749
|
-
if
|
750
|
-
|
751
|
-
|
752
|
-
|
592
|
+
if org_teams.include?(name)
|
593
|
+
enviroment.config['Team'] = name
|
594
|
+
enviroment.config['TeamID'] = org_teams_id[name]
|
595
|
+
enviroment.config['team_url'] = 'https://github.com/orgs/' << enviroment.config['Org'] << '/teams/' << enviroment.config['Team']
|
596
|
+
enviroment.deep = Team
|
753
597
|
else
|
754
|
-
puts "
|
598
|
+
puts Rainbow("Maybe #{name} is not a #{enviroment.config['Org']} team or currently does not exist.").color(WARNING_CODE)
|
599
|
+
puts
|
600
|
+
return
|
755
601
|
end
|
756
602
|
end
|
603
|
+
enviroment
|
757
604
|
end
|
758
605
|
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
puts "All the students has been deleted from the assignment"
|
772
|
-
Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
|
773
|
-
end
|
606
|
+
# cd method contains a hash representing where user can navigate depending on context. In this case
|
607
|
+
# inside an organization user is allowed to change to a org repo and org team.
|
608
|
+
# In order to add new 'cd types' see example below.
|
609
|
+
#
|
610
|
+
# @param type [String] name of the navigating option (repo, org, team, etc.)
|
611
|
+
# @param name [String, Regexp] String or Regexp to find the repository.
|
612
|
+
# @example add 'chuchu' cd scope
|
613
|
+
# add to cd_scopes = {'chuchu => method(:cd_chuchu)}
|
614
|
+
# call it with (name, client, enviroment) parameters
|
615
|
+
def cd(type, name, client, enviroment)
|
616
|
+
cd_scopes = { 'repo' => method(:cd_repo), 'team' => method(:cd_team) }
|
617
|
+
cd_scopes[type].call(name, client, enviroment)
|
774
618
|
end
|
775
619
|
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
620
|
+
# Show commits of current repo. If user is not in a repo, repository name for commit showing must be provided.
|
621
|
+
#
|
622
|
+
# @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
|
623
|
+
# @param params [Array<String>] if user is not on a repo then params[0] is repo name. Optionally,
|
624
|
+
# branch can be specified (value is in params[1]), if not provided default branch is master
|
625
|
+
def show_commits(enviroment, params)
|
626
|
+
options = {}
|
627
|
+
if !enviroment.config['Repo'].nil?
|
628
|
+
repo = enviroment.config['Repo']
|
629
|
+
options[:sha] = if params.empty?
|
630
|
+
'master'
|
631
|
+
else
|
632
|
+
params[0]
|
633
|
+
end
|
786
634
|
else
|
787
|
-
|
788
|
-
|
789
|
-
|
635
|
+
repo = params[0]
|
636
|
+
options[:sha] = if params[1].nil?
|
637
|
+
'master'
|
638
|
+
else
|
639
|
+
params[1]
|
640
|
+
end
|
790
641
|
end
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
puts "Add the suffix of the repository \"#{reponumber}\", in order to differentiate it from the other repositories: "
|
799
|
-
op=gets.chomp
|
800
|
-
end
|
801
|
-
else
|
802
|
-
while op=="" or op=="\n"
|
803
|
-
puts "Add the suffix of the first repository, in order to differentiate it from the other repositories: "
|
804
|
-
op=gets.chomp
|
642
|
+
begin
|
643
|
+
enviroment.client.commits("#{enviroment.config['Org']}/#{repo}", options).each do |i|
|
644
|
+
puts "\tSHA: #{i[:sha]}"
|
645
|
+
puts "\t\t Commit date: #{i[:commit][:author][:date]}"
|
646
|
+
puts "\t\t Commit author: #{i[:commit][:author][:name]}"
|
647
|
+
puts "\t\t\t Commit message: #{i[:commit][:message]}"
|
648
|
+
puts
|
805
649
|
end
|
650
|
+
rescue StandardError => exception
|
651
|
+
puts exception
|
652
|
+
puts Rainbow("If you are not currently on a repo, USAGE TIP: `commits <repo_name> [branch_name]` (default: 'master')").color(INFO_CODE)
|
806
653
|
end
|
807
|
-
return op
|
808
654
|
end
|
809
655
|
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
else
|
824
|
-
puts "Doesn't exist a repository with that identifier"
|
825
|
-
end
|
656
|
+
# Repo creation: creates new repo inside current org
|
657
|
+
#
|
658
|
+
# @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
|
659
|
+
# @param repo_name [String] repository name
|
660
|
+
# @param options [Hash] repository options (repo organization, visibility, etc)
|
661
|
+
def create_repo(enviroment, repo_name, options)
|
662
|
+
client = enviroment.client
|
663
|
+
options[:organization] = enviroment.config['Org'].to_s
|
664
|
+
client.create_repository(repo_name, options)
|
665
|
+
puts Rainbow('Repository created correctly!').color(79, 138, 16)
|
666
|
+
rescue StandardError => exception
|
667
|
+
puts Rainbow(exception.message.to_s).color(ERROR_CODE)
|
668
|
+
puts
|
826
669
|
end
|
827
670
|
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
671
|
+
# Removes repository by name
|
672
|
+
#
|
673
|
+
# @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
|
674
|
+
# @param repo_name [String] name of the repo to be removed
|
675
|
+
def remove_repo(enviroment, repo_name)
|
676
|
+
client = enviroment.client
|
677
|
+
client.delete_repository("#{enviroment.config['Org']}/#{repo_name}")
|
678
|
+
puts Rainbow('Repository deleted.').color(INFO_CODE)
|
679
|
+
rescue StandardError => exception
|
680
|
+
puts Rainbow(exception.message.to_s).color(ERROR_CODE)
|
837
681
|
puts
|
838
|
-
return orgslist
|
839
682
|
end
|
840
683
|
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
684
|
+
# Removes a group of GitHub users from an organization
|
685
|
+
#
|
686
|
+
#
|
687
|
+
def remove_org_member; end
|
688
|
+
|
689
|
+
# Team creation: opens team creation page on default browser when calling new_team
|
690
|
+
# with no options. If option provided, it must be the directory and name of the JSON file
|
691
|
+
# somewhere in your HOME directory for bulk creation of teams and members.
|
692
|
+
#
|
693
|
+
# @param client [Object] Octokit client object
|
694
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
695
|
+
# @param params [Array<String>] user specified parameters, if not nil must be path to teams JSON file.
|
696
|
+
# @example Open current org URL and create new team
|
697
|
+
# User > Org > new_team
|
698
|
+
# @example Create multiple teams with its members inside current org
|
699
|
+
# User > Org > new_team (HOME)/path/to/file/creation.json
|
700
|
+
def create_team(client, config, params)
|
701
|
+
if params.nil?
|
702
|
+
team_creation_url = "https://github.com/orgs/#{config['Org']}/new-team"
|
703
|
+
open_url(team_creation_url)
|
704
|
+
else
|
705
|
+
members_not_added = []
|
706
|
+
begin
|
707
|
+
file_path = "#{Dir.home}#{params}"
|
708
|
+
teams_json = File.read(file_path)
|
709
|
+
teams_file = JSON.parse(teams_json)
|
710
|
+
spinner = custom_spinner("Creating teams in #{config['Orgs']} :spinner ...")
|
711
|
+
teams_file['teams'].each do |team|
|
712
|
+
# assigned to 'created_team' to grab the ID (and use it for adding members)
|
713
|
+
# of the newly created team
|
714
|
+
created_team = client.create_team(config['Org'].to_s,
|
715
|
+
name: team['name'].to_s,
|
716
|
+
privacy: team['privacy'])
|
717
|
+
team['members'].each do |member|
|
718
|
+
member_addition = client.add_team_member(created_team[:id], member)
|
719
|
+
members_not_added.push(member) if member_addition == false # if !!member_adition
|
720
|
+
end
|
721
|
+
end
|
722
|
+
spinner.stop(Rainbow('done!').color(4, 255, 0))
|
723
|
+
puts Rainbow('Teams created correctly!').color(79, 138, 16)
|
724
|
+
puts Rainbow("Could not add following members: #{members_not_added}").color(WARNING_CODE) unless members_not_added.empty?
|
725
|
+
rescue StandardError => e
|
726
|
+
puts Rainbow(e.message.to_s).color(ERROR_CODE)
|
847
727
|
end
|
848
728
|
end
|
849
|
-
return list
|
850
729
|
end
|
851
730
|
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
orgslist.push(o[:login])
|
860
|
-
end
|
861
|
-
print "\n"
|
862
|
-
return orgslist
|
731
|
+
# Open org's team list and delete manually (faster than checking teams IDs and delete it)
|
732
|
+
#
|
733
|
+
# @param client [Object] Octokit client object
|
734
|
+
# @param config [Hash] user configuration tracking current org, repo, etc.
|
735
|
+
def remove_team(config)
|
736
|
+
teams_url = "https://github.com/orgs/#{config['Org']}/teams"
|
737
|
+
open_url(teams_url)
|
863
738
|
end
|
864
739
|
|
865
740
|
def read_orgs(client)
|
866
|
-
orgslist=[]
|
867
|
-
org=client.organizations
|
741
|
+
orgslist = []
|
742
|
+
org = client.organizations
|
868
743
|
org.each do |i|
|
869
|
-
o=eval(i.inspect)
|
744
|
+
o = eval(i.inspect)
|
870
745
|
orgslist.push(o[:login])
|
871
746
|
end
|
872
|
-
|
873
|
-
end
|
874
|
-
|
875
|
-
def open_org(client,config)
|
876
|
-
mem=client.organization(config["Org"])
|
877
|
-
Sys.new.open_url(mem[:html_url])
|
878
|
-
end
|
879
|
-
|
880
|
-
def open_user_url(client,config,user,field)
|
881
|
-
list=self.load_people()
|
882
|
-
inpeople=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
|
883
|
-
found=0
|
884
|
-
|
885
|
-
if inpeople==nil
|
886
|
-
list["orgs"].push({"name"=>config["Org"],"users"=>[]})
|
887
|
-
Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
|
888
|
-
puts "Extended information has not been added yet"
|
889
|
-
else
|
890
|
-
if user.downcase.start_with?("/") and user.downcase.count("/")==2
|
891
|
-
sp=user.split('/')
|
892
|
-
exp=Regexp.new(sp[1],sp[2])
|
893
|
-
inuser=Sys.new.search_rexp_peoplehash(list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"],exp)
|
894
|
-
user.slice!(0); user=user.chop
|
895
|
-
else
|
896
|
-
inuser=[]
|
897
|
-
inuser.push(list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["github"]==user})
|
898
|
-
end
|
899
|
-
if inuser==nil
|
900
|
-
puts "Not extended information has been added of that user."
|
901
|
-
else
|
902
|
-
if field==nil
|
903
|
-
inuser.each do |i|
|
904
|
-
i.each_value do |j|
|
905
|
-
if j.include?("github.com")
|
906
|
-
if !j.include?("https://") && !j.include?("http://")
|
907
|
-
Sys.new.open_url("https://"+j)
|
908
|
-
else
|
909
|
-
Sys.new.open_url(j)
|
910
|
-
end
|
911
|
-
found=1
|
912
|
-
end
|
913
|
-
end
|
914
|
-
end
|
915
|
-
if found==0
|
916
|
-
puts "No github web profile in the aditional information"
|
917
|
-
end
|
918
|
-
else
|
919
|
-
if inuser!=[]
|
920
|
-
if field.downcase.start_with?("/") and field.downcase.end_with?("/") ##regexp
|
921
|
-
field=field.delete("/")
|
922
|
-
inuser.each do |i|
|
923
|
-
if i!=nil
|
924
|
-
i.each_value do |j|
|
925
|
-
if j.include?(field)
|
926
|
-
if j.include?("https://") || j.include?("http://")
|
927
|
-
Sys.new.open_url(j)
|
928
|
-
end
|
929
|
-
end
|
930
|
-
end
|
931
|
-
end
|
932
|
-
end
|
933
|
-
else
|
934
|
-
inuser.each do |i|
|
935
|
-
if inuser.keys.include?(field.downcase)
|
936
|
-
if inuser[field.downcase].include?("https://") or inuser[field.downcase].include?("http://")
|
937
|
-
url=inuser["#{field.downcase}"]
|
938
|
-
else
|
939
|
-
url="http://"+inuser["#{field.downcase}"]
|
940
|
-
end
|
941
|
-
Sys.new.open_url(url)
|
942
|
-
else
|
943
|
-
puts "No field found with that name"
|
944
|
-
end
|
945
|
-
end
|
946
|
-
end
|
947
|
-
else
|
948
|
-
puts "No field found with that name"
|
949
|
-
end
|
950
|
-
end
|
951
|
-
end
|
952
|
-
end
|
747
|
+
orgslist
|
953
748
|
end
|
954
|
-
|
955
749
|
end
|