alphasights-integrity 0.1.9.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.
Files changed (93) hide show
  1. data/.gitignore +13 -0
  2. data/CHANGES +48 -0
  3. data/README.md +82 -0
  4. data/Rakefile +58 -0
  5. data/bin/integrity +4 -0
  6. data/config/config.sample.ru +21 -0
  7. data/config/config.sample.yml +41 -0
  8. data/config/heroku/.gems +1 -0
  9. data/config/heroku/Rakefile +6 -0
  10. data/config/heroku/config.ru +7 -0
  11. data/config/heroku/integrity-config.rb +14 -0
  12. data/config/thin.sample.yml +13 -0
  13. data/integrity.gemspec +137 -0
  14. data/lib/integrity.rb +77 -0
  15. data/lib/integrity/app.rb +138 -0
  16. data/lib/integrity/author.rb +39 -0
  17. data/lib/integrity/build.rb +52 -0
  18. data/lib/integrity/commit.rb +61 -0
  19. data/lib/integrity/core_ext/object.rb +6 -0
  20. data/lib/integrity/helpers.rb +16 -0
  21. data/lib/integrity/helpers/authorization.rb +33 -0
  22. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  23. data/lib/integrity/helpers/gravatar.rb +16 -0
  24. data/lib/integrity/helpers/pretty_output.rb +45 -0
  25. data/lib/integrity/helpers/rendering.rb +49 -0
  26. data/lib/integrity/helpers/resources.rb +19 -0
  27. data/lib/integrity/helpers/urls.rb +59 -0
  28. data/lib/integrity/installer.rb +138 -0
  29. data/lib/integrity/migrations.rb +153 -0
  30. data/lib/integrity/notifier.rb +44 -0
  31. data/lib/integrity/notifier/base.rb +74 -0
  32. data/lib/integrity/notifier/test.rb +52 -0
  33. data/lib/integrity/notifier/test/fixtures.rb +108 -0
  34. data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
  35. data/lib/integrity/project.rb +94 -0
  36. data/lib/integrity/project/notifiers.rb +31 -0
  37. data/lib/integrity/project/push.rb +43 -0
  38. data/lib/integrity/project_builder.rb +56 -0
  39. data/lib/integrity/scm.rb +19 -0
  40. data/lib/integrity/scm/git.rb +84 -0
  41. data/lib/integrity/scm/git/uri.rb +57 -0
  42. data/public/buttons.css +82 -0
  43. data/public/reset.css +7 -0
  44. data/public/spinner.gif +0 -0
  45. data/test/acceptance/api_test.rb +97 -0
  46. data/test/acceptance/browse_project_builds_test.rb +65 -0
  47. data/test/acceptance/browse_project_test.rb +99 -0
  48. data/test/acceptance/build_notifications_test.rb +114 -0
  49. data/test/acceptance/create_project_test.rb +97 -0
  50. data/test/acceptance/delete_project_test.rb +53 -0
  51. data/test/acceptance/edit_project_test.rb +117 -0
  52. data/test/acceptance/error_page_test.rb +21 -0
  53. data/test/acceptance/installer_test.rb +81 -0
  54. data/test/acceptance/manual_build_project_test.rb +82 -0
  55. data/test/acceptance/not_found_page_test.rb +29 -0
  56. data/test/acceptance/project_syndication_test.rb +30 -0
  57. data/test/acceptance/stylesheet_test.rb +26 -0
  58. data/test/acceptance/unauthorized_page_test.rb +20 -0
  59. data/test/helpers.rb +75 -0
  60. data/test/helpers/acceptance.rb +82 -0
  61. data/test/helpers/acceptance/email_notifier.rb +52 -0
  62. data/test/helpers/acceptance/git_helper.rb +99 -0
  63. data/test/helpers/acceptance/notifier_helper.rb +47 -0
  64. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  65. data/test/helpers/expectations.rb +4 -0
  66. data/test/helpers/expectations/be_a.rb +23 -0
  67. data/test/helpers/expectations/change.rb +90 -0
  68. data/test/helpers/expectations/have.rb +105 -0
  69. data/test/helpers/expectations/predicates.rb +37 -0
  70. data/test/helpers/initial_migration_fixture.sql +44 -0
  71. data/test/unit/build_test.rb +72 -0
  72. data/test/unit/commit_test.rb +66 -0
  73. data/test/unit/helpers_test.rb +103 -0
  74. data/test/unit/integrity_test.rb +35 -0
  75. data/test/unit/migrations_test.rb +57 -0
  76. data/test/unit/notifier/base_test.rb +43 -0
  77. data/test/unit/notifier_test.rb +96 -0
  78. data/test/unit/project_builder_test.rb +118 -0
  79. data/test/unit/project_test.rb +344 -0
  80. data/test/unit/scm_test.rb +54 -0
  81. data/views/_commit_info.haml +30 -0
  82. data/views/build.haml +2 -0
  83. data/views/error.haml +37 -0
  84. data/views/home.haml +22 -0
  85. data/views/integrity.sass +424 -0
  86. data/views/layout.haml +29 -0
  87. data/views/new.haml +50 -0
  88. data/views/not_found.haml +31 -0
  89. data/views/notifier.haml +7 -0
  90. data/views/project.builder +21 -0
  91. data/views/project.haml +31 -0
  92. data/views/unauthorized.haml +38 -0
  93. metadata +324 -0
@@ -0,0 +1,43 @@
1
+ module Integrity
2
+ class Project
3
+ module Push
4
+ def push(payload)
5
+ payload = parse_payload(payload)
6
+ raise ArgumentError unless valid_payload?(payload)
7
+
8
+ commits =
9
+ if Integrity.config[:build_all_commits]
10
+ payload["commits"]
11
+ else
12
+ [ payload["commits"].first ]
13
+ end
14
+
15
+ commits.each { |commit_data|
16
+ commit = commit_from(commit_data)
17
+ commit.create
18
+ build(commit.identifier)
19
+ }
20
+ end
21
+
22
+ private
23
+ def commit_from(data)
24
+ commits.new(:identifier => data["id"],
25
+ :author => "#{data["author"]["name"]} <#{data["author"]["email"]}>",
26
+ :message => data["message"],
27
+ :committed_at => data["timestamp"])
28
+ end
29
+
30
+ def valid_payload?(payload)
31
+ payload && payload["ref"].to_s.include?(branch) &&
32
+ !payload["commits"].nil? &&
33
+ !payload["commits"].to_a.empty?
34
+ end
35
+
36
+ def parse_payload(payload)
37
+ JSON.parse(payload.to_s)
38
+ rescue JSON::ParserError
39
+ false
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,56 @@
1
+ require "forwardable"
2
+
3
+ module Integrity
4
+ class ProjectBuilder
5
+ extend Forwardable
6
+
7
+ attr_accessor :project, :scm
8
+ def_delegators :project, :name, :uri, :command, :branch
9
+
10
+ def self.build(commit)
11
+ new(commit.project).build(commit)
12
+ end
13
+
14
+ def self.delete_working_directory(project)
15
+ new(project).delete_code
16
+ end
17
+
18
+ def initialize(project)
19
+ @project = project
20
+ @scm = SCM.new(uri, branch, export_directory)
21
+ end
22
+
23
+ def build(commit)
24
+ build = commit.build
25
+ build.start!
26
+
27
+ Integrity.log "Building #{commit.identifier} (#{branch}) of #{name} in" +
28
+ "#{export_directory} using #{scm.name}"
29
+
30
+ scm.with_revision(commit.identifier) do
31
+ Integrity.log "Running `#{command}` in #{scm.working_directory}"
32
+
33
+ IO.popen("(cd #{scm.working_directory} && #{command}) 2>&1", "r") {
34
+ |output| build.output = output.read }
35
+ build.successful = $?.success?
36
+ end
37
+
38
+ build
39
+ ensure
40
+ build.complete!
41
+ commit.update_attributes(scm.info(commit.identifier) || {})
42
+ project.enabled_notifiers.each { |notifier| notifier.notify_of_build(build) }
43
+ end
44
+
45
+ def delete_code
46
+ FileUtils.rm_r export_directory
47
+ rescue Errno::ENOENT
48
+ nil
49
+ end
50
+
51
+ private
52
+ def export_directory
53
+ Integrity.config[:export_directory] / "#{SCM.working_tree_path(uri)}-#{branch}"
54
+ end
55
+ end
56
+ 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,84 @@
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=nil)
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 name
25
+ self.class.name.split("::").last
26
+ end
27
+
28
+ def head
29
+ log "Getting the HEAD of '#{uri}' at '#{branch}'"
30
+ `git ls-remote --heads #{uri} #{branch} | awk '{print $1}'`.chomp
31
+ end
32
+
33
+ def info(revision)
34
+ format = %Q(---%n:author: %an <%ae>%n:message: >-%n %s%n:committed_at: %ci%n)
35
+ YAML.load(`cd #{working_directory} && git show -s --pretty=format:"#{format}" #{revision}`)
36
+ end
37
+
38
+ private
39
+
40
+ def fetch_code
41
+ clone unless cloned?
42
+ checkout unless on_branch?
43
+ pull
44
+ end
45
+
46
+ def clone
47
+ log "Cloning #{uri} to #{working_directory}"
48
+ `git clone #{uri} #{working_directory} &>/dev/null`
49
+ end
50
+
51
+ def checkout(treeish=nil)
52
+ strategy = case
53
+ when treeish then treeish
54
+ when local_branches.include?(branch) then branch
55
+ else "origin/#{branch}"
56
+ end
57
+
58
+ log "Checking-out #{strategy}"
59
+ `cd #{working_directory} && git reset --hard #{strategy} &>/dev/null`
60
+ end
61
+
62
+ def pull
63
+ log "Pull-ing in #{working_directory}"
64
+ `cd #{working_directory} && git pull &>/dev/null`
65
+ end
66
+
67
+ def local_branches
68
+ `cd #{working_directory} && git branch`.split("\n").map {|b| b.delete("*").strip }
69
+ end
70
+
71
+ def cloned?
72
+ File.directory?(working_directory / ".git")
73
+ end
74
+
75
+ def on_branch?
76
+ File.basename(`cd #{working_directory} && git symbolic-ref HEAD &>/dev/null`).chomp == branch
77
+ end
78
+
79
+ def log(message)
80
+ Integrity.log("Git") { message }
81
+ end
82
+ end
83
+ end
84
+ 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
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + "/../helpers/acceptance"
2
+
3
+ class ApiTest < Test::Unit::AcceptanceTestCase
4
+ story <<-EOF
5
+ As a project owner,
6
+ I want to be able to use GitHub as a build triggerer
7
+ So that my project is built everytime I push to the Holy Hub
8
+ EOF
9
+
10
+ def payload(after, branch="master", commits=[])
11
+ payload = { "after" => "#{after}", "ref" => "refs/heads/#{branch}" }
12
+ payload["commits"] = commits if commits.any?
13
+ payload.to_json
14
+ end
15
+
16
+ def post(path, data)
17
+ request_page(path, "post", data)
18
+ end
19
+
20
+ scenario "it only build commits for the branch being monitored" do
21
+ repo = git_repo(:my_test_project) # initial commit && successful commit
22
+ Project.gen(:my_test_project, :uri => repo.path, :branch => "my-branch")
23
+
24
+ basic_auth "admin", "test"
25
+
26
+ lambda do
27
+ post "/my-test-project/push", :payload => payload(repo.head, "master", repo.commits)
28
+ response_code.should == 422
29
+ end.should_not change(Build, :count)
30
+
31
+ visit "/my-test-project"
32
+
33
+ assert_contain("No builds for this project")
34
+ end
35
+
36
+ it "receiving a build request with build_all_commits *enabled* builds all commits, most recent first" do
37
+ Integrity.config[:build_all_commits] = true
38
+
39
+ repo = git_repo(:my_test_project) # initial commit && successful commit
40
+ 3.times do |i|
41
+ repo.add_commit("commit #{i}") do
42
+ system "echo commit_#{i} >> test-file"
43
+ system "git add test-file &>/dev/null"
44
+ end
45
+ end
46
+
47
+ Project.gen(:my_test_project, :uri => repo.path, :command => "echo successful", :branch => "master")
48
+
49
+ basic_auth "admin", "test"
50
+ post "/my-test-project/push", :payload => payload(repo.head, "master", repo.commits)
51
+
52
+ visit "/my-test-project"
53
+
54
+ assert_have_tag("h1", :content => "Built #{git_repo(:my_test_project).short_head} successfully")
55
+ assert_have_tag(".attribution", :content => "by John Doe")
56
+
57
+ assert_have_tag("#previous_builds li", :count => 4)
58
+ end
59
+
60
+ scenario "receiving a build request with build_all_commits *disabled* only builds the last commit passed" do
61
+ Integrity.config[:build_all_commits] = false
62
+
63
+ Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path)
64
+
65
+ git_repo(:my_test_project).add_failing_commit
66
+ git_repo(:my_test_project).add_successful_commit
67
+ head = git_repo(:my_test_project).head
68
+
69
+ basic_auth "admin", "test"
70
+ post "/my-test-project/push", :payload => payload(head, "master", git_repo(:my_test_project).commits)
71
+
72
+ response_code.should == 201
73
+
74
+ visit "/my-test-project"
75
+
76
+ assert_have_tag("h1", :content => "Built #{git_repo(:my_test_project).short_head} successfully")
77
+
78
+ assert_have_no_tag("#previous_builds li")
79
+ end
80
+
81
+ scenario "an unauthenticated request returns a 401" do
82
+ Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path)
83
+ head = git_repo(:my_test_project).head
84
+ post "/my-test-project/push", :payload => payload(head, "master")
85
+
86
+ response_code.should == 401
87
+ end
88
+
89
+ scenario "receiving a build request with an invalid payload returns an Invalid Request error" do
90
+ Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path)
91
+
92
+ basic_auth "admin", "test"
93
+
94
+ post "/my-test-project/push", :payload => "foo"
95
+ response_code.should == 422
96
+ end
97
+ end