osbro-git-deploy 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2009 Mislav Marohnić
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,86 @@
1
+ Easy git deployment
2
+ ===================
3
+
4
+ Straightforward, [Heroku][]-style, push-based deployment. Your deploys will look like this:
5
+
6
+ $ git push production master
7
+
8
+ To get started, install the "git-deploy" gem.
9
+
10
+ $ gem install git-deploy
11
+
12
+
13
+ What application frameworks/languages are supported?
14
+ ----------------------------------------------------
15
+
16
+ Regardless of the fact that this tool is mostly written in Ruby, git-deploy can be useful for any kind of code that needs deploying on a remote server. The default scripts are suited for Ruby web apps, but can be edited to accommodate other frameworks.
17
+
18
+ Your deployment is customized with per-project callback scripts which can be written in any language.
19
+
20
+ The assumption is that you're deploying to a single host to which you connect over SSH using public/private key authentication.
21
+
22
+
23
+ Setup steps
24
+ -----------
25
+
26
+ 1. Create a git remote for where you'll push the code on your server. The name of this remote in the examples is "production", but it can be whatever you wish ("online", "website", or other).
27
+
28
+ $ git remote add production user@example.com:/path/to/myapp
29
+
30
+ The "/path/to/myapp" is the directory where your code will reside. It doesn't have to exist; it will be created for you during this setup.
31
+
32
+ 2. Run the setup task:
33
+
34
+ $ git deploy setup -r production
35
+
36
+ This will initialize the remote git repository in the target directory ("/path/to/myapp" in the above example) and install the remote git hooks.
37
+
38
+ 3. Run the init task:
39
+
40
+ $ git deploy init
41
+
42
+ This generates default deploy callback scripts in the "deploy/" directory. You must check them in version control. They are going to be executed on the server on each deploy.
43
+
44
+ 4. Push the code.
45
+
46
+ $ git push production master
47
+
48
+ 3. Login to your server and manually perform necessary one-time administrative operations. This might include:
49
+ * set up the Apache/nginx virtual host for this application;
50
+ * check your "config/database.yml" and create the production database.
51
+
52
+
53
+ Deployment
54
+ ----------
55
+
56
+ If you've set your app correctly, visiting "http://example.com" in your browser should show it up and running.
57
+
58
+ Now, subsequent deployments are done simply by pushing to the branch that is currently checked out on the remote:
59
+
60
+ $ git push production master
61
+
62
+ Because the deployments are done with git, not everyone on the team had to install git-deploy. Just the person who was doing the setup.
63
+
64
+ Deployments are logged to "log/deploy.log" in your application.
65
+
66
+ On every deploy, the "deploy/after_push" script performs the following:
67
+
68
+ 1. updates git submodules (if there are any);
69
+ 2. runs `bundle install --deployment` if there is a Gemfile;
70
+ 3. runs `rake db:migrate` if new migrations have been added;
71
+ 4. clears cached CSS/JS assets in "public/stylesheets" and "public/javascripts";
72
+ 5. restarts the web application.
73
+
74
+ You can customize all of this by editing scripts in the "deploy/" directory of your app.
75
+
76
+ How it works
77
+ ------------
78
+
79
+ The "setup" task installed a "post-receive" hook in the remote git repository. This is how your working copy on the server is kept up to date. This hook, after checking out latest code, asynchronously dispatches to "deploy/after_push" script in your application. This script executes on the server and also calls "deploy/before_restart", "restart", and "after_restart" callbacks if they are present.
80
+
81
+ These scripts are ordinary unix executable files. The ones which are generated for you are written in shell script and Ruby.
82
+
83
+ It's worth remembering that "after_push" is done **asynchronously from your git push**. This is because migrating the database and updating submodules might take a long time and you don't want to wait for all that during a git push. But, this means that when the push is done, the server has *not yet restarted*. You might need to wait a few seconds or a minute.
84
+
85
+
86
+ [heroku]: http://heroku.com/
data/bin/git-deploy ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'git_deploy'
3
+ GitDeploy.start
data/lib/git_deploy.rb ADDED
@@ -0,0 +1,110 @@
1
+ require 'thor'
2
+ require 'net/ssh'
3
+ require 'net/scp'
4
+
5
+ class GitDeploy < Thor
6
+ LOCAL_DIR = File.expand_path('..', __FILE__)
7
+
8
+ require 'git_deploy/configuration'
9
+ require 'git_deploy/ssh_methods'
10
+ include Configuration
11
+ include SSHMethods
12
+
13
+ class_option :remote, :aliases => '-r', :type => :string, :default => 'origin'
14
+ class_option :noop, :aliases => '-n', :type => :boolean, :default => false
15
+ class_option :env, :aliases => '-e', :type => :string, :default => 'production'
16
+
17
+ desc "init", "Generates deployment customization scripts for your app"
18
+ def init
19
+ require 'git_deploy/generator'
20
+ Generator::start([])
21
+ end
22
+
23
+ desc "setup", "Create the remote git repository and install push hooks for it"
24
+ method_option :shared, :aliases => '-g', :type => :boolean, :default => true
25
+ method_option :sudo, :aliases => '-s', :type => :boolean, :default => false
26
+ def setup
27
+ sudo = options.sudo? ? "#{sudo_cmd} " : ''
28
+
29
+ unless run_test("test -x #{deploy_to}")
30
+ run ["#{sudo}mkdir -p #{deploy_to}"] do |cmd|
31
+ cmd << "#{sudo}chown $USER #{deploy_to}" if options.sudo?
32
+ cmd
33
+ end
34
+ end
35
+
36
+ run [] do |cmd|
37
+ cmd << "chmod g+ws #{deploy_to}" if options.shared?
38
+ cmd << "cd #{deploy_to}"
39
+ cmd << "git init #{options.shared? ? '--shared' : ''}"
40
+ cmd << "sed -i'' -e 's/master/#{branch}/' .git/HEAD" unless branch == 'master'
41
+ cmd << "git config --bool receive.denyNonFastForwards false" if options.shared?
42
+ cmd << "git config receive.denyCurrentBranch ignore"
43
+ end
44
+
45
+ invoke :hooks
46
+ end
47
+
48
+ desc "hooks", "Installs git hooks to the remote repository"
49
+ def hooks
50
+ hooks_dir = File.join(LOCAL_DIR, 'hooks')
51
+ remote_dir = "#{deploy_to}/.git/hooks"
52
+ scp_upload "#{hooks_dir}/post-receive.sh" => "#{remote_dir}/post-receive"
53
+ run "sed -i'' -e 's/production/#{options[:env]}/' #{remote_dir}/post-receive" unless options[:env] == 'production'
54
+ run "chmod +x #{remote_dir}/post-receive"
55
+ end
56
+
57
+ desc "restart", "Restarts the application on the server"
58
+ def restart
59
+ run "cd #{deploy_to} && deploy/restart | tee -a log/deploy.log"
60
+ end
61
+
62
+ desc "rollback", "Rolls back the checkout to before the last push"
63
+ def rollback
64
+ run "cd #{deploy_to} && git reset --hard ORIG_HEAD"
65
+ invoke :restart
66
+ end
67
+
68
+ desc "tag", "Checkout the tag <tag>"
69
+ def tag(tag)
70
+ run "cd #{deploy_to} && git reset --hard && git checkout #{tag}"
71
+ restart
72
+ end
73
+
74
+ desc "reset", "Bring the remote up to date with the current branch"
75
+ def reset
76
+ run "cd #{deploy_to} && git reset --hard && git checkout master"
77
+ restart
78
+ end
79
+
80
+ desc "log", "Shows the last part of the deploy log on the server"
81
+ method_option :tail, :aliases => '-t', :type => :boolean, :default => false
82
+ method_option :lines, :aliases => '-l', :type => :numeric, :default => 20
83
+ def log(n = nil)
84
+ tail_args = options.tail? ? '-f' : "-n#{n || options.lines}"
85
+ run "tail #{tail_args} #{deploy_to}/log/deploy.log"
86
+ end
87
+
88
+ desc "upload <files>", "Copy local files to the remote app"
89
+ def upload(*files)
90
+ files = files.map { |f| Dir[f.strip] }.flatten
91
+ abort "Error: Specify at least one file to upload" if files.empty?
92
+
93
+ scp_upload files.inject({}) { |all, file|
94
+ all[file] = File.join(deploy_to, file)
95
+ all
96
+ }
97
+ end
98
+ end
99
+
100
+ __END__
101
+ Multiple hosts:
102
+ # deploy:
103
+ invoke :code
104
+ command = ["cd #{deploy_to}"]
105
+ command << ".git/hooks/post-reset `cat .git/ORIG_HEAD` HEAD 2>&1 | tee -a log/deploy.log"
106
+
107
+ # code:
108
+ command = ["cd #{deploy_to}"]
109
+ command << source.scm('fetch', remote, "+refs/heads/#{branch}:refs/remotes/origin/#{branch}")
110
+ command << source.scm('reset', '--hard', "origin/#{branch}")
@@ -0,0 +1,81 @@
1
+ class GitDeploy
2
+ module Configuration
3
+ private
4
+
5
+ def host
6
+ extract_host_and_user unless defined? @host
7
+ @host
8
+ end
9
+
10
+ def remote_user
11
+ extract_host_and_user unless defined? @user
12
+ @user
13
+ end
14
+
15
+ def extract_host_and_user
16
+ info = remote_url.split(':').first.split('@')
17
+ if info.size < 2
18
+ @user, @host = `whoami`.chomp, info.first
19
+ else
20
+ @user, @host = *info
21
+ end
22
+ end
23
+
24
+ def deploy_to
25
+ @deploy_to ||= remote_url.split(':').last
26
+ end
27
+
28
+ def branch
29
+ 'master'
30
+ end
31
+
32
+ def git_config
33
+ @git_config ||= Hash.new do |cache, cmd|
34
+ git = ENV['GIT'] || 'git'
35
+ out = `#{git} #{cmd}`
36
+ if $?.success? then cache[cmd] = out.chomp
37
+ else cache[cmd] = nil
38
+ end
39
+ cache[cmd]
40
+ end
41
+ end
42
+
43
+ def remote_urls(remote)
44
+ git_config["config --get-all remote.#{remote}.url"].to_s.split("\n")
45
+ end
46
+
47
+ def remote_url(remote = options[:remote])
48
+ @remote_url ||= {}
49
+ @remote_url[remote] ||= begin
50
+ url = remote_urls(remote).first
51
+ if url.nil?
52
+ abort "Error: Remote url not found for remote #{remote.inspect}"
53
+ elsif url =~ /\bgithub\.com\b/
54
+ abort "Error: Remote url for #{remote.inspect} points to GitHub. Can't deploy there!"
55
+ end
56
+ url
57
+ end
58
+ end
59
+
60
+ def current_branch
61
+ git_config['symbolic-ref -q HEAD']
62
+ end
63
+
64
+ def tracked_branch
65
+ branch = current_branch && tracked_for(current_branch)
66
+ normalize_branch(branch) if branch
67
+ end
68
+
69
+ def normalize_branch(branch)
70
+ branch.sub('refs/heads/', '')
71
+ end
72
+
73
+ def remote_for(branch)
74
+ git_config['config branch.%s.remote' % normalize_branch(branch)]
75
+ end
76
+
77
+ def tracked_for(branch)
78
+ git_config['config branch.%s.merge' % normalize_branch(branch)]
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,28 @@
1
+ require 'thor/group'
2
+
3
+ class GitDeploy::Generator < Thor::Group
4
+ include Thor::Actions
5
+
6
+ def self.source_root
7
+ File.expand_path('../templates', __FILE__)
8
+ end
9
+
10
+ def copy_main_hook
11
+ copy_hook 'after_push.sh', 'deploy/after_push'
12
+ end
13
+
14
+ def copy_restart_hook
15
+ copy_hook 'restart.sh', 'deploy/restart'
16
+ end
17
+
18
+ def copy_restart_callbacks
19
+ copy_hook 'before_restart.rb', 'deploy/before_restart'
20
+ end
21
+
22
+ private
23
+
24
+ def copy_hook(template, destination)
25
+ copy_file template, destination
26
+ chmod destination, 0744 unless File.executable? destination
27
+ end
28
+ end
@@ -0,0 +1,99 @@
1
+ class GitDeploy
2
+ module SSHMethods
3
+ private
4
+
5
+ def sudo_cmd
6
+ "sudo -p 'sudo password: '"
7
+ end
8
+
9
+ def system(*args)
10
+ puts "[local] $ " + args.join(' ').gsub(' && ', " && \\\n ")
11
+ super unless options.noop?
12
+ end
13
+
14
+ def run(cmd = nil)
15
+ cmd = yield(cmd) if block_given?
16
+ cmd = cmd.join(' && ') if Array === cmd
17
+ puts "[#{options[:remote]}] $ " + cmd.gsub(' && ', " && \\\n ")
18
+
19
+ unless options.noop?
20
+ status, output = ssh_exec cmd do |ch, stream, data|
21
+ case stream
22
+ when :stdout then $stdout.print data
23
+ when :stderr then $stderr.print data
24
+ end
25
+ ch.send_data(askpass) if data =~ /^sudo password: /
26
+ end
27
+ output
28
+ end
29
+ end
30
+
31
+ def run_test(cmd)
32
+ status, output = ssh_exec(cmd) { }
33
+ status == 0
34
+ end
35
+
36
+ def ssh_exec(cmd, &block)
37
+ status = nil
38
+ output = ''
39
+
40
+ channel = ssh_connection.open_channel do |chan|
41
+ chan.exec(cmd) do |ch, success|
42
+ raise "command failed: #{cmd.inspect}" unless success
43
+ # ch.request_pty
44
+
45
+ ch.on_data do |c, data|
46
+ output << data
47
+ yield(c, :stdout, data)
48
+ end
49
+
50
+ ch.on_extended_data do |c, type, data|
51
+ output << data
52
+ yield(c, :stderr, data)
53
+ end
54
+
55
+ ch.on_request "exit-status" do |ch, data|
56
+ status = data.read_long
57
+ end
58
+ end
59
+ end
60
+
61
+ channel.wait
62
+ [status, output]
63
+ end
64
+
65
+ # TODO: use Highline for cross-platform support
66
+ def askpass
67
+ tty_state = `stty -g`
68
+ system 'stty raw -echo -icanon isig' if $?.success?
69
+ pass = ''
70
+ while char = $stdin.getbyte and not (char == 13 or char == 10)
71
+ if char == 127 or char == 8
72
+ pass[-1,1] = '' unless pass.empty?
73
+ else
74
+ pass << char.chr
75
+ end
76
+ end
77
+ pass
78
+ ensure
79
+ system "stty #{tty_state}" unless tty_state.empty?
80
+ end
81
+
82
+ def scp_upload(files)
83
+ channels = []
84
+ files.each do |local, remote|
85
+ puts "FILE: [local] #{local.sub(LOCAL_DIR + '/', '')} -> [#{options[:remote]}] #{remote}"
86
+ channels << ssh_connection.scp.upload(local, remote) unless options.noop?
87
+ end
88
+ channels.each { |c| c.wait }
89
+ end
90
+
91
+ def ssh_connection
92
+ @ssh ||= begin
93
+ ssh = Net::SSH.start(host, remote_user)
94
+ at_exit { ssh.close }
95
+ ssh
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+ oldrev=$1
4
+ newrev=$2
5
+
6
+ run() {
7
+ [ -x $1 ] && $1 $oldrev $newrev
8
+ }
9
+
10
+ echo files changed: $(git diff $oldrev $newrev --diff-filter=ACDMR --name-only | wc -l)
11
+
12
+ umask 002
13
+
14
+ git submodule init && git submodule sync && git submodule update
15
+
16
+ run deploy/before_restart
17
+ run deploy/restart && run deploy/after_restart
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ oldrev, newrev = ARGV
3
+
4
+ def run(cmd)
5
+ exit($?.exitstatus) unless system "umask 002 && #{cmd}"
6
+ end
7
+
8
+ RAILS_ENV = ENV['RAILS_ENV'] || 'production'
9
+ use_bundler = File.file? 'Gemfile'
10
+ rake_cmd = use_bundler ? 'bundle exec rake' : 'rake'
11
+
12
+ if use_bundler
13
+ bundler_args = ['--deployment']
14
+ BUNDLE_WITHOUT = ENV['BUNDLE_WITHOUT'] || 'development:test'
15
+ bundler_args << '--without' << BUNDLE_WITHOUT unless BUNDLE_WITHOUT.empty?
16
+
17
+ # update gem bundle
18
+ run "bundle install #{bundler_args.join(' ')}"
19
+ end
20
+
21
+ if File.file? 'Rakefile'
22
+ tasks = []
23
+
24
+ num_migrations = `git diff #{oldrev} #{newrev} --diff-filter=A --name-only -z db/migrate`.split("\0").size
25
+ # run migrations if new ones have been added
26
+ tasks << "db:migrate" if num_migrations > 0
27
+
28
+ # precompile assets
29
+ changed_assets = `git diff #{oldrev} #{newrev} --name-only -z app/assets`.split("\0")
30
+ tasks << "assets:precompile" if changed_assets.size > 0
31
+
32
+ run "#{rake_cmd} #{tasks.join(' ')} RAILS_ENV=#{RAILS_ENV}" if tasks.any?
33
+ end
34
+
35
+ # clear cached assets (unversioned/ignored files)
36
+ run "git clean -x -f -- public/stylesheets public/javascripts"
37
+
38
+ # clean unversioned files from vendor/plugins (e.g. old submodules)
39
+ run "git clean -d -f -- vendor/plugins"
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ touch tmp/restart.txt
3
+ echo "restarting Passenger app"
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ if [ "$GIT_DIR" = "." ]; then
3
+ # The script has been called as a hook; chdir to the working copy
4
+ cd ..
5
+ GIT_DIR=.git
6
+ export GIT_DIR
7
+ fi
8
+
9
+ # try to obtain the usual system PATH
10
+ if [ -f /etc/profile ]; then
11
+ PATH=$(source /etc/profile; echo $PATH)
12
+ export PATH
13
+ fi
14
+
15
+ # get the current branch
16
+ head="$(git symbolic-ref HEAD)"
17
+ # abort if we're on a detached head
18
+ [ "$?" != "0" ] && exit 1
19
+
20
+ # read the STDIN to detect if this push changed the current branch
21
+ while read oldrev newrev refname
22
+ do
23
+ [ "$refname" = "$head" ] && break
24
+ done
25
+ # abort if there's no update, or in case the branch is deleted
26
+ [ -z "${newrev//0}" ] && exit
27
+
28
+ export RAILS_ENV=production
29
+ export RACK_ENV=production
30
+
31
+ # check out the latest code into the working copy
32
+ umask 002
33
+ git reset --hard
34
+
35
+ logfile=log/deploy.log
36
+ restart=tmp/restart.txt
37
+
38
+ if [ -z "${oldrev//0}" ]; then
39
+ # this is the first push; this branch was just created
40
+ mkdir -p log tmp
41
+ chmod 0775 log tmp
42
+ touch $logfile $restart
43
+ chmod 0664 $logfile $restart
44
+
45
+ # init submodules
46
+ git submodule update --init | tee -a $logfile
47
+ else
48
+ # log timestamp
49
+ echo ==== $(date) ==== >> $logfile
50
+
51
+ # execute the deploy hook in background
52
+ [ -x deploy/after_push ] && nohup deploy/after_push $oldrev $newrev 1>>$logfile 2>>$logfile &
53
+ fi
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: osbro-git-deploy
3
+ version: !ruby/object:Gem::Version
4
+ hash: 1
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 5
10
+ version: 0.5.5
11
+ platform: ruby
12
+ authors:
13
+ - "Mislav Marohni\xC4\x87"
14
+ - Simon Brook
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2012-06-26 00:00:00 +01:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: thor
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: net-ssh
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: net-scp
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :runtime
63
+ version_requirements: *id003
64
+ description: A tool to install useful git hooks on your remote repository to enable push-based, Heroku-like deployment on your host.
65
+ email: simon@obdev.co.uk
66
+ executables:
67
+ - git-deploy
68
+ extensions: []
69
+
70
+ extra_rdoc_files: []
71
+
72
+ files:
73
+ - bin/git-deploy
74
+ - lib/git_deploy/configuration.rb
75
+ - lib/git_deploy/generator.rb
76
+ - lib/git_deploy/ssh_methods.rb
77
+ - lib/git_deploy/templates/after_push.sh
78
+ - lib/git_deploy/templates/before_restart.rb
79
+ - lib/git_deploy/templates/restart.sh
80
+ - lib/git_deploy.rb
81
+ - lib/hooks/post-receive.sh
82
+ - README.markdown
83
+ - LICENSE
84
+ has_rdoc: true
85
+ homepage: https://github.com/osbornebrook/git-deploy
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options: []
90
+
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.3.7
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Simple git push-based application deployment
118
+ test_files: []
119
+