ghedsh 1.1.40 → 2.3.8

Sign up to get free protection for your applications and to get access to all the features.
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
- require_rel '.'
7
- require 'readline'
8
-
9
- GITHUB_LIST=['githubid','github','idgithub','github_id','id_github','githubuser','github_user']
10
- MAIL_LIST=['email','mail','e-mail']
11
-
12
- class Organizations
13
-
14
- attr_accessor :orgslist
15
- attr_accessor :assiglist
16
- attr_accessor :peoplelist
17
-
18
-
19
- def load_assig()
20
- @assiglist=Hash.new()
21
- @assiglist=Sys.new.load_assig_db("#{ENV['HOME']}/.ghedsh")
22
- return @assiglist
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
- def load_people()
26
- @peoplelist=Hash.new()
27
- @peoplelist=Sys.new.load_people_db("#{ENV['HOME']}/.ghedsh")
28
- return @peoplelist
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
- def show_assignments(client, config) #client,orgs
32
- list=self.load_assig()
33
-
34
- assig=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
35
- if assig!=nil
36
- if assig["assigs"].empty?
37
- puts "No assignments are available yet"
38
- else
39
- assig["assigs"].each do |i|
40
- puts "\n"
41
- puts i["name_assig"]
42
- i.each{|key, value| if key.include?("repo") then puts "Repository #{key.delete("repo")}: #{value}" end}
43
- if i["groups"]!=[] and i["groups"]!=nil
44
- puts "\tGroups: "
45
- i["groups"].each do |y|
46
- puts "\t\t#{y}"
47
- end
48
- end
49
- if i["people"]!=[] and i["people"]!=nil
50
- puts "\tStudents: "
51
- i["people"].each do |y|
52
- puts "\t\t#{y}"
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
- puts "No assignments are available yet"
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
- def assignment_repository(client,config,name)
67
- ex=false
68
- until ex==true
69
- puts "\n"
70
- puts "Assignment: #{name}"
71
- puts "1) Add a repository already created "
72
- puts "2) Create a new empty repository"
73
- puts "3) Don't assign a repository yet"
74
- print "option => "
75
- op=gets.chomp
76
- puts "\n"
77
- if op=="1" or op=="2" or op=="3"
78
- ex=true
79
- end
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
- case
82
- when op=="1" || op=="2"
94
+ end
83
95
 
84
- if op=="1"
85
- ex2=false
86
- until ex2==true
87
- exname=false
88
- until exname==true
89
- puts "Name of the repository -> Owner/Repository or Organization/Repository :"
90
- reponame=gets.chomp
91
- if reponame.split("/").size!=2 and reponame!=""
92
- puts "Please introduce a valid format."
93
- else
94
- exname=true
95
- end
96
- if reponame==""
97
- exname=true
98
- ex2=true
99
- end
100
- end
101
- if reponame!=""
102
- if client.repository?("#{reponame}")==false
103
- puts "\e[31m The repository #{reponame} doesn't exist\n \e[0m"
104
- puts "\nName of the repository (To skip and add the repository later, only press enter): "
105
- if reponame==""
106
- ex2=true
107
- end
108
- else
109
- ex2=true
110
- end
111
- end
112
- end
113
- end
114
- if op=="2"
115
- ex2=false
116
- until ex2==true
117
- puts "Name of the repository (To skip and add the repository later, press enter): "
118
- reponame=gets.chomp
119
- if reponame==""
120
- ex2=true
121
- else
122
- # ex2=Repositories.new().create_repository(client,config,reponame,false,ORGS)
123
- if client.repository?("#{config["Org"]}/#{reponame}")
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
- def assignment_groups(client,config)
138
- team=Teams.new()
139
- sys=Sys.new()
140
- groupslist=team.get_groupslist(config)
141
- groupsadd=[]
142
- teamlist=team.read_teamlist(client,config)
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
- puts "\n"
145
- puts "Groups currently available:\n\n"
146
- groupslist.each do |aux|
147
- puts "\t#{aux}"
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
- puts "\nAdd groups to your assignment (Press enter to skip): "
151
- op=gets.chomp
152
- if op==""
153
- puts "Do you want to create a new group? (Press any key and enter to proceed, or only enter to skip)"
154
- an=gets.chomp
155
-
156
- if an!=""
157
- time=Time.new
158
- puts "Put the name of the group (If you skip with enter, the group's name will be \"Group-#{time.ctime}\")"
159
- name=gets.chomp
160
- if name==""
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
- groupsadd=op.split(" ")
214
- groupsadd.each do |item|
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
- return groupsadd
201
+ rescue StandardError => e
202
+ puts Rainbow(e.message.to_s).color(ERROR_CODE)
221
203
  end
222
204
 
223
- def assignment_people(client,config)
224
- refuse=false
225
- members=self.get_organization_members(client,config)
226
- sys=Sys.new()
227
- begin
228
- puts "\n1)Put a list of students"
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
- return []
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
- def create_assig(client,config,name)
280
- list=self.load_assig()
281
- assigs=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
282
-
283
- if assigs==nil
284
- list["orgs"].push({"name"=>config["Org"],"assigs"=>[]})
285
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
286
- end
287
-
288
- assig_exist=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux| aux["name_assig"]==name}
289
- if assig_exist==nil
290
- begin
291
- puts "\n1)Individual assignment"
292
- puts "2)Group assignment"
293
- puts "3)Discard assignment"
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 "Already exists an Assignment with that name"
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
- def get_assigs(client,config,show)
321
- list=self.load_assig()
322
- assiglist=[]
323
- assig=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
324
- if assig!=nil
325
- if assig["assigs"].empty?
326
- if show==true
327
- puts "\e[31m No assignments are available yet\e[0m"
328
- end
329
- else
330
- assig["assigs"].each do |i|
331
- assiglist.push(i["name_assig"])
332
- end
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
- list["orgs"].push({"name"=>config["Org"],"assigs"=>[]})
336
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
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
- def show_assig_info(config,assig)
349
- assig=self.get_single_assig(config,assig)
350
- team=Teams.new()
351
- puts "\n"
352
- puts assig["name_assig"]
353
- assig=assig.sort.to_h
354
- assig.each{|key, value| if key.include?("repo") then puts "Repository #{key.delete("repo")}: #{value}" end}
355
- puts
356
- assig.each{|key, value| if key.include?("sufix") then puts "Sufix of Repository #{key.delete("sufix")} : #{value}" end}
357
- print "\n"
358
- if assig["groups"]!=[]
359
- puts "\tGroups: "
360
- assig["groups"].each do |y|
361
- puts "\t\t#{y}"
362
- t=team.get_single_group(config,y)
363
- if t!=nil
364
- t.each do |z|
365
- puts "\t\t\t#{z}"
366
- end
367
- end
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
- if assig["people"]!=[] and assig["people"]!=nil
371
- puts "\tStudents: "
372
- assig["people"].each do |y|
373
- puts "\t\t#{y}"
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 "\n"
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
- def make_assig(client,config,assig)
380
- web="https://github.com/"
381
- web2="git@github.com:"
382
- r=Repositories.new()
383
- team=Teams.new()
384
- sys=Sys.new()
385
- teamlist=team.read_teamlist(client,config)
386
- assig=self.get_single_assig(config,assig)
387
- repolist=[]
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 "\e[31m No repository is given for this assignment\e[0m"
312
+ puts Rainbow('Change to repo in order to create an issue.').color(INFO_CODE)
443
313
  end
444
314
  end
445
315
 
446
- def add_team_to_assig(client,config,assig,data)
447
- assig=self.get_single_assig(config,assig)
448
- end
449
-
450
- def add_people_to_assig(client,config,assig)
451
- list=self.load_assig()
452
- people=self.assignment_people(client,config)
453
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"]==nil
454
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"]=[]
455
- end
456
-
457
- if people!=""
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
- if !list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].include?(i)
460
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].push(i)
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
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
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
- def add_group_to_assig(client,config,assig)
470
- list=self.load_assig()
471
-
472
- groups=self.assignment_groups(client,config)
473
- if groups!=""
474
- groups.each do |i|
475
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["groups"].push(i)
476
- end
477
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
478
- end
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
- def open_assig(config,assig)
482
- Sys.new.open_url("https://github.com/search?q=org%3A#{config["Org"]}+#{assig}")
483
- end
484
-
485
- #Takes people info froma a csv file and gets into ghedsh people information
486
- def add_people_info(client,config,file,relation)
487
- list=self.load_people()
488
- csvoptions={:quote_char => "|",:headers=>true,:skip_blanks=>true}
489
- members=self.get_organization_members(client,config) #members of the organization
490
- change=false
491
- indexname=""
492
-
493
- inpeople=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
494
- if inpeople==nil
495
- list["orgs"].push({"name"=>config["Org"],"users"=>[]})
496
- Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
497
- end
498
-
499
- if file.end_with?(".csv")==false
500
- file=file+".csv"
501
- end
502
- if File.exist?(file)
503
- begin
504
- mem = CSV.read(file,csvoptions)
505
- rescue
506
- print "Invalid csv format."
507
- end
508
-
509
- fields=mem.headers
510
- users=Hash.new;
511
- users=[]
512
- puts "\nFields found: "
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
- def rm_people_info(client,config)
587
- list=self.load_people()
588
- inpeople=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
589
- if inpeople==nil
590
- puts "Extended information has not been added yet"
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
- if inpeople["users"].empty?
593
- puts "Extended information has not been added yet"
594
- else
595
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"]=[]
596
- Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
597
- puts "The aditional information of #{config["Org"]} has been removed"
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
- def search_rexp_people_info(client,config,exp)
603
- list=self.load_people()
604
- if list!=nil
605
- if list["users"]!=[]
606
- list=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
607
- if exp.match(/^\//)
608
- sp=exp.split('/')
609
- exp=Regexp.new(sp[1],sp[2])
610
- end
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
- list["orgs"].push({"name"=>config["Org"],"users"=>[]})
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
- def show_people_info(client,config,user)
634
- list=self.load_people()
635
-
636
- inpeople=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
637
- peopleinfolist=[]
638
-
639
- if inpeople==nil
640
- list["orgs"].push({"name"=>config["Org"],"users"=>[]})
641
- Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
642
- puts "Extended information has not been added yet"
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
- if inpeople["users"]!=[]
645
- if user==nil
646
- fields=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"][0].keys
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
- def add_repo_to_assig(client,config,assig,change)
679
- list=self.load_assig()
680
- notexist=false
681
-
682
- if change!=nil #change an specific repository
683
- reponumber=change
684
- fields=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.keys
685
- if reponumber=="1"
686
- if !fields.include?("repo")
687
- notexist=true
688
- end
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
- if !fields.include?("repo"+change.to_s)
691
- notexist=true
692
- end
693
- end
694
- else #adding new repository
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
- reponame=self.assignment_repository(client,config,assig["name_assig"])
719
- if reponumber.to_i>1
720
- sufix="sufix#{reponumber}"
721
- sufixname=self.assignment_repo_sufix(reponumber,1)
722
- end
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
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["repo#{reponumber}"]=reponame
731
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["#{sufix}"]=sufixname
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
- def rm_assigment_repo(config,assig,reponumber)
738
- list=self.load_assig()
739
-
740
- if reponumber==1
741
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["repo"]!=nil
742
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.delete("repo")
743
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.delete("sufix1")
744
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
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
- puts "Doesn't exist that repository"
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 list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["repo#{reponumber}"]!=nil
750
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.delete("repo#{reponumber}")
751
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.delete("sufix#{reponumber}")
752
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
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 "Doesn't exist that repository"
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
- def rm_assigment_student(config,assig,student,mode)
760
- list=self.load_assig()
761
- if mode==1
762
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].include?(student)
763
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].delete(student)
764
- puts "Student #{student} correctly deleted from the assignment"
765
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
766
- else
767
- puts "Student not found"
768
- end
769
- else
770
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].clear()
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
- def rm_assigment_group(config,assig,group,mode)
777
- list=self.load_assig()
778
- if mode==1
779
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["groups"].include?(group)
780
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["groups"].delete(group)
781
- puts "Group #{group} correctly deleted from the assignment"
782
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
783
- else
784
- puts "Group not found"
785
- end
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
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["groups"].clear()
788
- puts "All the groups has been deleted from the assignment"
789
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
635
+ repo = params[0]
636
+ options[:sha] = if params[1].nil?
637
+ 'master'
638
+ else
639
+ params[1]
640
+ end
790
641
  end
791
- end
792
-
793
- def assignment_repo_sufix(reponumber,order)
794
- op=""
795
- print "\n"
796
- if order==1
797
- while op=="" or op=="\n"
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
- def change_repo_sufix(config,assig,reponumber)
811
- list=self.load_assig()
812
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["sufix#{reponumber}"]!=nil
813
- if reponumber==1
814
- sufixname=self.assignment_repo_sufix(reponumber,2)
815
- sufix="sufix1"
816
- else
817
- sufixname=self.assignment_repo_sufix(reponumber,1)
818
- sufix="sufix#{reponumber}"
819
- end
820
-
821
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["#{sufix}"]=sufixname
822
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
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
- def show_organization_members_bs(client,config)
829
- orgslist=[]
830
- print "\n"
831
- mem=client.organization_members(config["Org"])
832
- mem.each do |i|
833
- m=eval(i.inspect)
834
- orgslist.push(m[:login])
835
- puts m[:login]
836
- end
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
- def get_organization_members(client,config)
842
- mem=client.organization_members(config["Org"])
843
- list=[]
844
- if mem!=nil
845
- mem.each do |i|
846
- list<<i[:login]
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
- def show_orgs(client,config)
853
- orgslist=[]
854
- print "\n"
855
- org=client.organizations
856
- org.each do |i|
857
- o=eval(i.inspect)
858
- puts o[:login]
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
- return orgslist
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