cap_git_tools 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|