Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

big rewrite: scripts are now in per-project "deploy/" dir

  • Loading branch information...
commit 013b39a7f54abed697ca393e196294eaa93cda4f 1 parent cb7dc14
Mislav Marohnić authored October 03, 2011
59  README.markdown
Source Rendered
@@ -5,10 +5,20 @@ Straightforward, [Heroku][]-style, push-based deployment. Your deploys will look
5 5
 
6 6
     $ git push production master
7 7
 
8  
-Assumptions are that you are deploying to a single host. Also, that you have Phusion Passenger on the server running your application.
9  
-
10 8
 To get started, install the "git-deploy" gem.
11 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
+
12 22
 
13 23
 Setup steps
14 24
 -----------
@@ -23,7 +33,17 @@ Setup steps
23 33
     
24 34
         $ git deploy setup -r production
25 35
     
26  
-    This will initialize the remote git repository in the target directory ("/path/to/myapp" in the above example), install the remote git hooks and push the master branch to the server.
  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
27 47
 
28 48
 3.  Login to your server and manually perform necessary one-time administrative operations. This might include:
29 49
     * set up the Apache/nginx virtual host for this application;
@@ -33,41 +53,34 @@ Setup steps
33 53
 Deployment
34 54
 ----------
35 55
 
36  
-After you've set everything up, visiting "http://example.com" in your browser should show your app up and running.
  56
+If you've set your app correctly, visiting "http://example.com" in your browser should show it up and running.
37 57
 
38 58
 Now, subsequent deployments are done simply by pushing to the branch that is currently checked out on the remote:
39 59
 
40 60
     $ git push production master
41 61
 
42  
-Deployments are logged to "log/deploy.log" in your application.
43  
-
44  
-On first push your server automatically:
  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.
45 63
 
46  
-1. creates the "log" and "tmp" directories;
47  
-2. copies "config/database.example.yml" to "config/database.yml".
  64
+Deployments are logged to "log/deploy.log" in your application.
48 65
 
49  
-On every subsequent deploy, the "post-reset" script analyzes changes and:
  66
+On every deploy, the "deploy/after_push" script performs the following:
50 67
 
51  
-4. sync submodule urls if ".gitmodules" file has changed;
52  
-5. initialize and update submodules;
53  
-1. clears cached CSS/JS assets if any versioned files under "public/stylesheets" and "public/javascripts" have changed;
54  
-2. runs `bundle install --deployment` if Gemfile or Gemfile.lock have been changed
  68
+1. updates git submodules (if there are any);
  69
+2. runs `bundle install --deployment` if there is a Gemfile;
55 70
 3. runs `rake db:migrate` if new migrations have been added;
56  
-6. `touch tmp/restart.txt` if app restart is needed.
57  
-
58  
-Finally, these are the conditions that trigger the app restart:
59  
-
60  
-1. some CSS/JS assets have been cleared;
61  
-2. the database schema has been migrated;
62  
-3. one or more files/submodules under "app", "config", "lib", "public", or "vendor" have changed.
  71
+4. clears cached CSS/JS assets in "public/stylesheets" and "public/javascripts";
  72
+5. restarts the web application.
63 73
 
  74
+You can customize all of this by editing scripts in the "deploy/" directory of your app.
64 75
 
65 76
 How it works
66 77
 ------------
67 78
 
68  
-The "setup" task installed a couple of hooks in the remote git repository: "post-receive" and "post-reset". The former is a git hook which is invoked after every push to your server, while the latter is a *custom* hook that's called asynchronously by "post-receive" when we updated the deployment branch. This is how your working copy on the server is kept up-to-date.
  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.
69 82
 
70  
-It's worth knowing that "post-reset" is done **asynchronously from your push operation**. This is because migrating the database and updating submodules might take a long time and we don't want to wait for all that while we're doing 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.
  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.
71 84
 
72 85
 
73 86
   [heroku]: http://heroku.com/
164  lib/git_deploy.rb
@@ -5,17 +5,33 @@
5 5
 class GitDeploy < Thor
6 6
   LOCAL_DIR = File.expand_path('..', __FILE__)
7 7
 
  8
+  require 'git_deploy/configuration'
  9
+  require 'git_deploy/ssh_methods'
  10
+  include Configuration
  11
+  include SSHMethods
  12
+
8 13
   class_option :remote, :aliases => '-r', :type => :string, :default => 'origin'
9 14
   class_option :noop, :aliases => '-n', :type => :boolean, :default => false
10 15
 
11  
-  desc "setup", "Create the remote git repository, install git hooks, push the code"
  16
+  desc "init", "Generates deployment customization scripts for your app"
  17
+  def init
  18
+    require 'git_deploy/generator'
  19
+    Generator::start([])
  20
+  end
  21
+
  22
+  desc "setup", "Create the remote git repository and install push hooks for it"
12 23
   method_option :shared, :aliases => '-g', :type => :boolean, :default => true
13 24
   method_option :sudo, :aliases => '-s', :type => :boolean, :default => true
14 25
   def setup
15  
-    sudo_cmd = options.sudo? ? 'sudo' : ''
16  
-    
17  
-    run ["#{sudo_cmd} mkdir -p #{deploy_to}"] do |cmd|
18  
-      cmd << "#{sudo_cmd} chown $USER #{deploy_to}" if options.sudo?
  26
+    sudo = options.sudo? ? "#{sudo_cmd} " : ''
  27
+
  28
+    if run_test("test -x #{deploy_to}")
  29
+      run ["#{sudo}mkdir -p #{deploy_to}"] do |cmd|
  30
+        cmd << "#{sudo}chown $USER #{deploy_to}" if options.sudo?
  31
+      end
  32
+    end
  33
+
  34
+    run [] do |cmd|
19 35
       cmd << "chmod g+ws #{deploy_to}" if options.shared?
20 36
       cmd << "cd #{deploy_to}"
21 37
       cmd << "git init #{options.shared? ? '--shared' : ''}"
@@ -23,9 +39,8 @@ def setup
23 39
       cmd << "git config --bool receive.denyNonFastForwards false" if options.shared?
24 40
       cmd << "git config receive.denyCurrentBranch ignore"
25 41
     end
26  
-    
  42
+
27 43
     invoke :hooks
28  
-    system 'git', 'push', options[:remote], branch
29 44
   end
30 45
 
31 46
   desc "hooks", "Installs git hooks to the remote repository"
@@ -33,15 +48,19 @@ def hooks
33 48
     hooks_dir = File.join(LOCAL_DIR, 'hooks')
34 49
     remote_dir = "#{deploy_to}/.git/hooks"
35 50
 
36  
-    scp_upload "#{hooks_dir}/post-receive.rb" => "#{remote_dir}/post-receive",
37  
-               "#{hooks_dir}/post-reset.rb" => "#{remote_dir}/post-reset"
38  
-
39  
-    run "chmod +x #{remote_dir}/post-receive #{remote_dir}/post-reset"
  51
+    scp_upload "#{hooks_dir}/post-receive.sh" => "#{remote_dir}/post-receive"
  52
+    run "chmod +x #{remote_dir}/post-receive"
40 53
   end
41 54
   
42  
-  desc "restart", "Restarts the application"
  55
+  desc "restart", "Restarts the application on the server"
43 56
   def restart
44  
-    run "touch #{deploy_to}/tmp/restart.txt"
  57
+    run "cd #{deploy_to} && deploy/restart | tee -a log/deploy.log"
  58
+  end
  59
+
  60
+  desc "rollback", "Rolls back the checkout to before the last push"
  61
+  def rollback
  62
+    run "cd #{deploy_to} && git reset --hard ORIG_HEAD"
  63
+    invoke :restart
45 64
   end
46 65
 
47 66
   desc "log [n=20]", "Shows the last part of the deploy log on the server"
@@ -59,125 +78,6 @@ def upload(*files)
59 78
       all
60 79
     }
61 80
   end
62  
-  
63  
-  private
64  
-  
65  
-  def host
66  
-    extract_host_and_user unless defined? @host
67  
-    @host
68  
-  end
69  
-  
70  
-  def remote_user
71  
-    extract_host_and_user unless defined? @user
72  
-    @user
73  
-  end
74  
-  
75  
-  def extract_host_and_user
76  
-    info = remote_url.split(':').first.split('@')
77  
-    if info.size < 2
78  
-      @user, @host = `whoami`.chomp, info.first
79  
-    else
80  
-      @user, @host = *info
81  
-    end
82  
-  end
83  
-  
84  
-  def deploy_to
85  
-    @deploy_to ||= remote_url.split(':').last
86  
-  end
87  
-  
88  
-  def branch
89  
-    'master'
90  
-  end
91  
-  
92  
-  def run(cmd = nil)
93  
-    cmd = yield(cmd) if block_given?
94  
-    cmd = cmd.join(' && ') if Array === cmd
95  
-    ssh_exec cmd
96  
-  end
97  
-  
98  
-  def system(*args)
99  
-    puts "[local] $ " + args.join(' ').gsub(' && ', " && \\\n  ")
100  
-    super unless options.noop?
101  
-  end
102  
-  
103  
-  def ssh_exec(cmd)
104  
-    puts "[#{options[:remote]}] $ " + cmd.gsub(' && ', " && \\\n  ")
105  
-
106  
-    ssh_connection.exec!(cmd) do |channel, stream, data|
107  
-      case stream
108  
-      when :stdout then $stdout.puts data
109  
-      when :stderr then $stderr.puts data
110  
-      else
111  
-        raise "unknown stream: #{stream.inspect}"
112  
-      end
113  
-    end unless options.noop?
114  
-  end
115  
-  
116  
-  def scp_upload(files)
117  
-    channels = []
118  
-    files.each do |local, remote|
119  
-      puts "FILE: [local] #{local.sub(LOCAL_DIR + '/', '')}  ->  [#{options[:remote]}] #{remote}"
120  
-      channels << ssh_connection.scp.upload(local, remote) unless options.noop?
121  
-    end
122  
-    channels.each { |c| c.wait }
123  
-  end
124  
-  
125  
-  def ssh_connection
126  
-    @ssh ||= begin
127  
-      ssh = Net::SSH.start(host, remote_user)
128  
-      at_exit { ssh.close }
129  
-      ssh
130  
-    end
131  
-  end
132  
-  
133  
-  def git_config
134  
-    @git_config ||= Hash.new do |cache, cmd|
135  
-      git = ENV['GIT'] || 'git'
136  
-      out = `#{git} #{cmd}`
137  
-      if $?.success? then cache[cmd] = out.chomp
138  
-      else cache[cmd] = nil
139  
-      end
140  
-      cache[cmd]
141  
-    end
142  
-  end
143  
-  
144  
-  def remote_urls(remote)
145  
-    git_config["config --get-all remote.#{remote}.url"].to_s.split("\n")
146  
-  end
147  
-  
148  
-  def remote_url(remote = options[:remote])
149  
-    @remote_url ||= {}
150  
-    @remote_url[remote] ||= begin
151  
-      url = remote_urls(remote).first
152  
-      if url.nil?
153  
-        abort "Error: Remote url not found for remote #{remote.inspect}"
154  
-      elsif url =~ /\bgithub\.com\b/
155  
-        abort "Error: Remote url for #{remote.inspect} points to GitHub. Can't deploy there!"
156  
-      end
157  
-      url
158  
-    end
159  
-  end
160  
-
161  
-  def current_branch
162  
-    git_config['symbolic-ref -q HEAD']
163  
-  end
164  
-
165  
-  def tracked_branch
166  
-    branch = current_branch && tracked_for(current_branch)
167  
-    normalize_branch(branch) if branch
168  
-  end
169  
-
170  
-  def normalize_branch(branch)
171  
-    branch.sub('refs/heads/', '')
172  
-  end
173  
-
174  
-  def remote_for(branch)
175  
-    git_config['config branch.%s.remote' % normalize_branch(branch)]
176  
-  end
177  
-
178  
-  def tracked_for(branch)
179  
-    git_config['config branch.%s.merge' % normalize_branch(branch)]
180  
-  end
181 81
 end
182 82
 
183 83
 __END__
81  lib/git_deploy/configuration.rb
... ...
@@ -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
28  lib/git_deploy/generator.rb
... ...
@@ -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
99  lib/git_deploy/ssh_methods.rb
... ...
@@ -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
17  lib/git_deploy/templates/after_push.sh
... ...
@@ -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
25  lib/git_deploy/templates/before_restart.rb
... ...
@@ -0,0 +1,25 @@
  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
+# update gem bundle
  13
+run "bundle install --deployment" if use_bundler
  14
+
  15
+if File.file? 'Rakefile'
  16
+  num_migrations = `git diff #{oldrev} #{newrev} --diff-filter=A --name-only`.split("\n").size
  17
+  # run migrations if new ones have been added
  18
+  run "#{rake_cmd} db:migrate RAILS_ENV=#{RAILS_ENV}" if num_migrations > 0
  19
+end
  20
+
  21
+# clear cached assets (unversioned/ignored files)
  22
+run "git clean -x -f -- public/stylesheets public/javascripts"
  23
+
  24
+# clean unversioned files from vendor/plugins (e.g. old submodules)
  25
+run "git clean -d -f -- vendor/plugins"
3  lib/git_deploy/templates/restart.sh
... ...
@@ -0,0 +1,3 @@
  1
+#!/bin/sh
  2
+touch tmp/restart.txt
  3
+echo "restarting Passenger app"
60  lib/hooks/post-receive.rb
... ...
@@ -1,60 +0,0 @@
1  
-#!/usr/bin/env ruby
2  
-if ENV['GIT_DIR'] == '.'
3  
-  # this means the script has been called as a hook, not manually.
4  
-  # get the proper GIT_DIR so we can descend into the working copy dir;
5  
-  # if we don't then `git reset --hard` doesn't affect the working tree.
6  
-  Dir.chdir('..')
7  
-  ENV['GIT_DIR'] = '.git'
8  
-end
9  
-
10  
-cmd = %(bash -c "[ -f /etc/profile ] && source /etc/profile; echo $PATH")
11  
-envpath = IO.popen(cmd, 'r') { |io| io.read.chomp }
12  
-ENV['PATH'] = envpath
13  
-
14  
-# find out the current branch
15  
-head = `git symbolic-ref HEAD`.chomp
16  
-# abort if we're on a detached head
17  
-exit unless $?.success?
18  
-
19  
-oldrev = newrev = nil
20  
-null_ref = '0' * 40
21  
-
22  
-# read the STDIN to detect if this push changed the current branch
23  
-while newrev.nil? and gets
24  
-  # each line of input is in form of "<oldrev> <newrev> <refname>"
25  
-  revs = $_.split
26  
-  oldrev, newrev = revs if head == revs.pop
27  
-end
28  
-
29  
-# abort if there's no update, or in case the branch is deleted
30  
-exit if newrev.nil? or newrev == null_ref
31  
-
32  
-# update the working copy
33  
-`umask 002 && git reset --hard`
34  
-
35  
-config = 'config/database.yml'
36  
-logfile = 'log/deploy.log'
37  
-restart = 'tmp/restart.txt'
38  
-
39  
-if oldrev == null_ref
40  
-  # this is the first push; this branch was just created
41  
-  require 'fileutils'
42  
-  FileUtils.mkdir_p %w(log tmp)
43  
-  FileUtils.chmod 0775, %w(log tmp)
44  
-  FileUtils.touch [logfile, restart]
45  
-  FileUtils.chmod 0664, [logfile, restart]
46  
-  
47  
-  unless File.exists?(config)
48  
-    # install the database config from the example file
49  
-    example = ['config/database.example.yml', config + '.example'].find { |f| File.exists? f }
50  
-    FileUtils.cp example, config if example
51  
-  end
52  
-  
53  
-  # init submodules
54  
-  system %(umask 002 && git submodule update --init | tee -a #{logfile})
55  
-else
56  
-  # log timestamp
57  
-  File.open(logfile, 'a') { |log| log.puts "==== #{Time.now} ====" }
58  
-  # start the post-reset hook in background
59  
-  system %(nohup .git/hooks/post-reset #{oldrev} #{newrev} 1>>#{logfile} 2>>#{logfile} &)
60  
-end
50  lib/hooks/post-receive.sh
... ...
@@ -0,0 +1,50 @@
  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
+# check out the latest code into the working copy
  29
+umask 002
  30
+git reset --hard
  31
+
  32
+logfile=log/deploy.log
  33
+restart=tmp/restart.txt
  34
+
  35
+if [ -z "${oldrev//0}" ]; then
  36
+  # this is the first push; this branch was just created
  37
+  mkdir -p log tmp
  38
+  chmod 0775 log tmp
  39
+  touch $logfile $restart
  40
+  chmod 0664 $logfile $restart
  41
+
  42
+  # init submodules
  43
+  git submodule update --init | tee -a $logfile
  44
+else
  45
+  # log timestamp
  46
+  echo ==== $(date) ==== >> $logfile
  47
+
  48
+  # execute the deploy hook in background
  49
+  [ -x deploy/after_push ] && nohup deploy/after_push $oldrev $newrev 1>>$logfile 2>>$logfile &
  50
+fi
118  lib/hooks/post-reset.rb
... ...
@@ -1,118 +0,0 @@
1  
-#!/usr/bin/env ruby
2  
-RAILS_ENV = 'production'
3  
-oldrev, newrev = ARGV
4  
-$stdout.sync = true
5  
-
6  
-def parse_configuration(file)
7  
-  config = {}
8  
-  current = nil
9  
-
10  
-  File.open(file).each_line do |line|
11  
-    case line
12  
-    when /^\[(\w+)(?: "(.+)")\]/
13  
-      key, subkey = $1, $2
14  
-      current = (config[key] ||= {})
15  
-      current = (current[subkey] ||= {}) if subkey
16  
-    else
17  
-      key, value = line.strip.split(' = ')
18  
-      current[key] = value
19  
-    end
20  
-  end
21  
-  
22  
-  config
23  
-end
24  
-
25  
-class Array
26  
-  # scans the list of files to see if any of them are under the given path
27  
-  def any_in_dir?(dir)
28  
-    if Array === dir
29  
-      exp = %r{^(?:#{dir.join('|')})/}
30  
-      any? { |file| file =~ exp }
31  
-    else
32  
-      dir += '/'
33  
-      any? { |file| file.index(dir) == 0 }
34  
-    end
35  
-  end
36  
-end
37  
-
38  
-# get a list of files that changed
39  
-changes = `git diff #{oldrev} #{newrev} --diff-filter=ACDMR --name-status`.split("\n")
40  
-
41  
-# make a hash of files that changed and how they changed
42  
-changes_hash = changes.inject(Hash.new { |h, k| h[k] = [] }) do |hash, line|
43  
-  modifier, filename = line.split("\t", 2)
44  
-  hash[modifier] << filename
45  
-  hash
46  
-end
47  
-
48  
-# create an array of files added, copied, modified or renamed
49  
-modified_files = %w(A C M R).inject([]) { |files, bit| files.concat changes_hash[bit] }
50  
-added_files = changes_hash['A'] # added
51  
-deleted_files = changes_hash['D'] # deleted
52  
-changed_files = modified_files + deleted_files # all
53  
-puts "files changed: #{changed_files.size}"
54  
-
55  
-if modified_files.include?('.gitmodules')
56  
-  # initialize new submodules
57  
-  system %(umask 002 && git submodule init)
58  
-  # sync submodule remote urls in case of changes
59  
-  config = parse_configuration('.gitmodules')
60  
-
61  
-  if config['submodule']
62  
-    config['submodule'].values.each do |submodule|
63  
-      path = submodule['path']
64  
-      subconf = "#{path}/.git/config"
65  
-
66  
-      if File.exists? subconf
67  
-        old_url = `git config -f "#{subconf}" remote.origin.url`.chomp
68  
-        new_url = submodule['url']
69  
-        unless old_url == new_url
70  
-          puts "changing #{path.inspect} URL:\n  #{old_url.inspect} → #{new_url.inspect}"
71  
-          `git config -f "#{subconf}" remote.origin.url "#{new_url}"`
72  
-        end
73  
-      else
74  
-        $stderr.puts "a submodule in #{path.inspect} doesn't exist"
75  
-      end
76  
-    end
77  
-  end
78  
-end
79  
-# update existing submodules
80  
-system %(umask 002 && git submodule update)
81  
-
82  
-cached_assets_cleared = false
83  
-
84  
-# detect modified asset dirs
85  
-asset_dirs = %w(public/stylesheets public/javascripts).select do |dir|
86  
-  # did any on the assets under this dir change?
87  
-  changed_files.any_in_dir?(dir)
88  
-end
89  
-
90  
-unless asset_dirs.empty?
91  
-  # clear cached assets (unversioned/ignored files)
92  
-  system %(git clean -x -f -- #{asset_dirs.join(' ')})
93  
-  cached_assets_cleared = true
94  
-end
95  
-
96  
-if changed_files.include?('Gemfile') || changed_files.include?('Gemfile.lock')
97  
-  # update bundled gems if manifest file has changed
98  
-  system %(umask 002 && bundle install --deployment)
99  
-end
100  
-
101  
-rake_cmd = File.exists?('Gemfile') ? 'bundle exec rake' : 'rake'
102  
-
103  
-# run migrations when new ones added
104  
-if new_migrations = added_files.any_in_dir?('db/migrate')
105  
-  system %(umask 002 && #{rake_cmd} db:migrate RAILS_ENV=#{RAILS_ENV})
106  
-end
107  
-
108  
-# clean unversioned files from vendor/plugins (e.g. old submodules)
109  
-system %(git clean -d -f vendor/plugins)
110  
-
111  
-# determine if app restart is needed
112  
-if cached_assets_cleared or new_migrations or !File.exists?('config/environment.rb') or
113  
-    changed_files.any_in_dir?(%w(app config lib public vendor))
114  
-  require 'fileutils'
115  
-  # tell Passenger to restart this app
116  
-  FileUtils.touch 'tmp/restart.txt'
117  
-  puts "restarting Passenger app"
118  
-end

0 notes on commit 013b39a

Please sign in to comment.
Something went wrong with that request. Please try again.