Browse files

Initial commit

  • Loading branch information...
0 parents commit ccacf8bc3c23e2546aa73b8a526a304baf546552 @aizatto aizatto committed Mar 12, 2010
Showing with 316 additions and 0 deletions.
  1. +20 −0 MIT-LICENSE
  2. +68 −0 README.rdoc
  3. +228 −0 bin/git-deploy
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Ezwan Aizat Bin Abdullah Faiz
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
68 README.rdoc
@@ -0,0 +1,68 @@
+= git-deploy
+
+= Description
+
+git-deploy is a deployment tool to allow for quick and easy deployments based on
+the changes in a git repository.
+
+It was originally used to update multiple WordPress installations on shared hosting environments.
+Ideally Capistrano would have been the perfect tool, but generally shared hosting environments only
+allow for FTP access, which Capistrano does not support. Likewise rather than using a standard FTP program,
+when doing a deployment, I only need to upload the files that have been changed. This saves me both time and bandwidth.
+
+I needed something like Capistrano, but simpler.
+
+git-deploy supports deployment over SSH and FTP.
+
+= Installation
+
+git-deploy requires the Ruby gems <code>net-ssh</code>, <code>net-sftp</code>, <code>net-scp</code>.
+
+You can install them like so:
+
+ sudo gem install net-ssh net-sftp net-scp
+
+= Usage
+
+In the root directory of your source code, create a <code>deploy.yml</code> file.
+
+Here is a sample code:
+
+ 'settings':
+ ignore_if_same_revision: true
+
+ 'ftp://example:password@example.com:21/path/to/installation':
+ skip: false
+
+You use the URI scheme to define the location of your installation.
+If you do not want to use the URI scheme, and instead use another identifier. Feel free to do so.
+Just enter the settings for the host like so:
+
+ 'example':
+ skip: false
+ scheme: ftp
+ user: example
+ password: password
+ host: example.com
+ port: 21
+ path: /path/to/installation
+
+Note: The port field is optional in both the URI scheme, and when broken down
+
+Once you have done creating the <code>deploy.yml</code>, upload to your server a file called +REVISION+ with the revision string for the current revision residing on the server
+
+After you have commited your code to the repository. You can run
+
+ git deploy
+
+= How It Works
+
+<code>git-deploy</code> stores file called +REVISION+ on your server inside the root path to your application.
+This file stores the current revision of your application residing on your server.
+
+When you run a <code>git deploy</code>, git-deploy downloads the +REVISION+ file, and checks to see what
+files are different between revisions and either upload the changed files or deletes them from the server.
+
+= TODO
+
+* Create a Ruby gem for the script. Sadly <code>gem-deploy</code> was taken.
228 bin/git-deploy
@@ -0,0 +1,228 @@
+#!/usr/bin/env ruby
+
+unless File.exists? 'deploy.yml'
+ puts "File does not exist: deploy.yml"
+ exit
+end
+
+require 'rubygems'
+require 'yaml'
+require 'uri'
+require 'net/ftp'
+require 'net/ssh'
+require 'net/sftp'
+require 'tempfile'
+
+# We figure out only which files need updating
+# so don't upload duplicate files
+def changed_files(revision = nil)
+ remote_files = {}
+ if revision
+ command = "git log --name-status --pretty=oneline #{revision}"
+ else
+ command = "git log -n 1 --name-status --pretty=oneline"
+ end
+
+# puts command
+
+ files = `#{command}`
+
+ case$?.exitstatus
+ when 0, 141
+ # pass
+ else
+ return false
+ end
+
+ files.split("\n").reverse.each do |line|
+ c = line[0..0]
+ c = 'M' if c == 'A'
+
+ next unless c == 'M' || c == 'D'
+
+ file = line[2..-1]
+
+ if remote_files.key? file
+ if remote_files[file] == 'M' && c == 'D'
+ remote_files[file] = 'D'
+ elsif remote_files[file] == 'D' && c == 'M'
+ remote_files[file] = 'M'
+ end
+ else
+ remote_files[file] = c
+ end
+ end
+
+ remote_files
+end
+
+# Store the current revision
+revision = `git log -n 1 --oneline --format=%H`.chomp.strip
+revision_file = Tempfile.new revision
+revision_file.write revision
+revision_file.close
+
+failures = []
+
+services = YAML.load_file 'deploy.yml'
+settings = services.delete('settings') || {}
+
+puts settings.inspect
+
+services.each do |uri, options|
+ next if options['skip']
+
+ puts uri
+ uri = URI.parse(uri)
+
+ options['scheme'] = uri.scheme if uri.scheme
+ options['user'] = uri.user if uri.user
+ options['password'] = uri.password if uri.password
+ options['host'] = uri.host if uri.host
+ options['port'] = uri.port if uri.port
+ options['path'] = uri.path if uri.path
+
+ begin
+ case options['scheme']
+ when 'sftp'
+ sftp = {}
+ sftp[:port] = uri.port if uri.port
+
+ Net::SFTP.start(options['host'], options['user'], sftp) do |sftp|
+ path = options['path'].empty? ? options['chdir'] : '/' + options['path']
+
+ remote_revision = false
+
+ begin
+ sftp.file.open("#{path}/REVISION", "r") do |f|
+ remote_revision = f.gets
+ end
+
+ puts "Remote Revision: #{remote_revision}"
+ rescue Net::SFTP::StatusException => e
+ raise e unless e.code == 2 && e.description == "no such file"
+ remote_revision = revision
+ end
+
+ if revision == remote_revision
+ if settings['ignore_if_same_revision']
+ puts "Same revision, ignoring"
+ next
+ end
+ remote_files = changed_files
+ else
+ remote_files = changed_files("#{revision}...#{remote_revision}")
+ end
+
+ next unless remote_files
+
+ remote_files.each do |file, modifier|
+ case modifier
+ when 'A', 'M'
+ puts "Uploading #{file}"
+ sftp.upload!(file, path + "/" + file)
+ when 'D'
+ sftp.remove(path + "/" + file)
+ end
+ end
+
+ puts 'Uploading REVISION'
+ sftp.upload(revision_file.path, "#{path}/REVISION")
+ end
+ when 'ftp'
+ directories = {}
+ puts "Connecting to #{uri.host}"
+ options['port'] = options['port'] || uri.port || 21
+
+ ftp = Net::FTP.new
+
+ if options['passive']
+ ftp.passive = true
+ end
+
+ if options['debug_mode']
+ ftp.debug_mode = true
+ end
+
+ ftp.binary = true
+
+ ftp.connect(options['host'], options['port'])
+ ftp.login(options['user'], options['password'])
+
+ ftp.chdir(options['path'].empty? ? options['chdir'] : '/' + options['path'])
+
+ # Read which revision is on the server
+ begin
+ remote_revision = false
+
+ ftp.getbinaryfile('REVISION', Tempfile.new(options['host']).path) do |line|
+ remote_revision = line.strip
+ end
+
+ puts "Remote Revision: #{remote_revision}"
+ rescue Net::FTPPermError => e
+ raise e unless e.message[0..2] == '550'
+ remote_revision = revision
+ end
+
+ if revision == remote_revision
+ if settings['ignore_if_same_revision']
+ puts "Same revision, ignoring"
+ else
+ puts "Same revision, overwriting"
+ end
+
+ remote_files = changed_files
+ else
+ remote_files = changed_files("#{revision}...#{remote_revision}")
+ end
+
+ next unless remote_files
+
+ remote_files.each do |file, modifier|
+ dir = File.dirname(file)
+
+ unless directories[dir]
+ begin
+ ftp.ls(dir)
+ rescue Net::FTPTempError
+ puts dir
+ ftp.mkdir(dir)
+ end
+
+ directories[dir] = true
+ end
+
+ case modifier
+ when 'A', 'M'
+ puts "Uploading #{file}"
+ ftp.puttextfile(file, file)
+ when 'D'
+ begin
+ puts "Deleting #{file}"
+ ftp.delete(file)
+ rescue Net::FTPPermError, Net::FTPReplyError => e
+ #
+ end
+ end
+ end
+
+ puts 'Uploading REVISION'
+ ftp.puttextfile(revision_file.path, 'REVISION')
+
+ ftp.quit
+ end
+ rescue Net::FTPTempError, Net::FTPPermError, Errno::ETIMEDOUT, Errno::ECONNRESET => e
+ failures << [uri, e]
+ end
+end
+
+unless failures.empty?
+ puts "Failures"
+ failures.each do |site|
+ uri, e = *site
+ puts "#{uri}: #{e.class} #{e}"
+ end
+end
+
+revision_file.delete

0 comments on commit ccacf8b

Please sign in to comment.