auto_tagger 0.0.9

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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 [Jeff Dean]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # IMPORTANT NOTE
2
+
3
+ This gem is still in it's infancy, and lots of things might change. Since this creates and pushes tags to your git repository, please use with caution.
4
+
5
+ # AutoTagger
6
+
7
+ AutoTagger is a gem that helps you automatically create a date-stamped tag for each stage of your deployment, and deploy from the last tag from the previous environment.
8
+
9
+ Let's say you have the following workflow:
10
+
11
+ * Run all test on a Continuous Integration (CI) server
12
+ * Deploy to a staging server
13
+ * Deploy to a production server
14
+
15
+ You can use the `autotag` command to tag releases on your CI box, then use the capistrano tasks to auto-tag each release.
16
+
17
+ ## Installation
18
+
19
+ gem sources -a http://gems.github.com
20
+ sudo gem install zilkey-auto_tagger
21
+
22
+ ## Contribute
23
+
24
+ * [Tracker Project](http://www.pivotaltracker.com/projects/11988)
25
+ * [GitHub Repository](http://github.com/zilkey/auto_tagger/tree/master)
26
+
27
+ ## The autotag executable
28
+
29
+ Installing the gem creates an executable file named autotag, which takes two arguments: the stage, and optionally the path to the git repo:
30
+
31
+ $ autotag demo # => creates a tag like demo/200804041234 in the current directory
32
+ $ autotag demo . # => same as above
33
+ $ autotag demo /Users/me/foo # => cd's to /Users/me/foo before creating the tag
34
+
35
+ Running autotag does the following:
36
+
37
+ $ git fetch origin --tags
38
+ $ git tag <stage>/<timestamp>
39
+ $ git push origin --tags
40
+
41
+ ## Capistrano Integration
42
+
43
+ AutoTagger comes with 2 capistrano tasks:
44
+
45
+ * `release_tagger:set_branch` tries to set the branch to the last tag from the previous environment.
46
+ * `release_tagger:create_tag` runs autotag for the current stage
47
+
48
+ Example `config/deploy.rb` file:
49
+
50
+ require 'release_tagger'
51
+
52
+ # The :autotagger_stages variable is required
53
+ set :autotagger_stages, [:ci, :staging, :production]
54
+
55
+ # The :working_directory variable is optional, and defaults to Dir.pwd
56
+ # :working_directory can be an absolute or relative path
57
+ set :working_directory, "../../"
58
+
59
+ task :production do
60
+ # In each of your environments that need auto-branch setting, you need to set :stage
61
+ set :stage, :production
62
+ end
63
+
64
+ task :staging do
65
+ # If you do not set stage, it will not auto-set your branch
66
+ # set :stage, :staging
67
+ end
68
+
69
+ # You need to add the before/ater callbacks yourself
70
+ before "deploy:update_code", "release_tagger:set_branch"
71
+ after "deploy", "release_tagger:create_tag"
72
+ after "deploy", "release_tagger:write_tag_to_shared"
73
+ after "deploy", "release_tagger:print_latest_tags"
74
+
75
+ ### Cpistano-ext multistage support
76
+
77
+ If you use capistano-ext multistage, you can use auto_tagger.
78
+
79
+ set :autotagger_stages, [:ci, :staging, :production]
80
+ set :stages, [:staging, :production]
81
+ set :default_stage, :staging
82
+ require 'capistrano/ext/multistage'
83
+
84
+ When you deploy, autotagger will auto-detect your current stage.
85
+
86
+ ### release_tagger:set_branch
87
+
88
+ This task sets the git branch to the latest tag from the previous stage. Assume you have the following tags in your git repository:
89
+
90
+ * ci/01
91
+ * staging/01
92
+ * production/01
93
+
94
+ And the following stages in your capistrano file:
95
+
96
+ set :autotagger_stages, [:ci, :staging, :production]
97
+
98
+ The deployments would look like this:
99
+
100
+ cap staging release_tagger:set_branch # => sets branch to ci/01
101
+ cap production release_tagger:set_branch # => sets branch to staging/01
102
+
103
+ You can override with with the -Shead and -Stag options
104
+
105
+ cap staging release_tagger:set_branch -Shead=true # => sets branch to master
106
+ cap staging release_tagger:set_branch -Stag=staging/01 # => sets branch to staging/01
107
+
108
+ If you add `before "deploy:update_code", "release_tagger:set_branch"`, you can just deploy with:
109
+
110
+ cap staging deploy
111
+
112
+ and the branch will be set for you automatically.
113
+
114
+ ### release_tagger:create_tag
115
+
116
+ This cap task creates a new tag, based on the latest tag from the previous environment.
117
+
118
+ If there is no tag from the previous stage, it creates a new tag from the latest commit in your _working directory_.
119
+
120
+ If you don't specify any `autotagger_stages`, autotagger will create a tag that starts with "production".
121
+
122
+ ### release_tagger:print_latest_tags
123
+
124
+ This task reads the git version from the text file in shared:
125
+
126
+ cap staging release_tagger:read_tag_from_shared
127
+
128
+ ### release_tagger:print_latest_tags
129
+
130
+ This task takes the latest tag from each environment and prints it to the screen. You can add it to your deploy.rb like so:
131
+
132
+ after "deploy", "release_tagger:print_latest_tags"
133
+
134
+ Or call it directly, like:
135
+
136
+ cap production release_tagger:print_latest_tags
137
+
138
+ This will produce output like:
139
+
140
+ ** AUTO TAGGER: release tag history is:
141
+ ** ci ci/20090331045345 8031807feb5f4f99dd83257cdc07081fa6080cba some commit message
142
+ ** staging staging/20090331050908 8031807feb5f4f99dd83257cdc07081fa6080cba some commit message
143
+ ** production production/20090331050917 8031807feb5f4f99dd83257cdc07081fa6080cba some commit message
144
+
145
+ ## Running tests:
146
+
147
+ You must be able to ssh into your box via localhost (remote login). To make this easier, add your own key to your own account:
148
+
149
+ cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys
150
+
151
+ To ensure that this has worked, try this:
152
+
153
+ ssh localhost
154
+
155
+ If it asks you for a password, you've done something wrong.
156
+
157
+ To run the specs, execute:
158
+
159
+ spec spec/
160
+
161
+ To run the cucumber features, execute:
162
+
163
+ cucumber features/
164
+
165
+ ## Acknowledgments
166
+
167
+ Special thanks to
168
+
169
+ * Brian Takita for the original recipes
170
+ * Mike Dalessio for his git fu
171
+ * Chad Wooley for his feature ideas
172
+ * Tim Holahan for his QA
173
+
174
+ ## Links
175
+
176
+ * http://codeintensity.blogspot.com/2008/06/changelogs-and-deployment-notification.html
177
+
178
+ Copyright (c) 2009 [Jeff Dean], released under the MIT license
data/bin/autotag ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "auto_tagger"))
3
+ require 'getoptlong'
4
+
5
+ opts = GetoptLong.new(
6
+ ['--help', '-h', '-?', GetoptLong::NO_ARGUMENT]
7
+ )
8
+
9
+ def usage
10
+ puts
11
+ puts "USAGE: #{File.basename($0)} <stage> [<repository>]"
12
+ puts
13
+ puts ' where: stage sets the tag prefix'
14
+ puts ' repository sets the repository to act on - defualts to the current directory'
15
+ puts
16
+ puts ' examples: autotag'
17
+ puts ' autotag .'
18
+ puts ' autotag ../'
19
+ puts ' autotag /data/myrepo'
20
+ puts ' autotag demo'
21
+ puts
22
+ puts
23
+ exit 0
24
+ end
25
+
26
+ opts.each do |opt, arg|
27
+ case opt
28
+ when "--help"
29
+ usage
30
+ end
31
+ end
32
+
33
+ if ARGV[0]
34
+ AutoTagger.new(ARGV[0], ARGV[1]).create_tag
35
+ exit 0
36
+ else
37
+ usage
38
+ end
@@ -0,0 +1,26 @@
1
+ class AutoTagger
2
+
3
+ class EnvironmentCannotBeBlankError < StandardError; end
4
+
5
+ attr_reader :stage, :repository, :working_directory
6
+
7
+ def initialize(stage, path = nil)
8
+ raise EnvironmentCannotBeBlankError if stage.to_s.strip == ""
9
+ @working_directory = File.expand_path(path ||= Dir.pwd)
10
+ @repository = Repository.new(@working_directory)
11
+ @stage = stage
12
+ end
13
+
14
+ def create_tag(commit = nil)
15
+ repository.tags.fetch
16
+ new_tag = repository.tags.create(stage, commit)
17
+ repository.tags.push
18
+ new_tag
19
+ end
20
+
21
+ def latest_tag
22
+ repository.tags.fetch
23
+ repository.tags.latest_from(stage)
24
+ end
25
+
26
+ end
@@ -0,0 +1,39 @@
1
+ class CapistranoHelper
2
+
3
+ attr_reader :variables, :stage, :working_directory
4
+
5
+ def initialize(variables)
6
+ @stage_manager = StageManager.new(variables[:autotagger_stages])
7
+ @variables = variables
8
+ @stage = variables[:stage]
9
+ @working_directory = variables[:working_directory] || Dir.pwd
10
+ end
11
+
12
+ def previous_stage
13
+ @stage_manager.previous_stage(stage)
14
+ end
15
+
16
+ def branch
17
+ if variables.has_key?(:head)
18
+ variables[:branch]
19
+ elsif variables.has_key?(:tag)
20
+ variables[:tag]
21
+ elsif previous_stage && (latest = AutoTagger.new(previous_stage, working_directory).latest_tag)
22
+ latest
23
+ else
24
+ variables[:branch]
25
+ end
26
+ end
27
+
28
+ def release_tag_entries
29
+ entries = []
30
+ @stage_manager.stages.each do |stage|
31
+ tagger = AutoTagger.new(stage, working_directory)
32
+ tag = tagger.latest_tag
33
+ commit = tagger.repository.commit_for(tag)
34
+ entries << "#{stage.to_s.ljust(10, " ")} #{tag.to_s.ljust(30, " ")} #{commit.to_s}"
35
+ end
36
+ entries
37
+ end
38
+
39
+ end
@@ -0,0 +1,15 @@
1
+ class Commander
2
+ class << self
3
+ def execute(path, cmd)
4
+ `#{command_in_context(path, cmd)}`
5
+ end
6
+
7
+ def execute!(path, cmd)
8
+ system command_in_context(path, cmd)
9
+ end
10
+
11
+ def command_in_context(path, cmd)
12
+ "cd #{path} && #{cmd}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ class Repository
2
+
3
+ class NoPathProvidedError < StandardError; end
4
+ class NoSuchPathError < StandardError; end
5
+ class InvalidGitRepositoryError < StandardError; end
6
+ class GitCommandFailedError < StandardError; end
7
+
8
+ attr_reader :path
9
+
10
+ def initialize(path)
11
+ if path.to_s.strip == ""
12
+ raise NoPathProvidedError
13
+ elsif ! File.exists?(path)
14
+ raise NoSuchPathError
15
+ elsif ! File.exists?(File.join(path, ".git"))
16
+ raise InvalidGitRepositoryError
17
+ else
18
+ @path = path
19
+ end
20
+ end
21
+
22
+ def ==(other)
23
+ other.is_a?(Repository) && other.path == path
24
+ end
25
+
26
+ def tags
27
+ @tags ||= Tag.new(self)
28
+ end
29
+
30
+ def commit_for(tag)
31
+ Commander.execute(path, "git --no-pager log #{tag} --pretty=oneline -1")
32
+ end
33
+
34
+ def run(cmd)
35
+ Commander.execute(path, cmd)
36
+ end
37
+
38
+ def run!(cmd)
39
+ Commander.execute!(path, cmd) || raise(GitCommandFailedError)
40
+ end
41
+
42
+ end
@@ -0,0 +1,23 @@
1
+ class StageManager
2
+
3
+ class NoStagesSpecifiedError < StandardError
4
+ def message
5
+ "You must set the :stages variable to an array, like set :stages, [:ci, :demo]"
6
+ end
7
+ end
8
+
9
+ attr_reader :stages
10
+
11
+ def initialize(stages)
12
+ raise NoStagesSpecifiedError unless stages && stages.is_a?(Array)
13
+ @stages = stages
14
+ end
15
+
16
+ def previous_stage(stage)
17
+ if stage
18
+ index = stages.index(stage) - 1
19
+ stages[index] if index > -1
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,41 @@
1
+ # git --no-pager log --pretty=oneline -1
2
+ # git tag -a -m 'Successful continuous integration build on #{timestamp}' #{tag_name}"
3
+ class Tag
4
+
5
+ attr_reader :repository
6
+
7
+ def initialize(repository)
8
+ @repository = repository
9
+ end
10
+
11
+ def find_all
12
+ repository.run("git tag").split("\n")
13
+ end
14
+
15
+ def fetch
16
+ repository.run! "git fetch origin --tags"
17
+ end
18
+
19
+ def latest_from(stage)
20
+ find_all.select{|tag| tag =~ /^#{stage}\//}.sort.last
21
+ end
22
+
23
+ def push
24
+ repository.run! "git push origin --tags"
25
+ end
26
+
27
+ def create(stage, commit = nil)
28
+ tag_name = name_for(stage)
29
+ cmd = "git tag #{tag_name}"
30
+ cmd += " #{commit}" if commit
31
+ repository.run! cmd
32
+ tag_name
33
+ end
34
+
35
+ private
36
+
37
+ def name_for(stage)
38
+ "%s/%s" % [stage, Time.now.utc.strftime('%Y%m%d%H%M%S')]
39
+ end
40
+
41
+ end
@@ -0,0 +1,10 @@
1
+ [
2
+ 'commander',
3
+ 'repository',
4
+ 'tag',
5
+ 'auto_tagger',
6
+ 'stage_manager',
7
+ 'capistrano_helper'
8
+ ].each do |file|
9
+ require File.expand_path(File.join(File.dirname(__FILE__), "auto_tagger", file))
10
+ end
@@ -0,0 +1,55 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "auto_tagger"))
2
+
3
+ Capistrano::Configuration.instance(:must_exist).load do
4
+ namespace :release_tagger do
5
+ desc %Q{
6
+ Sets the branch to the latest tag from the previous stage.
7
+ Use -Shead=true to set the branch to master, -Stag=<tag> to specify the tag explicitly.
8
+ }
9
+ task :set_branch do
10
+ if branch_name = CapistranoHelper.new(variables).branch
11
+ set :branch, branch_name
12
+ logger.info "setting branch to #{branch_name}"
13
+ else
14
+ logger.info "AUTO TAGGER: skipping auto-assignment of branch. Branch will remain the default.}"
15
+ end
16
+ end
17
+
18
+ desc %Q{Prints the most current tags from all stages}
19
+ task :print_latest_tags, :roles => :app do
20
+ logger.info "AUTO TAGGER: release tag history is:"
21
+ entries = CapistranoHelper.new(variables).release_tag_entries
22
+ entries.each do |entry|
23
+ logger.info entry
24
+ end
25
+ end
26
+
27
+ desc %Q{Reads the text file with the latest tag from the shared directory}
28
+ task :read_tag_from_shared, :roles => :app do
29
+ logger.info "AUTO TAGGER: latest tag deployed to this environment was:"
30
+ run "cat #{shared_path}/released_git_tag.txt"
31
+ end
32
+
33
+ desc %Q{Writes the tag name to a file in the shared directory}
34
+ task :write_tag_to_shared, :roles => :app do
35
+ if exists?(:branch)
36
+ logger.info "AUTO TAGGER: writing tag to shared text file on remote server"
37
+ run "echo '#{branch}' > #{shared_path}/released_git_tag.txt"
38
+ else
39
+ logger.info "AUTO TAGGER: no branch available. Text file was not written to server"
40
+ end
41
+ end
42
+
43
+ desc %Q{Creates a tag using the stage variable}
44
+ task :create_tag, :roles => :app do
45
+ if variables[:stage]
46
+ previous_tag = AutoTagger.new(StageManager.new(autotagger_stages).previous_stage(stage), Dir.pwd).latest_tag
47
+ tag_name = AutoTagger.new(variables[:stage], variables[:working_directory]).create_tag(previous_tag)
48
+ logger.info "AUTO TAGGER created tag #{tag_name} from #{previous_tag.inspect}"
49
+ else
50
+ tag_name = AutoTagger.new(:production, variables[:working_directory]).create_tag
51
+ logger.info "AUTO TAGGER created tag #{tag_name}"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,85 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe AutoTagger do
4
+ before(:each) do
5
+ stub(Dir).pwd { File.join(File.dirname(__FILE__), '..', '..') }
6
+ end
7
+
8
+ describe ".new" do
9
+ it "blows up if you don't pass an stage" do
10
+ proc do
11
+ AutoTagger.new(nil)
12
+ end.should raise_error(AutoTagger::EnvironmentCannotBeBlankError)
13
+ end
14
+
15
+ it "sets the stage when it's passed" do
16
+ AutoTagger.new("ci").stage.should == "ci"
17
+ end
18
+
19
+ it "sets the path to Dir.pwd when nil" do
20
+ mock(Dir).pwd { "/foo" }
21
+ mock(Repository).new("/foo")
22
+ AutoTagger.new("ci")
23
+ end
24
+
25
+ it "expands the path when the path is passed" do
26
+ mock(Repository).new(File.expand_path("."))
27
+ AutoTagger.new("ci", ".")
28
+ end
29
+
30
+ it "exposes the working directory" do
31
+ mock(Repository).new(File.expand_path("."))
32
+ AutoTagger.new("ci", ".").working_directory.should == File.expand_path(".")
33
+ end
34
+ end
35
+
36
+ describe "#create_tag" do
37
+ it "generates the correct commands" do
38
+ time = Time.local(2001,1,1)
39
+ mock(Time).now.once {time}
40
+ timestamp = time.utc.strftime('%Y%m%d%H%M%S')
41
+ mock(File).exists?(anything).twice { true }
42
+
43
+ mock(Commander).execute!("/foo", "git fetch origin --tags") {true}
44
+ mock(Commander).execute!("/foo", "git tag ci/#{timestamp}") {true}
45
+ mock(Commander).execute!("/foo", "git push origin --tags") {true}
46
+
47
+ AutoTagger.new("ci", "/foo").create_tag
48
+ end
49
+
50
+ it "allows you to base it off an existing tag or commit" do
51
+ time = Time.local(2001,1,1)
52
+ mock(Time).now.once {time}
53
+ timestamp = time.utc.strftime('%Y%m%d%H%M%S')
54
+ mock(File).exists?(anything).twice { true }
55
+
56
+ mock(Commander).execute!("/foo", "git fetch origin --tags") {true}
57
+ mock(Commander).execute!("/foo", "git tag ci/#{timestamp} guid") {true}
58
+ mock(Commander).execute!("/foo", "git push origin --tags") {true}
59
+
60
+ AutoTagger.new("ci", "/foo").create_tag("guid")
61
+ end
62
+
63
+ it "returns the tag that was created" do
64
+ time = Time.local(2001,1,1)
65
+ mock(Time).now.once {time}
66
+ timestamp = time.utc.strftime('%Y%m%d%H%M%S')
67
+ mock(File).exists?(anything).twice { true }
68
+ mock(Commander).execute!(anything, anything).times(any_times) {true}
69
+
70
+ AutoTagger.new("ci", "/foo").create_tag.should == "ci/#{timestamp}"
71
+ end
72
+ end
73
+
74
+ describe "#latest_tag" do
75
+ it "generates the correct commands" do
76
+ mock(File).exists?(anything).twice { true }
77
+
78
+ mock(Commander).execute!("/foo", "git fetch origin --tags") {true}
79
+ mock(Commander).execute("/foo", "git tag") { "ci_01" }
80
+
81
+ AutoTagger.new("ci", "/foo").latest_tag
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,146 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe CapistranoHelper do
4
+
5
+ describe ".new" do
6
+ it "blows up if there are no stages" do
7
+ proc do
8
+ CapistranoHelper.new({})
9
+ end.should raise_error(StageManager::NoStagesSpecifiedError)
10
+ end
11
+ end
12
+
13
+ describe "#variables" do
14
+ it "returns all variables" do
15
+ CapistranoHelper.new({:autotagger_stages => [:bar]}).variables.should == {:autotagger_stages => [:bar]}
16
+ end
17
+ end
18
+
19
+ describe "#working_directory" do
20
+ it "returns the hashes' working directory value" do
21
+ CapistranoHelper.new({:autotagger_stages => [:bar], :working_directory => "/foo"}).working_directory.should == "/foo"
22
+ end
23
+
24
+ it "defaults to Dir.pwd if it's not set, or it's nil" do
25
+ mock(Dir).pwd { "/bar" }
26
+ CapistranoHelper.new({:autotagger_stages => [:bar]}).working_directory.should == "/bar"
27
+ end
28
+ end
29
+
30
+ describe "#stage" do
31
+ it "returns the hashes' current stage value" do
32
+ CapistranoHelper.new({:autotagger_stages => [:bar], :stage => :bar}).stage.should == :bar
33
+ CapistranoHelper.new({:autotagger_stages => [:bar]}).stage.should be_nil
34
+ end
35
+ end
36
+
37
+ describe "#release_tag_entries" do
38
+ it "returns a column-justifed version of all the commits" do
39
+ mock(Commander).execute("/foo", "git tag").times(any_times) { "ci/01\nstaging/01\nproduction/01" }
40
+ mock(Commander).execute("/foo", "git --no-pager log ci/01 --pretty=oneline -1") { "guid1" }
41
+ mock(Commander).execute("/foo", "git --no-pager log staging/01 --pretty=oneline -1") { "guid2" }
42
+ mock(Commander).execute("/foo", "git --no-pager log production/01 --pretty=oneline -1") { "guid3" }
43
+ mock(Commander).execute!("/foo", "git fetch origin --tags").times(any_times) { true }
44
+ mock(File).exists?(anything).times(any_times) {true}
45
+
46
+ variables = {
47
+ :working_directory => "/foo",
48
+ :autotagger_stages => [:ci, :staging, :production]
49
+ }
50
+ histories = CapistranoHelper.new(variables).release_tag_entries
51
+ histories.length.should == 3
52
+ histories[0].should include("ci/01", "guid1")
53
+ histories[1].should include("staging/01", "guid2")
54
+ histories[2].should include("production/01", "guid3")
55
+ end
56
+
57
+ it "ignores tags delimited with '_'" do
58
+ mock(Commander).execute("/foo", "git tag").times(any_times) { "ci/01\nci_02" }
59
+ mock(Commander).execute("/foo", "git --no-pager log ci/01 --pretty=oneline -1") { "guid1" }
60
+ mock(Commander).execute!("/foo", "git fetch origin --tags").times(any_times) { true }
61
+ mock(File).exists?(anything).times(any_times) {true}
62
+
63
+ variables = {
64
+ :working_directory => "/foo",
65
+ :autotagger_stages => [:ci]
66
+ }
67
+ histories = CapistranoHelper.new(variables).release_tag_entries
68
+ histories.length.should == 1
69
+ histories[0].should include("ci/01", "guid1")
70
+ end
71
+ end
72
+
73
+ describe "#branch" do
74
+ describe "with :head and :branch specified" do
75
+ it "returns master" do
76
+ variables = {
77
+ :autotagger_stages => [:bar],
78
+ :head => nil,
79
+ :branch => "foo"
80
+ }
81
+ CapistranoHelper.new(variables).branch.should == "foo"
82
+ end
83
+ end
84
+
85
+ describe "with :head specified, but no branch specified" do
86
+ it "returns master" do
87
+ variables = {
88
+ :autotagger_stages => [:bar],
89
+ :head => nil
90
+ }
91
+ CapistranoHelper.new(variables).branch.should == nil
92
+ end
93
+ end
94
+
95
+ describe "with :branch specified" do
96
+ it "returns the value of branch" do
97
+ variables = {
98
+ :autotagger_stages => [:bar],
99
+ :branch => "foo"
100
+ }
101
+ CapistranoHelper.new(variables).branch.should == "foo"
102
+ end
103
+ end
104
+
105
+ describe "with a previous stage with a tag" do
106
+ it "returns the latest tag for the previous stage" do
107
+ variables = {
108
+ :autotagger_stages => [:foo, :bar],
109
+ :stage => :bar,
110
+ :branch => "master",
111
+ :working_directory => "/foo"
112
+ }
113
+ tagger = Object.new
114
+ mock(tagger).latest_tag { "foo_01" }
115
+ mock(AutoTagger).new(:foo, "/foo") { tagger }
116
+ CapistranoHelper.new(variables).branch.should == "foo_01"
117
+ end
118
+ end
119
+
120
+ describe "with no branch and a previous stage with no tag" do
121
+ it "returns nil" do
122
+ variables = {
123
+ :autotagger_stages => [:foo, :bar],
124
+ :stage => :bar,
125
+ :working_directory => "/foo"
126
+ }
127
+ tagger = Object.new
128
+ mock(tagger).latest_tag { nil }
129
+ mock(AutoTagger).new(:foo, "/foo") { tagger }
130
+ CapistranoHelper.new(variables).branch.should == nil
131
+ end
132
+ end
133
+
134
+ describe "with no branch and previous stage" do
135
+ it "returns nil" do
136
+ variables = {
137
+ :autotagger_stages => [:bar],
138
+ :stage => :bar
139
+ }
140
+ CapistranoHelper.new(variables).previous_stage.should be_nil
141
+ CapistranoHelper.new(variables).branch.should == nil
142
+ end
143
+ end
144
+ end
145
+
146
+ end
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Commander do
4
+ describe ".execute" do
5
+ it "execute the command and returns the results" do
6
+ mock(Commander).`("cd /foo && ls") { "" } #`
7
+ Commander.execute("/foo", "ls")
8
+ end
9
+ end
10
+
11
+ describe "system" do
12
+ it "executes and doesn't return anything" do
13
+ mock(Commander).system("cd /foo && ls")
14
+ Commander.execute!("/foo", "ls")
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Repository do
4
+ describe ".new" do
5
+ it "sets the repo" do
6
+ mock(File).exists?(anything).twice { true }
7
+ repo = Repository.new("/foo")
8
+ repo.path.should == "/foo"
9
+ end
10
+
11
+ it "raises an error when the path is blank" do
12
+ proc do
13
+ Repository.new(" ")
14
+ end.should raise_error(Repository::NoPathProvidedError)
15
+ end
16
+
17
+ it "raises an error when the path is nil" do
18
+ proc do
19
+ Repository.new(nil)
20
+ end.should raise_error(Repository::NoPathProvidedError)
21
+ end
22
+
23
+ it "raises an error with a file that doesn't exist" do
24
+ mock(File).exists?("/foo") { false }
25
+ proc do
26
+ Repository.new("/foo")
27
+ end.should raise_error(Repository::NoSuchPathError)
28
+ end
29
+
30
+ it "raises an error with a non-git repository" do
31
+ mock(File).exists?("/foo") { true }
32
+ mock(File).exists?("/foo/.git") { false }
33
+ proc do
34
+ Repository.new("/foo")
35
+ end.should raise_error(Repository::InvalidGitRepositoryError)
36
+ end
37
+ end
38
+
39
+ describe "#==" do
40
+ it "compares paths" do
41
+ mock(File).exists?(anything).times(any_times) { true }
42
+ Repository.new("/foo").should_not == "/foo"
43
+ Repository.new("/foo").should_not == Repository.new("/bar")
44
+ Repository.new("/foo").should == Repository.new("/foo")
45
+ end
46
+ end
47
+
48
+ describe "#run" do
49
+ it "sends the correct command" do
50
+ mock(File).exists?(anything).twice { true }
51
+ mock(Commander).execute("/foo", "bar")
52
+ Repository.new("/foo").run("bar")
53
+ end
54
+ end
55
+
56
+ describe "run!" do
57
+ it "sends the correct command" do
58
+ mock(File).exists?(anything).twice { true }
59
+ mock(Commander).execute!("/foo", "bar") { true }
60
+ Repository.new("/foo").run!("bar")
61
+ end
62
+
63
+ it "raises an exception if it the command returns false" do
64
+ mock(File).exists?(anything).twice { true }
65
+ mock(Commander).execute!("/foo", "bar") { false }
66
+ proc do
67
+ Repository.new("/foo").run!("bar")
68
+ end.should raise_error(Repository::GitCommandFailedError)
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe StageManager do
4
+
5
+ describe ".new" do
6
+ [nil, ""].each do |value|
7
+ it "blows up if there are stages == #{value.inspect}" do
8
+ proc do
9
+ StageManager.new(value)
10
+ end.should raise_error(StageManager::NoStagesSpecifiedError)
11
+ end
12
+ end
13
+ end
14
+
15
+ describe "#previous_stage" do
16
+ it "returns the previous stage if there is more than one stage, and there is a current stage" do
17
+ StageManager.new([:foo, :bar]).previous_stage(:bar).should == :foo
18
+ end
19
+
20
+ it "returns nil if there is no previous stage" do
21
+ StageManager.new([:bar]).previous_stage(:bar).should be_nil
22
+ end
23
+
24
+ it "returns nil if there is no current stage" do
25
+ StageManager.new([:bar]).previous_stage(nil).should be_nil
26
+ end
27
+ end
28
+
29
+
30
+ end
@@ -0,0 +1,66 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Tag do
4
+
5
+ before(:each) do
6
+ @repository = Object.new
7
+ end
8
+
9
+ describe ".new" do
10
+ it "sets the repository" do
11
+ Tag.new(@repository).repository.should == @repository
12
+ end
13
+ end
14
+
15
+ describe "#find_all" do
16
+ it "returns an array of tags" do
17
+ mock(@repository).run("git tag") { "ci_01\nci_02" }
18
+ Tag.new(@repository).find_all.should == ["ci_01", "ci_02"]
19
+ end
20
+
21
+ it "returns an empty array if there are none" do
22
+ mock(@repository).run("git tag") { "" }
23
+ Tag.new(@repository).find_all.should be_empty
24
+ end
25
+ end
26
+
27
+ describe "#latest_from" do
28
+ before do
29
+ @tag = Tag.new(@repository)
30
+ mock(@tag).find_all { ["ci/01", "ci/02"] }
31
+ end
32
+
33
+ it "returns the latest tag that starts with the specified stage" do
34
+ @tag.latest_from(:ci).should == "ci/02"
35
+ end
36
+
37
+ it "returns nil if none match" do
38
+ @tag.latest_from(:staging).should be_nil
39
+ end
40
+ end
41
+
42
+ describe "#fetch_tags" do
43
+ it "sends the correct command" do
44
+ mock(@repository).run!("git fetch origin --tags")
45
+ Tag.new(@repository).fetch
46
+ end
47
+ end
48
+
49
+ describe "#push" do
50
+ it "sends the correct command" do
51
+ mock(@repository).run!("git push origin --tags")
52
+ Tag.new(@repository).push
53
+ end
54
+ end
55
+
56
+ describe "#create" do
57
+ it "creates the right command and returns the name" do
58
+ time = Time.local(2001,1,1)
59
+ mock(Time).now.once {time}
60
+ tag_name = "ci/#{time.utc.strftime('%Y%m%d%H%M%S')}"
61
+ mock(@repository).run!("git tag #{tag_name}")
62
+ Tag.new(@repository).create("ci").should == tag_name
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec'
2
+ require 'rr'
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "auto_tagger"))
4
+
5
+ Spec::Runner.configure do |config|
6
+ config.mock_with :rr
7
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: auto_tagger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.9
5
+ platform: ruby
6
+ authors:
7
+ - Jeff Dean
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-28 00:00:00 -04:00
13
+ default_executable: autotag
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: capistrano
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.5.3
24
+ version:
25
+ description:
26
+ email:
27
+ executables:
28
+ - autotag
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/auto_tagger/auto_tagger.rb
35
+ - lib/auto_tagger/capistrano_helper.rb
36
+ - lib/auto_tagger/commander.rb
37
+ - lib/auto_tagger/repository.rb
38
+ - lib/auto_tagger/stage_manager.rb
39
+ - lib/auto_tagger/tag.rb
40
+ - lib/auto_tagger.rb
41
+ - recipes/release_tagger.rb
42
+ - bin/autotag
43
+ - spec/auto_tagger/auto_tagger_spec.rb
44
+ - spec/auto_tagger/capistrano_helper_spec.rb
45
+ - spec/auto_tagger/commander_spec.rb
46
+ - spec/auto_tagger/repository_spec.rb
47
+ - spec/auto_tagger/stage_manager_spec.rb
48
+ - spec/auto_tagger/tag_spec.rb
49
+ - spec/spec_helper.rb
50
+ - MIT-LICENSE
51
+ - README.md
52
+ has_rdoc: true
53
+ homepage: http://github.com/zilkey/git_tagger/tree/master
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ - recipes
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.3
78
+ signing_key:
79
+ specification_version: 2
80
+ summary: Helps you automatically create tags for each stage in a multi-stage deploment and deploy from the latest tag from the previous environment
81
+ test_files: []
82
+