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.
Files changed (59) hide show
  1. data/README.markdown +73 -0
  2. data/Rakefile +91 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/integrity +4 -0
  5. data/config/config.sample.ru +21 -0
  6. data/config/config.sample.yml +41 -0
  7. data/config/thin.sample.yml +13 -0
  8. data/integrity.gemspec +55 -0
  9. data/lib/integrity/app.rb +137 -0
  10. data/lib/integrity/author.rb +39 -0
  11. data/lib/integrity/build.rb +84 -0
  12. data/lib/integrity/commit.rb +71 -0
  13. data/lib/integrity/core_ext/object.rb +6 -0
  14. data/lib/integrity/helpers/authorization.rb +33 -0
  15. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  16. data/lib/integrity/helpers/forms.rb +28 -0
  17. data/lib/integrity/helpers/pretty_output.rb +45 -0
  18. data/lib/integrity/helpers/rendering.rb +14 -0
  19. data/lib/integrity/helpers/resources.rb +13 -0
  20. data/lib/integrity/helpers/urls.rb +49 -0
  21. data/lib/integrity/helpers.rb +16 -0
  22. data/lib/integrity/installer.rb +121 -0
  23. data/lib/integrity/migrations.rb +140 -0
  24. data/lib/integrity/notifier/base.rb +65 -0
  25. data/lib/integrity/notifier.rb +50 -0
  26. data/lib/integrity/project.rb +142 -0
  27. data/lib/integrity/project_builder.rb +56 -0
  28. data/lib/integrity/scm/git/uri.rb +57 -0
  29. data/lib/integrity/scm/git.rb +84 -0
  30. data/lib/integrity/scm.rb +19 -0
  31. data/lib/integrity.rb +80 -0
  32. data/public/buttons.css +82 -0
  33. data/public/reset.css +7 -0
  34. data/public/spinner.gif +0 -0
  35. data/test/helpers/acceptance/git_helper.rb +99 -0
  36. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  37. data/test/helpers/acceptance.rb +80 -0
  38. data/test/helpers/expectations/be_a.rb +23 -0
  39. data/test/helpers/expectations/change.rb +90 -0
  40. data/test/helpers/expectations/have.rb +105 -0
  41. data/test/helpers/expectations/have_tag.rb +128 -0
  42. data/test/helpers/expectations/predicates.rb +37 -0
  43. data/test/helpers/expectations.rb +5 -0
  44. data/test/helpers/fixtures.rb +107 -0
  45. data/test/helpers/initial_migration_fixture.sql +44 -0
  46. data/test/helpers.rb +70 -0
  47. data/views/_commit_info.haml +24 -0
  48. data/views/build.haml +2 -0
  49. data/views/error.haml +37 -0
  50. data/views/home.haml +21 -0
  51. data/views/integrity.sass +400 -0
  52. data/views/layout.haml +28 -0
  53. data/views/new.haml +51 -0
  54. data/views/not_found.haml +31 -0
  55. data/views/notifier.haml +7 -0
  56. data/views/project.builder +21 -0
  57. data/views/project.haml +30 -0
  58. data/views/unauthorized.haml +38 -0
  59. 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
@@ -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,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