giraffesoft-integrity 0.1.4

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.
@@ -0,0 +1,49 @@
1
+ module Integrity
2
+ class Notifier
3
+ include DataMapper::Resource
4
+
5
+ property :id, Integer, :serial => true
6
+ property :name, String, :nullable => false
7
+ property :config, Yaml, :nullable => false, :lazy => false
8
+
9
+ belongs_to :project, :class_name => "Integrity::Project"
10
+
11
+ validates_is_unique :name, :scope => :project_id
12
+ validates_present :project_id
13
+
14
+ def self.available
15
+ @available ||= constants.map {|name| const_get(name) }.select do |notifier|
16
+ notifier.respond_to?(:to_haml) && notifier.respond_to?(:notify_of_build)
17
+ end - [Notifier::Base]
18
+ end
19
+
20
+ def self.enable_notifiers(project, enabled, config={})
21
+ all.destroy!
22
+ list_of_enabled_notifiers(enabled).each do |name|
23
+ create! :project_id => project, :name => name, :config => config[name]
24
+ end
25
+ end
26
+
27
+ def notify_of_build(build)
28
+ to_const.notify_of_build(build, config)
29
+ end
30
+
31
+ private
32
+
33
+ def to_const
34
+ self.class.module_eval(name)
35
+ end
36
+
37
+ def self.list_of_enabled_notifiers(names)
38
+ case names
39
+ when Array then names
40
+ when NilClass then []
41
+ else [names]
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ require File.dirname(__FILE__) / 'notifier' / 'base'
48
+
49
+ Dir["#{File.dirname(__FILE__)}/notifier/*.rb"].each &method(:require)
@@ -0,0 +1,55 @@
1
+ module Integrity
2
+ class Notifier
3
+ class Base
4
+ def self.notify_of_build(build, config)
5
+ Timeout.timeout(8) { new(build, config).deliver! }
6
+ end
7
+
8
+ def self.to_haml
9
+ raise NoMethodError, "you need to implement this method in your notifier"
10
+ end
11
+
12
+ attr_reader :build
13
+
14
+ def initialize(build, config)
15
+ @build = build
16
+ @config = config
17
+ end
18
+
19
+ def deliver!
20
+ raise NoMethodError, "you need to implement this method in your notifier"
21
+ end
22
+
23
+ def short_message
24
+ "Build #{build.short_commit_identifier} #{build.successful? ? "was successful" : "failed"}"
25
+ end
26
+
27
+ def full_message
28
+ <<-EOM
29
+ "Build #{build.commit_identifier} #{build.successful? ? "was successful" : "failed"}"
30
+
31
+ Commit Message: #{build.commit_message}
32
+ Commit Date: #{build.commited_at}
33
+ Commit Author: #{build.commit_author.name}
34
+
35
+ Link: #{build_url}
36
+
37
+ Build Output:
38
+
39
+ #{stripped_build_output}
40
+ EOM
41
+ end
42
+
43
+ def build_url
44
+ raise if Integrity.config[:base_uri].nil?
45
+ Integrity.config[:base_uri] / build.project.permalink / "builds" / build.commit_identifier
46
+ end
47
+
48
+ private
49
+
50
+ def stripped_build_output
51
+ build.output.gsub("\e[0m", '').gsub(/\e\[3[1-7]m/, '')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,90 @@
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, :builds, :class_name => "Integrity::Build"
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 build(commit_identifier="HEAD")
25
+ return if building?
26
+ update_attributes(:building => true)
27
+ Builder.new(self).build(commit_identifier)
28
+ ensure
29
+ update_attributes(:building => false)
30
+ send_notifications
31
+ end
32
+
33
+ def last_build
34
+ builds.last
35
+ end
36
+
37
+ def previous_builds
38
+ builds.all(:order => [:created_at.desc]).tap do |builds|
39
+ builds.shift
40
+ end
41
+ end
42
+
43
+ def status
44
+ last_build && last_build.status
45
+ end
46
+
47
+ def public=(flag)
48
+ attribute_set(:public, !!flag)
49
+ end
50
+
51
+ def config_for(notifier)
52
+ notifier = notifiers.first(:name => notifier.to_s.split(/::/).last)
53
+ notifier.blank? ? {} : notifier.config
54
+ end
55
+
56
+ def notifies?(notifier)
57
+ !notifiers.first(:name => notifier.to_s.split(/::/).last).blank?
58
+ end
59
+
60
+ def enable_notifiers(*args)
61
+ Notifier.enable_notifiers(id, *args)
62
+ end
63
+
64
+ private
65
+ def set_permalink
66
+ self.permalink = (name || "").downcase.
67
+ gsub(/'s/, "s").
68
+ gsub(/&/, "and").
69
+ gsub(/[^a-z0-9]+/, "-").
70
+ gsub(/-*$/, "")
71
+ end
72
+
73
+ def delete_code
74
+ builds.destroy!
75
+ Builder.new(self).delete_code
76
+ end
77
+
78
+ def send_notifications
79
+ notifiers.each do |notifier|
80
+ begin
81
+ Integrity.log "Notifying of build #{last_build.short_commit_identifier} using the #{notifier.name} notifier"
82
+ notifier.notify_of_build last_build
83
+ rescue Timeout::Error
84
+ Integrity.log "#{notifier.name} notifier timed out"
85
+ next
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,22 @@
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
+
15
+ def self.scm_class_for(string)
16
+ case string.to_s
17
+ when /\.git\/?/ then Git
18
+ else raise SCMUnknownError, "could not find any SCM based on string '#{string}'"
19
+ end
20
+ end
21
+ end
22
+ 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}`
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}`
59
+ end
60
+
61
+ def pull
62
+ log "Pull-ing in #{working_directory}"
63
+ `cd #{working_directory} && git pull`
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`).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
+ }