├── .document ├── .gitignore ├── Gemfile ├── README.rdoc ├── Rakefile ├── capistrano-gitflow.gemspec ├── lib └── capistrano │ ├── gitflow.rb │ ├── gitflow │ ├── helpers │ │ ├── helper.rb │ │ └── natcmp.rb │ ├── legacy │ │ └── gitflow.rb │ └── version.rb │ └── tasks │ └── gitflow.rb ├── recipes └── gitflow_recipes.rb └── spec ├── gitflow_spec.rb ├── spec.opts └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | coverage.data 4 | 5 | # rdoc generated 6 | rdoc 7 | 8 | # yard generated 9 | doc 10 | .yardoc 11 | 12 | # bundler 13 | .bundle 14 | 15 | # jeweler generated 16 | pkg 17 | 18 | # For TextMate 19 | *.tmproj 20 | tmtags 21 | 22 | # For emacs: 23 | *~ 24 | \#* 25 | .\#* 26 | 27 | # For vim: 28 | *.swp 29 | 30 | # For redcar: 31 | .redcar 32 | 33 | # For rubinius: 34 | *.rbc 35 | /nbproject/private/ 36 | 37 | /tmp/ 38 | 39 | 40 | # Logs and databases # 41 | ###################### 42 | *.log 43 | *.sql 44 | *.sqlite 45 | 46 | # OS generated files # 47 | ###################### 48 | *~ 49 | .DS_Store 50 | .DS_Store? 51 | ._* 52 | .Spotlight-V100 53 | .Trashes 54 | Icon? 55 | ehthumbs.db 56 | Thumbs.db 57 | .rvmrc 58 | s.rbx/ 59 | .bundle/ 60 | *.gem 61 | .idea/ 62 | .rvmrc 63 | *.swp 64 | log/*.log 65 | pkg/ 66 | spec/dummy/db/*.sqlite3 67 | spec/dummy/log/*.log 68 | spec/dummy/tmp/ 69 | gemfiles 70 | coverage 71 | tags 72 | /nbproject/ 73 | /.git-rewrite/ 74 | .rbenv-version 75 | .ruby-version 76 | Gemfile.lock 77 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = gitflow: a Capistrano recipe for git deployment using tags in a multistage environment. 2 | 3 | The best thing about this recipe is that there is almost nothing to learn -- your cap deploy process barely changes. 4 | Gitflow simply adds some tagging/logging/workflow magic. 5 | 6 | # BEFORE 7 | $ cap deploy # 'master' goes to staging 8 | $ cap production deploy # 'master' goes to production 9 | 10 | # AFTER 11 | $ cap deploy 12 | # 'master' goes to staging; tag staging-YYYY-MM-DD.X created 13 | 14 | $ cap production deploy 15 | # deploys latest staging tag, or if last tag is a production tag then that, to production 16 | # displays a commit log of what will be pushed to production, requests confirmation before deploying 17 | # tag 'staging-YYYY-MM-DD-X' goes to production 18 | # tag 'production-YYYY-MM-DD-X' created; points to staging-YYYY-MM-DD-X 19 | 20 | # BONUS 21 | $ cap gitflow:commit_log 22 | # displays a commit log pushed to staging 23 | 24 | $ cap production gitflow:commit_log 25 | # displays a commit log of what will be pushed to production 26 | 27 | == INSTALLATION 28 | 29 | First, install the gem: 30 | 31 | gem install capistrano-gitflow 32 | 33 | Then update config/deploy.rb 34 | 35 | require 'capistrano/ext/multistage' 36 | require 'capistrano/gitflow' # needs to come after multistage 37 | 38 | More info at: http://rubygems.org/gems/capistrano-gitflow 39 | 40 | == DETAILS 41 | 42 | After experimenting with several workflows for deployment in git, I've finally found one I really like. 43 | 44 | * You can push to staging at any time; every staging push is automatically tagged with a unique tag. 45 | * You can only push a staging tag to production. This helps to enforce QA of all pushes to production. 46 | 47 | === PUSH TO STAGING 48 | 49 | Whenever you want to push the currently checked-out code to staging, just do: 50 | 51 | cap staging deploy 52 | 53 | gitflow will automatically: 54 | 55 | * create a unique tag in the format of 'staging-YYYY-MM-DD.X' 56 | * configure multistage to use that tag for the deploy 57 | * push the code and tags to the remote "origin" 58 | * and run the normal deploy task for the staging stage. 59 | 60 | === PUSH TO PRODUCTION: 61 | 62 | Whenever you want to push code to production, just do: 63 | 64 | cap production deploy 65 | 66 | gitflow will automatically: 67 | 68 | * determine the last staging tag created, show a commit log of last-production-tag..last-staging-tag 69 | * (alternatively, specify the tag to push to production via `-s tag=staging-YYYY-MM-DD-X-user-description` 70 | * prompt for confirmation of deploy 71 | * alias the staging tag to a production tag like: production-2008-09-08.2 72 | * configure multistage to use that tag for the deploy 73 | * push the code and tags to the remote "origin" 74 | * and run the normal deploy task for the production stage. 75 | 76 | === CLEANING OLD TAGS: 77 | Whenever you want to clean the old tags generated by this gem you can simply run this command. 78 | 79 | cap gitflow:cleanup_tags 80 | 81 | This will delete all tags that have this pattern [staging|production]{1}-[0-9]{4}-[0-9]{2}-[0-9]{2}\-([0-9]*) 82 | The task will delete first the local tasks and then the remote tasks from your git repository. 83 | For more details you can check here: https://github.com/technicalpickles/capistrano-gitflow/blob/master/lib/capistrano/gitflow/helpers/helper.rb#L219 84 | 85 | === NOTES: 86 | 87 | * you may need to wipe out the cached-copy on the remote server that cap uses when switching to this workflow; I have seen situations where the cached copy cannot cleanly checkout to the new branch/tag. it's safe to try without wiping it out first, it will fail gracefully. 88 | * if your stages already have a "set :branch, 'my-staging-branch'" call in your configs, remove it. This workflow configures it automatically. 89 | 90 | == CREDIT 91 | 92 | Originally created by Alan Pinstein. 93 | 94 | Gemified and hacked by Josh Nichols. 95 | 96 | == LICENSE 97 | 98 | MIT licensed. 99 | 100 | Copyright (c) 2009-2011 Alan Pinstein 101 | 102 | Copyright (c) 2010-2011 Josh Nichols 103 | 104 | Permission is hereby granted, free of charge, to any person obtaining a copy 105 | of this software and associated documentation files (the "Software"), to deal 106 | in the Software without restriction, including without limitation the rights 107 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 108 | copies of the Software, and to permit persons to whom the Software is 109 | furnished to do so, subject to the following conditions: 110 | 111 | The above copyright notice and this permission notice shall be included in 112 | all copies or substantial portions of the Software. 113 | 114 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 115 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 116 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 117 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 118 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 119 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 120 | THE SOFTWARE. 121 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | begin 5 | require 'jeweler' 6 | Jeweler::Tasks.new do |gem| 7 | gem.name = "capistrano-gitflow" 8 | gem.summary = %Q{Capistrano recipe for a deployment workflow based on git tags } 9 | gem.description = %Q{Capistrano recipe for a deployment workflow based on git tags} 10 | gem.email = "josh@technicalpickles.com" 11 | gem.homepage = "http://github.com/technicalpickles/capistrano-gitflow" 12 | gem.authors = ["Joshua Nichols"] 13 | gem.add_dependency "capistrano" 14 | gem.add_dependency "stringex" 15 | gem.add_development_dependency "rspec", ">= 1.2.9" 16 | # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings 17 | end 18 | Jeweler::GemcutterTasks.new 19 | rescue LoadError 20 | puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler" 21 | end 22 | 23 | require 'spec/rake/spectask' 24 | Spec::Rake::SpecTask.new(:spec) do |spec| 25 | spec.libs << 'lib' << 'spec' 26 | spec.spec_files = FileList['spec/**/*_spec.rb'] 27 | end 28 | 29 | Spec::Rake::SpecTask.new(:rcov) do |spec| 30 | spec.libs << 'lib' << 'spec' 31 | spec.pattern = 'spec/**/*_spec.rb' 32 | spec.rcov = true 33 | end 34 | 35 | task :spec => :check_dependencies 36 | 37 | task :default => :spec 38 | 39 | require 'rake/rdoctask' 40 | Rake::RDocTask.new do |rdoc| 41 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 42 | 43 | rdoc.rdoc_dir = 'rdoc' 44 | rdoc.title = "gitflow #{version}" 45 | rdoc.rdoc_files.include('README*') 46 | rdoc.rdoc_files.include('lib/**/*.rb') 47 | end 48 | -------------------------------------------------------------------------------- /capistrano-gitflow.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | require File.expand_path("../lib/capistrano/gitflow/version", __FILE__) 7 | 8 | Gem::Specification.new do |s| 9 | s.name = %q{capistrano-gitflow} 10 | s.version = CapistranoGitFlow.gem_version 11 | 12 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 13 | s.authors = ["Joshua Nichols"] 14 | s.date =%q{2011-04-07} 15 | s.description = %q{Capistrano recipe for a deployment workflow based on git tags} 16 | s.email = %q{josh@technicalpickles.com} 17 | s.extra_rdoc_files = [ 18 | "README.rdoc" 19 | ] 20 | s.files = `git ls-files`.split("\n") 21 | 22 | s.homepage = %q{http://github.com/technicalpickles/capistrano-gitflow} 23 | s.require_paths = ["lib"] 24 | s.rubygems_version = %q{1.3.7} 25 | s.summary = %q{Capistrano recipe for a deployment workflow based on git tags} 26 | s.test_files =s.files.grep(/^(spec)/) 27 | 28 | if s.respond_to? :specification_version then 29 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 30 | s.specification_version = 3 31 | 32 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 33 | s.add_runtime_dependency(%q, [">= 0"]) 34 | s.add_runtime_dependency(%q, [">= 0"]) 35 | s.add_development_dependency(%q, [">= 1.2.9"]) 36 | else 37 | s.add_dependency(%q, [">= 0"]) 38 | s.add_dependency(%q, [">= 0"]) 39 | s.add_dependency(%q, [">= 1.2.9"]) 40 | end 41 | else 42 | s.add_dependency(%q, [">= 0"]) 43 | s.add_dependency(%q, [">= 0"]) 44 | s.add_dependency(%q, [">= 1.2.9"]) 45 | end 46 | end 47 | 48 | -------------------------------------------------------------------------------- /lib/capistrano/gitflow.rb: -------------------------------------------------------------------------------- 1 | # require 'capistrano' 2 | require 'stringex' 3 | Gem.find_files('capistrano/gitflow/helpers/**/*.rb').each { |path| require path } 4 | require 'capistrano/version' 5 | 6 | self.extend CapistranoGitFlow::Helper 7 | include CapistranoGitFlow::Helper 8 | 9 | unless defined?(Sinatra) 10 | if gitflow_using_cap3? 11 | require 'capistrano/all' 12 | require File.join(File.dirname(__FILE__), 'tasks', 'gitflow') 13 | else 14 | require 'capistrano' 15 | require File.join(File.dirname(__FILE__), 'gitflow','legacy', 'gitflow') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/capistrano/gitflow/helpers/helper.rb: -------------------------------------------------------------------------------- 1 | module CapistranoGitFlow 2 | module Helper 3 | 4 | 5 | def gitflow_stage 6 | original_stage = fetch(:stage) 7 | original_stage.to_s.include?(":") ? original_stage.split(':').reverse[0] : original_stage 8 | end 9 | 10 | def gitflow_using_cap3? 11 | defined?(Capistrano::VERSION) && Capistrano::VERSION.to_s.split('.').first.to_i >= 3 12 | end 13 | 14 | def gitflow_callbacks 15 | if gitflow_using_cap3? 16 | before "deploy", "gitflow:verify_up_to_date" 17 | else 18 | before "deploy:update_code", "gitflow:verify_up_to_date" 19 | end 20 | after "gitflow:verify_up_to_date", "gitflow:calculate_tag" 21 | end 22 | 23 | def gitflow_find_task(name) 24 | defined?(::Rake) ? ::Rake::Task[name] : exists?(name) 25 | rescue 26 | nil 27 | end 28 | 29 | def gitflow_execute_task(name) 30 | defined?(::Rake) ? gitflow_find_task(name).invoke : find_and_execute_task(name) 31 | end 32 | 33 | def gitflow_capistrano_tag 34 | defined?(capistrano_configuration) ? capistrano_configuration[:tag] : ENV['TAG'] 35 | end 36 | 37 | def gitflow_last_tag_matching(pattern) 38 | # search for most recent (chronologically) tag matching the passed pattern, then get the name of that tag. 39 | last_tag = `git describe --exact-match --tags --match='#{pattern}' $(git log --tags='#{pattern}*' -n1 --pretty='%h')`.chomp 40 | last_tag == '' ? nil : last_tag 41 | end 42 | 43 | def gitflow_last_staging_tag 44 | gitflow_last_tag_matching('staging-*') 45 | end 46 | 47 | def gitflow_ask_confirm(message) 48 | if gitflow_using_cap3? 49 | $stdout.print "#{message}" 50 | $stdin.gets.to_s.chomp 51 | else 52 | Capistrano::CLI.ui.ask("#{message}") 53 | end 54 | end 55 | 56 | def gitflow_next_staging_tag 57 | hwhen = Time.now.utc.to_date.to_s 58 | who = `whoami`.chomp.to_url 59 | what = ENV['TAG_NAME'] ? ENV['TAG_NAME'] : gitflow_ask_confirm("What does this release introduce? (this will be normalized and used in the tag for this release) ") 60 | 61 | abort "No tag has been provided: #{what.inspect}" if what == '' 62 | 63 | last_staging_tag = gitflow_last_tag_matching("staging-#{hwhen}-*") 64 | new_tag_serial = if last_staging_tag && last_staging_tag =~ /staging-[0-9]{4}-[0-9]{2}-[0-9]{2}\-([0-9]*)/ 65 | $1.to_i + 1 66 | else 67 | 1 68 | end 69 | 70 | "#{gitflow_stage}-#{hwhen}-#{new_tag_serial}-#{who}-#{what.to_url}" 71 | end 72 | 73 | def gitflow_last_production_tag() 74 | gitflow_last_tag_matching('production-*') 75 | end 76 | 77 | def gitflow_using_git? 78 | fetch(:scm, :git).to_sym == :git 79 | end 80 | 81 | 82 | def gitflow_verify_up_to_date 83 | if gitflow_using_git? 84 | set :local_branch, `git branch --no-color 2> /dev/null | sed -e '/^[^*]/d'`.gsub(/\* /, '').chomp 85 | set :local_sha, `git log --pretty=format:%H HEAD -1`.chomp 86 | set :origin_sha, `git log --pretty=format:%H #{fetch(:local_branch)} -1` 87 | unless fetch(:local_sha) == fetch(:origin_sha) 88 | abort """ 89 | Your #{fetch(:local_branch)} branch is not up to date with origin/#{fetch(:local_branch)}. 90 | Please make sure you have pulled and pushed all code before deploying: 91 | 92 | git pull origin #{fetch(:local_branch)} 93 | # run tests, etc 94 | git push origin #{fetch(:local_branch)} 95 | 96 | """ 97 | end 98 | end 99 | end 100 | 101 | 102 | 103 | def gitflow_calculate_tag 104 | if gitflow_using_git? 105 | # make sure we have any other deployment tags that have been pushed by others so our auto-increment code doesn't create conflicting tags 106 | `git fetch` 107 | rake_task_name = "gitflow:tag_#{gitflow_stage}" 108 | task_exists = gitflow_find_task(rake_task_name) 109 | if !task_exists.nil? && task_exists!= false 110 | 111 | gitflow_execute_task(rake_task_name) 112 | 113 | system "git push --tags origin #{fetch(:local_branch)}" 114 | if $? != 0 115 | abort "git push failed" 116 | end 117 | else 118 | puts "Will deploy tag: #{fetch(:local_branch)}" 119 | set :branch, fetch(:local_branch) 120 | end 121 | end 122 | end 123 | 124 | def gitflow_commit_log 125 | from_tag = if gitflow_stage.to_s == 'production' 126 | gitflow_last_production_tag 127 | elsif gitflow_stage.to_s == 'staging' 128 | gitflow_last_staging_tag 129 | else 130 | abort "Unsupported stage #{gitflow_stage}" 131 | end 132 | 133 | # no idea how to properly test for an optional cap argument a la '-s tag=x' 134 | to_tag = gitflow_capistrano_tag 135 | to_tag ||= begin 136 | puts "Calculating 'end' tag for :commit_log for '#{gitflow_stage}'" 137 | to_tag = if gitflow_stage.to_s == 'production' 138 | gitflow_last_staging_tag 139 | elsif gitflow_stage.to_s == 'staging' 140 | 'master' 141 | else 142 | abort "Unsupported stage #{gitflow_stage}" 143 | end 144 | end 145 | 146 | 147 | 148 | # use custom compare command if set 149 | if ENV['git_log_command'] && ENV['git_log_command'].strip != '' 150 | command = "git #{ENV['git_log_command']} #{from_tag}..#{to_tag}" 151 | else 152 | # default compare command 153 | # be awesome for github 154 | if `git config remote.origin.url` =~ /git@github.com:(.*)\/(.*).git/ 155 | command = "open https://github.com/#{$1}/#{$2}/compare/#{from_tag}...#{to_tag}" 156 | else 157 | command = "git log #{from_tag}..#{to_tag}" 158 | end 159 | end 160 | puts "Displaying commits from #{from_tag} to #{to_tag} via:\n#{command}" 161 | system command 162 | 163 | puts "" 164 | end 165 | 166 | 167 | def gitflow_tag_staging 168 | current_sha = `git log --pretty=format:%H HEAD -1` 169 | last_staging_tag_sha = if gitflow_last_staging_tag 170 | `git log --pretty=format:%H #{gitflow_last_staging_tag} -1` 171 | end 172 | 173 | if last_staging_tag_sha == current_sha 174 | puts "Not re-tagging staging because latest tag (#{gitflow_last_staging_tag}) already points to HEAD" 175 | new_staging_tag = gitflow_last_staging_tag 176 | else 177 | new_staging_tag = gitflow_next_staging_tag 178 | puts "Tagging current branch for deployment to staging as '#{new_staging_tag}'" 179 | system "git tag -a -m 'tagging current code for deployment to staging' #{new_staging_tag}" 180 | end 181 | 182 | set :branch, new_staging_tag 183 | end 184 | 185 | 186 | def gitflow_tag_production 187 | promote_to_production_tag = gitflow_capistrano_tag || gitflow_last_staging_tag 188 | 189 | unless promote_to_production_tag && promote_to_production_tag =~ /staging-.*/ 190 | abort "Couldn't find a staging tag to deploy; use '-s tag=staging-YYYY-MM-DD.X'" 191 | end 192 | unless gitflow_last_tag_matching(promote_to_production_tag) 193 | abort "Staging tag #{promote_to_production_tag} does not exist." 194 | end 195 | 196 | promote_to_production_tag =~ /^staging-(.*)$/ 197 | new_production_tag = "production-#{$1}" 198 | 199 | if new_production_tag == gitflow_last_production_tag 200 | puts "Not re-tagging #{gitflow_last_production_tag} because it already exists" 201 | really_deploy = gitflow_ask_confirm("Do you really want to deploy #{gitflow_last_production_tag}? [y/N]") 202 | 203 | exit(1) unless really_deploy.to_url =~ /^[Yy]$/ 204 | else 205 | puts "Preparing to promote staging tag '#{promote_to_production_tag}' to '#{new_production_tag}'" 206 | gitflow_commit_log 207 | unless gitflow_capistrano_tag 208 | really_deploy = gitflow_ask_confirm("Do you really want to deploy #{new_production_tag}? [y/N]") 209 | 210 | exit(1) unless really_deploy.to_url =~ /^[Yy]$/ 211 | end 212 | puts "Promoting staging tag #{promote_to_production_tag} to production as '#{new_production_tag}'" 213 | system "git tag -a -m 'tagging current code for deployment to production' #{new_production_tag} #{promote_to_production_tag}" 214 | end 215 | 216 | set :branch, new_production_tag 217 | end 218 | 219 | def gitflow_cleanup_tags 220 | return if fetch(:gitflow_keep_tags).nil? 221 | tags = `git log --tags --pretty="format:%at %D" | grep 'tag:' |sort -n | awk '{$1=""; print $0}' | tr "," "\n"| sed 's/tag:*//' | sed -e 's/^[ \t]*//'` 222 | tags = tags.split.reject{|tag| tag.nil? || tag.empty? } 223 | tags = tags.select { |tag| tag =~ /^(staging|production){1}-[0-9]{4}-[0-9]{2}-[0-9]{2}\-([0-9]*)/ } 224 | if tags.count >= fetch(:gitflow_keep_tags) 225 | puts "Keeping #{fetch(:gitflow_keep_tags)} Tags from total #{tags.count}" 226 | tags_to_delete = (tags - tags.last(fetch(:gitflow_keep_tags))) 227 | if tags_to_delete.any? 228 | system "git tag -d #{tags_to_delete.join(' ')}" 229 | tags_with_dots = tags_to_delete.map{ |tag| tag.prepend(':refs/tags/') }.join(' ') 230 | system "git push origin #{tags_with_dots}" 231 | else 232 | puts "No tags to delete" 233 | end 234 | else 235 | puts "No tags to delete" 236 | end 237 | 238 | end 239 | 240 | end 241 | end 242 | -------------------------------------------------------------------------------- /lib/capistrano/gitflow/helpers/natcmp.rb: -------------------------------------------------------------------------------- 1 | # natcmp.rb 2 | # 3 | # Natural order comparison of two strings 4 | # e.g. "my_prog_v1.1.0" < "my_prog_v1.2.0" < "my_prog_v1.10.0" 5 | # which does not follow alphabetically 6 | # 7 | # Based on Martin Pool's "Natural Order String Comparison" originally written in C 8 | # http://sourcefrog.net/projects/natsort/ 9 | # 10 | # This implementation is Copyright (C) 2003 by Alan Davies 11 | # 12 | # 13 | # This software is provided 'as-is', without any express or implied 14 | # warranty. In no event will the authors be held liable for any damages 15 | # arising from the use of this software. 16 | # 17 | # Permission is granted to anyone to use this software for any purpose, 18 | # including commercial applications, and to alter it and redistribute it 19 | # freely, subject to the following restrictions: 20 | # 21 | # 1. The origin of this software must not be misrepresented; you must not 22 | # claim that you wrote the original software. If you use this software 23 | # in a product, an acknowledgment in the product documentation would be 24 | # appreciated but is not required. 25 | # 2. Altered source versions must be plainly marked as such, and must not be 26 | # misrepresented as being the original software. 27 | # 3. This notice may not be removed or altered from any source distribution. 28 | 29 | class String 30 | 31 | # 'Natural order' comparison of two strings 32 | def String.natcmp(str1, str2, caseInsensitive=false) 33 | str1, str2 = str1.dup, str2.dup 34 | compareExpression = /^(\D*)(\d*)(.*)$/ 35 | 36 | if caseInsensitive 37 | str1.downcase! 38 | str2.downcase! 39 | end 40 | 41 | # Remove all whitespace 42 | str1.gsub!(/\s*/, '') 43 | str2.gsub!(/\s*/, '') 44 | 45 | while (str1.length > 0) or (str2.length > 0) do 46 | # Extract non-digits, digits and rest of string 47 | str1 =~ compareExpression 48 | chars1, num1, str1 = $1.dup, $2.dup, $3.dup 49 | 50 | str2 =~ compareExpression 51 | chars2, num2, str2 = $1.dup, $2.dup, $3.dup 52 | 53 | # Compare the non-digits 54 | case (chars1 <=> chars2) 55 | when 0 # Non-digits are the same, compare the digits... 56 | # If either number begins with a zero, then compare alphabetically, 57 | # otherwise compare numerically 58 | if (num1[0] != 48) and (num2[0] != 48) 59 | num1, num2 = num1.to_i, num2.to_i 60 | end 61 | 62 | case (num1 <=> num2) 63 | when -1 then return -1 64 | when 1 then return 1 65 | end 66 | when -1 then return -1 67 | when 1 then return 1 68 | end # case 69 | 70 | end # while 71 | 72 | # Strings are naturally equal 73 | return 0 74 | end 75 | 76 | end # class String 77 | -------------------------------------------------------------------------------- /lib/capistrano/gitflow/legacy/gitflow.rb: -------------------------------------------------------------------------------- 1 | require 'capistrano' 2 | 3 | module Capistrano 4 | class Gitflow 5 | def self.load_into(capistrano_configuration) 6 | capistrano_configuration.load File.join(File.dirname(File.dirname(File.dirname(__FILE__))), 'tasks', 'gitflow.rb') 7 | end 8 | end 9 | end 10 | 11 | if Capistrano::Configuration.instance 12 | Capistrano::Gitflow.load_into(Capistrano::Configuration.instance) 13 | end 14 | -------------------------------------------------------------------------------- /lib/capistrano/gitflow/version.rb: -------------------------------------------------------------------------------- 1 | module CapistranoGitFlow # Returns the version of the currently loaded Rails as a Gem::Version 2 | def self.gem_version 3 | Gem::Version.new CapistranoGitFlow::VERSION::STRING 4 | end 5 | 6 | module VERSION 7 | MAJOR = 1 8 | MINOR = 6 9 | TINY = 0 10 | PRE = nil 11 | 12 | STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') 13 | end 14 | end 15 | 16 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/gitflow.rb: -------------------------------------------------------------------------------- 1 | 2 | namespace :gitflow do 3 | 4 | task :verify_up_to_date do 5 | gitflow_verify_up_to_date 6 | end 7 | 8 | desc "Calculate the tag to deploy" 9 | task :calculate_tag do 10 | gitflow_calculate_tag 11 | end 12 | 13 | desc "Show log between most recent staging tag (or given tag=XXX) and last production release." 14 | task :commit_log do 15 | gitflow_commit_log 16 | end 17 | 18 | desc "Mark the current code as a staging/qa release" 19 | task :tag_staging do 20 | gitflow_tag_staging 21 | end 22 | 23 | desc "Push the approved tag to production. Pass in tag to deploy with '-s tag=staging-YYYY-MM-DD-X-feature'." 24 | task :tag_production do 25 | gitflow_tag_production 26 | end 27 | 28 | task :cleanup_tags do 29 | gitflow_cleanup_tags 30 | end 31 | 32 | gitflow_callbacks 33 | end 34 | 35 | namespace :deploy do 36 | namespace :pending do 37 | task :compare do 38 | gitflow_execute_task("gitflow:commit_log") 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /recipes/gitflow_recipes.rb: -------------------------------------------------------------------------------- 1 | # Just need to add to LOAD_PATH, so we can require 'gitflow' 2 | $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') 3 | -------------------------------------------------------------------------------- /spec/gitflow_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe "Gitflow" do 4 | it "fails" do 5 | fail "hey buddy, you should probably rename this file and start specing for real" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 2 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 3 | require 'gitflow' 4 | require 'spec' 5 | require 'spec/autorun' 6 | 7 | Spec::Runner.configure do |config| 8 | 9 | end 10 | --------------------------------------------------------------------------------