fast_git_deploy 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'capistrano'
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fast_git_deploy (0.2.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ capistrano (2.13.5)
10
+ highline
11
+ net-scp (>= 1.0.0)
12
+ net-sftp (>= 2.0.0)
13
+ net-ssh (>= 2.0.14)
14
+ net-ssh-gateway (>= 1.1.0)
15
+ highline (1.6.15)
16
+ net-scp (1.0.4)
17
+ net-ssh (>= 1.99.1)
18
+ net-sftp (2.0.5)
19
+ net-ssh (>= 2.0.9)
20
+ net-ssh (2.6.2)
21
+ net-ssh-gateway (1.1.0)
22
+ net-ssh (>= 1.99.1)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ capistrano
29
+ fast_git_deploy!
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009-2013 Scott Taylor
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.rdoc ADDED
@@ -0,0 +1,36 @@
1
+ = fast_git_deploy
2
+
3
+ Before (with fast_remote_cache deploy strategy):
4
+
5
+ $ time cap staging deploy:update
6
+
7
+ ...
8
+
9
+ real 1m56.811s
10
+ user 0m0.560s
11
+ sys 0m0.118s
12
+
13
+
14
+ After:
15
+
16
+ $ time cap staging deploy:update
17
+
18
+ ...
19
+
20
+ real 0m19.987s
21
+ user 0m0.538s
22
+ sys 0m0.110s
23
+
24
+ == Install it into your rails app:
25
+
26
+ gem 'fast_git_deploy'
27
+
28
+ == Switch an existing project:
29
+
30
+ cap deploy:warm
31
+
32
+ == Setup a new project:
33
+
34
+ cap deploy:setup
35
+ cap deploy:cold
36
+ cap deploy
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fast_git_deploy/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "fast_git_deploy"
8
+ gem.version = FastGitDeploy::VERSION::STRING
9
+ gem.authors = ["Scott Taylor"]
10
+ gem.email = ["scott@railsnewbie.com"]
11
+ gem.description = %q{The Fast Git Deploy Method - just a git reset --hard to update code}
12
+ gem.summary = <<-HERE
13
+ Amazingly fast git deploys by using only a current directory (using git as the version the control history).
14
+
15
+ It's the same technique github uses at their company.
16
+ HERE
17
+ gem.homepage = "http://github.com/smtlaissezfaire/fast_git_deploy"
18
+ gem.files = `git ls-files`.split($/)
19
+ gem.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,15 @@
1
+
2
+ if defined?(Capistrano) &&
3
+ Capistrano::Configuration.respond_to?(:instance) &&
4
+ instance = Capistrano::Configuration.instance
5
+
6
+ require File.dirname(__FILE__) + "/fast_git_deploy/recipes/fast_git_deploy"
7
+ require File.dirname(__FILE__) + "/fast_git_deploy/recipes/fast_git_deploy/rollback"
8
+ require File.dirname(__FILE__) + "/fast_git_deploy/recipes/fast_git_deploy/setup"
9
+
10
+ FastGitDeploy::Main.load_into(instance)
11
+ FastGitDeploy::Rollback.load_into(instance)
12
+ FastGitDeploy::Setup.load_into(instance)
13
+ end
14
+
15
+
@@ -0,0 +1,161 @@
1
+ module FastGitDeploy
2
+ module Main
3
+ def self.load_into(configuration)
4
+ configuration.load do
5
+ current_dir = File.expand_path(File.dirname(__FILE__))
6
+
7
+ set :scm, "git"
8
+ set :scm_command, "git"
9
+ set(:revision_log) { "#{deploy_to}/revisions.log" }
10
+ set(:version_file) { "#{current_path}/REVISION" }
11
+ set :migrate_target, :current
12
+ set :releases, ['current']
13
+ set(:release_path) { File.join(releases_path, "current") }
14
+ set(:releases_path) { File.join(deploy_to) }
15
+
16
+ # set :branch, "master"
17
+
18
+ namespace :deploy do
19
+ desc <<-DESC
20
+ Deploy a "cold" application (deploy the app for the first time).
21
+ DESC
22
+ task :cold do
23
+ fast_git_setup.cold
24
+ end
25
+
26
+ desc <<-DESC
27
+ Deploy a "warm" application - one which is already running, but was
28
+ setup with deploy:cold provided by capistrano's default tasks
29
+ DESC
30
+ task :warm do
31
+ fast_git_setup.warm
32
+ end
33
+
34
+ desc <<-DESC
35
+ Deploy and run pending migrations. This will work similarly to the
36
+ `deploy' task, but will also run any pending migrations (via the
37
+ `deploy:migrate' task) prior to updating the symlink. Note that the
38
+ update in this case it is not atomic, and transactions are not used,
39
+ because migrations are not guaranteed to be reversible.
40
+ DESC
41
+ task :migrations do
42
+ set :migrate_target, :current
43
+ update_code
44
+ symlink
45
+ migrate
46
+ restart
47
+ end
48
+
49
+ desc "Just like deploy:migrations, but puts up the maintenance page while migrating"
50
+ task :long do
51
+ set :migrate_target, :current
52
+ deploy.web.disable
53
+ update_code
54
+ symlink
55
+ migrate
56
+ restart
57
+ deploy.web.enable
58
+ end
59
+
60
+ desc "Updates code in the repos by fetching and resetting to the latest in the branch"
61
+ task :update_code, :except => { :no_release => true } do
62
+ run [
63
+ "cd #{current_path}",
64
+ "#{scm_command} fetch",
65
+ "#{scm_command} reset --hard origin/#{branch}"
66
+ ].join(" && ")
67
+
68
+ set_revisions
69
+ finalize_update
70
+ end
71
+
72
+ desc <<-DESC
73
+ [internal] Touches up the released code. This is called by update_code
74
+ after the basic deploy finishes. It assumes a Rails project was deployed,
75
+ so if you are deploying something else, you may want to override this
76
+ task with your own environment's requirements.
77
+
78
+ This will touch all assets in public/images,
79
+ public/stylesheets, and public/javascripts so that the times are
80
+ consistent (so that asset timestamping works). This touch process
81
+ is only carried out if the :normalize_asset_timestamps variable is
82
+ set to true, which is the default.
83
+ DESC
84
+ task :finalize_update, :except => { :no_release => true } do
85
+ if fetch(:normalize_asset_timestamps, true)
86
+ stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
87
+ asset_paths = %w(images stylesheets javascripts).map { |p| "#{current_path}/public/#{p}" }.join(" ")
88
+ run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
89
+ end
90
+ end
91
+
92
+ desc "Symlink system files & set revision info"
93
+ task :symlink, :except => { :no_release => true } do
94
+ symlink_system_files
95
+ end
96
+
97
+ desc "Symlink system files"
98
+ task :symlink_system_files, :except => { :no_release => true } do
99
+ run [
100
+ "rm -rf #{current_path}/log #{current_path}/public/system #{current_path}/tmp/pids",
101
+ "mkdir -p #{current_path}/public",
102
+ "mkdir -p #{current_path}/tmp",
103
+ "ln -s #{shared_path}/log #{current_path}/log",
104
+ "ln -s #{shared_path}/system #{current_path}/public/system",
105
+ "ln -s #{shared_path}/pids #{current_path}/tmp/pids"
106
+ ].join(" && ")
107
+ end
108
+
109
+ desc "Set the revisions file. This allows us to go back to previous versions."
110
+ task :set_revisions, :except => { :no_release => true } do
111
+ set_version_file
112
+ update_revisions_log
113
+ end
114
+
115
+ task :set_version_file, :except => { :no_release => true } do
116
+ run [
117
+ "cd #{current_path}",
118
+ "#{scm_command} rev-list HEAD | head -n 1 > #{version_file}"
119
+ ].join(" && ")
120
+ end
121
+
122
+ task :update_revisions_log, :except => { :no_release => true } do
123
+ run "echo `date +\"%Y-%m-%d %H:%M:%S\"` $USER $(cat #{version_file}) >> #{deploy_to}/revisions.log"
124
+ end
125
+
126
+ desc "Do nothing (since we have no releases directory)"
127
+ task :cleanup do
128
+ end
129
+
130
+ desc "Do nothing (No need to create a symlink)"
131
+ task :create_symlink do
132
+ end
133
+
134
+ desc <<-DESC
135
+ Prepares one or more servers for deployment. Before you can use any \
136
+ of the Capistrano deployment tasks with your project, you will need to \
137
+ make sure all of your servers have been prepared with `cap deploy:setup'. When \
138
+ you add a new server to your cluster, you can easily run the setup task \
139
+ on just that server by specifying the HOSTS environment variable:
140
+
141
+ $ cap HOSTS=new.server.com deploy:setup
142
+
143
+ It is safe to run this task on servers that have already been set up; it \
144
+ will not destroy any deployed revisions or data.
145
+ DESC
146
+ task :setup, :except => { :no_release => true } do
147
+ dirs = [deploy_to, shared_path]
148
+ dirs += shared_children.map { |d| File.join(shared_path, d) }
149
+ dir_names = dirs.join(' ')
150
+
151
+ run [
152
+ "#{try_sudo} mkdir -p #{dir_names}",
153
+ "#{try_sudo} chown -R #{user}:#{user} #{dir_names}",
154
+ "#{try_sudo} chmod g+w #{dir_names}"
155
+ ].join(" && ")
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,62 @@
1
+ module FastGitDeploy
2
+ module Rollback
3
+ def self.load_into(configuration)
4
+ configuration.load do
5
+ namespace :deploy do
6
+ namespace :rollback do
7
+ desc "Rolls the app back one revision"
8
+ task :code, :except => { :no_release => true } do
9
+ current_revision = capture("cat #{version_file}").gsub(/\r?\n?/, "")
10
+ previous_revision = nil
11
+
12
+ revision_log_data = capture("cat #{revision_log}").split(/\r?\n/)
13
+ revisions = revision_log_data.map do |entry|
14
+ entry.split(" ").last
15
+ end
16
+
17
+ revisions.reverse!
18
+
19
+ revisions.each_with_index do |revision, index|
20
+ if current_revision == revision
21
+ # we have found the currently deployed revision
22
+ # so scan the file backwards until a different revision
23
+ # is found (removing duplicates - i.e.):
24
+ #
25
+ # 2010-04-21 15:22:59 deploy 2a285b0f600c7ed31b307390ad91c
26
+ # 2010-04-22 09:58:28 deploy 53cff5db28116ecd5ad32d11ee6e1
27
+ # 2010-04-22 10:02:41 deploy 53cff5db28116ecd5ad32d11ee6e1
28
+ # 2010-04-26 08:18:39 deploy 5494dc2a00beb5350eff6be151987
29
+ # 2010-04-27 09:10:06 deploy 5494dc2a00beb5350eff6be151987
30
+ # 2010-04-27 11:49:29 deploy 5494dc2a00beb5350eff6be151987
31
+ #
32
+ # Rolling back from 5494dc should yield 53cff5db
33
+ previous_revision = revisions[index..revision_log_data.length-1].detect do |rev|
34
+ rev != current_revision
35
+ end
36
+ end
37
+ end
38
+
39
+ if previous_revision
40
+ run [
41
+ "cd #{current_path}",
42
+ "#{scm_command} reset --hard #{previous_revision}"
43
+ ].join(" && ")
44
+ else
45
+ raise(Capistrano::Error, "Couldn't find a revision previous to #{current_revision}")
46
+ end
47
+ end
48
+
49
+ desc "Rolls back the app one revision, restarts mongrel, and writes the revision to the VERSION file (but not revisions.log)"
50
+ task :default do
51
+ transaction do
52
+ rollback.code
53
+ deploy.restart
54
+ deploy.set_version_file
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,107 @@
1
+ module FastGitDeploy
2
+ module Setup
3
+ def self.load_into(configuration)
4
+ configuration.load do
5
+ namespace :deploy do
6
+ namespace :fast_git_setup do
7
+ task :cold do
8
+ clone_repository
9
+ finalize_clone
10
+ create_revision_log
11
+
12
+ deploy.update
13
+ deploy.start
14
+ end
15
+
16
+ def self.clone_repository_command(path)
17
+ [
18
+ "if [ ! -e #{path} ]",
19
+ "then mkdir -p #{deploy_to}",
20
+ "cd #{deploy_to}",
21
+ "#{scm_command} clone #{repository} #{path}",
22
+ "fi"
23
+ ].join("; ")
24
+ end
25
+
26
+ desc "Clones the repos"
27
+ task :clone_repository, :except => { :no_release => true } do
28
+ run clone_repository_command(current_path)
29
+ end
30
+
31
+ task :create_revision_log, :except => { :no_release => true } do
32
+ run [
33
+ "if [ ! -e #{revision_log} ]",
34
+ "then touch #{revision_log}",
35
+ "chmod 664 #{revision_log}",
36
+ "fi"
37
+ ].join("; ")
38
+ end
39
+
40
+ task :warm do
41
+ clone_repository_to_tmp_path
42
+
43
+ deploy.web.disable
44
+ remove_old_app
45
+ rename_clone
46
+ finalize_clone
47
+ deploy.default
48
+ deploy.web.enable
49
+ end
50
+
51
+ task :clone_repository_to_tmp_path, :except => { :no_release => true } do
52
+ run clone_repository_command("#{current_path}.clone")
53
+ end
54
+
55
+ task :rename_clone, :except => { :no_release => true } do
56
+ run "mv #{current_path}.clone #{current_path}"
57
+ end
58
+
59
+ task :remove_old_app do
60
+ remove_releases
61
+ remove_current
62
+ end
63
+
64
+ task :remove_releases, :except => { :no_release => true } do
65
+ run [
66
+ "if [ -e #{deploy_to}/releases ]",
67
+ "then mv #{deploy_to}/releases #{deploy_to}/releases.old",
68
+ "fi"
69
+ ].join("; ")
70
+ end
71
+
72
+ task :remove_current, :except => { :no_release => true } do
73
+ # test -h => symlink
74
+ run [
75
+ "if [ -h #{current_path} ]",
76
+ "then mv #{current_path} #{current_path}.old",
77
+ "fi"
78
+ ].join("; ")
79
+ end
80
+
81
+
82
+ desc <<-HERE
83
+ This task will make the release group-writable (if the :group_writable
84
+ variable is set to true, which is the default). It will then set up
85
+ symlinks to the shared directory for the log, system, and tmp/pids
86
+ directories.
87
+ HERE
88
+ task :finalize_clone, :except => { :no_release => true } do
89
+ run "chmod -R g+w #{current_path}" if fetch(:group_writable, true)
90
+
91
+ # mkdir -p is making sure that the directories are there for some SCM's that don't
92
+ # save empty folders
93
+ run [
94
+ "rm -rf #{current_path}/log #{current_path}/public/system #{current_path}/tmp/pids",
95
+ "mkdir -p #{current_path}/public",
96
+ "mkdir -p #{current_path}/tmp",
97
+ "ln -s #{shared_path}/log #{current_path}/log",
98
+ "ln -s #{shared_path}/system #{current_path}/public/system",
99
+ "ln -s #{shared_path}/pids #{current_path}/tmp/pids"
100
+ ].join(" && ")
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,9 @@
1
+ module FastGitDeploy
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ TINY = 1
6
+
7
+ STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
8
+ end
9
+ end
data/spec/Capfile ADDED
@@ -0,0 +1,4 @@
1
+ load 'deploy' if respond_to?(:namespace) # cap2 differentiator
2
+ Dir[File.dirname(__FILE__) + '/../**/recipes/*.rb'].each { |plugin| load(plugin) }
3
+
4
+ load File.dirname(__FILE__) + '/config/deploy.rb' # remove this line to skip loading any of the default tasks
@@ -0,0 +1,35 @@
1
+ def self.join(*args)
2
+ current_path = File.dirname(__FILE__)
3
+ File.expand_path(File.join(current_path, *args))
4
+ end
5
+
6
+ set :application, "fast_git_deploy_test"
7
+ set :repository, join("..", "..", ".git")
8
+ set :deploy_to, join("..", "deployments")
9
+ set :scm_command, `which git`.chomp
10
+ set :user, `whoami`.chomp
11
+
12
+ set :scm, :git
13
+
14
+ ssh_options[:paranoid] = false
15
+ default_run_options[:pty] = true
16
+
17
+ role :web, "127.0.0.1"
18
+ role :app, "127.0.0.1"
19
+ role :db, "127.0.0.1", :primary => true
20
+
21
+ set :branch, "master"
22
+
23
+ namespace :deploy do
24
+ task :restart do
25
+ # do nothing
26
+ end
27
+
28
+ task :migrate do
29
+ # do nothing
30
+ end
31
+
32
+ task :start do
33
+ # do nothing
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ require "spec_helper"
2
+
3
+ describe "fast git deploy" do
4
+ def cap_execute(command)
5
+ commands = [
6
+ "--file", File.expand_path("#{File.dirname(__FILE__)}/Capfile"),
7
+ "--quiet"
8
+ ]
9
+ commands.push command.split(" ")
10
+ commands.flatten!
11
+
12
+ Capistrano::CLI.parse(commands).execute!
13
+ end
14
+
15
+ it "should be able to deploy with a dry-run" do
16
+ cap_execute "-n deploy"
17
+ end
18
+
19
+ it "should be able to deploy:setup" do
20
+ cap_execute "deploy:setup"
21
+ end
22
+
23
+ it "should be able to deploy:cold after deploy:setup" do
24
+ cap_execute "deploy:setup"
25
+ cap_execute "deploy:cold"
26
+ end
27
+
28
+ it "should be able to deploy" do
29
+ cap_execute "deploy:setup"
30
+ cap_execute "deploy:cold"
31
+ cap_execute "deploy"
32
+ end
33
+
34
+ it "should be able to deploy:migrations" do
35
+ cap_execute "deploy:setup"
36
+ cap_execute "deploy:cold"
37
+ cap_execute "deploy:migrations"
38
+ end
39
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,39 @@
1
+ require 'capistrano/cli'
2
+ require 'capistrano/configuration'
3
+
4
+ # load File.expand_path(File.dirname(__FILE__) + "/../recipes/fast_git_deploy.rb")
5
+
6
+ # load File.dirname(__FILE__) + '/config/deploy.rb'
7
+
8
+ Spec::Runner.configure do |config|
9
+ config.before :each do
10
+ FileUtils.rm_rf(File.dirname(__FILE__) + "/deployments")
11
+ end
12
+
13
+ config.after :each do
14
+ FileUtils.rm_rf(File.dirname(__FILE__) + "/deployments")
15
+ end
16
+ end
17
+
18
+ module Capistrano
19
+ class Configuration
20
+ module Actions
21
+ module Invocation
22
+ def sudo(*parameters, &block)
23
+ options = parameters.last.is_a?(Hash) ? parameters.pop.dup : {}
24
+ command = parameters.first
25
+ user = options[:as] && "-u #{options.delete(:as)}"
26
+
27
+ sudo_prompt_option = "-p '#{sudo_prompt}'" unless sudo_prompt.empty?
28
+ sudo_command = [fetch(:sudo, "sudo"), sudo_prompt_option, user].compact.join(" ")
29
+
30
+ if command
31
+ run(command, options, &block)
32
+ else
33
+ return sudo_command
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fast_git_deploy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Scott Taylor
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-08 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: The Fast Git Deploy Method - just a git reset --hard to update code
15
+ email:
16
+ - scott@railsnewbie.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - LICENSE.txt
25
+ - README.rdoc
26
+ - fast_git_deploy.gemspec
27
+ - lib/fast_git_deploy.rb
28
+ - lib/fast_git_deploy/recipes/fast_git_deploy.rb
29
+ - lib/fast_git_deploy/recipes/fast_git_deploy/rollback.rb
30
+ - lib/fast_git_deploy/recipes/fast_git_deploy/setup.rb
31
+ - lib/fast_git_deploy/version.rb
32
+ - spec/Capfile
33
+ - spec/config/deploy.rb
34
+ - spec/deploy_spec.rb
35
+ - spec/spec.opts
36
+ - spec/spec_helper.rb
37
+ homepage: http://github.com/smtlaissezfaire/fast_git_deploy
38
+ licenses: []
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 1.8.24
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: Amazingly fast git deploys by using only a current directory (using git as
61
+ the version the control history). It's the same technique github uses at their
62
+ company.
63
+ test_files: []