git_go 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.travis.yml +11 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +65 -0
- data/Guardfile +8 -0
- data/LICENSE +22 -0
- data/README.md +50 -0
- data/Rakefile +3 -0
- data/assets/instructions +164 -0
- data/bin/gg +10 -0
- data/gitgo.gemspec +24 -0
- data/lib/git_go/cli/gg.rb +120 -0
- data/lib/git_go/connection.rb +139 -0
- data/lib/git_go/formatter.rb +55 -0
- data/lib/git_go/version.rb +8 -0
- data/lib/git_go.rb +62 -0
- data/spec/lib/git_go/connection_spec.rb +108 -0
- data/spec/lib/git_go/formatter_spec.rb +37 -0
- data/spec/lib/git_go/version_spec.rb +10 -0
- data/spec/lib/git_go_spec.rb +23 -0
- data/spec/spec_helper.rb +12 -0
- metadata +116 -0
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gemspec
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem "rake"
|
9
|
+
gem "yard"
|
10
|
+
gem "rspec"
|
11
|
+
gem "mocha"
|
12
|
+
gem "guard-rspec"
|
13
|
+
gem "rb-inotify", :require => false
|
14
|
+
gem "rb-fsevent", :require => false
|
15
|
+
gem "rb-fchange", :require => false
|
16
|
+
gem "growl"
|
17
|
+
end
|
18
|
+
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
git_go (0.0.1)
|
5
|
+
net-ssh (~> 2.6.2)
|
6
|
+
thor (~> 0.16.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
coderay (1.0.8)
|
12
|
+
diff-lcs (1.1.3)
|
13
|
+
ffi (1.2.0)
|
14
|
+
growl (1.0.3)
|
15
|
+
guard (1.5.4)
|
16
|
+
listen (>= 0.4.2)
|
17
|
+
lumberjack (>= 1.0.2)
|
18
|
+
pry (>= 0.9.10)
|
19
|
+
thor (>= 0.14.6)
|
20
|
+
guard-rspec (2.3.1)
|
21
|
+
guard (>= 1.1)
|
22
|
+
rspec (~> 2.11)
|
23
|
+
listen (0.6.0)
|
24
|
+
lumberjack (1.0.2)
|
25
|
+
metaclass (0.0.1)
|
26
|
+
method_source (0.8.1)
|
27
|
+
mocha (0.13.1)
|
28
|
+
metaclass (~> 0.0.1)
|
29
|
+
net-ssh (2.6.2)
|
30
|
+
pry (0.9.10)
|
31
|
+
coderay (~> 1.0.5)
|
32
|
+
method_source (~> 0.8)
|
33
|
+
slop (~> 3.3.1)
|
34
|
+
rake (10.0.2)
|
35
|
+
rb-fchange (0.0.5)
|
36
|
+
ffi
|
37
|
+
rb-fsevent (0.9.2)
|
38
|
+
rb-inotify (0.8.8)
|
39
|
+
ffi (>= 0.5.0)
|
40
|
+
rspec (2.12.0)
|
41
|
+
rspec-core (~> 2.12.0)
|
42
|
+
rspec-expectations (~> 2.12.0)
|
43
|
+
rspec-mocks (~> 2.12.0)
|
44
|
+
rspec-core (2.12.1)
|
45
|
+
rspec-expectations (2.12.0)
|
46
|
+
diff-lcs (~> 1.1.3)
|
47
|
+
rspec-mocks (2.12.0)
|
48
|
+
slop (3.3.3)
|
49
|
+
thor (0.16.0)
|
50
|
+
yard (0.8.2.1)
|
51
|
+
|
52
|
+
PLATFORMS
|
53
|
+
ruby
|
54
|
+
|
55
|
+
DEPENDENCIES
|
56
|
+
git_go!
|
57
|
+
growl
|
58
|
+
guard-rspec
|
59
|
+
mocha
|
60
|
+
rake
|
61
|
+
rb-fchange
|
62
|
+
rb-fsevent
|
63
|
+
rb-inotify
|
64
|
+
rspec
|
65
|
+
yard
|
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Michael van Rooijen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Git Go
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/meskyanichi/git_go.png)](https://travis-ci.org/meskyanichi/git_go)
|
4
|
+
|
5
|
+
Git Go is a small command-line utility distributed as a RubyGem that allows you to easily create/destroy/rename/list all your private-hosted git repositories on your own server. All you need is a small VPS (256MB RAM / 10GB HDD / 1vCPUCore should be sufficient).
|
6
|
+
|
7
|
+
Git Go also provides you with detailed instructions on how to set up your server in order to be able to create remote repositories from your local machine using the provided command-line utility. The instructions also show you how to set up automatic/daily compressed/archived backups of all your git repositories, store them on Amazon S3, cycle them and send you success/error notifications by email, all using the [Backup](http://github.com/meskyanichi/backup) RubyGem. It's easy, and only takes a few minutes to set up.
|
8
|
+
|
9
|
+
## Get Git Go
|
10
|
+
|
11
|
+
Install Git Go with the following command:
|
12
|
+
|
13
|
+
```sh
|
14
|
+
gem install git_go
|
15
|
+
```
|
16
|
+
|
17
|
+
This will provide you with the `gg` executable. Simply run it without any arguments to see the list of tasks it can perform.
|
18
|
+
|
19
|
+
```sh
|
20
|
+
$ gg
|
21
|
+
|
22
|
+
Tasks:
|
23
|
+
gg create NAME # Create a new remote repository named NAME
|
24
|
+
gg destroy NAME # Destroy the remote repository named NAME
|
25
|
+
gg help [TASK] # Describe available tasks or one specific task
|
26
|
+
gg instructions # Display a detailed guide to setup Git Go locally and remotely
|
27
|
+
gg list # Display a list of all the remote repositories and their URL
|
28
|
+
gg rename NAME NEW_NAME # Rename the remote repository named NAME to NEW_NAME
|
29
|
+
```
|
30
|
+
|
31
|
+
I recommend you run `gg instructions` to get up and running. The instructions cover:
|
32
|
+
|
33
|
+
* Setting up two local environment variables in your .bashrc or .zshrc.
|
34
|
+
* Adding a git user to your remote machine.
|
35
|
+
* Setting up SSH keys.
|
36
|
+
* Setting up the Backup RubyGem to perform daily backups with:
|
37
|
+
* GZip Compression
|
38
|
+
* Email Notification on success/warning/error
|
39
|
+
* Amazon S3 storage
|
40
|
+
* Cycling (e.g. 30 day backup retention, then pop old backups for new backups)
|
41
|
+
|
42
|
+
|
43
|
+
## License
|
44
|
+
|
45
|
+
"Git Go" is released under the MIT License. See `LICENSE`.
|
46
|
+
|
47
|
+
## Author
|
48
|
+
|
49
|
+
Michael van Rooijen | [@meskyanichi](http://twitter.com/meskyanichi) | http://github.com/meskyanichi
|
50
|
+
|
data/Rakefile
ADDED
data/assets/instructions
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
|
2
|
+
GIT GO INSTRUCTIONS
|
3
|
+
===================
|
4
|
+
|
5
|
+
http://github.com/meskyanichi/git_go
|
6
|
+
|
7
|
+
Here are the instructions for getting your environments set up.
|
8
|
+
|
9
|
+
Local Environment
|
10
|
+
-----------------
|
11
|
+
|
12
|
+
Add the following to your .bashrc or .zshrc file:
|
13
|
+
|
14
|
+
export GIT_GO_USER=user
|
15
|
+
export GIT_GO_HOST=host
|
16
|
+
|
17
|
+
For example:
|
18
|
+
|
19
|
+
export GIT_GO_USER=git
|
20
|
+
export GIT_GO_HOST=repositories.example.com
|
21
|
+
|
22
|
+
GIT_GO_HOST can also be a direct IP, for example:
|
23
|
+
|
24
|
+
export GIT_GO_HOST=207.97.227.239
|
25
|
+
|
26
|
+
Remote Environment
|
27
|
+
------------------
|
28
|
+
|
29
|
+
The following guide assumes you are `root`. If you aren't root, prefix all commands with `sudo` or simply `sudo su` to become `root`.
|
30
|
+
|
31
|
+
The guide also assumes you want a user called `git` under which we'll create all the repositories. You can substitute `git` with anything you like.
|
32
|
+
|
33
|
+
Finally, the guide assumes you're using Ubuntu 12.10 as the remote machine, but this will likely work on some older versions of Ubuntu as well.
|
34
|
+
|
35
|
+
Log in to your remote machine using SSH.
|
36
|
+
Be sure you have root priviledges, either through sudo or as root.
|
37
|
+
|
38
|
+
Create the "git" user:
|
39
|
+
|
40
|
+
adduser \
|
41
|
+
--home /home/git \
|
42
|
+
--shell /bin/bash \
|
43
|
+
--disabled-password \
|
44
|
+
git
|
45
|
+
|
46
|
+
Create the .ssh folder and authorized_keys file for the git user.
|
47
|
+
|
48
|
+
mkdir /home/git/.ssh
|
49
|
+
|
50
|
+
Now add your key (usually located in `~/.ssh/id_rsa.pub` on your local machine) to `/home/git/.ssh/authorized_keys`. Finally, fix the owner and permissions.
|
51
|
+
|
52
|
+
chmod 700 /home/git/.ssh
|
53
|
+
chmod 600 /home/git/.ssh/authorized_keys
|
54
|
+
chown -R git:git /home/git/.ssh
|
55
|
+
|
56
|
+
|
57
|
+
Using Git Go
|
58
|
+
------------
|
59
|
+
|
60
|
+
With that, you're ready to use Git Go. Simply run `gg help` and try out any of the commands that Git Go provides.
|
61
|
+
|
62
|
+
|
63
|
+
Backups
|
64
|
+
-------
|
65
|
+
|
66
|
+
It is always a good idea to set up backups for your git repositories. Here's a nifty and clean way to set up daily backups for all your git repositories, along with email notifications, gzip compression, Amazon S3 storing and cycling old for new backups.
|
67
|
+
|
68
|
+
First, we'll need a recent version of Ruby. If Ruby isn't already installed on your machine, simply run the following:
|
69
|
+
|
70
|
+
apt-get update && apt-get upgrade -y
|
71
|
+
apt-get install -y python-software-properties software-properties-common
|
72
|
+
apt-add-repository ppa:brightbox/ruby-ng
|
73
|
+
apt-get update
|
74
|
+
apt-get install -y ruby rubygems ruby-switch ruby1.9.3 ruby1.9.1-dev \
|
75
|
+
libxml2 libxslt-dev libxml2-dev libopenssl-ruby
|
76
|
+
|
77
|
+
What this will do, is it will add a 3rd party repository Ruby, provided by Brightbox Systemd Ltd. It includes up-to-date Ruby versions which you can install directly through the apt package manager. After adding it and updating the repository list, we can simply install the latest 1.9.x Ruby (Ruby 1.9.3). We also include some other packages which are required for compiling certain extensions that come with the gems we'll be installing.
|
78
|
+
|
79
|
+
With Ruby installed, let us set up our backup system.
|
80
|
+
|
81
|
+
gem install backup fog mail
|
82
|
+
|
83
|
+
We'll be using `backup` for performing backups. We'll use `mail` with `backup` to send email notifications after each backup operation. We'll use `fog` with `backup` to send the backups to Amazon S3.
|
84
|
+
|
85
|
+
Note that Backup is flexible can support a few other storage locations, including Rackspace, Dropbox, etc. The same goes for the notifier, you can use Email, Twitter or a few other notification solutions. We'll stick to mail and Amazon S3 for this guide.
|
86
|
+
|
87
|
+
It is recommended that you become root before proceeding. Become root with the following command if you aren't already:
|
88
|
+
|
89
|
+
sudo su
|
90
|
+
|
91
|
+
Generate the initial Backup configuration and model.
|
92
|
+
|
93
|
+
backup generate:config
|
94
|
+
backup generate:model --trigger gitrepositories \
|
95
|
+
--archives --compressors=gzip --notifiers=mail --storages=s3
|
96
|
+
|
97
|
+
This should generate a configuration file in `/root/Backup/models/`. Basically what you want is the following:
|
98
|
+
|
99
|
+
Backup::Model.new(:gitrepositories, 'git repository backups') do
|
100
|
+
split_into_chunks_of 500
|
101
|
+
|
102
|
+
archive :git do |archive|
|
103
|
+
archive.add "/home/git/"
|
104
|
+
end
|
105
|
+
|
106
|
+
store_with S3 do |s3|
|
107
|
+
s3.access_key_id = "YOUR_ACCESS_KEY_ID"
|
108
|
+
s3.secret_access_key = "YOUR_SECRET_ACCESS_KEY"
|
109
|
+
s3.region = "us-east-1"
|
110
|
+
s3.bucket = "your_bucket_name"
|
111
|
+
s3.path = "/backup"
|
112
|
+
s3.keep = 30
|
113
|
+
end
|
114
|
+
|
115
|
+
compress_with Gzip
|
116
|
+
|
117
|
+
notify_by Mail do |mail|
|
118
|
+
mail.on_success = true
|
119
|
+
mail.on_warning = true
|
120
|
+
mail.on_failure = true
|
121
|
+
|
122
|
+
mail.from = "your@email.com"
|
123
|
+
mail.to = "your@email.com"
|
124
|
+
mail.address = "smtp.gmail.com"
|
125
|
+
mail.port = 587
|
126
|
+
mail.domain = "repositories.example.com"
|
127
|
+
mail.user_name = "username@email.com"
|
128
|
+
mail.password = "password"
|
129
|
+
mail.authentication = "plain"
|
130
|
+
mail.enable_starttls_auto = true
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
Just fill out all the settings. Once done, try it out by running:
|
135
|
+
|
136
|
+
backup perform -t gitrepositories
|
137
|
+
|
138
|
+
If all goes well, you should have received an email, and the backup should have been stored on your Amazon S3 account in the specified bucket under /backups.
|
139
|
+
|
140
|
+
Check out https://github.com/meskyanichi/backup for more information.
|
141
|
+
|
142
|
+
Now let's set up cron to automatically invoke this command every day. First, check to see where exactly backup has been installed on your machine.
|
143
|
+
|
144
|
+
which backup
|
145
|
+
|
146
|
+
You'll likely see the following:
|
147
|
+
|
148
|
+
/usr/local/bin/backup
|
149
|
+
|
150
|
+
Copy this path and we'll use it in the crontab. Edit the crontab by running:
|
151
|
+
|
152
|
+
crontab -e
|
153
|
+
|
154
|
+
Add this line to the crontab:
|
155
|
+
|
156
|
+
0 0 * * * /bin/bash -l -c '/usr/local/bin/backup perform -t gitrepositories'
|
157
|
+
|
158
|
+
Save and close. Now you should have set up daily backups of your entire /home/git directory. This is nice because if you experience any data loss due to for example hardware failures, just spin up or reset the server, create the git user again and simply extract the `git.tar.gz` to the `/home/git` directory to restore all your repositories and ssh configuration.
|
159
|
+
|
160
|
+
|
161
|
+
Michael van Rooijen
|
162
|
+
> http://github.com/meskyanichi
|
163
|
+
> http://twitter.com/meskyanichi
|
164
|
+
|
data/bin/gg
ADDED
data/gitgo.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
lib = File.expand_path("../lib", __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "git_go/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |gem|
|
8
|
+
gem.name = "git_go"
|
9
|
+
gem.version = GitGo::VERSION
|
10
|
+
gem.authors = ["Michael van Rooijen"]
|
11
|
+
gem.email = ["meskyanichi@gmail.com"]
|
12
|
+
gem.description = %q{Git Go is a small command-line utility distributed as a RubyGem that allows you to easily create/destroy/rename/list all your private-hosted git repositories on your own server.}
|
13
|
+
gem.summary = gem.description
|
14
|
+
gem.homepage = "http://github.com/meskyanichi/git_go"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_dependency "thor", ["~> 0.16.0"]
|
22
|
+
gem.add_dependency "net-ssh", ["~> 2.6.2"]
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module GitGo
|
4
|
+
class Repository < Thor
|
5
|
+
include Thor::Actions
|
6
|
+
|
7
|
+
desc "create NAME", "Create a new remote repository named NAME"
|
8
|
+
def create(name)
|
9
|
+
core = GitGo::Core.new
|
10
|
+
conn = core.connection
|
11
|
+
if conn.directory?("#{name}.git")
|
12
|
+
puts "'#{name}' already exists."
|
13
|
+
conn.close_and_exit(1)
|
14
|
+
else
|
15
|
+
conn.mkdir("#{name}.git")
|
16
|
+
conn.cd("#{name}.git")
|
17
|
+
response = conn.bash("git init --bare")
|
18
|
+
|
19
|
+
if response[:exit_code] == 0
|
20
|
+
puts "Repository created at: #{core.user}@#{core.host}:#{name}.git"
|
21
|
+
else
|
22
|
+
puts "Failed to create repository."
|
23
|
+
puts response[:stderr] + response[:stdout]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
conn.close
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "destroy NAME", "Destroy the remote repository named NAME"
|
31
|
+
def destroy(name)
|
32
|
+
core = GitGo::Core.new
|
33
|
+
conn = core.connection
|
34
|
+
repository = "#{name}.git"
|
35
|
+
if conn.directory?(repository)
|
36
|
+
if yes?("Are you sure you want to destroy '#{name}'?")
|
37
|
+
response = conn.rm(repository)
|
38
|
+
if response[:exit_code] == 0
|
39
|
+
puts "Repository '#{name}' was destroyed."
|
40
|
+
else
|
41
|
+
puts "Repository '#{name}' could not be destroyed."
|
42
|
+
puts response[:stdout] + response[:stderr]
|
43
|
+
conn.close_and_exit(1)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
puts "Repository '#{name}' does not exist."
|
48
|
+
conn.close_and_exit(1)
|
49
|
+
end
|
50
|
+
|
51
|
+
conn.close
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "rename NAME NEW_NAME", "Rename the remote repository named NAME to NEW_NAME"
|
55
|
+
def rename(name, new_name)
|
56
|
+
core = GitGo::Core.new
|
57
|
+
conn = core.connection
|
58
|
+
repository = "#{name}.git"
|
59
|
+
new_repository = "#{new_name}.git"
|
60
|
+
|
61
|
+
if not conn.directory?(repository)
|
62
|
+
puts "Repository '#{name}' does not exist."
|
63
|
+
conn.close_and_exit(1)
|
64
|
+
end
|
65
|
+
|
66
|
+
if conn.directory?(new_repository)
|
67
|
+
puts "Repository '#{new_name}' already exists."
|
68
|
+
conn.close_and_exit(1)
|
69
|
+
end
|
70
|
+
|
71
|
+
response = conn.mv(repository, new_repository)
|
72
|
+
if response[:exit_code] == 0
|
73
|
+
puts "Repository '#{name}' renamed to '#{new_name}'."
|
74
|
+
else
|
75
|
+
puts "Could not rename '#{name}' to '#{new_name}'."
|
76
|
+
puts response[:stderr] + response[:stdout]
|
77
|
+
conn.close_and_exit(1)
|
78
|
+
end
|
79
|
+
|
80
|
+
conn.close
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "list", "Display a list of all the remote repositories and their URL"
|
84
|
+
def list
|
85
|
+
core = GitGo::Core.new
|
86
|
+
conn = core.connection
|
87
|
+
|
88
|
+
response = conn.bash("find ./*.git/ -mindepth 0 -maxdepth 0")
|
89
|
+
|
90
|
+
if response[:exit_code] == 0
|
91
|
+
repositories = response[:stdout].split("\n").map { |r| File.basename(r) }
|
92
|
+
list = [["Repository", "Git Fetch/Push URL"]]
|
93
|
+
|
94
|
+
repositories.each do |repository|
|
95
|
+
list << [
|
96
|
+
repository.sub(".git", ""),
|
97
|
+
"#{core.user}@#{core.host}:#{repository}"
|
98
|
+
]
|
99
|
+
end
|
100
|
+
|
101
|
+
amount = list.count - 1
|
102
|
+
out = Formatter.columns(list, :spacing => 4, :header => true)
|
103
|
+
|
104
|
+
puts "\n" + out
|
105
|
+
puts "\nThere #{amount == 1 ? "is" : "are"} #{amount} git " +
|
106
|
+
"#{amount == 1 ? "repository" : "repositories"}.\n"
|
107
|
+
else
|
108
|
+
puts "There are no repositories at #{core.user}@#{core.host}."
|
109
|
+
end
|
110
|
+
|
111
|
+
conn.close
|
112
|
+
end
|
113
|
+
|
114
|
+
desc "instructions", "Display a detailed guide to setup Git Go locally and remotely"
|
115
|
+
def instructions
|
116
|
+
puts Core.instructions
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module GitGo
|
4
|
+
class Connection
|
5
|
+
|
6
|
+
# @return [GitGo::Core] instance.
|
7
|
+
attr_reader :core
|
8
|
+
|
9
|
+
# @return [Net::SSH::Connection::Session] instance.
|
10
|
+
attr_reader :ssh
|
11
|
+
|
12
|
+
# @return [String] the current path on the host.
|
13
|
+
attr_reader :current_path
|
14
|
+
|
15
|
+
# Creates a new instane of GitGo::Connection. Establishes
|
16
|
+
# a session with the host and stores that session in #ssh.
|
17
|
+
#
|
18
|
+
# @param [GitGo::Core] core takes and assigns a reference to parent class in #core.
|
19
|
+
# @return [GitGo::Connection] an instance of GitGo::Connection.
|
20
|
+
def initialize(core)
|
21
|
+
@core = core
|
22
|
+
@ssh = Net::SSH.start(core.host, core.user)
|
23
|
+
@current_path = ""
|
24
|
+
end
|
25
|
+
|
26
|
+
# Moves the session to the provided path and remembers it for the next
|
27
|
+
# action performed through the #bash method. It keeps the current path
|
28
|
+
# in #current_path
|
29
|
+
#
|
30
|
+
# @see #bash
|
31
|
+
# @see #current_path
|
32
|
+
# @param [String] path the path to navigate to.
|
33
|
+
# @return [String] the new path.
|
34
|
+
def cd(path)
|
35
|
+
@current_path = bash("cd '#{current_path}'; cd '#{path}'; pwd")[:stdout].chomp
|
36
|
+
end
|
37
|
+
|
38
|
+
# Tests to see whether the provided path is a directory.
|
39
|
+
# It will take the path relative from #current_path if no absolute
|
40
|
+
# path was provided.
|
41
|
+
#
|
42
|
+
# @param [String] path the path to test.
|
43
|
+
# @return [true, false]
|
44
|
+
def directory?(path)
|
45
|
+
bash("[ -d '#{path}' ]")[:exit_code] == 0
|
46
|
+
end
|
47
|
+
|
48
|
+
# Tests to see whether the provided path is a file.
|
49
|
+
# It will take the path relative from #current_path if no absolute
|
50
|
+
# path was provided.
|
51
|
+
#
|
52
|
+
# @param [String] path the path to test.
|
53
|
+
# @return [true, false]
|
54
|
+
def file?(path)
|
55
|
+
bash("[ -f '#{path}' ]")[:exit_code] == 0
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates a directory at the provided path.
|
59
|
+
# It will take the path relative from #current_path if no absolute
|
60
|
+
# path was provided.
|
61
|
+
#
|
62
|
+
# @param [String] path the path to create one or more directories for.
|
63
|
+
# @return [Hash] containing #bash response data.
|
64
|
+
def mkdir(path)
|
65
|
+
bash("mkdir -p '#{path}'")
|
66
|
+
end
|
67
|
+
|
68
|
+
# Removes a file or directory at the provided path.
|
69
|
+
# It will take the path relative from #current_path if no absolute
|
70
|
+
# path was provided.
|
71
|
+
#
|
72
|
+
# @param [String] path the path to the file or directory to remove.
|
73
|
+
# @return [Hash] containing #bash response data.
|
74
|
+
def rm(path)
|
75
|
+
bash("rm -rf '#{path}'")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Moves a file or directory from the provided path to the newly specified path.
|
79
|
+
# It will take the path relative from #current_path if no absolute
|
80
|
+
# path was provided.
|
81
|
+
#
|
82
|
+
# @param [String] path the path to the file or directory to move.
|
83
|
+
# @param [String] new_path the path to where the new file or directory should be placed.
|
84
|
+
# @return [Hash] containing #bash response data.
|
85
|
+
def mv(path, new_path)
|
86
|
+
bash("mv '#{path}' '#{new_path}'")
|
87
|
+
end
|
88
|
+
|
89
|
+
# Closes the #ssh connection and clears the `@connection` instance
|
90
|
+
# variable of this objects parent `Core` instance so new connections
|
91
|
+
# can be established.
|
92
|
+
def close
|
93
|
+
ssh.close
|
94
|
+
core.clear_connection
|
95
|
+
end
|
96
|
+
|
97
|
+
# Closes the #ssh connection and exits the program with CODE.
|
98
|
+
#
|
99
|
+
# @see #close
|
100
|
+
# @param [Integer] code the code to exit the program with.
|
101
|
+
def close_and_exit(code = 0)
|
102
|
+
close
|
103
|
+
exit(code)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Opens a channel for the #ssh connection and executes the provided COMMAND.
|
107
|
+
# This is a blocking (non-asynchronous) operation.
|
108
|
+
#
|
109
|
+
# @param [String] command the command to perform on the host.
|
110
|
+
# @return [Hash] containing response data (:stdout, :stderr, :exit_code, :exit_signal).
|
111
|
+
def bash(command)
|
112
|
+
stdout_data = ""
|
113
|
+
stderr_data = ""
|
114
|
+
exit_code = nil
|
115
|
+
exit_signal = nil
|
116
|
+
|
117
|
+
ssh.open_channel do |channel|
|
118
|
+
channel.exec("cd '#{current_path}'; #{command}") do |ch, success|
|
119
|
+
abort "FAILED: couldn't execute command (ssh.channel.exec)" unless success
|
120
|
+
|
121
|
+
channel.on_data { |ch,data| stdout_data += data }
|
122
|
+
channel.on_extended_data { |ch,type,data| stderr_data += data }
|
123
|
+
channel.on_request("exit-status") { |ch,data| exit_code = data.read_long }
|
124
|
+
channel.on_request("exit-signal") { |ch, data|exit_signal = data.read_long }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
ssh.loop
|
129
|
+
|
130
|
+
{
|
131
|
+
:stdout => stdout_data,
|
132
|
+
:stderr => stderr_data,
|
133
|
+
:exit_code => exit_code,
|
134
|
+
:exit_signal => exit_signal
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module GitGo
|
4
|
+
class Formatter
|
5
|
+
|
6
|
+
# Creates a nice column format to output an Array to the console.
|
7
|
+
#
|
8
|
+
# @param [Array] array the Array to output in a column format.
|
9
|
+
# @param [Hash] options the options to create the column format with.
|
10
|
+
# @option options [Integer] :spacing (4) the amount of spaces between the columns.
|
11
|
+
# @option options [true, false] :header (false) whether there is a header present or not.
|
12
|
+
# @return [String] the formatted Array.
|
13
|
+
def self.columns(array, options = {})
|
14
|
+
options[:spacing] ||= 4
|
15
|
+
options[:header] ||= false
|
16
|
+
|
17
|
+
columns = 1
|
18
|
+
column_lengths = []
|
19
|
+
out = ""
|
20
|
+
|
21
|
+
array.each { |row| columns = row.length if row.length > columns }
|
22
|
+
|
23
|
+
columns.times do |i|
|
24
|
+
array.each do |row|
|
25
|
+
column_lengths[i] ||= 0
|
26
|
+
column_lengths[i] = row[i].length if row[i].length > column_lengths[i]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
array.each_with_index do |row, row_index|
|
31
|
+
row.each_with_index do |column, index|
|
32
|
+
column_spacing = index + 1 == columns ? 0 : options[:spacing]
|
33
|
+
fill = column_lengths[index] - column.length
|
34
|
+
|
35
|
+
out << column + " " * (fill + column_spacing)
|
36
|
+
out << "\n" if index + 1 == columns
|
37
|
+
end
|
38
|
+
|
39
|
+
if options[:header]
|
40
|
+
row.each_with_index do |column, index|
|
41
|
+
if row_index == 0
|
42
|
+
column_spacing = index + 1 == columns ? 0 : options[:spacing]
|
43
|
+
out << "-" * column_lengths[index] + " " * column_spacing
|
44
|
+
end
|
45
|
+
|
46
|
+
out << "\n" if index + 1 == columns && row_index == 0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
out
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
data/lib/git_go.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "thor"
|
5
|
+
require "net/ssh"
|
6
|
+
|
7
|
+
require "git_go/connection"
|
8
|
+
require "git_go/formatter"
|
9
|
+
require "git_go/version"
|
10
|
+
|
11
|
+
module GitGo
|
12
|
+
class Core
|
13
|
+
|
14
|
+
# @return [String] the user for the #host.
|
15
|
+
attr_reader :user
|
16
|
+
|
17
|
+
# @return [String] the host/domain/ip of the remote machine.
|
18
|
+
attr_reader :host
|
19
|
+
|
20
|
+
# Creates a new instance of GitGo::Core. Will exit(1)
|
21
|
+
# the program if GIT_GO_USER or GIT_GO_HOST environment
|
22
|
+
# variables aren't present.
|
23
|
+
#
|
24
|
+
# @return [GitGo::Core] an instance of GitGo::Core
|
25
|
+
def initialize
|
26
|
+
@user = ENV["GIT_GO_USER"]
|
27
|
+
@host = ENV["GIT_GO_HOST"]
|
28
|
+
|
29
|
+
if [user, host].include?(nil)
|
30
|
+
puts "GIT_GO_USER and GIT_GO_HOST not set."
|
31
|
+
puts "Run `gg instructions` for more details."
|
32
|
+
exit(1)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates and returns a new instance of GitGo::Connection.
|
37
|
+
# Subsequent calls to this method will returned the same cached
|
38
|
+
# reference to the previously created instance.
|
39
|
+
#
|
40
|
+
# @return [GitGo::Connection] an instance of GitGo::Connection
|
41
|
+
def connection
|
42
|
+
@connection ||= Connection.new(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Clears a previously established connection reference. After
|
46
|
+
# invoking this method, it is again possible to establish a new
|
47
|
+
# connection with the remote machine using #connection.
|
48
|
+
#
|
49
|
+
# @see #connection
|
50
|
+
# @return [nil]
|
51
|
+
def clear_connection
|
52
|
+
@connection = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Prints installation instructions to the console.
|
56
|
+
def self.instructions
|
57
|
+
file = File.expand_path("../../assets/instructions", __FILE__)
|
58
|
+
File.read(file)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe GitGo::Connection do
|
6
|
+
|
7
|
+
let(:core) { GitGo::Core.new }
|
8
|
+
let(:conn) { GitGo::Connection.new(core) }
|
9
|
+
let(:ssh) { mock("Net::SSH") }
|
10
|
+
|
11
|
+
before do
|
12
|
+
ENV["GIT_GO_USER"] = "git"
|
13
|
+
ENV["GIT_GO_HOST"] = "127.0.0.1"
|
14
|
+
Net::SSH.stubs(:start).returns(ssh)
|
15
|
+
end
|
16
|
+
|
17
|
+
it("test if propperly mocked") { core; conn; ssh }
|
18
|
+
|
19
|
+
describe "#cd" do
|
20
|
+
it "should update the #current_path" do
|
21
|
+
conn.expects(:bash).with("cd ''; cd '/some/path'; pwd").
|
22
|
+
returns(:stdout => "/some/path\n")
|
23
|
+
|
24
|
+
conn.cd "/some/path"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should travel relative from the #current_path" do
|
28
|
+
conn.instance_variable_set("@current_path", "/some/path")
|
29
|
+
|
30
|
+
conn.expects(:bash).with("cd '/some/path'; cd '..'; pwd").
|
31
|
+
returns(:stdout => "/some\n")
|
32
|
+
|
33
|
+
conn.cd ".."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#directory?" do
|
38
|
+
it "should return true" do
|
39
|
+
conn.expects(:bash).with("[ -d '/some/path' ]").returns({:exit_code => 0})
|
40
|
+
conn.directory?("/some/path").should be_true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should return false" do
|
44
|
+
conn.expects(:bash).with("[ -d '/some/path' ]").returns({:exit_code => 1})
|
45
|
+
conn.directory?("/some/path").should be_false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#file?" do
|
50
|
+
it "should return true" do
|
51
|
+
conn.expects(:bash).with("[ -f '/some/path' ]").returns({:exit_code => 0})
|
52
|
+
conn.file?("/some/path").should be_true
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should return false" do
|
56
|
+
conn.expects(:bash).with("[ -f '/some/path' ]").returns({:exit_code => 1})
|
57
|
+
conn.file?("/some/path").should be_false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#mkdir" do
|
62
|
+
it "should run the unix command to make a directory recursively" do
|
63
|
+
conn.expects(:bash).with("mkdir -p '/some/path'")
|
64
|
+
conn.mkdir("/some/path")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#rm" do
|
69
|
+
it "should run the unix command to remove a file or directory recursively" do
|
70
|
+
conn.expects(:bash).with("rm -rf '/some/path'")
|
71
|
+
conn.rm("/some/path")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "#mv" do
|
76
|
+
it "should run the unix command to mv a file or directory" do
|
77
|
+
conn.expects(:bash).with("mv '/some/path' '/new/path'")
|
78
|
+
conn.mv("/some/path", "/new/path")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#close" do
|
83
|
+
it "should close the ssh connection and unset the instance variable on `core`" do
|
84
|
+
ssh.expects(:close)
|
85
|
+
|
86
|
+
core.connection
|
87
|
+
core.instance_variable_get("@connection").should_not be_nil
|
88
|
+
|
89
|
+
conn.close
|
90
|
+
core.instance_variable_get("@connection").should be_nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#close_and_exit" do
|
95
|
+
it "should #close the connection, and #exit the program" do
|
96
|
+
conn.expects(:close)
|
97
|
+
conn.expects(:exit).with(0)
|
98
|
+
conn.close_and_exit
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should #close the connection, and #exit the program" do
|
102
|
+
conn.expects(:close)
|
103
|
+
conn.expects(:exit).with(1)
|
104
|
+
conn.close_and_exit(1)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe GitGo::Formatter do
|
6
|
+
|
7
|
+
describe "#columns" do
|
8
|
+
it "should format without a header and with 4 spaces" do
|
9
|
+
array = [["Column 1", "Column 2"], ["Column 10", "Column 20"]]
|
10
|
+
out = GitGo::Formatter.columns(array)
|
11
|
+
|
12
|
+
out.should == (<<-EOS
|
13
|
+
Column 1 Column 2\s
|
14
|
+
Column 10 Column 20
|
15
|
+
EOS
|
16
|
+
).gsub(/^\s+/, "")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should format with a header and 2 spaces" do
|
20
|
+
array = [
|
21
|
+
["Example 1", "Example 2"],
|
22
|
+
["Column 1", "Column 2"],
|
23
|
+
["Column 3", "Column 4"]
|
24
|
+
]
|
25
|
+
out = GitGo::Formatter.columns(array, :header => true, :spaces => 2)
|
26
|
+
|
27
|
+
out.should == (<<-EOS
|
28
|
+
Example 1 Example 2
|
29
|
+
--------- ---------
|
30
|
+
Column 1 Column 2\s
|
31
|
+
Column 3 Column 4\s
|
32
|
+
EOS
|
33
|
+
).gsub(/^\s+/, "")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe GitGo do
|
6
|
+
it "should exit when initializing without the environment variables" do
|
7
|
+
ENV["GIT_GO_USER"] = nil
|
8
|
+
ENV["GIT_GO_HOST"] = nil
|
9
|
+
|
10
|
+
GitGo::Core.any_instance.expects(:exit).with(1)
|
11
|
+
GitGo::Core.any_instance.stubs(:puts)
|
12
|
+
GitGo::Core.new
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not exit when initializing with the environment variables" do
|
16
|
+
ENV["GIT_GO_USER"] = "git"
|
17
|
+
ENV["GIT_GO_HOST"] = "127.0.0.1"
|
18
|
+
|
19
|
+
GitGo::Core.any_instance.expects(:exit).never
|
20
|
+
GitGo::Core.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require "git_go"
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
config.filter_run :focus
|
9
|
+
config.mock_framework = :mocha
|
10
|
+
config.order = "random"
|
11
|
+
end
|
12
|
+
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git_go
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael van Rooijen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.16.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.16.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: net-ssh
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.6.2
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.6.2
|
46
|
+
description: Git Go is a small command-line utility distributed as a RubyGem that
|
47
|
+
allows you to easily create/destroy/rename/list all your private-hosted git repositories
|
48
|
+
on your own server.
|
49
|
+
email:
|
50
|
+
- meskyanichi@gmail.com
|
51
|
+
executables:
|
52
|
+
- gg
|
53
|
+
extensions: []
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- .gitignore
|
57
|
+
- .rspec
|
58
|
+
- .travis.yml
|
59
|
+
- Gemfile
|
60
|
+
- Gemfile.lock
|
61
|
+
- Guardfile
|
62
|
+
- LICENSE
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- assets/instructions
|
66
|
+
- bin/gg
|
67
|
+
- gitgo.gemspec
|
68
|
+
- lib/git_go.rb
|
69
|
+
- lib/git_go/cli/gg.rb
|
70
|
+
- lib/git_go/connection.rb
|
71
|
+
- lib/git_go/formatter.rb
|
72
|
+
- lib/git_go/version.rb
|
73
|
+
- spec/lib/git_go/connection_spec.rb
|
74
|
+
- spec/lib/git_go/formatter_spec.rb
|
75
|
+
- spec/lib/git_go/version_spec.rb
|
76
|
+
- spec/lib/git_go_spec.rb
|
77
|
+
- spec/spec_helper.rb
|
78
|
+
homepage: http://github.com/meskyanichi/git_go
|
79
|
+
licenses: []
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
hash: -1366446642123636879
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
hash: -1366446642123636879
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.8.23
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Git Go is a small command-line utility distributed as a RubyGem that allows
|
108
|
+
you to easily create/destroy/rename/list all your private-hosted git repositories
|
109
|
+
on your own server.
|
110
|
+
test_files:
|
111
|
+
- spec/lib/git_go/connection_spec.rb
|
112
|
+
- spec/lib/git_go/formatter_spec.rb
|
113
|
+
- spec/lib/git_go/version_spec.rb
|
114
|
+
- spec/lib/git_go_spec.rb
|
115
|
+
- spec/spec_helper.rb
|
116
|
+
has_rdoc:
|