desc 'deploy [STAGE]', 'Guided deployment across branches' long_desc <<-LONGDESC Example: `geordi deploy` or `geordi deploy p[roduction]` or `geordi deploy --current-branch` Merge, push and deploy with a single command! **It always tells what it will do before it does it.** There are different scenarios where this command is handy: - *Production deploy:* From the master branch, run `geordi deploy production`. This will merge `master` to `production`, push and deploy to production. - *Feature branch deploy:* From a feature branch, run `geordi deploy staging`. This will merge the feature branch to `master`, push and deploy to staging. To deploy a feature branch directly without merging, run `geordi deploy --current-branch`. This feature depends on the environment variable `DEPLOY_BRANCH` to be picked up in the respective deploy file. - *Simple deploy:* If the source branch matches the target branch, merging will be skipped. Calling the command without arguments will infer the target stage from the current branch and fall back to master/staging. Finds available Capistrano stages by their prefix, e.g. `geordi deploy p` will deploy production, `geordi deploy mak` will deploy a `makandra` stage if there is a file config/deploy/makandra.rb. When your project is running Capistrano 3, deployment will use `cap deploy` instead of `cap deploy:migrations`. You can force using `deploy` by passing the -M option: `geordi deploy -M staging`. LONGDESC option :no_migrations, aliases: '-M', type: :boolean, desc: 'Run cap deploy instead of cap deploy:migrations' option :current_branch, aliases: '-c', type: :boolean, desc: 'Set DEPLOY_BRANCH to the current branch during deploy' def deploy(target_stage = nil) # Set/Infer default values branch_stage_map = { 'master' => 'staging', 'production' => 'production' } if target_stage && !Util.deploy_targets.include?(target_stage) # Target stage autocompletion from available stages target_stage = Util.deploy_targets.find { |t| t.start_with? target_stage } target_stage || Interaction.warn('Given deployment stage not found') end # Ask for required information target_stage ||= Interaction.prompt 'Deployment stage:', branch_stage_map.fetch(Util.current_branch, 'staging') if options.current_branch stage_file = "config/deploy/#{target_stage}.rb" Util.file_containing?(stage_file, 'DEPLOY_BRANCH') || Interaction.fail(<<~ERROR) To deploy from the current branch, configure #{stage_file} to respect the environment variable DEPLOY_BRANCH. Example: set :branch, ENV['DEPLOY_BRANCH'] || 'master' ERROR source_branch = target_branch = Util.current_branch else source_branch = Interaction.prompt 'Source branch:', Util.current_branch target_branch = Interaction.prompt 'Deploy branch:', branch_stage_map.invert.fetch(target_stage, 'master') end merge_needed = (source_branch != target_branch) push_needed = merge_needed || `git cherry -v | wc -l`.strip.to_i > 0 push_needed = false if Util.testing? # Hard to test Interaction.announce "Checking whether your #{source_branch} branch is ready" ############ Util.run!("git checkout #{source_branch}") if (`git status -s | wc -l`.strip != '0') && !Util.testing? Interaction.warn "Your #{source_branch} branch holds uncommitted changes." Interaction.prompt('Continue anyway?', 'n', /y|yes/) || raise('Cancelled.') else Interaction.note 'All good.' end if merge_needed Interaction.announce "Checking what's in your #{target_branch} branch right now" ####### Util.run!("git checkout #{target_branch} && git pull") end Interaction.announce 'You are about to:' ################################################# Interaction.note "Merge branch #{source_branch} into #{target_branch}" if merge_needed if push_needed Interaction.note 'Push these commits:' if push_needed Util.run!("git --no-pager log origin/#{target_branch}..#{source_branch} --oneline") end Interaction.note "Deploy to #{target_stage}" Interaction.note "From current branch #{source_branch}" if options.current_branch if Interaction.prompt('Go ahead with the deployment?', 'n', /y|yes/) puts git_call = [] git_call << "git merge #{source_branch}" if merge_needed git_call << 'git push' if push_needed invoke_geordi 'bundle_install' capistrano_call = "cap #{target_stage} deploy" capistrano_call << ':migrations' unless Util.gem_major_version('capistrano') == 3 || options.no_migrations capistrano_call = "bundle exec #{capistrano_call}" if Util.file_containing?('Gemfile', /capistrano/) capistrano_call = "DEPLOY_BRANCH=#{source_branch} #{capistrano_call}" if options.current_branch if git_call.any? Util.run!(git_call.join(' && '), show_cmd: true) end Util.run!(capistrano_call, show_cmd: true) Interaction.success 'Deployment complete.' else Util.run!("git checkout #{source_branch}") Interaction.fail 'Deployment cancelled.' end end