factorylabs-auto_tagger 0.1.1
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/.document +6 -0
- data/.gitignore +6 -0
- data/CHANGELOG +30 -0
- data/MIT-LICENSE +20 -0
- data/README.md +176 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/auto_tagger.gemspec +83 -0
- data/bin/autotag +38 -0
- data/features/autotag.feature +39 -0
- data/features/deployment.feature +29 -0
- data/features/step_definitions/autotag_steps.rb +41 -0
- data/features/step_definitions/deployment_steps.rb +53 -0
- data/features/support/env.rb +8 -0
- data/features/support/step_helpers.rb +136 -0
- data/features/templates/cap_ext_deploy.erb +32 -0
- data/features/templates/deploy.erb +34 -0
- data/features/templates/stage.erb +1 -0
- data/geminstaller.yml +7 -0
- data/lib/auto_tagger.rb +10 -0
- data/lib/auto_tagger/auto_tagger.rb +26 -0
- data/lib/auto_tagger/capistrano_helper.rb +39 -0
- data/lib/auto_tagger/commander.rb +15 -0
- data/lib/auto_tagger/recipes.rb +54 -0
- data/lib/auto_tagger/repository.rb +42 -0
- data/lib/auto_tagger/stage_manager.rb +23 -0
- data/lib/auto_tagger/tag.rb +41 -0
- data/spec/auto_tagger/auto_tagger_spec.rb +85 -0
- data/spec/auto_tagger/capistrano_helper_spec.rb +146 -0
- data/spec/auto_tagger/commander_spec.rb +17 -0
- data/spec/auto_tagger/repository_spec.rb +72 -0
- data/spec/auto_tagger/stage_manager_spec.rb +34 -0
- data/spec/auto_tagger/tag_spec.rb +66 -0
- data/spec/spec_helper.rb +7 -0
- metadata +116 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "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
|
+
tag_name = AutoTagger.new(variables[:stage], variables[:working_directory]).create_tag(real_revision)
|
47
|
+
logger.info "AUTO TAGGER created tag #{tag_name} from #{real_revision}"
|
48
|
+
else
|
49
|
+
tag_name = AutoTagger.new(:production, variables[:working_directory]).create_tag
|
50
|
+
logger.info "AUTO TAGGER created tag #{tag_name}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
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.map{|stage| stage.to_s }
|
14
|
+
end
|
15
|
+
|
16
|
+
def previous_stage(stage)
|
17
|
+
if stage
|
18
|
+
index = stages.index(stage.to_s) - 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,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
|