integrity 0.1.8

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.
Files changed (57) hide show
  1. data/README.markdown +66 -0
  2. data/Rakefile +165 -0
  3. data/VERSION.yml +4 -0
  4. data/app.rb +138 -0
  5. data/bin/integrity +4 -0
  6. data/config/config.sample.ru +31 -0
  7. data/config/config.sample.yml +38 -0
  8. data/config/thin.sample.yml +13 -0
  9. data/integrity.gemspec +76 -0
  10. data/lib/integrity.rb +80 -0
  11. data/lib/integrity/build.rb +61 -0
  12. data/lib/integrity/core_ext/object.rb +6 -0
  13. data/lib/integrity/core_ext/string.rb +5 -0
  14. data/lib/integrity/helpers.rb +16 -0
  15. data/lib/integrity/helpers/authorization.rb +33 -0
  16. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  17. data/lib/integrity/helpers/forms.rb +28 -0
  18. data/lib/integrity/helpers/pretty_output.rb +45 -0
  19. data/lib/integrity/helpers/rendering.rb +14 -0
  20. data/lib/integrity/helpers/resources.rb +13 -0
  21. data/lib/integrity/helpers/urls.rb +47 -0
  22. data/lib/integrity/installer.rb +132 -0
  23. data/lib/integrity/migrations.rb +157 -0
  24. data/lib/integrity/notifier.rb +50 -0
  25. data/lib/integrity/notifier/base.rb +55 -0
  26. data/lib/integrity/project.rb +117 -0
  27. data/lib/integrity/project_builder.rb +47 -0
  28. data/lib/integrity/scm.rb +19 -0
  29. data/lib/integrity/scm/git.rb +83 -0
  30. data/lib/integrity/scm/git/uri.rb +57 -0
  31. data/public/buttons.css +82 -0
  32. data/public/reset.css +7 -0
  33. data/public/spinner.gif +0 -0
  34. data/test/helpers.rb +47 -0
  35. data/test/helpers/acceptance.rb +127 -0
  36. data/test/helpers/acceptance/git_helper.rb +99 -0
  37. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  38. data/test/helpers/expectations.rb +5 -0
  39. data/test/helpers/expectations/be_a.rb +23 -0
  40. data/test/helpers/expectations/change.rb +90 -0
  41. data/test/helpers/expectations/have.rb +105 -0
  42. data/test/helpers/expectations/have_tag.rb +128 -0
  43. data/test/helpers/expectations/predicates.rb +37 -0
  44. data/test/helpers/fixtures.rb +83 -0
  45. data/views/_build_info.haml +18 -0
  46. data/views/build.haml +2 -0
  47. data/views/error.haml +36 -0
  48. data/views/home.haml +23 -0
  49. data/views/integrity.sass +387 -0
  50. data/views/layout.haml +28 -0
  51. data/views/new.haml +51 -0
  52. data/views/not_found.haml +31 -0
  53. data/views/notifier.haml +7 -0
  54. data/views/project.builder +21 -0
  55. data/views/project.haml +28 -0
  56. data/views/unauthorized.haml +38 -0
  57. metadata +258 -0
@@ -0,0 +1,47 @@
1
+ module Integrity
2
+ class ProjectBuilder
3
+ attr_reader :build_script
4
+
5
+ def initialize(project)
6
+ @uri = project.uri
7
+ @build_script = project.command
8
+ @branch = project.branch
9
+ @scm = SCM.new(@uri, @branch, export_directory)
10
+ @build = Build.new(:project => project)
11
+ end
12
+
13
+ def build(commit)
14
+ Integrity.log "Building #{commit} (#{@branch}) of #{@build.project.name} in #{export_directory} using #{scm_name}"
15
+ @scm.with_revision(commit) { run_build_script }
16
+ @build
17
+ ensure
18
+ @build.commit_identifier = @scm.commit_identifier(commit)
19
+ @build.commit_metadata = @scm.commit_metadata(commit)
20
+ @build.save
21
+ end
22
+
23
+ def delete_code
24
+ FileUtils.rm_r export_directory
25
+ rescue Errno::ENOENT
26
+ nil
27
+ end
28
+
29
+ private
30
+ def export_directory
31
+ Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
32
+ end
33
+
34
+ def scm_name
35
+ @scm.name
36
+ end
37
+
38
+ def run_build_script
39
+ Integrity.log "Running `#{build_script}` in #{@scm.working_directory}"
40
+
41
+ IO.popen "(cd #{@scm.working_directory} && #{build_script}) 2>&1", "r" do |pipe|
42
+ @build.output = pipe.read
43
+ end
44
+ @build.successful = $?.success?
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,19 @@
1
+ module Integrity
2
+ module SCM
3
+ class SCMUnknownError < StandardError; end
4
+
5
+ def self.new(uri, *args)
6
+ scm_class_for(uri).new(uri, *args)
7
+ end
8
+
9
+ def self.working_tree_path(uri)
10
+ scm_class_for(uri).working_tree_path(uri)
11
+ end
12
+
13
+ private
14
+ def self.scm_class_for(uri)
15
+ return Git if uri.scheme == "git" || uri.path =~ /\.git\/?/
16
+ raise SCMUnknownError, "could not find any SCM based on URI '#{uri.to_s}'"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,83 @@
1
+ module Integrity
2
+ module SCM
3
+ class Git
4
+ require File.dirname(__FILE__) / "git/uri"
5
+
6
+ attr_reader :uri, :branch, :working_directory
7
+
8
+ def self.working_tree_path(uri)
9
+ Git::URI.new(uri).working_tree_path
10
+ end
11
+
12
+ def initialize(uri, branch, working_directory)
13
+ @uri = uri.to_s
14
+ @branch = branch.to_s
15
+ @working_directory = working_directory
16
+ end
17
+
18
+ def with_revision(revision)
19
+ fetch_code
20
+ checkout(revision)
21
+ yield
22
+ end
23
+
24
+ def commit_identifier(sha1)
25
+ `cd #{working_directory} && git show -s --pretty=format:%H #{sha1}`.chomp
26
+ end
27
+
28
+ def commit_metadata(sha1)
29
+ format = %Q(---%n:author: %an <%ae>%n:message: >-%n %s%n:date: %ci%n)
30
+ YAML.load(`cd #{working_directory} && git show -s --pretty=format:"#{format}" #{sha1}`)
31
+ end
32
+
33
+ def name
34
+ self.class.name.split("::").last
35
+ end
36
+
37
+ private
38
+
39
+ def fetch_code
40
+ clone unless cloned?
41
+ checkout unless on_branch?
42
+ pull
43
+ end
44
+
45
+ def clone
46
+ log "Cloning #{uri} to #{working_directory}"
47
+ `git clone #{uri} #{working_directory} &>/dev/null`
48
+ end
49
+
50
+ def checkout(treeish=nil)
51
+ strategy = case
52
+ when treeish then treeish
53
+ when local_branches.include?(branch) then branch
54
+ else "-b #{branch} origin/#{branch}"
55
+ end
56
+
57
+ log "Checking-out #{strategy}"
58
+ `cd #{working_directory} && git checkout #{strategy} &>/dev/null`
59
+ end
60
+
61
+ def pull
62
+ log "Pull-ing in #{working_directory}"
63
+ `cd #{working_directory} && git pull &>/dev/null`
64
+ end
65
+
66
+ def local_branches
67
+ `cd #{working_directory} && git branch`.split("\n").map {|b| b.delete("*").strip }
68
+ end
69
+
70
+ def cloned?
71
+ File.directory?(working_directory / ".git")
72
+ end
73
+
74
+ def on_branch?
75
+ File.basename(`cd #{working_directory} && git symbolic-ref HEAD &>/dev/null`).chomp == branch
76
+ end
77
+
78
+ def log(message)
79
+ Integrity.log("Git") { message }
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,57 @@
1
+ module Integrity
2
+ module SCM
3
+ class Git
4
+ # From the git-pull man page:
5
+ #
6
+ # GIT URLS
7
+ # One of the following notations can be used to name the remote repository:
8
+ #
9
+ # rsync://host.xz/path/to/repo.git/
10
+ # http://host.xz/path/to/repo.git/
11
+ # git://host.xz/~user/path/to/repo.git/
12
+ # ssh://[user@]host.xz[:port]/path/to/repo.git/
13
+ # ssh://[user@]host.xz/path/to/repo.git/
14
+ # ssh://[user@]host.xz/~user/path/to/repo.git/
15
+ # ssh://[user@]host.xz/~/path/to/repo.git
16
+ #
17
+ # SSH is the default transport protocol over the network. You can optionally
18
+ # specify which user to log-in as, and an alternate, scp-like syntax is also
19
+ # supported
20
+ #
21
+ # Both syntaxes support username expansion, as does the native git protocol,
22
+ # but only the former supports port specification. The following three are
23
+ # identical to the last three above, respectively:
24
+ #
25
+ # [user@]host.xz:/path/to/repo.git/
26
+ # [user@]host.xz:~user/path/to/repo.git/
27
+ # [user@]host.xz:path/to/repo.git
28
+ #
29
+ class URI
30
+ def initialize(uri_string)
31
+ @uri = Addressable::URI.parse(uri_string)
32
+ end
33
+
34
+ def working_tree_path
35
+ strip_extension(path).gsub("/", "-")
36
+ end
37
+
38
+ private
39
+
40
+ def strip_extension(string)
41
+ uri = Pathname.new(string)
42
+ if uri.extname.any?
43
+ uri = Pathname.new(string)
44
+ string.gsub(Regexp.new("#{uri.extname}\/?"), "")
45
+ else
46
+ string
47
+ end
48
+ end
49
+
50
+ def path
51
+ path = @uri.path
52
+ path.gsub(/\~[a-zA-Z0-9]*\//, "").gsub(/^\//, "")
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,82 @@
1
+ /* --------------------------------------------------------------
2
+
3
+ buttons.css
4
+ * Gives you some great CSS-only buttons.
5
+
6
+ Created by Kevin Hale [particletree.com]
7
+ * particletree.com/features/rediscovering-the-button-element
8
+
9
+ See Readme.txt in this folder for instructions.
10
+
11
+ -------------------------------------------------------------- */
12
+
13
+ button {
14
+ display:block;
15
+ float:left;
16
+ margin:0 0.583em 0.667em 0;
17
+ padding:5px 10px 5px 7px; /* Links */
18
+
19
+ border:1px solid #dedede;
20
+ border-top:1px solid #eee;
21
+ border-left:1px solid #eee;
22
+
23
+ background-color:#f5f5f5;
24
+ font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
25
+ font-size:100%;
26
+ line-height:130%;
27
+ text-decoration:none;
28
+ font-weight:bold;
29
+ color:#565656;
30
+ cursor:pointer;
31
+ }
32
+ button {
33
+ width:auto;
34
+ overflow:visible;
35
+ padding:4px 10px 3px 7px; /* IE6 */
36
+ }
37
+ button[type] {
38
+ padding:4px 10px 4px 7px; /* Firefox */
39
+ line-height:17px; /* Safari */
40
+ }
41
+ *:first-child+html button[type] {
42
+ padding:4px 10px 3px 7px; /* IE7 */
43
+ }
44
+ button img {
45
+ margin:0 3px -3px 0 !important;
46
+ padding:0;
47
+ border:none;
48
+ width:16px;
49
+ height:16px;
50
+ float:none;
51
+ }
52
+
53
+
54
+ /* Button colors
55
+ -------------------------------------------------------------- */
56
+
57
+ /* Standard */
58
+ button:hover {
59
+ background-color:#dff4ff;
60
+ border:1px solid #c2e1ef;
61
+ color:#336699;
62
+ }
63
+
64
+ /* Positive */
65
+ body .positive {
66
+ color:#529214;
67
+ }
68
+ button.positive:hover {
69
+ background-color:#E6EFC2;
70
+ border:1px solid #C6D880;
71
+ color:#529214;
72
+ }
73
+
74
+ /* Negative */
75
+ body .negative {
76
+ color:#d12f19;
77
+ }
78
+ button.negative:hover {
79
+ background:#fbe3e4;
80
+ border:1px solid #fbc2c4;
81
+ color:#d12f19;
82
+ }
data/public/reset.css ADDED
@@ -0,0 +1,7 @@
1
+ /*
2
+ Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3
+ Code licensed under the BSD License:
4
+ http://developer.yahoo.net/yui/license.txt
5
+ version: 2.5.2
6
+ */
7
+ html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
Binary file
data/test/helpers.rb ADDED
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + "/../lib/integrity"
2
+
3
+ begin
4
+ require "test/unit"
5
+ require "redgreen"
6
+ require "context"
7
+ require "storyteller"
8
+ require "pending"
9
+ require "matchy"
10
+ require "rr"
11
+ require "mocha"
12
+ require "ruby-debug"
13
+ rescue LoadError
14
+ puts "You're missing some gems required to run the tests."
15
+ puts "Please run `rake test:install_dependencies`"
16
+ puts "You'll probably need to run that command as root or with sudo."
17
+ puts
18
+ puts "Thanks :)"
19
+ puts
20
+
21
+ exit 1
22
+ end
23
+
24
+ require File.dirname(__FILE__) / "helpers" / "expectations"
25
+ require File.dirname(__FILE__) / "helpers" / "fixtures"
26
+
27
+ module TestHelper
28
+ def setup_and_reset_database!
29
+ DataMapper.setup(:default, "sqlite3::memory:")
30
+ DataMapper.auto_migrate!
31
+ end
32
+
33
+ def ignore_logs!
34
+ stub(Integrity).log { nil }
35
+ end
36
+ end
37
+
38
+ class Test::Unit::TestCase
39
+ class << self
40
+ alias_method :specify, :test
41
+ end
42
+
43
+ include RR::Adapters::TestUnit
44
+ include Integrity
45
+ include TestHelper
46
+ end
47
+
@@ -0,0 +1,127 @@
1
+ require 'webrat/rack'
2
+ require 'sinatra'
3
+ require 'sinatra/test'
4
+
5
+ set :environment, :test
6
+ disable :run
7
+ disable :reload
8
+
9
+ Webrat.configuration.instance_variable_set("@mode", :sinatra)
10
+
11
+ module Webrat
12
+ class SinatraSession < Session
13
+ DEFAULT_DOMAIN = "integrity.example.org"
14
+
15
+ def initialize(context = nil)
16
+ super(context)
17
+ @sinatra_test = Sinatra::TestHarness.new
18
+ end
19
+
20
+ %w(get head post put delete).each do |verb|
21
+ class_eval <<-METHOD
22
+ def #{verb}(path, data, headers = {})
23
+ params = data.inject({}) do |data, (key,value)|
24
+ data[key] = Rack::Utils.unescape(value)
25
+ data
26
+ end
27
+ headers['HTTP_HOST'] = DEFAULT_DOMAIN
28
+ @sinatra_test.#{verb}(path, params, headers)
29
+ end
30
+ METHOD
31
+ end
32
+
33
+ def response_body
34
+ @sinatra_test.body
35
+ end
36
+
37
+ def response_code
38
+ @sinatra_test.status
39
+ end
40
+
41
+ private
42
+
43
+ def response
44
+ @sinatra_test.response
45
+ end
46
+
47
+ def current_host
48
+ URI.parse(current_url).host || DEFAULT_DOMAIN
49
+ end
50
+
51
+ def response_location_host
52
+ URI.parse(response_location).host || DEFAULT_DOMAIN
53
+ end
54
+ end
55
+ end
56
+
57
+ require Integrity.root / "app"
58
+ require File.dirname(__FILE__) / "acceptance/git_helper"
59
+
60
+ module AcceptanceHelper
61
+ include FileUtils
62
+
63
+ def export_directory
64
+ Integrity.root / "exports"
65
+ end
66
+
67
+ def enable_auth!
68
+ Integrity.config[:use_basic_auth] = true
69
+ Integrity.config[:admin_username] = "admin"
70
+ Integrity.config[:admin_password] = "test"
71
+ Integrity.config[:hash_admin_password] = false
72
+ end
73
+
74
+ def login_as(user, password)
75
+ def AcceptanceHelper.logged_in; true; end
76
+ basic_auth user, password
77
+ visit "/login"
78
+ Sinatra::Application.before { login_required if AcceptanceHelper.logged_in }
79
+ end
80
+
81
+ def log_out
82
+ def AcceptanceHelper.logged_in; false; end
83
+ @_webrat_session = Webrat::SinatraSession.new(self)
84
+ end
85
+
86
+ def disable_auth!
87
+ Integrity.config[:use_basic_auth] = false
88
+ end
89
+
90
+ def set_and_create_export_directory!
91
+ FileUtils.rm_r(export_directory) if File.directory?(export_directory)
92
+ FileUtils.mkdir(export_directory)
93
+ Integrity.config[:export_directory] = export_directory
94
+ end
95
+
96
+ def setup_log!
97
+ pathname = Integrity.root / "integrity.log"
98
+ FileUtils.rm pathname if File.exists?(pathname)
99
+ Integrity.config[:log] = pathname
100
+ end
101
+ end
102
+
103
+ class Test::Unit::AcceptanceTestCase < Test::Unit::TestCase
104
+ include AcceptanceHelper
105
+ include Test::Storyteller
106
+ include GitHelper
107
+ include Webrat::Methods
108
+ Webrat::Methods.delegate_to_session :response_code
109
+
110
+ before(:all) do
111
+ Integrity.config[:base_uri] = "http://#{Webrat::SinatraSession::DEFAULT_DOMAIN}"
112
+ end
113
+
114
+ before(:each) do
115
+ # ensure each scenario is run in a clean sandbox
116
+ setup_and_reset_database!
117
+ enable_auth!
118
+ setup_log!
119
+ set_and_create_export_directory!
120
+ log_out
121
+ end
122
+
123
+ after(:each) do
124
+ destroy_all_git_repos
125
+ rm_r export_directory if File.directory?(export_directory)
126
+ end
127
+ end