propel 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/LICENSE +20 -0
- data/README.md +26 -0
- data/Rakefile +7 -0
- data/bin/propel +6 -0
- data/lib/propel.rb +5 -0
- data/lib/propel/configuration.rb +24 -0
- data/lib/propel/git_repository.rb +41 -0
- data/lib/propel/option_parser.rb +47 -0
- data/lib/propel/remote_build.rb +43 -0
- data/lib/propel/runner.rb +63 -0
- data/lib/propel/version.rb +3 -0
- data/propel.gemspec +29 -0
- data/spec/fixtures/ci_joe/failing_build.json +14 -0
- data/spec/fixtures/ci_joe/passing_build.json +14 -0
- data/spec/fixtures/jenkins/failing_build.rss +24 -0
- data/spec/fixtures/jenkins/passing_build.rss +17 -0
- data/spec/fixtures/team_city/failing_build.rss +13 -0
- data/spec/fixtures/team_city/passing_build.rss +13 -0
- data/spec/propel/configuration_spec.rb +12 -0
- data/spec/propel/git_repository_spec.rb +63 -0
- data/spec/propel/option_parser_spec.rb +25 -0
- data/spec/propel/remote_build_spec.rb +51 -0
- data/spec/propel/runner_spec.rb +104 -0
- data/spec/spec_helper.rb +6 -0
- metadata +125 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
propel (0.0.2)
|
5
|
+
json
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.1.2)
|
11
|
+
json (1.5.1)
|
12
|
+
rspec (2.5.0)
|
13
|
+
rspec-core (~> 2.5.0)
|
14
|
+
rspec-expectations (~> 2.5.0)
|
15
|
+
rspec-mocks (~> 2.5.0)
|
16
|
+
rspec-core (2.5.1)
|
17
|
+
rspec-expectations (2.5.0)
|
18
|
+
diff-lcs (~> 1.1.2)
|
19
|
+
rspec-mocks (2.5.0)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
propel!
|
26
|
+
rspec (~> 2.5.0)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Justin Leitgeb
|
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,26 @@
|
|
1
|
+
# Propel
|
2
|
+
|
3
|
+
Propel is a command that helps you to push to remote repositories while following best practices for
|
4
|
+
continuous integration. We believe that before you push to a shared git repository, you should check that
|
5
|
+
both the local and the Continuous Integration (CI) server are green. You should also pull with rebase to avoid pointless
|
6
|
+
merge commits, and Propel uses this behavior as a default.
|
7
|
+
|
8
|
+
## Compatibility
|
9
|
+
|
10
|
+
Propel currently works with Jenkins, Team City and CI Joe.
|
11
|
+
|
12
|
+
## Installing
|
13
|
+
|
14
|
+
Propel can be used from the command line. Install the gem:
|
15
|
+
|
16
|
+
gem install propel
|
17
|
+
|
18
|
+
To use propel, simply run 'propel' from the command line. Without a remote build server configured, it will
|
19
|
+
just do a pull --rebase && rake && git push. You can see all available options with propel --help.
|
20
|
+
|
21
|
+
You generally want to use propel in conjunction with a CI server. Just point propel to your CI server by
|
22
|
+
passing the option --status-url http://ci.example.com/yourbuild.rss. Propel will figure out if your build is
|
23
|
+
passing as of the latest commit for Jenkins, Team City and CI Joe.
|
24
|
+
|
25
|
+
Once you figure out the options that work for you, just put a .propel file in the root of your project.
|
26
|
+
Command line parameters override the options found in the configuration file.
|
data/Rakefile
ADDED
data/bin/propel
ADDED
data/lib/propel.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Propel
|
2
|
+
class Configuration
|
3
|
+
def initialize(command_line_arguments)
|
4
|
+
@command_line_options = command_line_arguments
|
5
|
+
end
|
6
|
+
|
7
|
+
def options
|
8
|
+
parse(options_from_config_file).merge(parse @command_line_options)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def options_from_config_file
|
13
|
+
File.exist?(config_file) ? File.read(config_file).split : [ ]
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(option_array)
|
17
|
+
Propel::OptionParser.parse!(option_array)
|
18
|
+
end
|
19
|
+
|
20
|
+
def config_file
|
21
|
+
'./.propel'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Propel
|
2
|
+
class GitRepository
|
3
|
+
def self.changed?
|
4
|
+
new.changed?
|
5
|
+
end
|
6
|
+
|
7
|
+
def changed?
|
8
|
+
local_last_commit != remote_last_commit
|
9
|
+
end
|
10
|
+
|
11
|
+
def remote_config
|
12
|
+
git("config branch.#{current_branch}.remote")
|
13
|
+
end
|
14
|
+
|
15
|
+
def merge_config
|
16
|
+
git("config branch.#{current_branch}.merge")
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def git git_args
|
21
|
+
`git #{git_args}`.strip
|
22
|
+
end
|
23
|
+
|
24
|
+
def local_last_commit
|
25
|
+
git("rev-parse HEAD")
|
26
|
+
end
|
27
|
+
|
28
|
+
def remote_last_commit
|
29
|
+
fetch!
|
30
|
+
git("ls-remote #{remote_config} #{merge_config}").gsub(/\t.*/, '')
|
31
|
+
end
|
32
|
+
|
33
|
+
def current_branch
|
34
|
+
git("branch").split("\n").detect{|l| l =~ /^\*/ }.gsub(/^\* /, '')
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch!
|
38
|
+
git("fetch")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Propel
|
4
|
+
class OptionParser
|
5
|
+
|
6
|
+
def self.parse!(args = [ ])
|
7
|
+
new.parse!(args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse!(args)
|
11
|
+
options = {}
|
12
|
+
parser(options).parse!(args)
|
13
|
+
options
|
14
|
+
end
|
15
|
+
|
16
|
+
def parser(options)
|
17
|
+
::OptionParser.new do |parser|
|
18
|
+
parser.banner = "Usage: propel [options]\n\n"
|
19
|
+
|
20
|
+
options[:force] = false
|
21
|
+
options[:rebase] = true
|
22
|
+
options[:verbose] = false
|
23
|
+
options[:wait] = false
|
24
|
+
|
25
|
+
parser.on('-s', '--status-url', 'Location of build status') do |o|
|
26
|
+
options[:status_url] = o
|
27
|
+
end
|
28
|
+
|
29
|
+
parser.on('-f', '--[no-]force', 'Use propel --force to ignore any remote build failures') do |o|
|
30
|
+
options[:force] = o
|
31
|
+
end
|
32
|
+
|
33
|
+
parser.on('-r', '--[no-]rebase', 'Use propel --no-rebase. Defaults to --rebase') do |o|
|
34
|
+
options[:rebase] = o
|
35
|
+
end
|
36
|
+
|
37
|
+
parser.on('-w', '--[no-]wait', 'Wait for the remote build to pass if it is currently failing. Use propel --wait.') do |o|
|
38
|
+
options[:wait] = o
|
39
|
+
end
|
40
|
+
|
41
|
+
parser.on('-v', '--verbose', 'Use propel --verbose.') do |o|
|
42
|
+
options[:verbose] = o
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rss'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
module Propel
|
7
|
+
class RemoteBuild
|
8
|
+
def initialize(status_url)
|
9
|
+
@status_url = status_url
|
10
|
+
end
|
11
|
+
|
12
|
+
FAIL_PATTERNS = {
|
13
|
+
:jenkins => /\(broken/,
|
14
|
+
:team_city => /(?:has failed)$/,
|
15
|
+
:ci_joe => /^failed$/
|
16
|
+
}
|
17
|
+
|
18
|
+
def passing?
|
19
|
+
!!FAIL_PATTERNS.values.detect{|pattern| most_recent_results =~ pattern }.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def most_recent_results
|
24
|
+
if contents = rss_contents
|
25
|
+
contents.entries.first.title.content
|
26
|
+
else
|
27
|
+
json_contents["jobs"].first["status"]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def rss_contents
|
32
|
+
RSS::Parser.parse(retrieve_test_results, false)
|
33
|
+
end
|
34
|
+
|
35
|
+
def json_contents
|
36
|
+
JSON.parse(retrieve_test_results)
|
37
|
+
end
|
38
|
+
|
39
|
+
def retrieve_test_results
|
40
|
+
@_test_results ||= Net::HTTP.get URI.parse(@status_url)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Propel
|
2
|
+
class Runner
|
3
|
+
def initialize(args = [ ])
|
4
|
+
@options = Configuration.new(args).options
|
5
|
+
end
|
6
|
+
|
7
|
+
def start
|
8
|
+
git_repository = GitRepository.new
|
9
|
+
|
10
|
+
if git_repository.changed?
|
11
|
+
check_remote_build! unless ignore_remote_build?
|
12
|
+
propel!
|
13
|
+
else
|
14
|
+
puts "There is nothing to propel - your HEAD is identical to #{git_repository.remote_config} #{git_repository.merge_config}."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def check_remote_build!
|
21
|
+
if remote_build_configured?
|
22
|
+
|
23
|
+
if !remote_build_passing?
|
24
|
+
raise "The remote build is broken. If your commit fixes the build, run propel with the --force (-f) option."
|
25
|
+
end
|
26
|
+
|
27
|
+
else
|
28
|
+
puts "Remote build is not configured, skipping check." if @options[:verbose]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def ignore_remote_build?
|
33
|
+
@options[:force]
|
34
|
+
end
|
35
|
+
|
36
|
+
def remote_build_configured?
|
37
|
+
!@options[:status_url].nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
def remote_build_passing?
|
41
|
+
if @options[:wait]
|
42
|
+
until remote_build_green? do
|
43
|
+
print "."
|
44
|
+
sleep 10
|
45
|
+
end
|
46
|
+
|
47
|
+
true
|
48
|
+
else
|
49
|
+
remote_build_green?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def remote_build_green?
|
54
|
+
RemoteBuild.new(@options[:status_url]).passing?
|
55
|
+
end
|
56
|
+
|
57
|
+
def propel!
|
58
|
+
pull_cmd = 'git pull'
|
59
|
+
pull_cmd << ' --rebase' if @options[:rebase]
|
60
|
+
system [ pull_cmd, 'rake', 'git push' ].join(' && ')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/propel.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "propel/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "propel"
|
7
|
+
s.version = Propel::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Justin Leitgeb", "Jose Carrion"]
|
10
|
+
s.email = ["justin@stackbuilders.com", "jcarrion@stackbuilders.com"]
|
11
|
+
s.homepage = "http://github.com/stackbuilders/propel"
|
12
|
+
s.summary = "Propel helps you to follow best practices for pushing code to a remote git repo"
|
13
|
+
s.description = <<-EOS
|
14
|
+
The 'propel' script helps you to push your code to a remote server while following best practices,
|
15
|
+
especially in regard to Continuous Integration (CI). Propel first checks the CI server to make sure
|
16
|
+
it's passing, and then runs the local spec suite and pushes changes. If the remote server is failing,
|
17
|
+
you can tell propel to wait until it passes, and then proceed to run your build and push if the local
|
18
|
+
build passes.
|
19
|
+
EOS
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
|
+
|
26
|
+
s.add_dependency('json')
|
27
|
+
|
28
|
+
s.add_development_dependency('rspec', ["~> 2.5.0"])
|
29
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{ "jobs": [
|
2
|
+
{"name":"Test Project",
|
3
|
+
"url":"http://joe.example.co",
|
4
|
+
"color":"red",
|
5
|
+
"status":"failed",
|
6
|
+
"started_at":"January 1, 2010 10:00 AM",
|
7
|
+
"finished_at":"January 1, 2010 10:05 AM",
|
8
|
+
"duration":"5 minutes",
|
9
|
+
"sha":"66bd3c89c1d1d25c18ad931b975473c7d592a2be",
|
10
|
+
"short_sha":"66bd3c89",
|
11
|
+
"commit_url":"http://github.com/myproj/somecommit",
|
12
|
+
"branch":"master"
|
13
|
+
}
|
14
|
+
]}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{ "jobs": [
|
2
|
+
{"name":"Test Project",
|
3
|
+
"url":"http://joe.example.co",
|
4
|
+
"color":"blue",
|
5
|
+
"status":"worked",
|
6
|
+
"started_at":"January 1, 2010 10:00 AM",
|
7
|
+
"finished_at":"January 1, 2010 10:05 AM",
|
8
|
+
"duration":"5 minutes",
|
9
|
+
"sha":"66bd3c89c1d1d25c18ad931b975473c7d592a2be",
|
10
|
+
"short_sha":"66bd3c89",
|
11
|
+
"commit_url":"http://github.com/myproj/somecommit",
|
12
|
+
"branch":"master"
|
13
|
+
}
|
14
|
+
]}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<feed xmlns="http://www.w3.org/2005/Atom">
|
3
|
+
<title>All last builds only</title>
|
4
|
+
<link type="text/html" href="http://ci.example.com/job/FRED/" rel="alternate"/>
|
5
|
+
<updated>2011-04-22T18:20:32Z</updated>
|
6
|
+
<author>
|
7
|
+
<name>Jenkins Server</name>
|
8
|
+
</author>
|
9
|
+
<id>urn:uuid:903deee0-7bfa-11db-9fe1-0800200c9a66</id>
|
10
|
+
<entry>
|
11
|
+
<title>FRED #72 (broken since build #70)</title>
|
12
|
+
<link type="text/html" href="http://ci.example.com/job/FRED/72/" rel="alternate"/>
|
13
|
+
<id>tag:hudson.dev.java.net,2011:FRED:2011-04-22_14-20-32</id>
|
14
|
+
<published>2011-04-22T18:20:32Z</published>
|
15
|
+
<updated>2011-04-22T18:20:32Z</updated>
|
16
|
+
</entry>
|
17
|
+
<entry>
|
18
|
+
<title>FRED #70 (broken since this build)</title>
|
19
|
+
<link type="text/html" href="http://ci.example.com/job/FRED/70/" rel="alternate"/>
|
20
|
+
<id>tag:hudson.dev.java.net,2011:FRED:2011-04-22_12-59-34</id>
|
21
|
+
<published>2011-04-22T16:59:34Z</published>
|
22
|
+
<updated>2011-04-22T16:59:34Z</updated>
|
23
|
+
</entry>
|
24
|
+
</feed>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<feed xmlns="http://www.w3.org/2005/Atom">
|
3
|
+
<title>FRED all builds</title>
|
4
|
+
<link type="text/html" href="http://ci.example.com/job/FRED_never_broken/" rel="alternate"/>
|
5
|
+
<updated>2011-04-27T23:52:12Z</updated>
|
6
|
+
<author>
|
7
|
+
<name>Jenkins Server</name>
|
8
|
+
</author>
|
9
|
+
<id>urn:uuid:903deee0-7bfa-11db-9fe1-0800200c9a66</id>
|
10
|
+
<entry>
|
11
|
+
<title>FRED #109 (stable)</title>
|
12
|
+
<link type="text/html" href="http://ci.example.com/job/FRED/109/" rel="alternate"/>
|
13
|
+
<id>tag:hudson.dev.java.net,2011:FRED:2011-04-27_19-52-12</id>
|
14
|
+
<published>2011-04-27T23:52:12Z</published>
|
15
|
+
<updated>2011-04-27T23:52:12Z</updated>
|
16
|
+
</entry>
|
17
|
+
</feed>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/">
|
3
|
+
<title>Builds of SomeProject :: SomeProject master branch</title>
|
4
|
+
<link rel="alternate" href="http://ci.example.com/viewType.html?buildTypeId=bt3" />
|
5
|
+
<subtitle>Builds of [SomeProject :: SomeProject master branch {id=bt3}] build configuration(s) of TeamCity server at http://ci.example.com. (no more then 100 items, only for items since Apr 26, 2011)</subtitle>
|
6
|
+
<updated>2011-05-01T20:28:55Z</updated>
|
7
|
+
<dc:creator>TeamCity server</dc:creator>
|
8
|
+
<dc:date>2011-05-01T20:28:55Z</dc:date>
|
9
|
+
<entry>
|
10
|
+
<title>Build Fred::FirstProject master branch #216 has failed</title>
|
11
|
+
</entry>
|
12
|
+
</feed>
|
13
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/">
|
3
|
+
<title>Builds of SomeProject :: SomeProject master branch</title>
|
4
|
+
<link rel="alternate" href="http://ci.example.com/viewType.html?buildTypeId=bt3" />
|
5
|
+
<subtitle>Builds of [SomeProject :: SomeProject master branch {id=bt3}] build configuration(s) of TeamCity server at http://ci.example.com. (no more then 100 items, only for items since Apr 26, 2011)</subtitle>
|
6
|
+
<updated>2011-05-01T20:28:55Z</updated>
|
7
|
+
<dc:creator>TeamCity server</dc:creator>
|
8
|
+
<dc:date>2011-05-01T20:28:55Z</dc:date>
|
9
|
+
<entry>
|
10
|
+
<title>Build SomeProject::SomeProject master branch #220 was successful</title>
|
11
|
+
</entry>
|
12
|
+
</feed>
|
13
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Propel::Configuration do
|
4
|
+
describe "#options" do
|
5
|
+
it "should prefer options given on the command line over options in a configuration file" do
|
6
|
+
configuration = Propel::Configuration.new(['--rebase'])
|
7
|
+
configuration.stub!(:options_from_config_file).and_return(['--no-rebase'])
|
8
|
+
|
9
|
+
configuration.options[:rebase].should be_true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Propel::GitRepository do
|
4
|
+
describe ".changed?" do
|
5
|
+
it "should call #changed? on a new instance of the GitRepository" do
|
6
|
+
git_repository = Propel::GitRepository.new
|
7
|
+
git_repository.should_receive(:changed?)
|
8
|
+
Propel::GitRepository.should_receive(:new).and_return git_repository
|
9
|
+
|
10
|
+
Propel::GitRepository.changed?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#changed?" do
|
15
|
+
it "should return false when the remote branch has the same SHA1 as the local HEAD" do
|
16
|
+
git_repository = Propel::GitRepository.new
|
17
|
+
git_repository.stub!(:fetch!)
|
18
|
+
git_repository.stub!(:git).with("branch").and_return("* master\n testbranch")
|
19
|
+
|
20
|
+
git_repository.should_receive(:git).with("rev-parse HEAD").and_return("ef2c8125b1923950a9cd776298516ad9ed3eb568")
|
21
|
+
git_repository.should_receive(:git).with("config branch.master.remote").and_return("origin")
|
22
|
+
git_repository.should_receive(:git).with("config branch.master.merge").and_return("refs/heads/master")
|
23
|
+
|
24
|
+
git_repository.should_receive(:git).with("ls-remote origin refs/heads/master").and_return("ef2c8125b1923950a9cd776298516ad9ed3eb568\trefs/heads/master")
|
25
|
+
|
26
|
+
git_repository.should_not be_changed
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return true when the remote branch has a different SHA1 than the local HEAD" do
|
30
|
+
git_repository = Propel::GitRepository.new
|
31
|
+
git_repository.stub!(:fetch!)
|
32
|
+
git_repository.stub!(:git).with("branch").and_return("* master\n testbranch")
|
33
|
+
|
34
|
+
git_repository.should_receive(:git).with("rev-parse HEAD").and_return("ef2c8125b1923950a9cd776298516ad9ed3eb568")
|
35
|
+
git_repository.should_receive(:git).with("config branch.master.remote").and_return("origin")
|
36
|
+
git_repository.should_receive(:git).with("config branch.master.merge").and_return("refs/heads/master")
|
37
|
+
|
38
|
+
git_repository.should_receive(:git).with("ls-remote origin refs/heads/master").and_return("bf2c8125b1923950a9cd776298516ad9ed3eb568\trefs/heads/master")
|
39
|
+
|
40
|
+
git_repository.should be_changed
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#remote_config" do
|
45
|
+
it "should call the git command to determine the remote repository" do
|
46
|
+
git_repository = Propel::GitRepository.new
|
47
|
+
git_repository.stub!(:git).with("branch").and_return("* master\n testbranch")
|
48
|
+
git_repository.should_receive(:git).with("config branch.master.remote").and_return("origin")
|
49
|
+
|
50
|
+
git_repository.remote_config.should == 'origin'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#merge_config" do
|
55
|
+
it "should call the git command to determine the remote branch" do
|
56
|
+
git_repository = Propel::GitRepository.new
|
57
|
+
git_repository.stub!(:git).with("branch").and_return("* master\n testbranch")
|
58
|
+
git_repository.should_receive(:git).with("config branch.master.merge").and_return("refs/heads/master")
|
59
|
+
|
60
|
+
git_repository.merge_config.should == 'refs/heads/master'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Propel::OptionParser do
|
4
|
+
describe ".parse!" do
|
5
|
+
it "should set default options" do
|
6
|
+
Propel::OptionParser.parse!.should == {:rebase => true, :force => false, :verbose => false, :wait => false}
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should set force to true when given as an option" do
|
10
|
+
Propel::OptionParser.parse!(['--force'])[:force].should be_true
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should set rebase to false when given as an option" do
|
14
|
+
Propel::OptionParser.parse!(['--no-rebase'])[:rebase].should be_false
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should set verbose to true when given as an option" do
|
18
|
+
Propel::OptionParser.parse!(['--verbose'])[:verbose].should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set wait to true when given as an option" do
|
22
|
+
Propel::OptionParser.parse!(['--wait'])[:wait].should be_true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Propel::RemoteBuild do
|
4
|
+
before do
|
5
|
+
@remote_build = Propel::RemoteBuild.new('http://ci.example.com/someProject.rss')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#passing?" do
|
9
|
+
it "should retrieve the contents of the configured URL" do
|
10
|
+
Net::HTTP.stub!(:get).with(URI.parse('http://ci.example.com/someProject.rss')).and_return File.read(File.join(File.dirname(__FILE__), %w[ .. fixtures jenkins passing_build.rss ]))
|
11
|
+
@remote_build.passing?
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "with a Jenkins build feed" do
|
15
|
+
it "should return true if the tests are passing" do
|
16
|
+
@remote_build.stub!(:retrieve_test_results).and_return(File.read(File.join(File.dirname(__FILE__), %w[ .. fixtures jenkins passing_build.rss ])))
|
17
|
+
@remote_build.passing?.should be_true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return false if the tests are not passing" do
|
21
|
+
@remote_build.stub!(:retrieve_test_results).and_return(File.read(File.join(File.dirname(__FILE__), %w[ .. fixtures jenkins failing_build.rss ])))
|
22
|
+
@remote_build.passing?.should be_false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "with a TeamCity build feed" do
|
27
|
+
it "should return true if the tests are passing" do
|
28
|
+
@remote_build.stub!(:retrieve_test_results).and_return(File.read(File.join(File.dirname(__FILE__), %w[ .. fixtures team_city passing_build.rss ])))
|
29
|
+
@remote_build.passing?.should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return false if the tests are not passing" do
|
33
|
+
@remote_build.stub!(:retrieve_test_results).and_return(File.read(File.join(File.dirname(__FILE__), %w[ .. fixtures team_city failing_build.rss ])))
|
34
|
+
@remote_build.passing?.should be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "with a CI Joe build feed" do
|
39
|
+
it "should return true if the tests are passing" do
|
40
|
+
@remote_build.stub!(:retrieve_test_results).and_return(File.read(File.join(File.dirname(__FILE__), %w[ .. fixtures ci_joe passing_build.json ])))
|
41
|
+
@remote_build.passing?.should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return false if the tests are not passing" do
|
45
|
+
@remote_build.stub!(:retrieve_test_results).and_return(File.read(File.join(File.dirname(__FILE__), %w[ .. fixtures ci_joe failing_build.json ])))
|
46
|
+
@remote_build.passing?.should be_false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Propel::Runner do
|
4
|
+
before do
|
5
|
+
@git_repository = Propel::GitRepository.new
|
6
|
+
Propel::GitRepository.should_receive(:new).and_return(@git_repository)
|
7
|
+
@git_repository.stub!(:changed?).and_return(true)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".start" do
|
11
|
+
it "should not call propel! if there is nothing to push" do
|
12
|
+
runner = Propel::Runner.new
|
13
|
+
@git_repository.should_receive(:changed?).and_return(false)
|
14
|
+
runner.should_not_receive(:propel!)
|
15
|
+
runner.stub!(:puts)
|
16
|
+
runner.start
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should call propel! if there are changes to the current branch" do
|
20
|
+
runner = Propel::Runner.new
|
21
|
+
|
22
|
+
@git_repository.stub!(:changed?).and_return(true)
|
23
|
+
@git_repository.stub!(:remote_config).and_return('origin')
|
24
|
+
@git_repository.stub!(:remote_config).and_return('refs/heads/master')
|
25
|
+
|
26
|
+
runner.should_receive(:propel!)
|
27
|
+
runner.start
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should call propel! if the remote build is configured and passing" do
|
31
|
+
runner = Propel::Runner.new(%w[ --status-url http://ci.example.com/status ])
|
32
|
+
runner.stub!(:remote_build_passing?).and_return(true)
|
33
|
+
runner.stub!(:remote_build_configured?).and_return(true)
|
34
|
+
|
35
|
+
runner.should_receive(:propel!)
|
36
|
+
|
37
|
+
runner.start
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should call propel! if the remote build is not configured" do
|
41
|
+
runner = Propel::Runner.new
|
42
|
+
runner.stub!(:remote_build_configured?).and_return false
|
43
|
+
runner.should_receive(:propel!)
|
44
|
+
|
45
|
+
runner.start
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should raise an error and not call propel! if the remote build is configured but not passing" do
|
49
|
+
runner = Propel::Runner.new
|
50
|
+
runner.stub!(:remote_build_configured?).and_return true
|
51
|
+
runner.stub!(:remote_build_passing?).and_return false
|
52
|
+
|
53
|
+
runner.should_not_receive(:propel!)
|
54
|
+
|
55
|
+
lambda {
|
56
|
+
runner.start
|
57
|
+
}.should raise_error(RuntimeError, "The remote build is broken. If your commit fixes the build, run propel with the --force (-f) option.")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should call propel! when the remote build is failing if --force is specified" do
|
61
|
+
runner = Propel::Runner.new %w[ --force ]
|
62
|
+
runner.stub!(:remote_build_configured?).and_return true
|
63
|
+
runner.stub!(:remote_build_passing?).and_return false
|
64
|
+
runner.should_receive(:propel!)
|
65
|
+
|
66
|
+
runner.start
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should call propel! when the remote build is not configured if --force is specified" do
|
70
|
+
runner = Propel::Runner.new %w[ --force ]
|
71
|
+
runner.stub!(:remote_build_configured?).and_return false
|
72
|
+
|
73
|
+
runner.should_receive(:propel!)
|
74
|
+
|
75
|
+
runner.start
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should run a command using pull --rebase by default" do
|
79
|
+
runner = Propel::Runner.new
|
80
|
+
|
81
|
+
runner.should_receive(:system).with("git pull --rebase && rake && git push")
|
82
|
+
runner.start
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should run a command using pull without --rebase when --no-rebase is specified" do
|
86
|
+
runner = Propel::Runner.new(['--no-rebase'])
|
87
|
+
runner.should_receive(:system).with("git pull && rake && git push")
|
88
|
+
runner.start
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should wait for the build to pass if the user specifies the --wait option" do
|
92
|
+
runner = Propel::Runner.new(['--wait'])
|
93
|
+
runner.stub!(:remote_build_configured?).and_return true
|
94
|
+
|
95
|
+
runner.should_receive(:remote_build_green?).twice.and_return(false, true)
|
96
|
+
|
97
|
+
runner.stub!(:print).with('.')
|
98
|
+
runner.stub!(:sleep).with(10)
|
99
|
+
|
100
|
+
runner.should_receive(:propel!)
|
101
|
+
runner.start
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: propel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Justin Leitgeb
|
14
|
+
- Jose Carrion
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-05-01 00:00:00 -05:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: json
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 27
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 5
|
48
|
+
- 0
|
49
|
+
version: 2.5.0
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
description: " The 'propel' script helps you to push your code to a remote server while following best practices,\n especially in regard to Continuous Integration (CI). Propel first checks the CI server to make sure\n it's passing, and then runs the local spec suite and pushes changes. If the remote server is failing,\n you can tell propel to wait until it passes, and then proceed to run your build and push if the local\n build passes.\n"
|
53
|
+
email:
|
54
|
+
- justin@stackbuilders.com
|
55
|
+
- jcarrion@stackbuilders.com
|
56
|
+
executables:
|
57
|
+
- propel
|
58
|
+
extensions: []
|
59
|
+
|
60
|
+
extra_rdoc_files: []
|
61
|
+
|
62
|
+
files:
|
63
|
+
- .gitignore
|
64
|
+
- Gemfile
|
65
|
+
- Gemfile.lock
|
66
|
+
- LICENSE
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/propel
|
70
|
+
- lib/propel.rb
|
71
|
+
- lib/propel/configuration.rb
|
72
|
+
- lib/propel/git_repository.rb
|
73
|
+
- lib/propel/option_parser.rb
|
74
|
+
- lib/propel/remote_build.rb
|
75
|
+
- lib/propel/runner.rb
|
76
|
+
- lib/propel/version.rb
|
77
|
+
- propel.gemspec
|
78
|
+
- spec/fixtures/ci_joe/failing_build.json
|
79
|
+
- spec/fixtures/ci_joe/passing_build.json
|
80
|
+
- spec/fixtures/jenkins/failing_build.rss
|
81
|
+
- spec/fixtures/jenkins/passing_build.rss
|
82
|
+
- spec/fixtures/team_city/failing_build.rss
|
83
|
+
- spec/fixtures/team_city/passing_build.rss
|
84
|
+
- spec/propel/configuration_spec.rb
|
85
|
+
- spec/propel/git_repository_spec.rb
|
86
|
+
- spec/propel/option_parser_spec.rb
|
87
|
+
- spec/propel/remote_build_spec.rb
|
88
|
+
- spec/propel/runner_spec.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
has_rdoc: true
|
91
|
+
homepage: http://github.com/stackbuilders/propel
|
92
|
+
licenses: []
|
93
|
+
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
hash: 3
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
version: "0"
|
117
|
+
requirements: []
|
118
|
+
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 1.6.2
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: Propel helps you to follow best practices for pushing code to a remote git repo
|
124
|
+
test_files: []
|
125
|
+
|