popo 0.1.10 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +30 -2
- data/README.md +17 -0
- data/lib/popo.rb +1 -1
- data/lib/popo/constants.rb +16 -3
- data/lib/popo/error.rb +1 -1
- data/lib/popo/git_utils.rb +17 -15
- data/lib/popo/init.rb +8 -9
- data/lib/popo/opt_parse.rb +60 -0
- data/lib/popo/runner.rb +74 -90
- data/lib/popo/rvm.rb +18 -15
- data/lib/popo/sync.rb +22 -21
- data/lib/popo/utils.rb +33 -2
- data/lib/popo/version.rb +1 -1
- data/script/poporc +47 -30
- data/script/ps1_functions +162 -0
- metadata +12 -10
- data/README +0 -11
data/CHANGELOG
CHANGED
@@ -1,6 +1,34 @@
|
|
1
|
-
-- 0.
|
1
|
+
-- 1.0.0
|
2
|
+
* added popo update. this updates the manifest to respective branch and remigrates your
|
3
|
+
manifest data
|
4
|
+
|
5
|
+
* optparse separated
|
6
|
+
|
7
|
+
* interactive popo now displays colors for eye candy
|
8
|
+
|
9
|
+
* the runner is now passed to each command
|
10
|
+
|
11
|
+
* version bump
|
12
|
+
|
13
|
+
* a copy of ps1_functions scripts is now automatically sourced by poporc. if you're a fan of
|
14
|
+
changing your prompt with ps1_set, then you can call this inside popo.
|
15
|
+
remember that this will unset any PROMPT_COMMAND sourced before popo loads.
|
16
|
+
this means that any ps1_set or PROMPT_COMMAND defined and sourced in your local RVM will be gone.
|
17
|
+
this measure is to enforce prefixing your ps1 with the popo prompt to distinguish what world
|
18
|
+
you are in so that you will not be in limbo.
|
2
19
|
|
3
|
-
|
20
|
+
* ps1 functions in zsh are still not supported. sorry!
|
21
|
+
|
22
|
+
* for those who aren't familiar or interested with ps1_functions, you may visit
|
23
|
+
https://rvm.io/workflow/prompt/
|
24
|
+
|
25
|
+
* for CI, since colors render as pure text, you can export popo_no_color=true to your CI server.
|
26
|
+
this will disable the eye candy.
|
27
|
+
|
28
|
+
bug fixes
|
29
|
+
* changed some variable names for more readability
|
30
|
+
|
31
|
+
-- 0.1.10
|
4
32
|
* popo bash is now popo shell. bash still works though. just changed
|
5
33
|
underlying code to be more generic
|
6
34
|
|
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Popo Repo Tool
|
2
|
+
Contain all of your related Rails applications into one single virtual system.
|
3
|
+
|
4
|
+
## Features:
|
5
|
+
* Customizable manifest
|
6
|
+
* Separate shell, rvm. No conflict worries
|
7
|
+
* Easy on resources (RAM and CPU)
|
8
|
+
* Integrated with cableguy to ease Rails configuration file deployment
|
9
|
+
* Is best used when deploying and working with multiple Rails applications
|
10
|
+
|
11
|
+
# HOWTO
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
> TODO
|
15
|
+
|
16
|
+
## Configuration
|
17
|
+
> TODO
|
data/lib/popo.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'palmade/cableguy'
|
3
|
-
require 'optparse'
|
4
3
|
require 'yaml'
|
5
4
|
require 'fileutils'
|
6
5
|
require File.join(File.dirname(__FILE__), 'popo/version')
|
@@ -13,6 +12,7 @@ module Popo
|
|
13
12
|
autoload :Error, File.join(POPO_LIB_ROOT, 'error')
|
14
13
|
autoload :GitUtils, File.join(POPO_LIB_ROOT, 'git_utils')
|
15
14
|
autoload :Init, File.join(POPO_LIB_ROOT, 'init')
|
15
|
+
autoload :OptParse, File.join(POPO_LIB_ROOT, 'opt_parse')
|
16
16
|
autoload :Runner, File.join(POPO_LIB_ROOT, 'runner')
|
17
17
|
autoload :RVM, File.join(POPO_LIB_ROOT, 'rvm')
|
18
18
|
autoload :Sync, File.join(POPO_LIB_ROOT, 'sync')
|
data/lib/popo/constants.rb
CHANGED
@@ -1,13 +1,26 @@
|
|
1
1
|
module Popo
|
2
2
|
module Constants
|
3
|
-
POPORC =
|
3
|
+
POPORC = "script/poporc"
|
4
4
|
DEFAULT_CONFIG_FILE = "popo_config.yml"
|
5
5
|
DEFAULT_POPO_TARGET = "development"
|
6
6
|
POPO_WORK_PATH = ".manifest"
|
7
7
|
POPO_YML_FILE = "popo.yml"
|
8
8
|
POPO_CONFIG = {}
|
9
9
|
POPO_DIR_KEY = "sync.directories"
|
10
|
-
POPO_KEY_VALUES =
|
11
|
-
POPO_COMMANDS =
|
10
|
+
POPO_KEY_VALUES = %w(branch repo)
|
11
|
+
POPO_COMMANDS = %w(sync rvm migrate diff info update)
|
12
|
+
REQUIRED_COMMANDS = %w(git env)
|
13
|
+
|
14
|
+
# colors
|
15
|
+
COLOR_RED = "\e[1;31m"
|
16
|
+
COLOR_GREEN = "\e[1;32m"
|
17
|
+
COLOR_YELLOW = "\e[1;33m"
|
18
|
+
COLOR_NONE = "\e[1;0m"
|
19
|
+
|
20
|
+
POST_INSTALL_NOTE = <<NOTE
|
21
|
+
You're almost done!\n
|
22
|
+
Do the following inside popo:\n
|
23
|
+
1. rvm reload\n
|
24
|
+
NOTE
|
12
25
|
end
|
13
26
|
end
|
data/lib/popo/error.rb
CHANGED
data/lib/popo/git_utils.rb
CHANGED
@@ -7,23 +7,23 @@ module Popo
|
|
7
7
|
branch = 'master' if branch.nil?
|
8
8
|
|
9
9
|
if clone_path.nil?
|
10
|
-
cmd = "#{GIT_CMD} clone -b #{branch} #{repo}"
|
10
|
+
cmd = "#{GIT_CMD} clone -b #{branch} #{repo} 2>&1"
|
11
11
|
else
|
12
|
-
cmd = "#{GIT_CMD} clone -b #{branch} #{repo} #{clone_path}"
|
12
|
+
cmd = "#{GIT_CMD} clone -b #{branch} #{repo} #{clone_path} 2>&1"
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
Utils.git_say_with_time "CLONE".green, nil do
|
16
|
+
Utils.say "#{'[Source]'.yellow} => #{repo}", true
|
17
|
+
Utils.say "#{'[Target]'.yellow} => #{clone_path}", true
|
18
|
+
Utils.say "#{'[Branch]'.yellow} => #{branch}", true
|
19
19
|
`#{cmd}`
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
def self.git_update(repo, branch)
|
24
|
-
Utils.
|
24
|
+
Utils.git_say_with_time "FETCH".green, "#{repo}" do
|
25
25
|
Dir.chdir(repo) do
|
26
|
-
`#{GIT_CMD} fetch`
|
26
|
+
`#{GIT_CMD} fetch 2>&1`
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -32,7 +32,7 @@ module Popo
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def self.git_stash(repo)
|
35
|
-
Utils.
|
35
|
+
Utils.git_say_with_time "STASH".yellow, "#{repo}" do
|
36
36
|
Dir.chdir(repo) do
|
37
37
|
`#{GIT_CMD} stash`
|
38
38
|
end
|
@@ -40,17 +40,19 @@ module Popo
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def self.git_reset(repo, branch)
|
43
|
-
Utils.
|
43
|
+
Utils.git_say_with_time "RESET".red, "#{repo}" do
|
44
44
|
Dir.chdir(repo) do
|
45
|
-
`#{GIT_CMD} reset --hard origin/#{branch}`
|
45
|
+
out = `#{GIT_CMD} reset --hard origin/#{branch} 2>&1`
|
46
|
+
Utils.say(out, true)
|
46
47
|
end
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
51
|
def self.git_checkout(repo, branch)
|
51
|
-
Utils.
|
52
|
+
Utils.git_say_with_time "CHECKOUT".yellow, "#{branch} branch" do
|
52
53
|
Dir.chdir(repo) do
|
53
|
-
`#{GIT_CMD} checkout #{branch}`
|
54
|
+
out = `#{GIT_CMD} checkout #{branch} 2>&1`
|
55
|
+
Utils.say(out, true)
|
54
56
|
end
|
55
57
|
end
|
56
58
|
end
|
@@ -62,14 +64,14 @@ module Popo
|
|
62
64
|
|
63
65
|
parsed = diff_msg.scan(/(commit [0-9a-f]+)\n+(.*?)\n+(.*?)(?:\n|$)/)
|
64
66
|
|
65
|
-
Utils.
|
67
|
+
Utils.git_say "#{File.basename(cwd).capitalize}"
|
66
68
|
|
67
69
|
parsed.each do |p|
|
68
70
|
commit_id = p[0].gsub(/commit/,'').strip
|
69
71
|
author = p[1].scan(/Author: (.*) <.*>/)
|
70
72
|
commit_msg = p[2].strip
|
71
73
|
|
72
|
-
Utils.
|
74
|
+
Utils.git_say "#{commit_id} \<#{author}\> #{commit_msg}", true
|
73
75
|
end
|
74
76
|
else
|
75
77
|
repos = Dir.entries(cwd) - [ '.', '..' ]
|
data/lib/popo/init.rb
CHANGED
@@ -2,17 +2,16 @@ module Popo
|
|
2
2
|
class Init
|
3
3
|
include Constants
|
4
4
|
|
5
|
-
def self.boot(
|
6
|
-
self.new(
|
5
|
+
def self.boot(runner)
|
6
|
+
self.new(runner)
|
7
7
|
end
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@manifest = config['manifests'][@options[:manifest]]
|
13
|
-
@default_target = @options[:target] || 'development'
|
14
|
-
@deploy_path = File.absolute_path(@options[:path])
|
9
|
+
def initialize(runner)
|
10
|
+
@db = runner.database
|
11
|
+
@options = runner.options
|
15
12
|
@location = @options[:location] || nil
|
13
|
+
@manifest = runner.config['manifests'][@options[:manifest]]
|
14
|
+
@deploy_path = File.absolute_path(@options[:path])
|
16
15
|
end
|
17
16
|
|
18
17
|
def print_variables
|
@@ -43,7 +42,7 @@ module Popo
|
|
43
42
|
|
44
43
|
def write_config
|
45
44
|
popo = {}
|
46
|
-
popo['target'] =
|
45
|
+
popo['target'] = DEFAULT_POPO_TARGET
|
47
46
|
popo['path'] = @deploy_path
|
48
47
|
popo['location'] = @location if !@location.nil?
|
49
48
|
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Popo
|
4
|
+
class OptParse
|
5
|
+
include Constants
|
6
|
+
|
7
|
+
attr_reader :optparse, :options
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@options = {}
|
11
|
+
@options[:verbose] = false
|
12
|
+
|
13
|
+
@optparse = OptionParser.new do |options|
|
14
|
+
options.banner = "Usage: popo COMMAND [options]"
|
15
|
+
options.separator ""
|
16
|
+
options.separator "Commands: #{POPO_COMMANDS.join(',')}"
|
17
|
+
options.separator ""
|
18
|
+
options.separator "options:"
|
19
|
+
|
20
|
+
options.on('-p', '--path PATH', 'Target path') do |path|
|
21
|
+
@options[:path] = path
|
22
|
+
end
|
23
|
+
|
24
|
+
options.on('-t', '--target TARGET', 'Deployment target') do |target|
|
25
|
+
@options[:target] = target
|
26
|
+
end
|
27
|
+
|
28
|
+
options.on('-u', '--user USER', 'Specify a username for git') do |user|
|
29
|
+
@options[:user] = user
|
30
|
+
end
|
31
|
+
|
32
|
+
options.on('-m', '--manifest MANIFEST', 'Specify a manifest repo') do |manifest|
|
33
|
+
@options[:manifest] = manifest
|
34
|
+
end
|
35
|
+
|
36
|
+
options.on('-l', '--location LOCATION', 'Set the deployment location') do |location|
|
37
|
+
@options[:location] = location
|
38
|
+
end
|
39
|
+
|
40
|
+
options.on('-r', '--reset', 'WARNING: It will not stash changes') do
|
41
|
+
@options[:reset] = true
|
42
|
+
end
|
43
|
+
|
44
|
+
options.on('-v', '--verbose', 'Prints more info about what you executed') do
|
45
|
+
@options[:verbose] = true
|
46
|
+
end
|
47
|
+
|
48
|
+
options.on('-V', '--version', 'Show popo\'s current version') do
|
49
|
+
puts Popo::VERSION
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
|
53
|
+
options.on('-h', '--help', 'Display this screen') do
|
54
|
+
puts options
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/popo/runner.rb
CHANGED
@@ -2,96 +2,67 @@ module Popo
|
|
2
2
|
class Runner
|
3
3
|
include Constants
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
attr_reader :config
|
6
|
+
attr_reader :database, :db_opts
|
7
|
+
attr_reader :app_root, :args, :options
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def boot(args)
|
11
|
+
Utils.colorize
|
12
|
+
self.new(args) if has_requirements?
|
12
13
|
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def has_requirements?
|
16
|
+
if ENV.include?('SHELL')
|
17
|
+
Popo::Constants.const_set("SHELL", ENV['SHELL'])
|
18
|
+
else
|
19
|
+
Error.say("SHELL is empty.")
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
+
Popo::Constants::REQUIRED_COMMANDS.each do |b|
|
23
|
+
const_fn = "#{b.upcase}_CMD"
|
22
24
|
|
23
|
-
|
25
|
+
Popo::Constants.const_set(const_fn, get_bin_path(b))
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
+
if Popo::Constants.const_get(const_fn).empty?
|
28
|
+
Error.say "#{b} is needed and is not found in PATH!"
|
29
|
+
end
|
27
30
|
end
|
31
|
+
|
32
|
+
true
|
28
33
|
end
|
29
|
-
end
|
30
34
|
|
31
|
-
|
32
|
-
|
35
|
+
def get_bin_path(bin)
|
36
|
+
`which #{bin}`.strip
|
37
|
+
end
|
33
38
|
end
|
34
39
|
|
35
40
|
def initialize(args)
|
36
|
-
@
|
37
|
-
@
|
38
|
-
@
|
39
|
-
@
|
41
|
+
@args = args
|
42
|
+
@db_opts = {}
|
43
|
+
@options = {}
|
44
|
+
@app_root = ENV['popo_path'] || Dir.pwd
|
40
45
|
|
41
46
|
if Utils.has_popo_config?(@app_root)
|
42
47
|
@options[:target] = POPO_CONFIG['target']
|
43
48
|
end
|
44
49
|
|
45
|
-
|
46
|
-
opts.banner = "Popo tool."
|
47
|
-
opts.separator "Options:"
|
48
|
-
|
49
|
-
opts.on('-p', '--path PATH', 'Target path') do |path|
|
50
|
-
@options[:path] = path
|
51
|
-
end
|
52
|
-
|
53
|
-
opts.on('-t', '--target TARGET', 'Deployment target') do |target|
|
54
|
-
@options[:target] = target
|
55
|
-
end
|
56
|
-
|
57
|
-
opts.on('-u', '--user USER', 'Username') do |user|
|
58
|
-
@options[:user] = user
|
59
|
-
end
|
60
|
-
|
61
|
-
opts.on('-m', '--manifest MANIFEST', 'Manifest') do |manifest|
|
62
|
-
@options[:manifest] = manifest
|
63
|
-
end
|
64
|
-
|
65
|
-
opts.on('-l', '--location LOCATION', 'Location') do |location|
|
66
|
-
@options[:location] = location
|
67
|
-
end
|
68
|
-
|
69
|
-
opts.on('-v', '--verbose', 'Verbose') do
|
70
|
-
@options[:verbose] = true
|
71
|
-
end
|
72
|
-
|
73
|
-
opts.on('-V', '--version', 'Version') do
|
74
|
-
puts Popo::VERSION
|
75
|
-
exit
|
76
|
-
end
|
50
|
+
opt = OptParse.new
|
77
51
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
82
|
-
end
|
52
|
+
if @args.length >= 1
|
53
|
+
opt.optparse.parse!
|
54
|
+
@options.merge!(opt.options)
|
83
55
|
|
84
|
-
|
56
|
+
db_options = {
|
57
|
+
:path => File.join(@app_root, POPO_WORK_PATH),
|
58
|
+
:target => ENV['CABLING_TARGET'] || @options[:target] || DEFAULT_POPO_TARGET,
|
59
|
+
:verbose => @options[:verbose],
|
60
|
+
:location => ENV['CABLING_LOCATION'] || @options[:location]
|
61
|
+
}
|
85
62
|
|
86
|
-
|
87
|
-
Utils.say optparse.help
|
88
|
-
else
|
89
|
-
@db_opts[:path] = File.join(@app_root, POPO_WORK_PATH)
|
90
|
-
@db_opts[:target] = ENV['CABLING_TARGET'] || @options[:target] || DEFAULT_POPO_TARGET
|
91
|
-
@db_opts[:verbose] = @options[:verbose]
|
92
|
-
@db_opts[:location] = ENV['CABLING_LOCATION'] || @options[:location]
|
63
|
+
@db_opts.merge!(db_options)
|
93
64
|
|
94
|
-
# set
|
65
|
+
# let set these so we can use them in migration files
|
95
66
|
Object.const_set("POPO_PATH", @app_root)
|
96
67
|
Object.const_set("POPO_USER", @options[:user])
|
97
68
|
Object.const_set("POPO_TARGET", @db_opts[:target])
|
@@ -100,16 +71,18 @@ module Popo
|
|
100
71
|
mandatory = [:path, :manifest]
|
101
72
|
mandatory = mandatory.select { |p| @options[p].nil? }
|
102
73
|
|
103
|
-
if args.first
|
104
|
-
Error.say "
|
74
|
+
if @args.first.eql? 'init' and !mandatory.empty?
|
75
|
+
Error.say "Required options: #{mandatory.join(', ')}"
|
105
76
|
end
|
106
77
|
|
107
|
-
|
78
|
+
run
|
79
|
+
else
|
80
|
+
Utils.say opt.optparse.help
|
108
81
|
end
|
109
82
|
end
|
110
83
|
|
111
|
-
def run
|
112
|
-
if POPO_COMMANDS.include?(args.first)
|
84
|
+
def run
|
85
|
+
if POPO_COMMANDS.include?(@args.first)
|
113
86
|
Utils.in_popo?(@app_root)
|
114
87
|
end
|
115
88
|
|
@@ -117,31 +90,44 @@ module Popo
|
|
117
90
|
|
118
91
|
case cmd
|
119
92
|
when 'init'
|
120
|
-
config = get_config!
|
93
|
+
@config = get_config!
|
121
94
|
|
122
|
-
if config['manifests'][@options[:manifest]].nil?
|
95
|
+
if @config['manifests'][@options[:manifest]].nil?
|
123
96
|
Error.say "\'#{@options[:manifest]}\' does not exist in #{DEFAULT_CONFIG_FILE}!"
|
124
97
|
end
|
125
98
|
|
126
99
|
if !File.exist?(File.join(@app_root, @options[:path]))
|
127
100
|
@db_opts[:path] = File.join(@app_root, @options[:path], POPO_WORK_PATH)
|
128
|
-
|
101
|
+
get_database
|
129
102
|
|
130
|
-
Init.boot(
|
103
|
+
Init.boot(self).setup
|
131
104
|
else
|
132
105
|
Error.say "Path \'#{@options[:path]}\' already exists!"
|
133
106
|
end
|
134
107
|
when 'sync'
|
135
|
-
|
108
|
+
get_database
|
136
109
|
|
137
|
-
Sync.new(
|
110
|
+
Sync.new(self).sync
|
138
111
|
when 'rvm'
|
139
|
-
|
112
|
+
get_database
|
113
|
+
|
114
|
+
RVM.new(self).setup
|
115
|
+
when 'update'
|
116
|
+
manifest_path = File.join(@app_root, POPO_WORK_PATH)
|
140
117
|
|
141
|
-
|
118
|
+
case POPO_TARGET
|
119
|
+
when DEFAULT_POPO_TARGET
|
120
|
+
GitUtils.git_update(manifest_path, DEFAULT_POPO_TARGET)
|
121
|
+
else
|
122
|
+
GitUtils.git_update(manifest_path, 'master')
|
123
|
+
end
|
124
|
+
|
125
|
+
get_database
|
126
|
+
@database.migrate_database
|
142
127
|
when 'migrate'
|
143
|
-
get_database
|
144
|
-
|
128
|
+
get_database
|
129
|
+
@database.migrate_database
|
130
|
+
when 'info'
|
145
131
|
Utils.say `cat #{File.join(@app_root, POPO_WORK_PATH, POPO_YML_FILE)}`
|
146
132
|
when 'shell', 'bash'
|
147
133
|
sh!
|
@@ -152,7 +138,7 @@ module Popo
|
|
152
138
|
end
|
153
139
|
end
|
154
140
|
|
155
|
-
|
141
|
+
private
|
156
142
|
|
157
143
|
def sh!
|
158
144
|
if Utils.has_popo_config?(@app_root)
|
@@ -184,10 +170,8 @@ module Popo
|
|
184
170
|
end
|
185
171
|
|
186
172
|
def get_database
|
187
|
-
database = Database.new(@app_root, @db_opts)
|
188
|
-
database.boot_database
|
189
|
-
|
190
|
-
database
|
173
|
+
@database = Database.new(@app_root, @db_opts)
|
174
|
+
@database.boot_database
|
191
175
|
end
|
192
176
|
|
193
177
|
def get_config!
|
data/lib/popo/rvm.rb
CHANGED
@@ -1,18 +1,28 @@
|
|
1
1
|
module Popo
|
2
2
|
class RVM
|
3
|
-
|
4
|
-
@db = db
|
5
|
-
@app_root = app_root
|
6
|
-
@rvm_bin = File.join(@app_root, 'rvm/bin/rvm')
|
7
|
-
@repos = args
|
3
|
+
include Constants
|
8
4
|
|
9
|
-
|
10
|
-
@
|
11
|
-
@
|
5
|
+
def initialize(runner)
|
6
|
+
@db = runner.database
|
7
|
+
@repos = runner.args
|
8
|
+
@rvm_bin = File.join(runner.app_root, 'rvm/bin/rvm')
|
9
|
+
@app_root = runner.app_root
|
12
10
|
|
13
11
|
if @db.has_key?("rvm.gems.source")
|
14
12
|
@default_gem_source = @db.get("rvm.gems.source")
|
15
13
|
end
|
14
|
+
|
15
|
+
get_rubies
|
16
|
+
set_rubies
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_rubies
|
20
|
+
@rubies = @db.get("rvm.rubies").split(",")
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_rubies
|
24
|
+
@default_ruby = @db.get("rvm.ruby.default")
|
25
|
+
@default_gems = @db.get_children("rvm.gems.default")
|
16
26
|
end
|
17
27
|
|
18
28
|
def setup
|
@@ -60,11 +70,4 @@ module Popo
|
|
60
70
|
end
|
61
71
|
end
|
62
72
|
end
|
63
|
-
|
64
|
-
POST_INSTALL_NOTE = <<NOTE
|
65
|
-
You're almost done!\n
|
66
|
-
Do the following inside popo:\n
|
67
|
-
1. rvm reload\n
|
68
|
-
NOTE
|
69
|
-
|
70
73
|
end
|
data/lib/popo/sync.rb
CHANGED
@@ -2,22 +2,23 @@ module Popo
|
|
2
2
|
class Sync
|
3
3
|
include Constants
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@db
|
5
|
+
def initialize(runner)
|
6
|
+
@db = runner.database
|
7
|
+
@cwd = Dir.pwd
|
8
|
+
@info = {}
|
9
|
+
@projects = runner.args
|
10
|
+
@options = runner.options
|
7
11
|
@sync_list = @db.get(POPO_DIR_KEY).split(',')
|
8
|
-
@
|
9
|
-
@cwd = Dir.pwd
|
10
|
-
@projects = args
|
11
|
-
@info = {}
|
12
|
+
@app_root = runner.app_root
|
12
13
|
end
|
13
14
|
|
14
15
|
def sync
|
15
16
|
@projects.each do |project|
|
16
17
|
if project =~ /\//
|
17
|
-
@info[
|
18
|
-
@info[
|
18
|
+
@info[:key] = convert_to_key(project)
|
19
|
+
@info[:path] = File.join(@app_root, project)
|
19
20
|
|
20
|
-
get_values(@info[
|
21
|
+
get_values(@info[:key])
|
21
22
|
|
22
23
|
get_project
|
23
24
|
else
|
@@ -26,11 +27,11 @@ module Popo
|
|
26
27
|
end
|
27
28
|
|
28
29
|
if @projects.empty?
|
29
|
-
if @cwd.eql? @
|
30
|
+
if @cwd.eql? @app_root
|
30
31
|
@sync_list.each { |p| sync_all(p) }
|
31
32
|
else
|
32
33
|
key = @cwd.split('/')
|
33
|
-
key.shift(@
|
34
|
+
key.shift(@app_root.split('/').count)
|
34
35
|
|
35
36
|
sync_all(convert_to_key(key))
|
36
37
|
end
|
@@ -46,38 +47,38 @@ module Popo
|
|
46
47
|
end
|
47
48
|
|
48
49
|
children.each do |c|
|
49
|
-
@info[
|
50
|
+
@info[:key] = [project, c].join('.')
|
50
51
|
|
51
|
-
get_values(@info[
|
52
|
+
get_values(@info[:key])
|
52
53
|
|
53
|
-
@info[
|
54
|
+
@info[:path] = File.join(@app_root, project.gsub('.','/'), c)
|
54
55
|
|
55
56
|
get_project
|
56
57
|
end
|
57
58
|
else
|
58
59
|
get_values(project)
|
59
60
|
|
60
|
-
@info[
|
61
|
+
@info[:path] = File.join(@app_root, project.gsub('.','/'))
|
61
62
|
|
62
63
|
get_project
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
66
67
|
def get_project
|
67
|
-
if !File.exists?(@info[
|
68
|
-
GitUtils.git_clone(@info[
|
68
|
+
if !File.exists?(@info[:path])
|
69
|
+
GitUtils.git_clone(@info[:repo], @info[:path], @info[:branch])
|
69
70
|
else
|
70
|
-
if POPO_CONFIG['target'] == DEFAULT_POPO_TARGET
|
71
|
-
GitUtils.git_stash(@info[
|
71
|
+
if POPO_CONFIG['target'] == DEFAULT_POPO_TARGET and !@options[:reset]
|
72
|
+
GitUtils.git_stash(@info[:path])
|
72
73
|
end
|
73
74
|
|
74
|
-
GitUtils.git_update(@info[
|
75
|
+
GitUtils.git_update(@info[:path], @info[:branch])
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
78
79
|
def get_values(key)
|
79
80
|
POPO_KEY_VALUES.each do |v|
|
80
|
-
@info[v] = @db.get("#{key}.#{v}")
|
81
|
+
@info[v.to_sym] = @db.get("#{key}.#{v}")
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|
data/lib/popo/utils.rb
CHANGED
@@ -4,8 +4,26 @@ module Popo
|
|
4
4
|
module Utils
|
5
5
|
include Constants
|
6
6
|
|
7
|
+
def self.colorize
|
8
|
+
String.class_eval do
|
9
|
+
include Popo::Constants
|
10
|
+
|
11
|
+
constants.grep(/^COLOR/).each do |c|
|
12
|
+
color = c.to_s.gsub('COLOR_','').downcase.to_sym
|
13
|
+
|
14
|
+
define_method color do
|
15
|
+
if !ENV['popo_no_color']
|
16
|
+
"#{Popo::Constants.const_get(c)}#{self}#{COLOR_NONE}"
|
17
|
+
else
|
18
|
+
self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
7
25
|
def self.say(message, subitem = false)
|
8
|
-
puts "#{subitem ? "
|
26
|
+
puts "#{subitem ? " \->" : "--"} #{message}"
|
9
27
|
end
|
10
28
|
|
11
29
|
def self.say_with_time(message)
|
@@ -17,6 +35,20 @@ module Popo
|
|
17
35
|
result
|
18
36
|
end
|
19
37
|
|
38
|
+
def self.git_say(action, message, subitem = false)
|
39
|
+
puts "#{subitem ? "\r->" : "\[#{action}\]"} #{message}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.git_say_with_time(action, message)
|
43
|
+
git_say(action, message)
|
44
|
+
result = nil
|
45
|
+
time = Benchmark.measure { result = yield }
|
46
|
+
say "%.4fs" % time.real, :subitem
|
47
|
+
say("#{result} rows", :subitem) if result.is_a?(Integer)
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
|
20
52
|
def self.in_popo?(root_path)
|
21
53
|
if !ENV.include?('popo_path')
|
22
54
|
Error.say "You must be inside popo!"
|
@@ -46,4 +78,3 @@ module Popo
|
|
46
78
|
end
|
47
79
|
end
|
48
80
|
end
|
49
|
-
|
data/lib/popo/version.rb
CHANGED
data/script/poporc
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
|
+
|
2
3
|
if [[ -z "$popo_path" ]] ; then
|
3
4
|
echo "FAIL Please set the popo_path environment var"
|
4
5
|
exit -1
|
@@ -9,62 +10,78 @@ if [[ -z "$popo_target" ]] ; then
|
|
9
10
|
exit -1
|
10
11
|
fi
|
11
12
|
|
12
|
-
|
13
|
-
NORMAL="\[\033[0;0m\]"
|
14
|
-
BASEPATH=`which basename`
|
15
|
-
BASENAME=`$BASEPATH $popo_path`
|
16
|
-
CUSTOMRC=$popo_path/.manifest/script/customrc
|
17
|
-
|
18
|
-
export rvm_prefix=$popo_path
|
13
|
+
# remove all rvm paths
|
19
14
|
unset $(env | awk -F= '/^rvm_/{print $1" "}')
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
|
16
|
+
# variables
|
17
|
+
scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
18
|
+
basename=`which basename`
|
19
|
+
path_basename=`$basename $popo_path`
|
20
|
+
customrc=$popo_path/.manifest/script/customrc
|
21
|
+
|
22
|
+
# load self contained rvm
|
23
|
+
rvm_prefix=$popo_path
|
24
|
+
rvm_path=$popo_path/rvm
|
23
25
|
rubies_path=$rvm_path/rubies
|
24
26
|
|
25
|
-
#
|
27
|
+
# load all relative rvm scripts
|
28
|
+
source $rvm_path/scripts/rvm
|
29
|
+
|
30
|
+
# load default rvm if rubies folder is not empty
|
26
31
|
if [ -d $rubies_path ] && [ "$(ls -A $rubies_path)" ] ; then
|
27
32
|
rvm default
|
28
33
|
fi
|
29
34
|
|
30
|
-
unset PALMADE_GEMS_DIR
|
31
|
-
export PATH="$rvm_path/bin:$popo_path/tools:$PATH"
|
32
|
-
|
33
35
|
if [[ $PS1 != "" ]] ; then
|
34
36
|
if [[ -f /etc/bash.bashrc ]] ; then
|
35
37
|
source /etc/bash.bashrc
|
36
38
|
fi
|
37
39
|
|
40
|
+
if [[ -f /etc/bashrc ]] ; then
|
41
|
+
source /etc/bashrc
|
42
|
+
fi
|
43
|
+
|
38
44
|
if [[ -f $HOME/.bashrc ]] ; then
|
39
45
|
source $HOME/.bashrc
|
40
46
|
fi
|
41
47
|
|
42
|
-
if [[
|
43
|
-
|
48
|
+
if [[ `uname` == 'Darwin' && -f $HOME/.bash_profile ]] ; then
|
49
|
+
source $HOME/.bash_profile
|
50
|
+
fi
|
51
|
+
|
52
|
+
if [[ $path_basename != $popo_target ]] ; then
|
53
|
+
popo_ps1="\[\033[1;33m\][popo $popo_target/$path_basename] \[\033[0m\]"
|
44
54
|
else
|
45
|
-
|
55
|
+
popo_ps1="\[\033[0;33m\][popo $popo_target] \[\033[0m\]"
|
46
56
|
fi
|
57
|
+
|
58
|
+
PS1="$popo_ps1 $PS1"
|
47
59
|
fi
|
48
60
|
|
61
|
+
# lets unset PROMPT_COMMAND. We use our own PS1!
|
62
|
+
unset PROMPT_COMMAND
|
63
|
+
|
64
|
+
# source popo ps1 functions
|
65
|
+
source $scriptdir/ps1_functions
|
66
|
+
|
49
67
|
rvm reload
|
50
68
|
|
51
69
|
if [[ -z "$PS1" ]] ; then
|
52
70
|
rvm $*
|
53
71
|
else
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
echo ""
|
72
|
+
echo "Welcome to the popoed environment, where you can play with your very own popo."
|
73
|
+
echo ""
|
74
|
+
echo " popo_path: $popo_path"
|
75
|
+
echo " popo_target: $popo_target"
|
76
|
+
echo ""
|
77
|
+
echo "Remember to keep things clean and civil around here."
|
78
|
+
echo "To go back to your normal world, just hit exit."
|
79
|
+
echo ""
|
80
|
+
echo "But wait, you ask, who needs popo? Baahh, popo's for n00bs!"
|
81
|
+
echo ""
|
65
82
|
fi
|
66
83
|
|
67
84
|
# source a custom rc file
|
68
|
-
if [[ -s $
|
69
|
-
source $
|
85
|
+
if [[ -s $customrc ]] ; then
|
86
|
+
source $customrc
|
70
87
|
fi
|
@@ -0,0 +1,162 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
#
|
4
|
+
# Source this file in your ~/.bash_profile or interactive startup file.
|
5
|
+
# This is done like so:
|
6
|
+
#
|
7
|
+
# [[ -s "$HOME/.rvm/contrib/ps1_functions" ]] &&
|
8
|
+
# source "$HOME/.rvm/contrib/ps1_functions"
|
9
|
+
#
|
10
|
+
# Then in order to set your prompt you simply do the following for example
|
11
|
+
#
|
12
|
+
# Examples:
|
13
|
+
#
|
14
|
+
# ps1_set --prompt ∫
|
15
|
+
#
|
16
|
+
# or
|
17
|
+
#
|
18
|
+
# ps1_set --prompt ∴
|
19
|
+
#
|
20
|
+
# This will yield a prompt like the following, for example,
|
21
|
+
#
|
22
|
+
# 00:00:50 wayneeseguin@GeniusAir:~/projects/db0/rvm/rvm (git:master:156d0b4) ruby-1.8.7-p334@rvm
|
23
|
+
# ∴
|
24
|
+
#
|
25
|
+
ps1_titlebar()
|
26
|
+
{
|
27
|
+
case $TERM in
|
28
|
+
(xterm*|rxvt*)
|
29
|
+
printf "%s" "\033]0;\\u@\\h: \W\\007"
|
30
|
+
;;
|
31
|
+
esac
|
32
|
+
}
|
33
|
+
|
34
|
+
ps1_identity()
|
35
|
+
{
|
36
|
+
if (( $UID == 0 )) ; then
|
37
|
+
printf "%s" "\[\033[36m\]\\u\[\033[0m\]@\[\033[36m\]\\h\[\033[35m\]:\w\[\033[0m\] "
|
38
|
+
else
|
39
|
+
printf "%s" "\[\033[32m\]\\u\[\033[0m\]@\[\033[36m\]\\h\[\033[35m\]:\w\[\033[0m\] "
|
40
|
+
fi
|
41
|
+
}
|
42
|
+
|
43
|
+
ps1_git()
|
44
|
+
{
|
45
|
+
local branch="" sha1="" line="" attr="" color=0
|
46
|
+
|
47
|
+
shopt -s extglob # Important, for our nice matchers :)
|
48
|
+
|
49
|
+
command -v git >/dev/null 2>&1 || {
|
50
|
+
printf " \033[1;37m\033[41m[git not found]\033[m "
|
51
|
+
return 0
|
52
|
+
}
|
53
|
+
|
54
|
+
branch=$(git symbolic-ref -q HEAD 2>/dev/null) || return 0 # Not in git repo.
|
55
|
+
branch=${branch##refs/heads/}
|
56
|
+
|
57
|
+
# Now we display the branch.
|
58
|
+
sha1=$(git rev-parse --short --quiet HEAD)
|
59
|
+
|
60
|
+
case "${branch:-"(no branch)"}" in
|
61
|
+
production|prod) attr="1;37m\033[" ; color=41 ;; # red
|
62
|
+
master|deploy) color=31 ;; # red
|
63
|
+
stage|staging) color=33 ;; # yellow
|
64
|
+
dev|develop|development) color=34 ;; # blue
|
65
|
+
next) color=36 ;; # gray
|
66
|
+
*)
|
67
|
+
if [[ -n "${branch}" ]] ; then # Feature Branch :)
|
68
|
+
color=32 # green
|
69
|
+
else
|
70
|
+
color=0 # reset
|
71
|
+
fi
|
72
|
+
;;
|
73
|
+
esac
|
74
|
+
|
75
|
+
[[ $color -gt 0 ]] &&
|
76
|
+
printf "\[\033[${attr}${color}m\](git:${branch}$(ps1_git_status):$sha1)\[\033[0m\] "
|
77
|
+
}
|
78
|
+
|
79
|
+
ps1_git_status()
|
80
|
+
{
|
81
|
+
local git_status="$(git status 2>/dev/null)"
|
82
|
+
|
83
|
+
[[ "${git_status}" = *deleted* ]] && printf "%s" "-"
|
84
|
+
[[ "${git_status}" = *Untracked[[:space:]]files:* ]] && printf "%s" "+"
|
85
|
+
[[ "${git_status}" = *modified:* ]] && printf "%s" "*"
|
86
|
+
}
|
87
|
+
|
88
|
+
ps1_rvm()
|
89
|
+
{
|
90
|
+
command -v rvm-prompt >/dev/null 2>&1 && printf "%s" " $(rvm-prompt) "
|
91
|
+
}
|
92
|
+
|
93
|
+
ps1_update()
|
94
|
+
{
|
95
|
+
local prompt_char='$' separator="\n" notime=0
|
96
|
+
|
97
|
+
(( $UID == 0 )) && prompt_char='#'
|
98
|
+
|
99
|
+
while [[ $# -gt 0 ]] ; do
|
100
|
+
local token="$1" ; shift
|
101
|
+
|
102
|
+
case "$token" in
|
103
|
+
--trace)
|
104
|
+
export PS4="+ \${BASH_SOURCE##\${rvm_path:-}} : \${FUNCNAME[0]:+\${FUNCNAME[0]}()} \${LINENO} > "
|
105
|
+
set -o xtrace
|
106
|
+
;;
|
107
|
+
--prompt)
|
108
|
+
prompt_char="$1"
|
109
|
+
shift
|
110
|
+
;;
|
111
|
+
--noseparator)
|
112
|
+
separator=""
|
113
|
+
;;
|
114
|
+
--separator)
|
115
|
+
separator="$1"
|
116
|
+
shift
|
117
|
+
;;
|
118
|
+
--notime)
|
119
|
+
notime=1
|
120
|
+
;;
|
121
|
+
*)
|
122
|
+
true # Ignore everything else.
|
123
|
+
;;
|
124
|
+
esac
|
125
|
+
done
|
126
|
+
|
127
|
+
if (( notime > 0 )) ; then
|
128
|
+
PS1="$popo_ps1$(ps1_titlebar)$(ps1_identity)$(ps1_git)$(ps1_rvm)${separator}${prompt_char} "
|
129
|
+
else
|
130
|
+
PS1="$popo_ps1$(ps1_titlebar)\D{%H:%M:%S} $(ps1_identity)$(ps1_git)$(ps1_rvm)${separator}${prompt_char} "
|
131
|
+
fi
|
132
|
+
}
|
133
|
+
|
134
|
+
ps2_set()
|
135
|
+
{
|
136
|
+
PS2=" \[\033[0;40m\]\[\033[0;33m\]> \[\033[1;37m\]\[\033[1m\]"
|
137
|
+
}
|
138
|
+
|
139
|
+
ps4_set()
|
140
|
+
{
|
141
|
+
export PS4="+ \${BASH_SOURCE##\${rvm_path:-}} : \${FUNCNAME[0]:+\${FUNCNAME[0]}()} \${LINENO} > "
|
142
|
+
}
|
143
|
+
|
144
|
+
# WARNING: This clobbers your PROMPT_COMMAND so if you need to write your own, call
|
145
|
+
# ps1_update within your PROMPT_COMMAND with the same arguments you pass
|
146
|
+
# to ps1_set
|
147
|
+
#
|
148
|
+
# The PROMPT_COMMAND is used to help the prompt work if the separator is not a new line.
|
149
|
+
# In the event that the separtor is not a new line, the prompt line may become distored if
|
150
|
+
# you add or delete a certian number of characters, making the string wider than the
|
151
|
+
# $COLUMNS + len(your_input_line).
|
152
|
+
# This orginally was done with callbacks within the PS1 to add in things like the git
|
153
|
+
# commit, but this results in the PS1 being of an unknown width which results in the prompt
|
154
|
+
# being distored if you add or remove a certain number of characters. To work around this
|
155
|
+
# it now uses the PROMPT_COMMAND callback to re-set the PS1 with a known width of chracters
|
156
|
+
# each time a new command is entered. see PROMPT_COMMAND for more details.
|
157
|
+
#
|
158
|
+
ps1_set()
|
159
|
+
{
|
160
|
+
PROMPT_COMMAND="ps1_update $@"
|
161
|
+
}
|
162
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: popo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-05-07 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sequel
|
16
|
-
requirement: &
|
16
|
+
requirement: &70203560628300 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70203560628300
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: sqlite3
|
27
|
-
requirement: &
|
27
|
+
requirement: &70203560627740 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70203560627740
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: cableguy
|
38
|
-
requirement: &
|
38
|
+
requirement: &70203560627200 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70203560627200
|
47
47
|
description: Ruby and rails repo tool
|
48
48
|
email:
|
49
49
|
- poymode@gmail.com
|
@@ -56,7 +56,7 @@ files:
|
|
56
56
|
- .gitignore
|
57
57
|
- CHANGELOG
|
58
58
|
- Gemfile
|
59
|
-
- README
|
59
|
+
- README.md
|
60
60
|
- Rakefile
|
61
61
|
- bin/envy
|
62
62
|
- bin/popo
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- lib/popo/error.rb
|
70
70
|
- lib/popo/git_utils.rb
|
71
71
|
- lib/popo/init.rb
|
72
|
+
- lib/popo/opt_parse.rb
|
72
73
|
- lib/popo/runner.rb
|
73
74
|
- lib/popo/rvm.rb
|
74
75
|
- lib/popo/sync.rb
|
@@ -81,6 +82,7 @@ files:
|
|
81
82
|
- script/envy.template
|
82
83
|
- script/envyrc
|
83
84
|
- script/poporc
|
85
|
+
- script/ps1_functions
|
84
86
|
homepage: https://github.com/poymode/popo
|
85
87
|
licenses: []
|
86
88
|
post_install_message:
|
@@ -101,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
103
|
version: '0'
|
102
104
|
requirements: []
|
103
105
|
rubyforge_project:
|
104
|
-
rubygems_version: 1.8.
|
106
|
+
rubygems_version: 1.8.17
|
105
107
|
signing_key:
|
106
108
|
specification_version: 3
|
107
109
|
summary: Popo ruby and rails repo tool
|
data/README
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
Popo Repo Tool
|
2
|
-
|
3
|
-
Deploy your rails app
|
4
|
-
|
5
|
-
Features:
|
6
|
-
- Customizable manifest
|
7
|
-
- Separate shell, rvm. No conflict worries
|
8
|
-
- Easy on resources (RAM and CPU)
|
9
|
-
- Integrated with cableguy to ease Rails configuration file deployment
|
10
|
-
- Is best used when deploying and working with multiple Rails applications
|
11
|
-
|