teachers_pet 0.2.0 → 0.3.0
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/CONTRIBUTING.md +7 -0
- data/Guardfile +3 -1
- data/README.md +53 -28
- data/Rakefile +1 -1
- data/bin/teachers_pet +4 -0
- data/lib/teachers_pet.rb +4 -1
- data/lib/teachers_pet/actions/add_to_team.rb +31 -0
- data/lib/teachers_pet/actions/base.rb +36 -111
- data/lib/teachers_pet/actions/clone_repos.rb +9 -36
- data/lib/teachers_pet/actions/create_repos.rb +16 -39
- data/lib/teachers_pet/actions/create_student_teams.rb +35 -0
- data/lib/teachers_pet/actions/fork_collab.rb +4 -19
- data/lib/teachers_pet/actions/open_issue.rb +17 -32
- data/lib/teachers_pet/actions/push_files.rb +8 -22
- data/lib/teachers_pet/cli.rb +22 -0
- data/lib/teachers_pet/client_decorator.rb +59 -0
- data/lib/teachers_pet/commands/add_to_team.rb +12 -0
- data/lib/teachers_pet/commands/clone_repos.rb +15 -0
- data/lib/teachers_pet/commands/create_repos.rb +15 -0
- data/lib/teachers_pet/commands/create_student_teams.rb +13 -0
- data/lib/teachers_pet/commands/fork_collab.rb +12 -0
- data/lib/teachers_pet/commands/open_issue.rb +18 -0
- data/lib/teachers_pet/commands/push_files.rb +15 -0
- data/lib/teachers_pet/configuration.rb +0 -13
- data/lib/teachers_pet/version.rb +1 -1
- data/spec/actions/base_spec.rb +13 -0
- data/spec/cli_spec.rb +17 -0
- data/spec/commands/add_to_team_spec.rb +43 -0
- data/spec/commands/clone_repos_spec.rb +33 -0
- data/spec/commands/create_repos_spec.rb +55 -0
- data/spec/commands/create_student_teams_spec.rb +90 -0
- data/spec/commands/fork_collab_spec.rb +95 -0
- data/spec/commands/open_issue_spec.rb +63 -0
- data/spec/commands/push_files_spec.rb +33 -0
- data/spec/fixtures/empty +0 -0
- data/spec/fixtures/teams +1 -0
- data/spec/spec_helper.rb +13 -53
- data/spec/support/command_helpers.rb +11 -0
- data/spec/support/common_helpers.rb +52 -0
- data/teachers_pet.gemspec +6 -2
- metadata +89 -29
- data/bin/clone_repos +0 -6
- data/bin/create_repos +0 -6
- data/bin/create_teams +0 -6
- data/bin/fork_collab +0 -6
- data/bin/open_issue +0 -6
- data/bin/push_files +0 -6
- data/lib/teachers_pet/actions/create_teams.rb +0 -101
- data/spec/actions/clone_repos_spec.rb +0 -44
- data/spec/actions/create_repos_spec.rb +0 -65
- data/spec/actions/create_teams_spec.rb +0 -40
- data/spec/actions/open_issue_spec.rb +0 -71
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 93ce5e5b5d6591f2c8d571ca24a6fca2c8b15366
|
|
4
|
+
data.tar.gz: f32ef808412ae9c2367ba836a3830fbd5ae96d05
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e06710f83c1fe8cb527dfe292b163b148b5ed163211624567a0a35b4238bda1f28eda81cdfacac8a2572a8241faf03f14a0226f79416e4122dff5e63b4d6a6fa
|
|
7
|
+
data.tar.gz: d58fc7219bcd4fa26d25c8428b074da3a93ea6a1a6c9ced5102e7428a4950848df90f6387917a0d51a1acfd16634c7a5401298eea9bc0355b55febb8903ba809
|
data/CONTRIBUTING.md
CHANGED
data/Guardfile
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# teachers_pet [](https://travis-ci.org/education/teachers_pet)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**WARNING: This documentation may contain unreleased changes. See [rubydoc.info/gems/teachers_pet](http://rubydoc.info/gems/teachers_pet) for the version of this README corresponding to the latest release.**
|
|
4
|
+
|
|
5
|
+
Command line tool to help teachers use GitHub in their classrooms.
|
|
4
6
|
|
|
5
7
|
## Philosophy
|
|
6
8
|
|
|
@@ -27,64 +29,87 @@ gem update teachers_pet
|
|
|
27
29
|
|
|
28
30
|
To use the latest-and-greatest code from this repository, see the instructions in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
29
31
|
|
|
30
|
-
##
|
|
32
|
+
## Typical workflow
|
|
33
|
+
|
|
34
|
+
...when using the [sandboxing](https://education.github.com/guide/sandboxing) method with [private repositories](https://education.github.com/guide/private_repos):
|
|
35
|
+
|
|
36
|
+
### Basic setup
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
1. Create an organization (you will be an owner by default). The organization should reflect the name of your course. See [the classroom guide](https://education.github.com/guide#2-create-an-organization-for-your-class) for more info.
|
|
39
|
+
1. Have each student/instructor create GitHub accounts.
|
|
40
|
+
1. Create a `students` file (you can use an alternate filename and specify with the `--students` option if you like)
|
|
34
41
|
* Individual assignments: one username per line
|
|
35
42
|
* Group assignments: one team per line in the format `teamName username username username`
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
1. Add the GitHub username of all instructors to an `Owners.csv` file (one per line)
|
|
44
|
+
1. Run the following:
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
```bash
|
|
47
|
+
teachers_pet create_student_teams ...
|
|
48
|
+
teachers_pet add_to_team --members Owners.csv ...
|
|
49
|
+
```
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
### Assignments
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
teachers_pet create_repos ...
|
|
55
|
+
teachers_pet push_files ...
|
|
56
|
+
# Multiple times:
|
|
57
|
+
teachers_pet open_issue ...
|
|
58
|
+
|
|
59
|
+
# Then, after the assignment is due,
|
|
60
|
+
teachers_pet clone_repos ...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Authentication
|
|
64
|
+
|
|
65
|
+
The scripts will ask for your GitHub password in order to run. If you have [two factor authentication](https://help.github.com/articles/about-two-factor-authentication) (2FA) enabled, [create a personal access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use) (replace `github.com` with your host for GitHub Enterprise):
|
|
42
66
|
|
|
43
67
|
https://github.com/settings/tokens/new?description=teachers_pet&scopes=repo%2Cpublic_repo%2Cwrite%3Aorg%2Crepo%3Astatus%2Cread%3Aorg%2Cuser%2Cadmin%3Aorg
|
|
44
68
|
|
|
45
|
-
|
|
69
|
+
Once created, specify the token using the `--token` option, or if you add the `TEACHERS_PET_GITHUB_TOKEN` environment variable to your `.bash_profile` (or equivalent – example below), it will be picked up by `teachers_pet`.
|
|
46
70
|
|
|
47
71
|
```bash
|
|
48
|
-
|
|
72
|
+
# replace YOUR_TOKEN_HERE below
|
|
73
|
+
echo "\n\nexport TEACHERS_PET_GITHUB_TOKEN=YOUR_TOKEN_HERE" >> ~/.bash_profile
|
|
74
|
+
source ~/.bash_profile
|
|
49
75
|
```
|
|
50
76
|
|
|
51
77
|
## Actions
|
|
52
78
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
For each assignment, run `create_repos` to create a repository for each student. The repositories are technically created per team, but if you use `create_teams` first, then there will be one team per student.
|
|
56
|
-
|
|
57
|
-
### Collaborator access
|
|
58
|
-
|
|
59
|
-
Give collaborator access to everyone who has forked your repository.
|
|
79
|
+
**To learn the options for each action, run**
|
|
60
80
|
|
|
61
81
|
```bash
|
|
62
|
-
|
|
82
|
+
teachers_pet help
|
|
83
|
+
# or
|
|
84
|
+
teachers_pet help COMMAND
|
|
63
85
|
```
|
|
64
86
|
|
|
65
|
-
###
|
|
87
|
+
### Giving others access
|
|
66
88
|
|
|
67
|
-
|
|
89
|
+
You may need to give other people access to various repositories using teams – the `add_to_team` command can help do this in bulk.
|
|
68
90
|
|
|
69
|
-
|
|
91
|
+
### Creating assignments
|
|
70
92
|
|
|
71
|
-
|
|
93
|
+
When using the [sandboxing](https://education.github.com/guide/sandboxing) setup, you will need to create the repositories for the students. For each assignment, use the `create_repos` action to create a repository for each student. The repositories are technically created per team, but if you use `create_student_teams` first, then there will be one team per student.
|
|
72
94
|
|
|
73
|
-
###
|
|
95
|
+
### Collaborator access
|
|
96
|
+
|
|
97
|
+
Give [collaborator access](https://help.github.com/articles/what-are-the-different-access-permissions#collaborator) to everyone who has forked your repository using `fork_collab`. Mostly useful for GitHub demonstrations, where the students can quickly be added to a repository without having to worry about collecting usernames.
|
|
74
98
|
|
|
75
|
-
|
|
99
|
+
### Pushing starter files
|
|
76
100
|
|
|
77
|
-
|
|
101
|
+
When creating repositories for students, you will often want to include boilerplate files. After running `create_repos`, create a canonical copy of the starter files (e.g. [`.gitignore`](https://github.com/github/gitignore#readme), `Makefile`s, etc.) in a repository. From the local clone of the repository, use the `push_files` action to place that code in the repositories for each student. This works by creating a Git remote for each student repository, and doing a `git push` to each one.
|
|
102
|
+
|
|
103
|
+
### Opening issues
|
|
78
104
|
|
|
79
|
-
|
|
105
|
+
After running `create_repos`, instructors can open issues in student repos as a way to list requirements of the assignment, goals, or instructions for patching, using the `open_issue` command.
|
|
80
106
|
|
|
81
107
|
### Pulling repositories for grading
|
|
82
108
|
|
|
83
|
-
When grading, use the `clone_repos`
|
|
109
|
+
When grading, use the `clone_repos` command to clone all the repositories in the organization that match the username-repository naming scheme that is generated when `create_repos` is run.
|
|
84
110
|
|
|
85
111
|
## Related projects
|
|
86
112
|
|
|
87
113
|
* https://education.github.com/guide
|
|
88
114
|
* https://github.com/hogbait/6170_repo_management
|
|
89
|
-
* https://github.com/mikehelmick/edugit-scripts
|
|
90
115
|
* https://github.com/UCSB-CS-Using-GitHub-In-Courses/github-acad-scripts
|
data/Rakefile
CHANGED
data/bin/teachers_pet
ADDED
data/lib/teachers_pet.rb
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module TeachersPet
|
|
2
|
+
module Actions
|
|
3
|
+
class AddToTeam < Base
|
|
4
|
+
def read_members_file
|
|
5
|
+
file = self.members
|
|
6
|
+
puts "Loading members to add:"
|
|
7
|
+
read_file(file).keys
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def team_name
|
|
11
|
+
file = self.members
|
|
12
|
+
File.basename(file, File.extname(file))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def team
|
|
16
|
+
teams_by_name = self.client.existing_teams_by_name(self.organization)
|
|
17
|
+
teams_by_name[self.team_name] || self.client.create_team(self.organization, team_name)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_members_to_owners
|
|
21
|
+
member_list = self.read_members_file
|
|
22
|
+
self.client.add_users_to_team(organization, self.team, member_list)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run
|
|
26
|
+
self.init_client
|
|
27
|
+
self.add_members_to_owners
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,120 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require 'configuration'
|
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
|
4
2
|
require 'octokit'
|
|
3
|
+
require_relative File.join('..', 'configuration')
|
|
5
4
|
|
|
6
|
-
## Common code for the edugit scripts.
|
|
7
5
|
module TeachersPet
|
|
8
6
|
module Actions
|
|
9
7
|
class Base
|
|
10
|
-
|
|
11
|
-
auth_method = nil
|
|
12
|
-
|
|
13
|
-
choose do |menu|
|
|
14
|
-
menu.prompt = "Login via oAuth or Password? "
|
|
15
|
-
menu.choice :oAuth do
|
|
16
|
-
auth_method = 'oauth'
|
|
17
|
-
end
|
|
18
|
-
menu.choice :Password do
|
|
19
|
-
auth_method = 'password'
|
|
20
|
-
end
|
|
21
|
-
end
|
|
8
|
+
attr_reader :client, :options
|
|
22
9
|
|
|
23
|
-
|
|
10
|
+
def initialize(opts={})
|
|
11
|
+
@options = opts.symbolize_keys
|
|
24
12
|
end
|
|
25
13
|
|
|
26
|
-
def
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@authmethod = self.get_auth_method
|
|
34
|
-
|
|
35
|
-
if @authmethod == 'oauth'
|
|
36
|
-
@oauthtoken = ask('What is your oAuth token?') { |q| q.default = ENV['ghe_oauth'] }
|
|
37
|
-
end
|
|
38
|
-
if @authmethod == 'password'
|
|
39
|
-
@password = ask('What is your password?') { |q| q.echo = false }
|
|
14
|
+
def method_missing(meth, *args, &block)
|
|
15
|
+
# Support boolean options ending, by calling them with '?' at the end
|
|
16
|
+
key = meth.to_s.sub(/\?\z/, '').to_sym
|
|
17
|
+
if self.options.has_key?(key)
|
|
18
|
+
self.options[key]
|
|
19
|
+
else
|
|
20
|
+
super
|
|
40
21
|
end
|
|
41
22
|
end
|
|
42
23
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
c.api_endpoint = @api_endpoint
|
|
49
|
-
c.web_endpoint = @web_endpoint
|
|
24
|
+
def octokit_config
|
|
25
|
+
opts = {
|
|
26
|
+
api_endpoint: self.api,
|
|
27
|
+
web_endpoint: self.web,
|
|
28
|
+
login: self.username,
|
|
50
29
|
# Organizations can get big, pull in all pages
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if @authmethod == 'password'
|
|
55
|
-
@client = Octokit::Client.new(:login => @username, :password => @password)
|
|
56
|
-
end
|
|
57
|
-
if @authmethod == 'oauth'
|
|
58
|
-
@client = Octokit::Client.new(:login => @username, :access_token => @oauthtoken)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def read_organization(organization)
|
|
63
|
-
abort("GitHub client not initialized") if @client.nil?
|
|
64
|
-
@client.organization(organization)
|
|
65
|
-
end
|
|
30
|
+
auto_paginate: true
|
|
31
|
+
}
|
|
66
32
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
protected
|
|
72
|
-
def repository?(organization, repo_name)
|
|
73
|
-
begin
|
|
74
|
-
@client.repository("#{organization}/#{repo_name}")
|
|
75
|
-
rescue
|
|
76
|
-
return false
|
|
33
|
+
if self.options[:token]
|
|
34
|
+
opts[:access_token] = self.token
|
|
35
|
+
else
|
|
36
|
+
opts[:password] = self.password
|
|
77
37
|
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def get_students_file_path
|
|
81
|
-
ask("What is the filename of the list of students?") { |q| q.default = TeachersPet::Configuration.studentsFile }
|
|
82
|
-
end
|
|
83
38
|
|
|
84
|
-
|
|
85
|
-
ask("What is the filename of the list of instructors?") { |q| q.default = TeachersPet::Configuration.instructorsFile }
|
|
39
|
+
opts
|
|
86
40
|
end
|
|
87
41
|
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
repos[repo[:name]] = repo
|
|
94
|
-
print " '#{repo[:name]}'"
|
|
95
|
-
end
|
|
96
|
-
print "\n";
|
|
97
|
-
return repos
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def get_teams_by_name(organization)
|
|
101
|
-
org_teams = @client.organization_teams(organization)
|
|
102
|
-
teams = Hash.new
|
|
103
|
-
org_teams.each do |team|
|
|
104
|
-
teams[team[:name]] = team
|
|
105
|
-
end
|
|
106
|
-
return teams
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def get_team_member_logins(team_id)
|
|
110
|
-
@client.team_members(team_id).map do |member|
|
|
111
|
-
member[:login]
|
|
112
|
-
end
|
|
42
|
+
def init_client
|
|
43
|
+
puts "=" * 50
|
|
44
|
+
puts "Authenticating to GitHub..."
|
|
45
|
+
octokit = Octokit::Client.new(self.octokit_config)
|
|
46
|
+
@client = TeachersPet::ClientDecorator.new(octokit)
|
|
113
47
|
end
|
|
114
48
|
|
|
115
|
-
def read_file(filename
|
|
49
|
+
def read_file(filename)
|
|
116
50
|
map = Hash.new
|
|
117
|
-
puts "Loading #{type}:"
|
|
118
51
|
File.open(filename).each_line do |team|
|
|
119
52
|
# Team can be a single user, or a team name and multiple users
|
|
120
53
|
# Trim whitespace, otherwise issues occur
|
|
@@ -139,22 +72,14 @@ module TeachersPet
|
|
|
139
72
|
end
|
|
140
73
|
end
|
|
141
74
|
end
|
|
142
|
-
return map
|
|
143
|
-
end
|
|
144
75
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
confirmed = false
|
|
148
|
-
choose do |menu|
|
|
149
|
-
menu.prompt = message
|
|
76
|
+
map
|
|
77
|
+
end
|
|
150
78
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
abort("Creation canceled by user")
|
|
156
|
-
end
|
|
157
|
-
return confirmed
|
|
79
|
+
def read_students_file
|
|
80
|
+
student_file = self.students
|
|
81
|
+
puts "Loading students:"
|
|
82
|
+
read_file(student_file)
|
|
158
83
|
end
|
|
159
84
|
end
|
|
160
85
|
end
|
|
@@ -1,58 +1,31 @@
|
|
|
1
|
-
# Author: Mike Helmick
|
|
2
|
-
# Clones all student repositories for a particular assignment
|
|
3
|
-
#
|
|
4
|
-
# Currently this will clone all student repositories into the current
|
|
5
|
-
|
|
6
|
-
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..')
|
|
7
|
-
|
|
8
|
-
require 'rubygems'
|
|
9
|
-
require 'highline/question'
|
|
10
|
-
require 'highline/import'
|
|
11
|
-
require 'highline/compatibility'
|
|
12
|
-
require 'teachers_pet/actions/base'
|
|
13
|
-
|
|
14
1
|
module TeachersPet
|
|
15
2
|
module Actions
|
|
16
3
|
class CloneRepos < Base
|
|
17
4
|
def read_info
|
|
18
|
-
@repository =
|
|
19
|
-
@organization =
|
|
20
|
-
@student_file = self.get_students_file_path
|
|
5
|
+
@repository = self.repository
|
|
6
|
+
@organization = self.organization
|
|
21
7
|
end
|
|
22
8
|
|
|
23
9
|
def load_files
|
|
24
|
-
@students =
|
|
10
|
+
@students = self.read_students_file
|
|
25
11
|
end
|
|
26
12
|
|
|
27
13
|
def get_clone_method
|
|
28
|
-
|
|
29
|
-
choose do |menu|
|
|
30
|
-
menu.prompt = "Clone via? "
|
|
31
|
-
menu.choice :ssh do
|
|
32
|
-
cloneMethod = 'ssh'
|
|
33
|
-
end
|
|
34
|
-
menu.choice :https do
|
|
35
|
-
cloneMethod = 'https'
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
cloneMethod
|
|
14
|
+
self.clone_method
|
|
40
15
|
end
|
|
41
16
|
|
|
42
17
|
def create
|
|
43
18
|
cloneMethod = self.get_clone_method
|
|
44
19
|
|
|
45
|
-
confirm("Clone all repositories?")
|
|
46
|
-
|
|
47
20
|
# create a repo for each student
|
|
48
21
|
self.init_client
|
|
49
22
|
|
|
50
|
-
org_hash =
|
|
23
|
+
org_hash = self.client.organization(@organization)
|
|
51
24
|
abort('Organization could not be found') if org_hash.nil?
|
|
52
25
|
puts "Found organization at: #{org_hash[:url]}"
|
|
53
26
|
|
|
54
27
|
# Load the teams - there should be one team per student.
|
|
55
|
-
org_teams = get_teams_by_name(@organization)
|
|
28
|
+
org_teams = self.client.get_teams_by_name(@organization)
|
|
56
29
|
# For each student - pull the repository if it exists
|
|
57
30
|
puts "\nCloning assignment repositories for students..."
|
|
58
31
|
@students.keys.each do |student|
|
|
@@ -62,16 +35,16 @@ module TeachersPet
|
|
|
62
35
|
end
|
|
63
36
|
repo_name = "#{student}-#{@repository}"
|
|
64
37
|
|
|
65
|
-
unless repository?(@organization, repo_name)
|
|
38
|
+
unless self.client.repository?(@organization, repo_name)
|
|
66
39
|
puts " ** ERROR ** - Can't find expected repository '#{repo_name}'"
|
|
67
40
|
next
|
|
68
41
|
end
|
|
69
42
|
|
|
70
43
|
|
|
71
|
-
sshEndpoint =
|
|
44
|
+
sshEndpoint = self.web.gsub("https://","git@").gsub("/",":")
|
|
72
45
|
command = "git clone #{sshEndpoint}#{@organization}/#{repo_name}.git"
|
|
73
46
|
if cloneMethod.eql?('https')
|
|
74
|
-
command = "git clone #{
|
|
47
|
+
command = "git clone #{self.web}#{@organization}/#{repo_name}.git"
|
|
75
48
|
end
|
|
76
49
|
puts " --> Cloning: '#{command}'"
|
|
77
50
|
self.execute(command)
|