zilkey-auto_tagger 0.0.2 → 0.0.3
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 +20 -0
- data/README.md +79 -0
- data/lib/auto_tagger/auto_tagger.rb +26 -0
- data/lib/auto_tagger/capistrano_helper.rb +38 -0
- data/lib/auto_tagger/commander.rb +16 -0
- data/lib/auto_tagger/repository.rb +38 -0
- data/lib/auto_tagger/tag.rb +38 -0
- data/lib/auto_tagger.rb +9 -0
- data/recipes/release_tagger.rb +26 -0
- data/spec/auto_tagger/auto_tagger_spec.rb +69 -0
- data/spec/auto_tagger/capistrano_helper_spec.rb +130 -0
- data/spec/auto_tagger/commander_spec.rb +17 -0
- data/spec/auto_tagger/repository_spec.rb +72 -0
- data/spec/auto_tagger/tag_spec.rb +66 -0
- data/spec/spec_helper.rb +7 -0
- metadata +20 -20
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,79 @@
|
|
1
|
+
# AutoTagger
|
2
|
+
|
3
|
+
AutoTagger allows you to create a date-stamped tag for each stage of your deployment, and deploy from the last tag from the previous environment.
|
4
|
+
|
5
|
+
Let's say you have the following workflow:
|
6
|
+
|
7
|
+
* Run all test on a Continuous Integration (CI) server
|
8
|
+
* Deploy to a staging server
|
9
|
+
* Deploy to a production server
|
10
|
+
|
11
|
+
You can use the `autotag` command to tag releases on your CI box, then use the capistrano tasks to auto-tag each release.
|
12
|
+
|
13
|
+
## Capistrano Integration
|
14
|
+
|
15
|
+
Example deploy file:
|
16
|
+
|
17
|
+
require 'release_tagger'
|
18
|
+
|
19
|
+
# The :stages variable is required
|
20
|
+
set :stages, [:ci, :staging, :production]
|
21
|
+
|
22
|
+
# The :working_directory variable is optional, and defaults to Dir.pwd
|
23
|
+
# :working_directory can be an absolute or relative path
|
24
|
+
set :working_directory, "../../"
|
25
|
+
|
26
|
+
task :production do
|
27
|
+
# In each of your environments that need auto-branch setting, you need to set :current_stage
|
28
|
+
set :current_stage, :production
|
29
|
+
end
|
30
|
+
|
31
|
+
task :staging do
|
32
|
+
# If you do not set current_stage, it will not auto-set your branch
|
33
|
+
# set :current_stage, :staging
|
34
|
+
end
|
35
|
+
|
36
|
+
# You need to add the before/ater callbacks yourself
|
37
|
+
before "deploy:update_code", "release_tagger:set_branch"
|
38
|
+
after "deploy:update_code", "release_tagger:create_tag"
|
39
|
+
|
40
|
+
Assume you have the following tags in your git repository:
|
41
|
+
|
42
|
+
* ci/01
|
43
|
+
* staging/01
|
44
|
+
* production/01
|
45
|
+
|
46
|
+
The deployments would look like this:
|
47
|
+
|
48
|
+
cap staging deploy # => ci/01
|
49
|
+
cap production deploy # => staging/01
|
50
|
+
|
51
|
+
You can override with with the -Shead and -Stag options
|
52
|
+
|
53
|
+
cap staging deploy -Shead=true # => master
|
54
|
+
cap staging deploy -Stag=staging/01 # => staging/01
|
55
|
+
|
56
|
+
## The autotag executable
|
57
|
+
|
58
|
+
autotag -h
|
59
|
+
autotag demo
|
60
|
+
autotag demo .
|
61
|
+
autotag demo /Users/me/foo
|
62
|
+
|
63
|
+
## Known Issues
|
64
|
+
|
65
|
+
* DOES NOT work with capistrano ext multi-stage
|
66
|
+
* It will accept invalid tag names (if you specify a tag name with a space, it will blow up when you try to create the tag)
|
67
|
+
|
68
|
+
## Things that might be useful
|
69
|
+
|
70
|
+
* Make it possible to define a different remote other than "origin"
|
71
|
+
* Make it possible to define a different default branch other than "master"
|
72
|
+
* Make it work with either cap-ext multistage or single-file deploy.rb files
|
73
|
+
* Make it possible to provide your own tag naming convention (like the PaperClip string interpolation), instead of relying on <prefix>/<timestamp>
|
74
|
+
|
75
|
+
## Links
|
76
|
+
|
77
|
+
* http://codeintensity.blogspot.com/2008/06/changelogs-and-deployment-notification.html
|
78
|
+
|
79
|
+
Copyright (c) 2009 [Jeff Dean], released under the MIT license
|
@@ -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
|
15
|
+
repository.tags.fetch
|
16
|
+
new_tag = repository.tags.create(stage)
|
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,38 @@
|
|
1
|
+
class CapistranoHelper
|
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 :variables, :stages, :current_stage, :working_directory
|
10
|
+
|
11
|
+
def initialize(variables)
|
12
|
+
raise NoStagesSpecifiedError unless variables[:stages]
|
13
|
+
@variables = variables
|
14
|
+
@stages = variables[:stages]
|
15
|
+
@current_stage = variables[:current_stage]
|
16
|
+
@working_directory = variables[:working_directory] || Dir.pwd
|
17
|
+
end
|
18
|
+
|
19
|
+
def previous_stage
|
20
|
+
if current_stage
|
21
|
+
index = stages.index(current_stage) - 1
|
22
|
+
stages[index] if index > -1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def branch
|
27
|
+
if variables.has_key?(:head)
|
28
|
+
variables[:branch]
|
29
|
+
elsif variables.has_key?(:tag)
|
30
|
+
variables[:tag]
|
31
|
+
elsif previous_stage && (latest = AutoTagger.new(previous_stage, working_directory).latest_tag)
|
32
|
+
latest
|
33
|
+
else
|
34
|
+
variables[:branch]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
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
|
16
|
+
|
@@ -0,0 +1,38 @@
|
|
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 run(cmd)
|
31
|
+
Commander.execute(path, cmd)
|
32
|
+
end
|
33
|
+
|
34
|
+
def run!(cmd)
|
35
|
+
Commander.execute!(path, cmd) || raise(GitCommandFailedError)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Tag
|
2
|
+
|
3
|
+
attr_reader :repository
|
4
|
+
|
5
|
+
def initialize(repository)
|
6
|
+
@repository = repository
|
7
|
+
end
|
8
|
+
|
9
|
+
def find_all
|
10
|
+
repository.run("git tag").split("\n")
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch
|
14
|
+
repository.run! "git fetch origin --tags"
|
15
|
+
end
|
16
|
+
|
17
|
+
def latest_from(stage)
|
18
|
+
find_all.select{|tag| tag =~ /^#{stage}/}.sort.last
|
19
|
+
end
|
20
|
+
|
21
|
+
def push
|
22
|
+
repository.run! "git push origin --tags"
|
23
|
+
end
|
24
|
+
|
25
|
+
def create(stage)
|
26
|
+
# git tag -a -m 'Successful continuous integration build on #{timestamp}' #{tag_name}"
|
27
|
+
tag_name = name_for(stage)
|
28
|
+
repository.run! "git tag #{tag_name}"
|
29
|
+
tag_name
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def name_for(stage)
|
35
|
+
"%s/%s" % [stage, Time.now.utc.strftime('%Y%m%d%H%M%S')]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/auto_tagger.rb
ADDED
@@ -0,0 +1,26 @@
|
|
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
|
+
branch_name = CapistranoHelper.new(variables).branch
|
11
|
+
set :branch, branch_name
|
12
|
+
logger.info "setting branch to #{branch_name.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
desc %Q{Creates a tag using the current_stage variable}
|
16
|
+
task :create_tag do
|
17
|
+
if variables[:current_stage]
|
18
|
+
tag_name = AutoTagger.new(variables[:current_stage], variables[:working_directory]).create_tag
|
19
|
+
logger.info "created and pushed tag #{tag_name}"
|
20
|
+
else
|
21
|
+
logger.info "AUTO TAGGER WARNING: skipping auto-creation of tag. Please specify :current_stage to enable auto-creation of tags (like set :current_stage, :ci)."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe AutoTagger do
|
4
|
+
|
5
|
+
describe ".new" do
|
6
|
+
it "blows up if you don't pass an stage" do
|
7
|
+
proc do
|
8
|
+
AutoTagger.new(nil)
|
9
|
+
end.should raise_error(AutoTagger::EnvironmentCannotBeBlankError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "sets the stage when it's passed" do
|
13
|
+
AutoTagger.new("ci").stage.should == "ci"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "sets the path to Dir.pwd when nil" do
|
17
|
+
mock(Dir).pwd { "/foo" }
|
18
|
+
mock(Repository).new("/foo")
|
19
|
+
AutoTagger.new("ci")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "expands the path when the path is passed" do
|
23
|
+
mock(Repository).new(File.expand_path("."))
|
24
|
+
AutoTagger.new("ci", ".")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "exposes the working directory" do
|
28
|
+
mock(Repository).new(File.expand_path("."))
|
29
|
+
AutoTagger.new("ci", ".").working_directory.should == File.expand_path(".")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#create_tag" do
|
34
|
+
it "generates the correct commands" do
|
35
|
+
time = Time.local(2001,1,1)
|
36
|
+
mock(Time).now.once {time}
|
37
|
+
timestamp = time.utc.strftime('%Y%m%d%H%M%S')
|
38
|
+
mock(File).exists?(anything).twice { true }
|
39
|
+
|
40
|
+
mock(Commander).execute!("/foo", "git fetch origin --tags") {true}
|
41
|
+
mock(Commander).execute!("/foo", "git tag ci/#{timestamp}") {true}
|
42
|
+
mock(Commander).execute!("/foo", "git push origin --tags") {true}
|
43
|
+
|
44
|
+
AutoTagger.new("ci", "/foo").create_tag
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns the tag that was created" do
|
48
|
+
time = Time.local(2001,1,1)
|
49
|
+
mock(Time).now.once {time}
|
50
|
+
timestamp = time.utc.strftime('%Y%m%d%H%M%S')
|
51
|
+
mock(File).exists?(anything).twice { true }
|
52
|
+
mock(Commander).execute!(anything, anything).times(any_times) {true}
|
53
|
+
|
54
|
+
AutoTagger.new("ci", "/foo").create_tag.should == "ci/#{timestamp}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#latest_tag" do
|
59
|
+
it "generates the correct commands" do
|
60
|
+
mock(File).exists?(anything).twice { true }
|
61
|
+
|
62
|
+
mock(Commander).execute!("/foo", "git fetch origin --tags") {true}
|
63
|
+
mock(Commander).execute("/foo", "git tag") { "ci_01" }
|
64
|
+
|
65
|
+
AutoTagger.new("ci", "/foo").latest_tag
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,130 @@
|
|
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(CapistranoHelper::NoStagesSpecifiedError)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#variables" do
|
14
|
+
it "returns all variables" do
|
15
|
+
CapistranoHelper.new({:stages => [:bar]}).variables.should == {:stages => [:bar]}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#stages" do
|
20
|
+
it "returns the hashes' stages value" do
|
21
|
+
CapistranoHelper.new({:stages => [:bar]}).stages.should == [:bar]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#working_directory" do
|
26
|
+
it "returns the hashes' working directory value" do
|
27
|
+
CapistranoHelper.new({:stages => [:bar], :working_directory => "/foo"}).working_directory.should == "/foo"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "defaults to Dir.pwd if it's not set, or it's nil" do
|
31
|
+
mock(Dir).pwd { "/bar" }
|
32
|
+
CapistranoHelper.new({:stages => [:bar]}).working_directory.should == "/bar"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#current_stage" do
|
37
|
+
it "returns the hashes' current stage value" do
|
38
|
+
CapistranoHelper.new({:stages => [:bar], :current_stage => :bar}).current_stage.should == :bar
|
39
|
+
CapistranoHelper.new({:stages => [:bar]}).current_stage.should be_nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#previous_stage" do
|
44
|
+
it "returns the previous stage if there is more than one stage, and there is a current stage" do
|
45
|
+
CapistranoHelper.new({:stages => [:foo, :bar], :current_stage => :bar}).previous_stage.should == :foo
|
46
|
+
end
|
47
|
+
|
48
|
+
it "returns nil if there is no previous stage" do
|
49
|
+
CapistranoHelper.new({:stages => [:bar], :current_stage => :bar}).previous_stage.should be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns nil if there is no current stage" do
|
53
|
+
CapistranoHelper.new({:stages => [:bar]}).previous_stage.should be_nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#branch" do
|
58
|
+
describe "with :head and :branch specified" do
|
59
|
+
it "returns master" do
|
60
|
+
variables = {
|
61
|
+
:stages => [:bar],
|
62
|
+
:head => nil,
|
63
|
+
:branch => "foo"
|
64
|
+
}
|
65
|
+
CapistranoHelper.new(variables).branch.should == "foo"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "with :head specified, but no branch specified" do
|
70
|
+
it "returns master" do
|
71
|
+
variables = {
|
72
|
+
:stages => [:bar],
|
73
|
+
:head => nil
|
74
|
+
}
|
75
|
+
CapistranoHelper.new(variables).branch.should == nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "with :branch specified" do
|
80
|
+
it "returns the value of branch" do
|
81
|
+
variables = {
|
82
|
+
:stages => [:bar],
|
83
|
+
:branch => "foo"
|
84
|
+
}
|
85
|
+
CapistranoHelper.new(variables).branch.should == "foo"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "with a previous stage with a tag" do
|
90
|
+
it "returns the latest tag for the previous stage" do
|
91
|
+
variables = {
|
92
|
+
:stages => [:foo, :bar],
|
93
|
+
:current_stage => :bar,
|
94
|
+
:branch => "master",
|
95
|
+
:working_directory => "/foo"
|
96
|
+
}
|
97
|
+
tagger = Object.new
|
98
|
+
mock(tagger).latest_tag { "foo_01" }
|
99
|
+
mock(AutoTagger).new(:foo, "/foo") { tagger }
|
100
|
+
CapistranoHelper.new(variables).branch.should == "foo_01"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "with no branch and a previous stage with no tag" do
|
105
|
+
it "returns nil" do
|
106
|
+
variables = {
|
107
|
+
:stages => [:foo, :bar],
|
108
|
+
:current_stage => :bar,
|
109
|
+
:working_directory => "/foo"
|
110
|
+
}
|
111
|
+
tagger = Object.new
|
112
|
+
mock(tagger).latest_tag { nil }
|
113
|
+
mock(AutoTagger).new(:foo, "/foo") { tagger }
|
114
|
+
CapistranoHelper.new(variables).branch.should == nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "with no branch and previous stage" do
|
119
|
+
it "returns nil" do
|
120
|
+
variables = {
|
121
|
+
:stages => [:bar],
|
122
|
+
:current_stage => :bar
|
123
|
+
}
|
124
|
+
CapistranoHelper.new(variables).previous_stage.should be_nil
|
125
|
+
CapistranoHelper.new(variables).branch.should == nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
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,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
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zilkey-auto_tagger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Dean
|
@@ -23,7 +23,7 @@ dependencies:
|
|
23
23
|
version: 2.5.3
|
24
24
|
version:
|
25
25
|
description:
|
26
|
-
email:
|
26
|
+
email:
|
27
27
|
executables:
|
28
28
|
- autotag
|
29
29
|
extensions: []
|
@@ -31,24 +31,24 @@ extensions: []
|
|
31
31
|
extra_rdoc_files: []
|
32
32
|
|
33
33
|
files:
|
34
|
-
-
|
35
|
-
-
|
36
|
-
-
|
37
|
-
-
|
38
|
-
-
|
39
|
-
-
|
40
|
-
-
|
41
|
-
-
|
42
|
-
-
|
43
|
-
-
|
44
|
-
-
|
45
|
-
-
|
46
|
-
-
|
47
|
-
-
|
48
|
-
-
|
49
|
-
-
|
50
|
-
-
|
51
|
-
-
|
34
|
+
- lib/auto_tagger
|
35
|
+
- lib/auto_tagger/auto_tagger.rb
|
36
|
+
- lib/auto_tagger/capistrano_helper.rb
|
37
|
+
- lib/auto_tagger/commander.rb
|
38
|
+
- lib/auto_tagger/repository.rb
|
39
|
+
- lib/auto_tagger/tag.rb
|
40
|
+
- lib/auto_tagger.rb
|
41
|
+
- recipes/release_tagger.rb
|
42
|
+
- bin/autotag
|
43
|
+
- spec/auto_tagger
|
44
|
+
- spec/auto_tagger/auto_tagger_spec.rb
|
45
|
+
- spec/auto_tagger/capistrano_helper_spec.rb
|
46
|
+
- spec/auto_tagger/commander_spec.rb
|
47
|
+
- spec/auto_tagger/repository_spec.rb
|
48
|
+
- spec/auto_tagger/tag_spec.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
- MIT-LICENSE
|
51
|
+
- README.md
|
52
52
|
has_rdoc: false
|
53
53
|
homepage: http://github.com/zilkey/git_tagger/tree/master
|
54
54
|
post_install_message:
|