sr-integrity 0.1.8.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/README.markdown +73 -0
- data/Rakefile +91 -0
- data/VERSION.yml +4 -0
- data/bin/integrity +4 -0
- data/config/config.sample.ru +21 -0
- data/config/config.sample.yml +41 -0
- data/config/thin.sample.yml +13 -0
- data/integrity.gemspec +55 -0
- data/lib/integrity/app.rb +137 -0
- data/lib/integrity/author.rb +39 -0
- data/lib/integrity/build.rb +84 -0
- data/lib/integrity/commit.rb +71 -0
- data/lib/integrity/core_ext/object.rb +6 -0
- data/lib/integrity/helpers/authorization.rb +33 -0
- data/lib/integrity/helpers/breadcrumbs.rb +20 -0
- data/lib/integrity/helpers/forms.rb +28 -0
- data/lib/integrity/helpers/pretty_output.rb +45 -0
- data/lib/integrity/helpers/rendering.rb +14 -0
- data/lib/integrity/helpers/resources.rb +13 -0
- data/lib/integrity/helpers/urls.rb +49 -0
- data/lib/integrity/helpers.rb +16 -0
- data/lib/integrity/installer.rb +121 -0
- data/lib/integrity/migrations.rb +140 -0
- data/lib/integrity/notifier/base.rb +65 -0
- data/lib/integrity/notifier.rb +50 -0
- data/lib/integrity/project.rb +142 -0
- data/lib/integrity/project_builder.rb +56 -0
- data/lib/integrity/scm/git/uri.rb +57 -0
- data/lib/integrity/scm/git.rb +84 -0
- data/lib/integrity/scm.rb +19 -0
- data/lib/integrity.rb +80 -0
- data/public/buttons.css +82 -0
- data/public/reset.css +7 -0
- data/public/spinner.gif +0 -0
- data/test/helpers/acceptance/git_helper.rb +99 -0
- data/test/helpers/acceptance/textfile_notifier.rb +26 -0
- data/test/helpers/acceptance.rb +80 -0
- data/test/helpers/expectations/be_a.rb +23 -0
- data/test/helpers/expectations/change.rb +90 -0
- data/test/helpers/expectations/have.rb +105 -0
- data/test/helpers/expectations/have_tag.rb +128 -0
- data/test/helpers/expectations/predicates.rb +37 -0
- data/test/helpers/expectations.rb +5 -0
- data/test/helpers/fixtures.rb +107 -0
- data/test/helpers/initial_migration_fixture.sql +44 -0
- data/test/helpers.rb +70 -0
- data/views/_commit_info.haml +24 -0
- data/views/build.haml +2 -0
- data/views/error.haml +37 -0
- data/views/home.haml +21 -0
- data/views/integrity.sass +400 -0
- data/views/layout.haml +28 -0
- data/views/new.haml +51 -0
- data/views/not_found.haml +31 -0
- data/views/notifier.haml +7 -0
- data/views/project.builder +21 -0
- data/views/project.haml +30 -0
- data/views/unauthorized.haml +38 -0
- metadata +190 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Project
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Integer, :serial => true
|
6
|
+
property :name, String, :nullable => false
|
7
|
+
property :permalink, String
|
8
|
+
property :uri, URI, :nullable => false, :length => 255
|
9
|
+
property :branch, String, :nullable => false, :default => "master"
|
10
|
+
property :command, String, :nullable => false, :length => 255, :default => "rake"
|
11
|
+
property :public, Boolean, :default => true
|
12
|
+
property :building, Boolean, :default => false
|
13
|
+
property :created_at, DateTime
|
14
|
+
property :updated_at, DateTime
|
15
|
+
|
16
|
+
has n, :commits, :class_name => "Integrity::Commit"
|
17
|
+
has n, :notifiers, :class_name => "Integrity::Notifier"
|
18
|
+
|
19
|
+
before :save, :set_permalink
|
20
|
+
before :destroy, :delete_code
|
21
|
+
|
22
|
+
validates_is_unique :name
|
23
|
+
|
24
|
+
def self.only_public_unless(condition)
|
25
|
+
if condition
|
26
|
+
all
|
27
|
+
else
|
28
|
+
all(:public => true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def build(commit_identifier="HEAD")
|
33
|
+
commit_identifier = head_of_remote_repo if commit_identifier == "HEAD"
|
34
|
+
commit = find_or_create_commit_with_identifier(commit_identifier)
|
35
|
+
commit.queue_build
|
36
|
+
end
|
37
|
+
|
38
|
+
def push(payload)
|
39
|
+
payload = JSON.parse(payload || "")
|
40
|
+
return unless payload["ref"] =~ /#{branch}/
|
41
|
+
return if payload["commits"].nil?
|
42
|
+
return if payload["commits"].empty?
|
43
|
+
|
44
|
+
commits = if Integrity.config[:build_all_commits]
|
45
|
+
payload["commits"]
|
46
|
+
else
|
47
|
+
[payload["commits"].first]
|
48
|
+
end
|
49
|
+
|
50
|
+
commits.each do |commit_data|
|
51
|
+
create_commit_from(commit_data)
|
52
|
+
build(commit_data['id'])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def last_commit
|
57
|
+
commits.first(:project_id => id, :order => [:committed_at.desc])
|
58
|
+
end
|
59
|
+
|
60
|
+
def last_build
|
61
|
+
warn "Project#last_build is deprecated, use Project#last_commit"
|
62
|
+
last_commit
|
63
|
+
end
|
64
|
+
|
65
|
+
def previous_commits
|
66
|
+
commits.all(:project_id => id, :order => [:committed_at.desc]).tap {|commits| commits.shift }
|
67
|
+
end
|
68
|
+
|
69
|
+
def previous_builds
|
70
|
+
warn "Project#previous_builds is deprecated, use Project#previous_commits"
|
71
|
+
previous_commits
|
72
|
+
end
|
73
|
+
|
74
|
+
def status
|
75
|
+
last_commit && last_commit.status
|
76
|
+
end
|
77
|
+
|
78
|
+
def human_readable_status
|
79
|
+
last_commit && last_commit.human_readable_status
|
80
|
+
end
|
81
|
+
|
82
|
+
def public=(flag)
|
83
|
+
attribute_set(:public, case flag
|
84
|
+
when "1", "0" then flag == "1"
|
85
|
+
else !!flag
|
86
|
+
end)
|
87
|
+
end
|
88
|
+
|
89
|
+
def config_for(notifier)
|
90
|
+
notifier = notifiers.first(:name => notifier.to_s.split(/::/).last, :project_id => id)
|
91
|
+
notifier.blank? ? {} : notifier.config
|
92
|
+
end
|
93
|
+
|
94
|
+
def notifies?(notifier)
|
95
|
+
!notifiers.first(:name => notifier.to_s.split(/::/).last, :project_id => id).blank?
|
96
|
+
end
|
97
|
+
|
98
|
+
def enable_notifiers(*args)
|
99
|
+
Notifier.enable_notifiers(id, *args)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
def find_or_create_commit_with_identifier(commit_identifier)
|
104
|
+
# We abuse +committed_at+ here setting it to Time.now because we use it
|
105
|
+
# to sort (for last_commit and previous_commits). I don't like this
|
106
|
+
# very much, but for now it's the only solution I can find.
|
107
|
+
#
|
108
|
+
# This also creates a dependency, as now we *always* have to update the
|
109
|
+
# +committed_at+ field after building to ensure the date is correct :(
|
110
|
+
#
|
111
|
+
# This might also make your commit listings a little jumpy, if some
|
112
|
+
# commits change place every time a build finishes =\
|
113
|
+
commits.first_or_create({ :identifier => commit_identifier, :project_id => id }, :committed_at => Time.now)
|
114
|
+
end
|
115
|
+
|
116
|
+
def head_of_remote_repo
|
117
|
+
SCM.new(uri, branch).head
|
118
|
+
end
|
119
|
+
|
120
|
+
def create_commit_from(data)
|
121
|
+
commits.create(:identifier => data["id"],
|
122
|
+
:author => data["author"],
|
123
|
+
:message => data["message"],
|
124
|
+
:committed_at => data["timestamp"])
|
125
|
+
end
|
126
|
+
|
127
|
+
def set_permalink
|
128
|
+
self.permalink = (name || "").downcase.
|
129
|
+
gsub(/'s/, "s").
|
130
|
+
gsub(/&/, "and").
|
131
|
+
gsub(/[^a-z0-9]+/, "-").
|
132
|
+
gsub(/-*$/, "")
|
133
|
+
end
|
134
|
+
|
135
|
+
def delete_code
|
136
|
+
commits.all(:project_id => id).destroy!
|
137
|
+
ProjectBuilder.new(self).delete_code
|
138
|
+
rescue SCM::SCMUnknownError => error
|
139
|
+
Integrity.log "Problem while trying to deleting code: #{error}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Integrity
|
2
|
+
class ProjectBuilder
|
3
|
+
def initialize(project)
|
4
|
+
@project = project
|
5
|
+
@uri = project.uri
|
6
|
+
@build_script = project.command
|
7
|
+
@branch = project.branch
|
8
|
+
@scm = SCM.new(@uri, @branch, export_directory)
|
9
|
+
end
|
10
|
+
|
11
|
+
def build(commit)
|
12
|
+
@commit = commit
|
13
|
+
@build = commit.build
|
14
|
+
@build.start!
|
15
|
+
Integrity.log "Building #{commit.identifier} (#{@branch}) of #{@project.name} in #{export_directory} using #{@scm.name}"
|
16
|
+
@scm.with_revision(commit.identifier) { run_build_script }
|
17
|
+
@build
|
18
|
+
ensure
|
19
|
+
@build.complete!
|
20
|
+
@commit.update_attributes(@scm.info(commit.identifier))
|
21
|
+
send_notifications
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete_code
|
25
|
+
FileUtils.rm_r export_directory
|
26
|
+
rescue Errno::ENOENT
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def send_notifications
|
32
|
+
@project.notifiers.each do |notifier|
|
33
|
+
begin
|
34
|
+
Integrity.log "Notifying of build #{@commit.short_identifier} using the #{notifier.name} notifier"
|
35
|
+
notifier.notify_of_build @commit
|
36
|
+
rescue Timeout::Error
|
37
|
+
Integrity.log "#{notifier.name} notifier timed out"
|
38
|
+
next
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def export_directory
|
44
|
+
Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_build_script
|
48
|
+
Integrity.log "Running `#{@build_script}` in #{@scm.working_directory}"
|
49
|
+
|
50
|
+
IO.popen "(cd #{@scm.working_directory} && #{@build_script}) 2>&1", "r" do |pipe|
|
51
|
+
@build.output = pipe.read
|
52
|
+
end
|
53
|
+
@build.successful = $?.success?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
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,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,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
|
data/lib/integrity.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "dm-core"
|
5
|
+
require "dm-validations"
|
6
|
+
require "dm-types"
|
7
|
+
require "dm-timestamps"
|
8
|
+
require "dm-aggregates"
|
9
|
+
require "sinatra/base"
|
10
|
+
|
11
|
+
require "yaml"
|
12
|
+
require "logger"
|
13
|
+
require "digest/sha1"
|
14
|
+
require "timeout"
|
15
|
+
require "ostruct"
|
16
|
+
require "pathname"
|
17
|
+
|
18
|
+
require "integrity/core_ext/object"
|
19
|
+
|
20
|
+
require "integrity/project"
|
21
|
+
require "integrity/author"
|
22
|
+
require "integrity/commit"
|
23
|
+
require "integrity/build"
|
24
|
+
require "integrity/project_builder"
|
25
|
+
require "integrity/scm"
|
26
|
+
require "integrity/scm/git"
|
27
|
+
require "integrity/notifier"
|
28
|
+
require "integrity/helpers"
|
29
|
+
require "integrity/app"
|
30
|
+
|
31
|
+
module Integrity
|
32
|
+
def self.new(config_file = nil)
|
33
|
+
self.config = YAML.load_file(config_file) unless config_file.nil?
|
34
|
+
DataMapper.setup(:default, config[:database_uri])
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.root
|
38
|
+
Pathname.new(File.dirname(__FILE__)).join("..").expand_path
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.default_configuration
|
42
|
+
@defaults ||= { :database_uri => "sqlite3::memory:",
|
43
|
+
:export_directory => root / "exports",
|
44
|
+
:log => STDOUT,
|
45
|
+
:base_uri => "http://localhost:8910",
|
46
|
+
:use_basic_auth => false,
|
47
|
+
:build_all_commits => true,
|
48
|
+
:log_debug_info => false }.dup
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.config
|
52
|
+
@config ||= default_configuration
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.config=(options)
|
56
|
+
@config = default_configuration.merge(options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.log(message, &block)
|
60
|
+
logger.info(message, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.logger
|
64
|
+
@logger ||= Logger.new(config[:log], "daily").tap do |logger|
|
65
|
+
logger.formatter = LogFormatter.new
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.version
|
70
|
+
YAML.load_file(File.dirname(__FILE__) + "/../VERSION.yml").
|
71
|
+
values.join(".")
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
class LogFormatter < Logger::Formatter
|
76
|
+
def call(severity, time, progname, msg)
|
77
|
+
time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/public/buttons.css
ADDED
@@ -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;}
|
data/public/spinner.gif
ADDED
Binary file
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module GitHelper
|
2
|
+
@@_git_repositories = Hash.new {|h,k| h[k] = Repo.new(k) }
|
3
|
+
|
4
|
+
def git_repo(name)
|
5
|
+
@@_git_repositories[name]
|
6
|
+
end
|
7
|
+
|
8
|
+
def destroy_all_git_repos
|
9
|
+
@@_git_repositories.each {|n,r| r.destroy }
|
10
|
+
@@_git_repositories.clear
|
11
|
+
end
|
12
|
+
|
13
|
+
class Repo
|
14
|
+
attr_reader :path
|
15
|
+
|
16
|
+
def initialize(name)
|
17
|
+
@name = name
|
18
|
+
@path = "/tmp" / @name.to_s
|
19
|
+
create
|
20
|
+
end
|
21
|
+
|
22
|
+
def path
|
23
|
+
@path / ".git"
|
24
|
+
end
|
25
|
+
|
26
|
+
def create
|
27
|
+
destroy
|
28
|
+
FileUtils.mkdir_p @path
|
29
|
+
|
30
|
+
Dir.chdir(@path) do
|
31
|
+
system 'git init &>/dev/null'
|
32
|
+
system 'git config user.name "John Doe"'
|
33
|
+
system 'git config user.email "johndoe@example.org"'
|
34
|
+
system 'echo "just a test repo" >> README'
|
35
|
+
system 'git add README &>/dev/null'
|
36
|
+
system 'git commit -m "First commit" &>/dev/null'
|
37
|
+
end
|
38
|
+
|
39
|
+
add_successful_commit
|
40
|
+
end
|
41
|
+
|
42
|
+
def commits
|
43
|
+
Dir.chdir(@path) do
|
44
|
+
commits = `git log --pretty=oneline`.collect { |line| line.split(" ").first }
|
45
|
+
commits.inject([]) do |commits, sha1|
|
46
|
+
format = %Q(---%n:message: >-%n %s%n:timestamp: %ci%n:id: %H%n:author: %an <%ae>)
|
47
|
+
commits << YAML.load(`git show -s --pretty=format:"#{format}" #{sha1}`)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_commit(message, &action)
|
53
|
+
Dir.chdir(@path) do
|
54
|
+
yield action
|
55
|
+
system %Q(git commit -m "#{message}" &>/dev/null)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_failing_commit
|
60
|
+
add_commit "This commit will fail" do
|
61
|
+
system %Q(echo '#{build_script(false)}' > test)
|
62
|
+
system %Q(chmod +x test)
|
63
|
+
system %Q(git add test &>/dev/null)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_successful_commit
|
68
|
+
add_commit "This commit will work" do
|
69
|
+
system %Q(echo '#{build_script(true)}' > test)
|
70
|
+
system %Q(chmod +x test)
|
71
|
+
system %Q(git add test &>/dev/null)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def head
|
76
|
+
Dir.chdir(@path) do
|
77
|
+
`git log --pretty=format:%H | head -1`.chomp
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def short_head
|
82
|
+
head[0..6]
|
83
|
+
end
|
84
|
+
|
85
|
+
def destroy
|
86
|
+
FileUtils.rm_rf @path if File.directory?(@path)
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
def build_script(successful=true)
|
92
|
+
<<-script
|
93
|
+
#!/bin/sh
|
94
|
+
echo "Running tests..."
|
95
|
+
exit #{successful ? 0 : 1}
|
96
|
+
script
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Notifier
|
3
|
+
class Textfile < Notifier::Base
|
4
|
+
def self.to_haml
|
5
|
+
<<-haml
|
6
|
+
%p.normal
|
7
|
+
%label{ :for => "textfile_notifier_file" } File
|
8
|
+
%input.text#textfile_notifier_file{ :name => "notifiers[Textfile][file]", :type => "text", :value => config["file"] }
|
9
|
+
haml
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(build, config={})
|
13
|
+
super
|
14
|
+
@file = @config["file"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def deliver!
|
18
|
+
File.open(@file, "a") do |f|
|
19
|
+
f.puts "=== #{short_message} ==="
|
20
|
+
f.puts
|
21
|
+
f.puts full_message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|