cap_git_tools 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +170 -0
- data/Rakefile +1 -0
- data/cap_git_tools.gemspec +22 -0
- data/lib/cap_git_tools.rb +5 -0
- data/lib/cap_git_tools/task_helpers.rb +194 -0
- data/lib/cap_git_tools/tasks.rb +301 -0
- data/lib/cap_git_tools/version.rb +3 -0
- metadata +71 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# cap_git_tools
|
2
|
+
|
3
|
+
Re-usable, composable [Capistrano](https://github.com/capistrano/capistrano) tasks for git tagging and other work with the
|
4
|
+
git repository you use for your Cap deploys.
|
5
|
+
|
6
|
+
* Ensure your local git is committed and pushed, so you are deploying what you
|
7
|
+
think you are.
|
8
|
+
* Automatically git tag the deploy
|
9
|
+
* Enforce a [multistage](https://github.com/capistrano/capistrano/wiki/2.x-Multistage-Extension) workflow where only a tagged staging release can be deployed
|
10
|
+
to production (ala [gitflow](https://github.com/apinstein/git-deployment))
|
11
|
+
|
12
|
+
Functionality is split into discrete yet composable tasks, with sensible defaults
|
13
|
+
but configurable, so you can build a cap recipe that fits *your*
|
14
|
+
requirements and workflow. Ordinary single stage or multi-stage; with or without
|
15
|
+
interactive confirmation prompts; using git however you're already or would like
|
16
|
+
to be using it.
|
17
|
+
|
18
|
+
(_Inspired by Alan Pinstein and Josh Nichols' neat
|
19
|
+
[gitflow](https://github.com/apinstein/git-deployment), but refactored for more
|
20
|
+
flexiblity with less hardcoded workflow. Some functionality changed in the
|
21
|
+
process.)_
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
gem install cap_git_tools
|
26
|
+
|
27
|
+
Or if in the context of something using bundler such as Rails, add to Gemfile
|
28
|
+
eg:
|
29
|
+
|
30
|
+
gem 'cap_git_tools', :group => :development
|
31
|
+
|
32
|
+
Add to top of a relevant Capistrano file (such as config/deploy.rb ordinarily):
|
33
|
+
|
34
|
+
require 'cap_git_tools/tasks'
|
35
|
+
|
36
|
+
This makes cap_git_tool's tasks available to you, but doesn't automatically wire
|
37
|
+
them up to be used by your `cap deploy`. See below.
|
38
|
+
|
39
|
+
You probably want to make sure you have an `ssh-agent` set up, or you'll have to
|
40
|
+
enter your private key password to access git several times. You will need to be
|
41
|
+
running your cap recipes from a directory with a git checkout (usual behavior
|
42
|
+
for cap usage, but not actually required otherwise by cap).
|
43
|
+
|
44
|
+
## Ensure git is committed and pushed when deploying
|
45
|
+
|
46
|
+
Have you ever deployed the 'wrong' thing, because you forgot to commit and/or
|
47
|
+
push your changes to git? I have.
|
48
|
+
|
49
|
+
Have cap make sure you're committed and pushed before deploying by adding to
|
50
|
+
your recipe in deploy.rb:
|
51
|
+
|
52
|
+
before "deploy:update_code", "git:guard_committed", "git:guard_upstream"
|
53
|
+
|
54
|
+
Or use just one or the other
|
55
|
+
|
56
|
+
* `guard_committed` makes sure you have no uncommitted changes. _IF_ you have a :branch set
|
57
|
+
in your cap recipe, it will also make sure your curent checkout matches that branch.
|
58
|
+
* `guard_upstream` makes sure the current working copy branch committed tip (or local branch matching Cap
|
59
|
+
:branch, if set) matches the upstream remote version.
|
60
|
+
|
61
|
+
## Automatically tag on deploy
|
62
|
+
|
63
|
+
Every time you deploy, want to have Capistrano automatically tag exactly what
|
64
|
+
gets deployed, with a tag like "deploy-2012-04-11-1517"?
|
65
|
+
|
66
|
+
Add this to your Cap recipe, usefully combining with the tasks to make sure
|
67
|
+
your git copy is 'clean' as discussed above:
|
68
|
+
|
69
|
+
before "deploy:update_code", "git:guard_committed", "git:guard_pushed", "git:tag"
|
70
|
+
|
71
|
+
That's a date and timestamp, deploy-yyyy-mm-dd-hhmm.
|
72
|
+
|
73
|
+
If you are using multistage, instead of "deploy-" as a prefix, it'll use
|
74
|
+
the current stage name like "production" or "staging" (but see below for
|
75
|
+
fancier multi-stage workflow).
|
76
|
+
|
77
|
+
Ordinarily what's in your current git checkout will be tagged; but if
|
78
|
+
you have set cap's `:branch`, it'll tag and deploy the HEAD of that branch
|
79
|
+
even if that's not your current checkout.
|
80
|
+
|
81
|
+
You can customize the prefix and other aspects of tagging, both in your recipe
|
82
|
+
and with command line over-rides, see `cap -E git:tag` for more info.
|
83
|
+
|
84
|
+
## Multistage workflow
|
85
|
+
|
86
|
+
Are you using Capistrano's [multistage
|
87
|
+
extension](https://github.com/capistrano/capistrano/wiki/2.x-Multistage-Extension)?
|
88
|
+
In one commonly desired multistage workflow (similar to what
|
89
|
+
[gitflow](https://github.com/apinstein/git-deployment) enforces):
|
90
|
+
|
91
|
+
* Under staging, you want automatic tagging with staging-yyyy-mm-dd-hhmm, just
|
92
|
+
as above under 'Automatically tag on deploy'. Add to your `config/staging.rb`:
|
93
|
+
|
94
|
+
before "deploy:update_code", "git:guard_committed", "git:guard_upstream", "git:tag"
|
95
|
+
|
96
|
+
* Under production, you want to take the most recent 'staging' tag, and promote
|
97
|
+
it by deploying that tag to production, re-tagging with a "production-" tag.
|
98
|
+
Maybe you also want to print out the commit log between the last production
|
99
|
+
tag and what you're about to deploy, and require interactive confirmation.
|
100
|
+
Add to your `config/deploy.rb`:
|
101
|
+
|
102
|
+
before "deploy:update_code", "git:commit_log", "git:retag"
|
103
|
+
set :confirm_tag, true
|
104
|
+
|
105
|
+
Say you `cap staging deploy` on April 1 2012 at noon, your deploy will be
|
106
|
+
tagged `staging-2012-04-01-1200`.
|
107
|
+
|
108
|
+
Say on April 2 at noon, you run `cap production deploy`
|
109
|
+
|
110
|
+
* you'll be a shown a commit log of changes between the previous `production-`
|
111
|
+
commit and your most recent `staging-` commit, `staging-2012-04-01-1200`.
|
112
|
+
(`git:commit_log`)
|
113
|
+
* You'll be asked to confirm, (`set :confirm_tag, true`)
|
114
|
+
* And then the deploy will happen, with new tag added `production-2012-04-02-1200`
|
115
|
+
(`git:retag`).
|
116
|
+
* Note it's timestamped with date of production deploy. The commit message
|
117
|
+
for the `production-` tag will say which `staging-` tag was retagged.
|
118
|
+
|
119
|
+
The `git:retag` task has some configurable options (in your recipe or on the
|
120
|
+
individual command line invocation) too, see `cap -e git:retag`.
|
121
|
+
|
122
|
+
## Make your own recipe
|
123
|
+
|
124
|
+
Look at `cap -T git` to see the tasks added by cap_git_tools. Run `cap -e
|
125
|
+
taskname` to see expanded documentation info on each one, covering more
|
126
|
+
specifics of what it does and what cap variables can alter it's behavior.
|
127
|
+
|
128
|
+
Some behaviors can be customized by 'capistrano variables'. These can be set in
|
129
|
+
a recipe:
|
130
|
+
|
131
|
+
set :variable, "value"
|
132
|
+
|
133
|
+
Or set/over-ridden on the individual cap command line invocation:
|
134
|
+
|
135
|
+
cap deploy -s variable=value
|
136
|
+
|
137
|
+
Doesn't matter if you use cap '-s' or '-S', cap_git_tools tasks always lazily
|
138
|
+
look up these values.
|
139
|
+
|
140
|
+
## Other tools
|
141
|
+
|
142
|
+
`cap git:commit_log` to see the commits between the *last* tagged release
|
143
|
+
and what you'd deploy now with `cap deploy`. Works in singlestage recipe, or
|
144
|
+
multistage under 'cap staging git:commit_log' or 'cap production
|
145
|
+
git:commit_log'.
|
146
|
+
|
147
|
+
`cap git:show_tags` to show the last 3 deploy tags, with meta information.
|
148
|
+
Works in single stage recipe or multistage.
|
149
|
+
|
150
|
+
## To Be Done
|
151
|
+
|
152
|
+
* Tag names are automatically created with a year-month-day-hour-minute timestamp.
|
153
|
+
However, if you try to deploy again before the minute's changed on the clock,
|
154
|
+
the tasks will try to re-tag using an already used name. You'll get an error and
|
155
|
+
the task will abort, but the task could be written to catch this and add a
|
156
|
+
suffix. But it ain't yet.
|
157
|
+
|
158
|
+
* There is some limited experimental functionality to change the format and add
|
159
|
+
new components to the automatically created tag name, using a
|
160
|
+
[:tag_template](https://github.com/jrochkind/cap_git_tools/blob/master/lib/cap_git_tools/task_helpers.rb#L162)
|
161
|
+
variable. This theoretically allows the `who` and `what` components used by
|
162
|
+
gitflow. But doing the 'right thing' in multistage (copying the 'what' from the
|
163
|
+
previous tag, but regenerating the rest) is a bit tricky, and hasn't been done
|
164
|
+
yet, which is what keeps this functionality limited and experimental at this
|
165
|
+
point.
|
166
|
+
|
167
|
+
## Let me know
|
168
|
+
|
169
|
+
Feedback, pull requests, complaints, welcome. Not sure if anyone's gonna use
|
170
|
+
this.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "cap_git_tools/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "cap_git_tools"
|
7
|
+
s.version = CapGitTools::VERSION
|
8
|
+
s.authors = ["Jonathan Rochkind"]
|
9
|
+
s.email = ["jonathan@dnil.net"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{re-usable, composable Capistrano tasks for git tagging and other work with a git repo}
|
12
|
+
|
13
|
+
s.rubyforge_project = "cap_git_tools"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
|
21
|
+
s.add_dependency "capistrano", "~> 2.0"
|
22
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# methods used by tasks defined in tasks.rb
|
2
|
+
#
|
3
|
+
# generally this module is 'include'd into a cap
|
4
|
+
# :namespace, seems to do what we want.
|
5
|
+
require 'cap_git_tools'
|
6
|
+
|
7
|
+
module CapGitTools::TaskHelpers
|
8
|
+
####
|
9
|
+
# Some functions used by the tasks
|
10
|
+
#
|
11
|
+
|
12
|
+
# say with an indent in spaces
|
13
|
+
def say_formatted(msg, options = {})
|
14
|
+
options.merge!(:indent => 4)
|
15
|
+
Capistrano::CLI.ui.say(' ' * options[:indent] + msg )
|
16
|
+
end
|
17
|
+
|
18
|
+
# execute a 'git fetch', but mark in a private variable that
|
19
|
+
# we have, so we only do it once per cap execution.
|
20
|
+
def ensure_git_fetch
|
21
|
+
unless @__git_fetched
|
22
|
+
local_sh "git fetch #{upstream_remote}"
|
23
|
+
@__git_fetched = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# execute locally as a shell command, echo'ing to output, as
|
28
|
+
# well as capturing error and aborting.
|
29
|
+
def local_sh(cmd)
|
30
|
+
say_formatted("executing locally: #{cmd}")
|
31
|
+
`#{cmd}`
|
32
|
+
abort("failed: #{cmd}") unless $? == 0
|
33
|
+
end
|
34
|
+
|
35
|
+
# How to refer to the upstream git repo configured in cap :repository?
|
36
|
+
# Will _usually_ return 'origin', will sometimes return another remote,
|
37
|
+
# will occasionally return a raw git url when it's not configured in
|
38
|
+
# remotes for some reason.
|
39
|
+
#
|
40
|
+
# This used to be hard-coded to 'origin'. Then it was configurable.
|
41
|
+
# Then I realized it _has_ to be whatever is set in cap :repository.
|
42
|
+
# We'll look up the remote alias for that, if available, and cache
|
43
|
+
# the lookup. Usually it'll be 'origin', yeah.
|
44
|
+
def upstream_remote
|
45
|
+
@__upstream_remote = begin
|
46
|
+
git_url = fetch(:repository)
|
47
|
+
|
48
|
+
remote_info =
|
49
|
+
`git remote -v`.
|
50
|
+
split("\n").
|
51
|
+
collect {|line| line.split(/[\t ]/) }.
|
52
|
+
find {|list| list[1] == git_url }
|
53
|
+
|
54
|
+
remote_info ? remote_info[0] : git_url
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# what branch we're going to tag and deploy -- if cap 'branch' is set,
|
60
|
+
# use that one, otherwise use current branch in checkout
|
61
|
+
def working_branch
|
62
|
+
@__git_working_branch ||= begin
|
63
|
+
if exists?("branch")
|
64
|
+
fetch(:branch)
|
65
|
+
else
|
66
|
+
b = `git symbolic-ref -q HEAD`.sub(%r{^refs/heads/}, '').chomp
|
67
|
+
b.empty? ? "HEAD" : b
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
# current SHA fingerprint of local branch mentioned in :branch
|
74
|
+
def local_sha
|
75
|
+
`git log --pretty=format:%H #{working_branch} -1`.chomp
|
76
|
+
end
|
77
|
+
|
78
|
+
def tag_prefix
|
79
|
+
fetch(:tag_prefix, fetch(:stage, "deploy"))
|
80
|
+
end
|
81
|
+
|
82
|
+
def from_prefix
|
83
|
+
fetch("from_prefix", "staging")
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# mostly used by git:retag, calculate the tag we'll be retagging FROM.
|
88
|
+
#
|
89
|
+
# can set cap :from_tag. Or else find last tag matching from_prefix,
|
90
|
+
# which by default is "staging-*"
|
91
|
+
def from_tag
|
92
|
+
t = nil
|
93
|
+
if exists?("from_tag")
|
94
|
+
t = fetch("from_tag")
|
95
|
+
else
|
96
|
+
t = fetch_last_tag( self.from_prefix )
|
97
|
+
|
98
|
+
if t.nil? || t.empty?
|
99
|
+
abort("failed: can't find existing tag matching #{self.from_prefix}-*")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
return t
|
103
|
+
end
|
104
|
+
|
105
|
+
# find the last (chronological) tag with given prefix.
|
106
|
+
# prefix can include shell-style wildcards like '*'. Defaults to
|
107
|
+
# last tag with current default tag_prefix.
|
108
|
+
#
|
109
|
+
# Note: Will only work on git 'full' annotated tags (those signed or
|
110
|
+
# with -m message or -a) because git only stores dates for annotated tags.
|
111
|
+
# others will end up sorted lexicographically BEFORE any annotated tags.
|
112
|
+
def fetch_last_tag(pattern_prefix = self.tag_prefix)
|
113
|
+
# make sure we've fetched to get latest from upstream.
|
114
|
+
ensure_git_fetch
|
115
|
+
|
116
|
+
# crazy git command, yeah. Sort by tagged date descending, one line only,
|
117
|
+
# output refname:short, look for tags matching our pattern.
|
118
|
+
last_tag = `git for-each-ref --count=1 --sort='-taggerdate' --format='%(refname:short)' 'refs/tags/#{pattern_prefix}-*' 2>/dev/null`.chomp
|
119
|
+
return nil if last_tag == ''
|
120
|
+
return last_tag
|
121
|
+
end
|
122
|
+
|
123
|
+
# show commit lot from commit-ish to commit-ish,
|
124
|
+
# using appropriate UI tool.
|
125
|
+
#
|
126
|
+
# If you have cap :github_browser_compare set and the remote is github,
|
127
|
+
# use `open` to open in browser.
|
128
|
+
#
|
129
|
+
# else if you have ENV['git_log_command'] set, pass to `git` (don't know
|
130
|
+
# what this is for, inherited from gitflow)
|
131
|
+
#
|
132
|
+
# else just use an ordinary command line git log
|
133
|
+
def show_commit_log(from_tag, to_tag)
|
134
|
+
if fetch("github_browser_compare", false ) && `git config remote.#{upstream_remote}.url` =~ /git@github.com:(.*)\/(.*).git/
|
135
|
+
# be awesome for github, use `open` in browser
|
136
|
+
command = "open https://github.com/#{$1}/#{$2}/compare/#{from_tag}...#{to_tag}"
|
137
|
+
elsif ENV['git_log_command'] && ENV['git_log_command'].strip != ''
|
138
|
+
# use custom compare command if set
|
139
|
+
command = "git #{ENV['git_log_command']} #{from_tag}..#{to_tag}"
|
140
|
+
else
|
141
|
+
# standard git log command
|
142
|
+
command = "git log #{from_tag}..#{to_tag}"
|
143
|
+
end
|
144
|
+
|
145
|
+
say_formatted "Displaying commits from #{from_tag} to #{to_tag}\n\n"
|
146
|
+
say_formatted command + "\n\n"
|
147
|
+
system command
|
148
|
+
puts "" # newline
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
def calculate_new_tag
|
153
|
+
# if capistrano :tag is already set, just use it
|
154
|
+
if exists?("tag")
|
155
|
+
return fetch("tag")
|
156
|
+
end
|
157
|
+
|
158
|
+
# otherwise calculate, based on template
|
159
|
+
|
160
|
+
tag_suffix = fetch("tag_template", "%{datetime}")
|
161
|
+
|
162
|
+
tag_suffix.gsub!(/\%\{([^}]+)\}/) do
|
163
|
+
case $1
|
164
|
+
when 'date'
|
165
|
+
Time.now.localtime.strftime('%Y-%m-%d')
|
166
|
+
when 'datetime'
|
167
|
+
Time.now.localtime.strftime('%Y-%m-%d-%H%M')
|
168
|
+
when 'what'
|
169
|
+
(@__git_what = Capistrano::CLI.ui.ask("What does this release introduce? (this will be normalized and used in the tag for this release) ").gsub(/[ '"]+/, "_"))
|
170
|
+
when 'who'
|
171
|
+
`whoami`.chomp
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
return "#{tag_prefix}-#{tag_suffix}"
|
176
|
+
end
|
177
|
+
|
178
|
+
# will prompt to confirm new tag, if :confirm_tag is true, otherwise
|
179
|
+
# no-op.
|
180
|
+
def guard_confirm_tag(new_tag)
|
181
|
+
if exists?("confirm_tag") && [true, "true"].include?( confirm_tag )
|
182
|
+
prompt = "Do you really want to deploy "
|
183
|
+
prompt += new_tag
|
184
|
+
prompt += " to #{stage}" if exists? :stage
|
185
|
+
prompt += "?"
|
186
|
+
|
187
|
+
unless Capistrano::CLI.ui.agree(prompt)
|
188
|
+
abort("exiting, user cancelled.")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
end
|
@@ -0,0 +1,301 @@
|
|
1
|
+
# Written using crazy meta-code cribbed from capistrano_ext so you
|
2
|
+
# can (and must) 'require' this file rather than 'load' it.
|
3
|
+
require 'capistrano'
|
4
|
+
|
5
|
+
unless Capistrano::Configuration.respond_to?(:instance)
|
6
|
+
abort "cap_git_tools requires Capistrano 2"
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'cap_git_tools/task_helpers'
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
Capistrano::Configuration.instance.load do
|
14
|
+
|
15
|
+
|
16
|
+
namespace :git do
|
17
|
+
# include our helper methods, I believe just into this namespace
|
18
|
+
# if we do it this way.
|
19
|
+
extend CapGitTools::TaskHelpers
|
20
|
+
|
21
|
+
desc <<-DESC
|
22
|
+
Ensure git working copy has no uncommitted changes, or abort.
|
23
|
+
|
24
|
+
If cap :branch is set, will also ensure git working copy is on same
|
25
|
+
branch as :cap branch.
|
26
|
+
|
27
|
+
The idea is to make sure you're deploying what you're looking at.
|
28
|
+
See also git:guard_upstream, you often want to use both to ensure
|
29
|
+
this.
|
30
|
+
|
31
|
+
before "git:tag", "git:check_committed", "git:check_upstream"
|
32
|
+
or
|
33
|
+
before "deploy", , "git:check_committed", "git:check_upstream"
|
34
|
+
|
35
|
+
setting cap :skip_guard_committed to true will skip even if task is
|
36
|
+
invoked. (eg, `cap deploy -s skip_guard_upstream=true`)
|
37
|
+
DESC
|
38
|
+
task :guard_committed do
|
39
|
+
if [true, "true"].include? fetch("skip_guard_committed", false)
|
40
|
+
say_formatted("Skipping git:guard_committed")
|
41
|
+
else
|
42
|
+
if exists?("branch")
|
43
|
+
working_branch = `git symbolic-ref -q HEAD`.sub(%r{^refs/heads/}, '').chomp
|
44
|
+
unless fetch("branch") == working_branch
|
45
|
+
abort %Q{failed: guard_committed: wrong branch
|
46
|
+
|
47
|
+
You have configured to deploy from branch #{fetch("branch")}
|
48
|
+
but your git working copy is on branch #{working_branch}
|
49
|
+
|
50
|
+
git checkout #{fetch("branch")}
|
51
|
+
|
52
|
+
and try again. Or, to skip this check, execute cap again with:
|
53
|
+
|
54
|
+
-s skip_guard_committed=true
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# cribbed from bundle release rake task
|
60
|
+
`git diff HEAD --exit-code`
|
61
|
+
return_code = $?.to_i
|
62
|
+
if return_code == 0
|
63
|
+
say_formatted("guard_clean: passed")
|
64
|
+
else
|
65
|
+
abort %Q{failed: guard_committed: uncomitted changes
|
66
|
+
|
67
|
+
There are files that need to be committed first.
|
68
|
+
|
69
|
+
Or, to skip this check, execute cap again with:
|
70
|
+
-s skip_guard_committed=true
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
desc <<-DESC
|
77
|
+
Ensure sure local git has been pushed to upstream, or abort
|
78
|
+
|
79
|
+
* 'upstream' is whatever you have configured as cap :repository
|
80
|
+
* Looks in :branch (default 'master') to see what branch should be checked,
|
81
|
+
Assumes local :branch tracks upstream_remote/branch
|
82
|
+
|
83
|
+
The idea is to ensure what you're deploying is what you're looking at.
|
84
|
+
See also git:guard_committed, you usually want to use both to ensure this.
|
85
|
+
|
86
|
+
before "git:tag", "git:check_committed", "git:check_upstream"
|
87
|
+
or if not using git:tag, eg
|
88
|
+
before "deploy", , "git:check_committed", "git:check_upstream"
|
89
|
+
|
90
|
+
setting cap :skip_guard_upstream to truewill skip even if task is invoked.
|
91
|
+
(eg, `cap deploy -s skip_guard_upstream=true`)
|
92
|
+
DESC
|
93
|
+
task :guard_upstream do
|
94
|
+
if [true, "true"].include? fetch("skip_guard_upstream", false)
|
95
|
+
say_formatted("Skipping git:guard_upstream")
|
96
|
+
else
|
97
|
+
|
98
|
+
ensure_git_fetch
|
99
|
+
|
100
|
+
remote_sha = `git log --pretty=format:%H #{upstream_remote}/#{working_branch} -1`.chomp
|
101
|
+
|
102
|
+
unless local_sha == remote_sha
|
103
|
+
abort %Q{failed:
|
104
|
+
Your local #{working_branch} branch is not up to date with #{upstream_remote}/#{working_branch}.
|
105
|
+
This will likely result in deploying something other than you expect.
|
106
|
+
|
107
|
+
Please make sure you have pulled and pushed all code before deploying:
|
108
|
+
|
109
|
+
git pull #{upstream_remote} #{working_branch}
|
110
|
+
# run tests, etc
|
111
|
+
git push #{upstream_remote} #{working_branch}
|
112
|
+
|
113
|
+
Or, to skip this check run cap again with `-s skip_guard_upstream=true`
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
say_formatted("guard_upstream: passed")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
desc <<-DESC
|
122
|
+
Tags the current checkout and pushes tag to remote.
|
123
|
+
|
124
|
+
Normally will tag and deploy whatever is in your current git working
|
125
|
+
copy -- you may want to use with the guard tasks to make sure
|
126
|
+
you're deploying what you think and sync'ing it to your upstream
|
127
|
+
repository:
|
128
|
+
|
129
|
+
before "deploy", "git:guard_committed", "git:guard_upstream", "git:tag"
|
130
|
+
|
131
|
+
However, if you have set cap :branch, git:retag will tag the HEAD
|
132
|
+
of THAT branch, rather than whatever is the current working copy
|
133
|
+
branch.
|
134
|
+
|
135
|
+
Either way, git:tag:
|
136
|
+
* pushes the new tag to upstream remote git
|
137
|
+
* sets the cap :branch variable to the newly created tag, to be
|
138
|
+
sure cap deploys that tag.
|
139
|
+
|
140
|
+
What will the created tag look like?
|
141
|
+
|
142
|
+
Without multi-stage, by default something like `deploy-yyyy-mm-dd-hhmm`.
|
143
|
+
|
144
|
+
* The deploy- prefix will be the current stage name if multi-stage.
|
145
|
+
* The prefix can be manually set in config file or command line
|
146
|
+
with cap :tag_prefix variable instead.
|
147
|
+
* Somewhat experimental, you can also set :tag_template to change the
|
148
|
+
part after the prefix.
|
149
|
+
* or you can specify a complete tag on the cap command line `-s tag=deploy-whatever`.
|
150
|
+
|
151
|
+
`set :confirm_tag, true` in the config file to force an interactive
|
152
|
+
prompt and confirmation before continuing.
|
153
|
+
DESC
|
154
|
+
task :tag do
|
155
|
+
|
156
|
+
# make sure we have any other deployment tags that have been pushed by
|
157
|
+
# others so our auto-increment code doesn't create conflicting tags
|
158
|
+
ensure_git_fetch
|
159
|
+
|
160
|
+
tag = calculate_new_tag
|
161
|
+
|
162
|
+
commit_msg = @__git_what || "cap git:tag: #{tag}"
|
163
|
+
|
164
|
+
self.guard_confirm_tag(tag)
|
165
|
+
|
166
|
+
# tag 'working_branch', means :branch if set, otherwise
|
167
|
+
# current working directory checkout.
|
168
|
+
local_sh "git tag -a -m '#{commit_msg}' #{tag} #{self.working_branch}"
|
169
|
+
|
170
|
+
# Push new tag back to origin
|
171
|
+
local_sh "git push -q #{upstream_remote} #{tag}"
|
172
|
+
|
173
|
+
# set :branch to tag, so cap will continue on to deploy the tag we just created!
|
174
|
+
set(:branch, tag)
|
175
|
+
end
|
176
|
+
|
177
|
+
desc <<-DESC
|
178
|
+
Takes an already existing tag, and retags it and deploys that tag.
|
179
|
+
|
180
|
+
Will push the new tag to upstream repo, and set the new tag as cap
|
181
|
+
:branch so cap willd deploy it.
|
182
|
+
|
183
|
+
Usually used in git multistage for moving from staging to production,
|
184
|
+
for instance in your production.rb:
|
185
|
+
|
186
|
+
before "deploy", "git:retag"
|
187
|
+
|
188
|
+
Or use with the guard tasks:
|
189
|
+
before "deploy", "git:guard_committed", "git:guard_upstream", "git:retag"
|
190
|
+
|
191
|
+
`set :confirm_tag, true` in the config file to force an interactive
|
192
|
+
prompt and confirmation before continuing.
|
193
|
+
|
194
|
+
What tag will be used as source tag?
|
195
|
+
|
196
|
+
* Normally the most recent tag beginning "staging-"
|
197
|
+
* Or set cap :tag_prefix in config file or command line
|
198
|
+
to use a different prefix.
|
199
|
+
* Or set :tag_from in config file or on command line
|
200
|
+
to specify a specific tag. `cap deploy -s tag_from=staging_something`
|
201
|
+
|
202
|
+
What will the newly created tag look like? Same rules as for
|
203
|
+
git:tag.
|
204
|
+
|
205
|
+
* By default in a production stage it's going to look
|
206
|
+
like `production-yyyy-mm-dd-hhmm`, but there are several
|
207
|
+
of cap variables you can set in a config file to change this,
|
208
|
+
including :tag_prefix and :tag_template
|
209
|
+
DESC
|
210
|
+
task :retag do
|
211
|
+
from_tag = self.from_tag
|
212
|
+
|
213
|
+
to_tag = calculate_new_tag
|
214
|
+
|
215
|
+
self.guard_confirm_tag(from_tag)
|
216
|
+
|
217
|
+
say_formatted("git:retag taking #{from_tag} and retagging as #{to_tag}")
|
218
|
+
|
219
|
+
local_sh "git tag -a -m 'tagging #{from_tag} for deployment as #{to_tag}' #{to_tag} #{from_tag}"
|
220
|
+
|
221
|
+
# Push new tag back to origin
|
222
|
+
local_sh "git push -q #{upstream_remote} #{to_tag}"
|
223
|
+
|
224
|
+
set(:branch, to_tag)
|
225
|
+
end
|
226
|
+
|
227
|
+
desc <<-DESC
|
228
|
+
Show 5 most recent tags set by git:tag or git:retag
|
229
|
+
|
230
|
+
Can be used to see what you've deployed recently.
|
231
|
+
|
232
|
+
cap git:show_tags
|
233
|
+
or for multi-stage:
|
234
|
+
cap staging git:show_tags
|
235
|
+
cap production git:show_tags
|
236
|
+
|
237
|
+
Looks for tags matching the prefix that the tag or retag task
|
238
|
+
would use to tag. Ie, 'deploy-', or 'stagename-' in multi-stage,
|
239
|
+
or according to :tag_prefix setting.
|
240
|
+
|
241
|
+
Will also output date of tag, commit message, and account doing the commit.
|
242
|
+
DESC
|
243
|
+
task :show_tags do
|
244
|
+
# in newer versions of git you could prob do this with a git-log instead with
|
245
|
+
# certain arguments, but my local git is too old to support --tags arg properly.
|
246
|
+
system "git for-each-ref --count=4 --sort='-taggerdate' --format='\n* %(refname:short)\n Tagger: %(taggeremail)\n Date: %(taggerdate)\n\n %(subject)' 'refs/tags/#{tag_prefix}-*' "
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
desc <<-DESC
|
251
|
+
Show log between most tagged deploy and what would be deployed now.
|
252
|
+
|
253
|
+
Requires you to be using git:tag or git:retag to make any sense,
|
254
|
+
so we can find the 'last deployed' tag to compare.
|
255
|
+
|
256
|
+
You can run this manually:
|
257
|
+
cap git:commit_log
|
258
|
+
Or for multi-stage, perhaps:
|
259
|
+
cap staging git:commit_log
|
260
|
+
cap production git:commit_log
|
261
|
+
Or you can use cap callbacks to ensure this is shown before
|
262
|
+
a deploy, for multi-stage for instance add to your production.rb:
|
263
|
+
before "git:retag", "git:commit_log"
|
264
|
+
# and force an interactive confirmation after they've seen it
|
265
|
+
set :confirm_retag, true
|
266
|
+
|
267
|
+
Ordinarily shows commit log between current git working copy
|
268
|
+
(forced to current head of :branch if cap :branch is set), and
|
269
|
+
last deployed tag, by default tag beginning "deploy-", or
|
270
|
+
tag beginning :stage if you are cap multi-stage, or beginning
|
271
|
+
with :tag_prefix if that is set.
|
272
|
+
|
273
|
+
Hard-coded to do something special if you are using multistage
|
274
|
+
cap, and are in stage 'production' -- in that case it will
|
275
|
+
show you the commits between the most recent production-* tag,
|
276
|
+
and the most recent staging-* tag. (Cap :tag_prefix and :from_prefix
|
277
|
+
can change those tag prefixes).
|
278
|
+
DESC
|
279
|
+
task :commit_log do
|
280
|
+
from, to = nil, nil
|
281
|
+
|
282
|
+
if exists?("stage") && stage.to_s == "production"
|
283
|
+
# production stage in multi-stage
|
284
|
+
from = fetch_last_tag # last deploy-* tag, or last :tag_prefix tag
|
285
|
+
to = from_tag # last staging-* tag, or last :from_prefix tag
|
286
|
+
else
|
287
|
+
# 'staging' stage in multi-stage, or else any old
|
288
|
+
# non-multistage.
|
289
|
+
from = fetch_last_tag # last deploy-* tag, or last :tag_prefix tag
|
290
|
+
to = local_sha.slice(0,8) # current git working copy, or local branch head.
|
291
|
+
end
|
292
|
+
|
293
|
+
show_commit_log(from, to)
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cap_git_tools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jonathan Rochkind
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: capistrano
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.0'
|
30
|
+
description:
|
31
|
+
email:
|
32
|
+
- jonathan@dnil.net
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- cap_git_tools.gemspec
|
42
|
+
- lib/cap_git_tools.rb
|
43
|
+
- lib/cap_git_tools/task_helpers.rb
|
44
|
+
- lib/cap_git_tools/tasks.rb
|
45
|
+
- lib/cap_git_tools/version.rb
|
46
|
+
homepage: ''
|
47
|
+
licenses: []
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project: cap_git_tools
|
66
|
+
rubygems_version: 1.8.24
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: re-usable, composable Capistrano tasks for git tagging and other work with
|
70
|
+
a git repo
|
71
|
+
test_files: []
|