bantic-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.
- data/README.markdown +153 -0
- data/Rakefile +82 -0
- data/VERSION.yml +4 -0
- data/app.rb +278 -0
- data/bin/integrity +82 -0
- data/config/config.sample.ru +30 -0
- data/config/config.sample.yml +34 -0
- data/config/thin.sample.yml +13 -0
- data/integrity.gemspec +72 -0
- data/lib/integrity/build.rb +56 -0
- data/lib/integrity/builder.rb +49 -0
- data/lib/integrity/core_ext/object.rb +6 -0
- data/lib/integrity/core_ext/string.rb +5 -0
- data/lib/integrity/core_ext/time.rb +13 -0
- data/lib/integrity/notifier/base.rb +55 -0
- data/lib/integrity/notifier.rb +50 -0
- data/lib/integrity/project.rb +94 -0
- data/lib/integrity/scm/git/uri.rb +57 -0
- data/lib/integrity/scm/git.rb +83 -0
- data/lib/integrity/scm.rb +22 -0
- data/lib/integrity.rb +71 -0
- data/public/buttons.css +82 -0
- data/public/reset.css +7 -0
- data/public/spinner.gif +0 -0
- data/spec/form_field_matchers.rb +91 -0
- data/spec/spec_helper.rb +135 -0
- data/vendor/sinatra-hacks/lib/hacks.rb +49 -0
- data/views/build.haml +2 -0
- data/views/build_info.haml +22 -0
- data/views/error.haml +29 -0
- data/views/home.haml +22 -0
- data/views/integrity.sass +387 -0
- data/views/layout.haml +25 -0
- data/views/new.haml +53 -0
- data/views/not_found.haml +31 -0
- data/views/notifier.haml +7 -0
- data/views/project.haml +32 -0
- data/views/unauthorized.haml +38 -0
- metadata +216 -0
data/integrity.gemspec
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{integrity}
|
3
|
+
s.version = "0.1.4"
|
4
|
+
|
5
|
+
|
6
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
7
|
+
s.authors = ["Nicol\303\241s Sanguinetti", "Simon Rozet"]
|
8
|
+
s.date = %q{2008-12-08}
|
9
|
+
s.default_executable = %q{integrity}
|
10
|
+
s.description = %q{Your Friendly Continuous Integration server. Easy, fun and painless!}
|
11
|
+
s.email = %q{contacto@nicolassanguinetti.info}
|
12
|
+
s.executables = ["integrity"]
|
13
|
+
s.files = ["README.markdown", "Rakefile", "VERSION.yml", "app.rb", "bin/integrity", "config/config.sample.ru", "config/config.sample.yml", "config/thin.sample.yml", "integrity.gemspec", "lib/integrity.rb", "lib/integrity/build.rb", "lib/integrity/builder.rb", "lib/integrity/core_ext/object.rb", "lib/integrity/core_ext/string.rb", "lib/integrity/core_ext/time.rb", "lib/integrity/notifier.rb", "lib/integrity/notifier/base.rb", "lib/integrity/project.rb", "lib/integrity/scm.rb", "lib/integrity/scm/git.rb", "lib/integrity/scm/git/uri.rb", "public/buttons.css", "public/reset.css", "public/spinner.gif", "vendor/sinatra-hacks/lib/hacks.rb", "views/build.haml", "views/build_info.haml", "views/error.haml", "views/home.haml", "views/integrity.sass", "views/layout.haml", "views/new.haml", "views/not_found.haml", "views/notifier.haml", "views/project.haml", "views/unauthorized.haml", "spec/spec_helper.rb", "spec/form_field_matchers.rb"]
|
14
|
+
s.homepage = %q{http://integrityapp.com}
|
15
|
+
s.post_install_message = %q{Run `integrity help` for information on how to setup Integrity.}
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{integrity}
|
18
|
+
s.rubygems_version = %q{1.2.0}
|
19
|
+
s.summary = %q{The easy and fun Continuous Integration server}
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 2
|
24
|
+
|
25
|
+
if current_version >= 3 then
|
26
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 0.3.2"])
|
27
|
+
s.add_runtime_dependency(%q<haml>, [">= 0"])
|
28
|
+
s.add_runtime_dependency(%q<dm-core>, [">= 0.9.5"])
|
29
|
+
s.add_runtime_dependency(%q<dm-validations>, [">= 0.9.5"])
|
30
|
+
s.add_runtime_dependency(%q<dm-types>, [">= 0.9.5"])
|
31
|
+
s.add_runtime_dependency(%q<dm-timestamps>, [">= 0.9.5"])
|
32
|
+
s.add_runtime_dependency(%q<dm-aggregates>, [">= 0.9.5"])
|
33
|
+
s.add_runtime_dependency(%q<data_objects>, [">= 0.9.5"])
|
34
|
+
s.add_runtime_dependency(%q<do_sqlite3>, [">= 0.9.5"])
|
35
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
36
|
+
s.add_runtime_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
|
37
|
+
s.add_runtime_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
|
38
|
+
s.add_runtime_dependency(%q<thor>, [">= 0"])
|
39
|
+
s.add_runtime_dependency(%q<bcrypt-ruby>, [">= 0"])
|
40
|
+
else
|
41
|
+
s.add_dependency(%q<sinatra>, [">= 0.3.2"])
|
42
|
+
s.add_dependency(%q<haml>, [">= 0"])
|
43
|
+
s.add_dependency(%q<dm-core>, [">= 0.9.5"])
|
44
|
+
s.add_dependency(%q<dm-validations>, [">= 0.9.5"])
|
45
|
+
s.add_dependency(%q<dm-types>, [">= 0.9.5"])
|
46
|
+
s.add_dependency(%q<dm-timestamps>, [">= 0.9.5"])
|
47
|
+
s.add_dependency(%q<dm-aggregates>, [">= 0.9.5"])
|
48
|
+
s.add_dependency(%q<data_objects>, [">= 0.9.5"])
|
49
|
+
s.add_dependency(%q<do_sqlite3>, [">= 0.9.5"])
|
50
|
+
s.add_dependency(%q<json>, [">= 0"])
|
51
|
+
s.add_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
|
52
|
+
s.add_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
|
53
|
+
s.add_dependency(%q<thor>, [">= 0"])
|
54
|
+
s.add_dependency(%q<bcrypt-ruby>, [">= 0"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<sinatra>, [">= 0.3.2"])
|
58
|
+
s.add_dependency(%q<haml>, [">= 0"])
|
59
|
+
s.add_dependency(%q<dm-core>, [">= 0.9.5"])
|
60
|
+
s.add_dependency(%q<dm-validations>, [">= 0.9.5"])
|
61
|
+
s.add_dependency(%q<dm-types>, [">= 0.9.5"])
|
62
|
+
s.add_dependency(%q<dm-timestamps>, [">= 0.9.5"])
|
63
|
+
s.add_dependency(%q<dm-aggregates>, [">= 0.9.5"])
|
64
|
+
s.add_dependency(%q<data_objects>, [">= 0.9.5"])
|
65
|
+
s.add_dependency(%q<do_sqlite3>, [">= 0.9.5"])
|
66
|
+
s.add_dependency(%q<json>, [">= 0"])
|
67
|
+
s.add_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
|
68
|
+
s.add_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
|
69
|
+
s.add_dependency(%q<thor>, [">= 0"])
|
70
|
+
s.add_dependency(%q<bcrypt-ruby>, [">= 0"])
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Integrity
|
4
|
+
class Build
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, Integer, :serial => true
|
8
|
+
property :output, Text, :nullable => false, :default => ''
|
9
|
+
property :successful, Boolean, :nullable => false, :default => false
|
10
|
+
property :commit_identifier, String, :nullable => false
|
11
|
+
property :commit_metadata, Yaml, :nullable => false, :lazy => false
|
12
|
+
property :created_at, DateTime
|
13
|
+
property :updated_at, DateTime
|
14
|
+
|
15
|
+
belongs_to :project, :class_name => "Integrity::Project"
|
16
|
+
|
17
|
+
def failed?
|
18
|
+
!successful?
|
19
|
+
end
|
20
|
+
|
21
|
+
def status
|
22
|
+
successful? ? :success : :failed
|
23
|
+
end
|
24
|
+
|
25
|
+
def human_readable_status
|
26
|
+
successful? ? 'Build Successful' : 'Build Failed'
|
27
|
+
end
|
28
|
+
|
29
|
+
def short_commit_identifier
|
30
|
+
sha1?(commit_identifier) ? commit_identifier[0..6] : commit_identifier
|
31
|
+
end
|
32
|
+
|
33
|
+
def commit_author
|
34
|
+
@author ||= begin
|
35
|
+
commit_metadata[:author] =~ /^(.*) <(.*)>$/
|
36
|
+
OpenStruct.new(:name => $1.strip, :email => $2.strip, :full => commit_metadata[:author])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def commit_message
|
41
|
+
commit_metadata[:message]
|
42
|
+
end
|
43
|
+
|
44
|
+
def commited_at
|
45
|
+
case commit_metadata[:date]
|
46
|
+
when String then Time.parse(commit_metadata[:date])
|
47
|
+
else commit_metadata[:date]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def sha1?(string)
|
53
|
+
string =~ /^[a-f0-9]{40}$/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Integrity
|
4
|
+
class Builder
|
5
|
+
attr_reader :build_script
|
6
|
+
|
7
|
+
def initialize(project)
|
8
|
+
@uri = project.uri
|
9
|
+
@build_script = project.command
|
10
|
+
@branch = project.branch
|
11
|
+
@scm = SCM.new(@uri, @branch, export_directory)
|
12
|
+
@build = Build.new(:project => project)
|
13
|
+
end
|
14
|
+
|
15
|
+
def build(commit)
|
16
|
+
Integrity.log "Building #{commit} (#{@branch}) of #{@build.project.name} in #{export_directory} using #{scm_name}"
|
17
|
+
@scm.with_revision(commit) { run_build_script }
|
18
|
+
@build
|
19
|
+
ensure
|
20
|
+
@build.commit_identifier = @scm.commit_identifier(commit)
|
21
|
+
@build.commit_metadata = @scm.commit_metadata(commit)
|
22
|
+
@build.save
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete_code
|
26
|
+
FileUtils.rm_r export_directory
|
27
|
+
rescue Errno::ENOENT
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def export_directory
|
33
|
+
Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def scm_name
|
37
|
+
@scm.name
|
38
|
+
end
|
39
|
+
|
40
|
+
def run_build_script
|
41
|
+
Integrity.log "Running `#{build_script}` in #{@scm.working_directory}"
|
42
|
+
|
43
|
+
IO.popen "(cd #{@scm.working_directory} && #{build_script}) 2>&1", "r" do |pipe|
|
44
|
+
@build.output = pipe.read
|
45
|
+
end
|
46
|
+
@build.successful = $?.success?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Time
|
2
|
+
alias :strftime_without_ordinals :strftime
|
3
|
+
def strftime(format_string)
|
4
|
+
format_string.gsub! "%o", case day
|
5
|
+
when 1, 21, 31 then "st"
|
6
|
+
when 2, 22 then "nd"
|
7
|
+
when 3, 23 then "rd"
|
8
|
+
else "th"
|
9
|
+
end
|
10
|
+
|
11
|
+
strftime_without_ordinals(format_string)
|
12
|
+
end
|
13
|
+
end
|
@@ -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,50 @@
|
|
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 { |notifier| valid_notifier?(notifier) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.enable_notifiers(project, enabled, config={})
|
19
|
+
all(:project_id => project).destroy!
|
20
|
+
list_of_enabled_notifiers(enabled).each do |name|
|
21
|
+
create! :project_id => project, :name => name, :config => config[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def notify_of_build(build)
|
27
|
+
to_const.notify_of_build(build, config)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def to_const
|
33
|
+
self.class.module_eval(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.list_of_enabled_notifiers(names)
|
37
|
+
[*names].reject { |n| n.nil? }
|
38
|
+
end
|
39
|
+
private_class_method :list_of_enabled_notifiers
|
40
|
+
|
41
|
+
def self.valid_notifier?(notifier)
|
42
|
+
notifier.respond_to?(:to_haml) && notifier.respond_to?(:notify_of_build) && notifier != Notifier::Base
|
43
|
+
end
|
44
|
+
private_class_method :valid_notifier?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
require File.dirname(__FILE__) / 'notifier' / 'base'
|
49
|
+
|
50
|
+
Dir["#{File.dirname(__FILE__)}/notifier/*.rb"].each &method(:require)
|
@@ -0,0 +1,94 @@
|
|
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 :started_build_at, DateTime
|
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(:started_build_at => Time.now)
|
27
|
+
Builder.new(self).build(commit_identifier)
|
28
|
+
ensure
|
29
|
+
update_attributes(:started_build_at => nil)
|
30
|
+
send_notifications
|
31
|
+
end
|
32
|
+
|
33
|
+
def building?
|
34
|
+
!started_build_at.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def last_build
|
38
|
+
builds.last
|
39
|
+
end
|
40
|
+
|
41
|
+
def previous_builds
|
42
|
+
builds.all(:order => [:created_at.desc]).tap do |builds|
|
43
|
+
builds.shift
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def status
|
48
|
+
last_build && last_build.status
|
49
|
+
end
|
50
|
+
|
51
|
+
def public=(flag)
|
52
|
+
attribute_set(:public, !!flag)
|
53
|
+
end
|
54
|
+
|
55
|
+
def config_for(notifier)
|
56
|
+
notifier = notifiers.first(:name => notifier.to_s.split(/::/).last)
|
57
|
+
notifier.blank? ? {} : notifier.config
|
58
|
+
end
|
59
|
+
|
60
|
+
def notifies?(notifier)
|
61
|
+
!notifiers.first(:name => notifier.to_s.split(/::/).last).blank?
|
62
|
+
end
|
63
|
+
|
64
|
+
def enable_notifiers(*args)
|
65
|
+
Notifier.enable_notifiers(id, *args)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def set_permalink
|
70
|
+
self.permalink = (name || "").downcase.
|
71
|
+
gsub(/'s/, "s").
|
72
|
+
gsub(/&/, "and").
|
73
|
+
gsub(/[^a-z0-9]+/, "-").
|
74
|
+
gsub(/-*$/, "")
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete_code
|
78
|
+
builds.destroy!
|
79
|
+
Builder.new(self).delete_code
|
80
|
+
end
|
81
|
+
|
82
|
+
def send_notifications
|
83
|
+
notifiers.each do |notifier|
|
84
|
+
begin
|
85
|
+
Integrity.log "Notifying of build #{last_build.short_commit_identifier} using the #{notifier.name} notifier"
|
86
|
+
notifier.notify_of_build last_build
|
87
|
+
rescue Timeout::Error
|
88
|
+
Integrity.log "#{notifier.name} notifier timed out"
|
89
|
+
next
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
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,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,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
|
data/lib/integrity.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
__DIR__ = File.dirname(__FILE__)
|
2
|
+
$:.unshift "#{__DIR__}/integrity", *Dir["#{__DIR__}/../vendor/**/lib"].to_a
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'json'
|
6
|
+
require 'dm-core'
|
7
|
+
require 'dm-validations'
|
8
|
+
require 'dm-types'
|
9
|
+
require 'dm-timestamps'
|
10
|
+
require 'dm-aggregates'
|
11
|
+
|
12
|
+
require 'yaml'
|
13
|
+
require 'logger'
|
14
|
+
require 'digest/sha1'
|
15
|
+
|
16
|
+
require 'core_ext/object'
|
17
|
+
require 'core_ext/string'
|
18
|
+
require 'core_ext/time'
|
19
|
+
|
20
|
+
require 'project'
|
21
|
+
require 'build'
|
22
|
+
require 'builder'
|
23
|
+
require 'scm'
|
24
|
+
require 'scm/git'
|
25
|
+
require 'notifier'
|
26
|
+
|
27
|
+
module Integrity
|
28
|
+
def self.new(config_file = nil)
|
29
|
+
self.config = config_file unless config_file.nil?
|
30
|
+
DataMapper.setup(:default, config[:database_uri])
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.root
|
34
|
+
File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.default_configuration
|
38
|
+
@defaults ||= { :database_uri => 'sqlite3::memory:',
|
39
|
+
:export_directory => root / 'exports',
|
40
|
+
:log => STDOUT,
|
41
|
+
:base_uri => 'http://localhost:8910',
|
42
|
+
:use_basic_auth => false,
|
43
|
+
:build_all_commits => true}
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.config
|
47
|
+
@config ||= default_configuration
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.config=(file)
|
51
|
+
@config = default_configuration.merge(YAML.load_file(file))
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.log(message, &block)
|
55
|
+
logger.info(message, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.logger
|
59
|
+
@logger ||= Logger.new(config[:log], "daily").tap do |logger|
|
60
|
+
logger.formatter = LogFormatter.new
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
class LogFormatter < Logger::Formatter
|
67
|
+
def call(severity, time, progname, msg)
|
68
|
+
time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|