damagecontrol 0.5.0 → 0.5.0.1391

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. data/README +67 -11
  2. data/Rakefile +15 -6
  3. data/app/controllers/admin_controller.rb +0 -3
  4. data/app/controllers/application.rb +42 -163
  5. data/app/controllers/build_controller.rb +33 -0
  6. data/app/controllers/files_controller.rb +1 -1
  7. data/app/controllers/project_controller.rb +23 -65
  8. data/app/controllers/rails_ext.rb +247 -0
  9. data/app/controllers/rscm_ext.rb +52 -0
  10. data/app/helpers/build_helper.rb +2 -0
  11. data/app/views/build/email.rhtml +18 -0
  12. data/app/views/build/status.rhtml +20 -0
  13. data/app/views/build/tests.rhtml +2 -0
  14. data/app/views/layouts/{rscm.rhtml → default.rhtml} +10 -4
  15. data/app/views/project/_changesets_list.rhtml +2 -2
  16. data/app/views/project/_cvs.rhtml +4 -5
  17. data/app/views/project/_project.rhtml +9 -9
  18. data/app/views/project/_select_pane.rhtml +26 -0
  19. data/app/views/project/_tab_pane.rhtml +23 -0
  20. data/app/views/project/changeset.rhtml +35 -0
  21. data/app/views/project/view.rhtml +18 -32
  22. data/app/views/setup/welcome.rhtml +118 -0
  23. data/config/database.yml +20 -20
  24. data/config/environment.rb +66 -60
  25. data/config/environments/development.rb +3 -2
  26. data/config/environments/production.rb +3 -2
  27. data/config/environments/test.rb +3 -2
  28. data/config/routes.rb +15 -0
  29. data/lib/damagecontrol/app.rb +11 -40
  30. data/lib/damagecontrol/build.rb +50 -8
  31. data/lib/damagecontrol/directories.rb +7 -6
  32. data/lib/damagecontrol/poller.rb +11 -20
  33. data/lib/damagecontrol/project.rb +83 -16
  34. data/lib/damagecontrol/publisher/ambient_orb.rb +16 -0
  35. data/lib/damagecontrol/publisher/archive.rb +16 -0
  36. data/lib/damagecontrol/publisher/base.rb +25 -0
  37. data/lib/damagecontrol/publisher/build_duration.rb +16 -0
  38. data/lib/damagecontrol/publisher/email.rb +59 -0
  39. data/lib/damagecontrol/publisher/execute.rb +49 -0
  40. data/lib/damagecontrol/publisher/ftp.rb +16 -0
  41. data/lib/damagecontrol/publisher/growl.rb +44 -0
  42. data/lib/damagecontrol/publisher/irc.rb +31 -0
  43. data/lib/damagecontrol/publisher/jabber.rb +68 -0
  44. data/lib/damagecontrol/publisher/scp.rb +16 -0
  45. data/lib/damagecontrol/publisher/x10cm11a.rb +17 -0
  46. data/lib/damagecontrol/publisher/x10cm17a.rb +17 -0
  47. data/lib/damagecontrol/publisher/yahoo.rb +16 -0
  48. data/lib/damagecontrol/standard_persister.rb +2 -2
  49. data/lib/damagecontrol/tracker.rb +48 -6
  50. data/lib/damagecontrol/visitor/rss_writer.rb +1 -1
  51. data/lib/damagecontrol/visitor/yaml_persister.rb +10 -1
  52. data/public/404.html +5 -5
  53. data/public/500.html +5 -5
  54. data/public/dispatch.cgi +2 -2
  55. data/public/dispatch.fcgi +1 -1
  56. data/public/dispatch.rb +2 -2
  57. data/public/images/growlicon.png +0 -0
  58. data/public/images/megaphone.png +0 -0
  59. data/public/images/monotone-logo.png +0 -0
  60. data/public/images/publisher/ambient_orb.png +0 -0
  61. data/public/images/publisher/build_duration.png +0 -0
  62. data/public/images/publisher/email.png +0 -0
  63. data/public/images/publisher/execute.png +0 -0
  64. data/public/images/publisher/growl.png +0 -0
  65. data/public/images/publisher/irc.png +0 -0
  66. data/public/images/publisher/jabber.png +0 -0
  67. data/public/images/publisher/x10cm11a.png +0 -0
  68. data/public/images/publisher/x10cm17a.png +0 -0
  69. data/public/images/publisher/yahoo.png +0 -0
  70. data/public/index.html +70 -1
  71. data/public/javascripts/dateFormat.js +283 -0
  72. data/public/javascripts/jscalendar/ChangeLog +500 -0
  73. data/public/javascripts/jscalendar/README +33 -0
  74. data/public/javascripts/jscalendar/bugtest-hidden-selects.html +108 -0
  75. data/public/javascripts/jscalendar/calendar-blue.css +231 -0
  76. data/public/javascripts/jscalendar/calendar-blue2.css +235 -0
  77. data/public/javascripts/jscalendar/calendar-brown.css +224 -0
  78. data/public/javascripts/jscalendar/calendar-green.css +228 -0
  79. data/public/javascripts/jscalendar/calendar-setup.js +181 -0
  80. data/public/javascripts/jscalendar/calendar-setup_stripped.js +21 -0
  81. data/public/javascripts/jscalendar/calendar-system.css +250 -0
  82. data/public/javascripts/jscalendar/calendar-tas.css +238 -0
  83. data/public/javascripts/jscalendar/calendar-win2k-1.css +270 -0
  84. data/public/javascripts/jscalendar/calendar-win2k-2.css +270 -0
  85. data/public/javascripts/jscalendar/calendar-win2k-cold-1.css +264 -0
  86. data/public/javascripts/jscalendar/calendar-win2k-cold-2.css +270 -0
  87. data/public/javascripts/jscalendar/calendar.js +1715 -0
  88. data/public/javascripts/jscalendar/calendar.php +119 -0
  89. data/public/javascripts/jscalendar/calendar_stripped.js +12 -0
  90. data/public/javascripts/jscalendar/doc/html/reference-Z-S.css +0 -0
  91. data/public/javascripts/jscalendar/doc/html/reference.css +34 -0
  92. data/public/javascripts/jscalendar/doc/html/reference.html +1316 -0
  93. data/public/javascripts/jscalendar/doc/reference.pdf +0 -0
  94. data/public/javascripts/jscalendar/img.gif +0 -0
  95. data/public/javascripts/jscalendar/index.html +333 -0
  96. data/public/javascripts/jscalendar/lang/calendar-af.js +39 -0
  97. data/public/javascripts/jscalendar/lang/calendar-br.js +45 -0
  98. data/public/javascripts/jscalendar/lang/calendar-ca.js +45 -0
  99. data/public/javascripts/jscalendar/lang/calendar-cs-win.js +34 -0
  100. data/public/javascripts/jscalendar/lang/calendar-da.js +63 -0
  101. data/public/javascripts/jscalendar/lang/calendar-de.js +100 -0
  102. data/public/javascripts/jscalendar/lang/calendar-du.js +45 -0
  103. data/public/javascripts/jscalendar/lang/calendar-el.js +89 -0
  104. data/public/javascripts/jscalendar/lang/calendar-en.js +123 -0
  105. data/public/javascripts/jscalendar/lang/calendar-es.js +114 -0
  106. data/public/javascripts/jscalendar/lang/calendar-fi.js +98 -0
  107. data/public/javascripts/jscalendar/lang/calendar-fr.js +86 -0
  108. data/public/javascripts/jscalendar/lang/calendar-hr-utf8.js +49 -0
  109. data/public/javascripts/jscalendar/lang/calendar-hr.js +0 -0
  110. data/public/javascripts/jscalendar/lang/calendar-hu.js +45 -0
  111. data/public/javascripts/jscalendar/lang/calendar-it.js +79 -0
  112. data/public/javascripts/jscalendar/lang/calendar-jp.js +45 -0
  113. data/public/javascripts/jscalendar/lang/calendar-ko-utf8.js +120 -0
  114. data/public/javascripts/jscalendar/lang/calendar-ko.js +120 -0
  115. data/public/javascripts/jscalendar/lang/calendar-lt-utf8.js +114 -0
  116. data/public/javascripts/jscalendar/lang/calendar-lt.js +114 -0
  117. data/public/javascripts/jscalendar/lang/calendar-nl.js +45 -0
  118. data/public/javascripts/jscalendar/lang/calendar-no.js +45 -0
  119. data/public/javascripts/jscalendar/lang/calendar-pl-utf8.js +93 -0
  120. data/public/javascripts/jscalendar/lang/calendar-pl.js +56 -0
  121. data/public/javascripts/jscalendar/lang/calendar-pt.js +45 -0
  122. data/public/javascripts/jscalendar/lang/calendar-ro.js +66 -0
  123. data/public/javascripts/jscalendar/lang/calendar-ru.js +45 -0
  124. data/public/javascripts/jscalendar/lang/calendar-si.js +94 -0
  125. data/public/javascripts/jscalendar/lang/calendar-sk.js +99 -0
  126. data/public/javascripts/jscalendar/lang/calendar-sp.js +63 -0
  127. data/public/javascripts/jscalendar/lang/calendar-sv.js +93 -0
  128. data/public/javascripts/jscalendar/lang/calendar-tr.js +58 -0
  129. data/public/javascripts/jscalendar/lang/calendar-zh.js +45 -0
  130. data/public/javascripts/jscalendar/menuarrow.gif +0 -0
  131. data/public/javascripts/jscalendar/menuarrow2.gif +0 -0
  132. data/public/javascripts/jscalendar/release-notes.html +334 -0
  133. data/public/javascripts/jscalendar/simple-1.html +244 -0
  134. data/public/javascripts/jscalendar/simple-2.html +108 -0
  135. data/public/javascripts/jscalendar/simple-3.html +130 -0
  136. data/public/javascripts/jscalendar/test-position.html +40 -0
  137. data/public/javascripts/jscalendar/test.php +116 -0
  138. data/public/javascripts/toggle_div.js +18 -0
  139. data/public/stylesheets/niceones.txt +1 -0
  140. data/public/stylesheets/style.css +8 -1
  141. data/script/breakpointer +4 -5
  142. data/script/console +19 -27
  143. data/script/console_sandbox.rb +7 -0
  144. data/script/destroy +5 -0
  145. data/script/generate +3 -68
  146. data/script/server +6 -16
  147. data/test/damagecontrol/build_test.rb +8 -8
  148. data/test/damagecontrol/poller_test.rb +10 -18
  149. data/test/damagecontrol/project_test.rb +49 -13
  150. data/test/damagecontrol/publisher/base_test.rb +26 -0
  151. data/test/damagecontrol/publisher/build/email.rhtml +0 -0
  152. data/test/damagecontrol/publisher/email_test.rb +26 -0
  153. data/test/damagecontrol/publisher/fixture.rb +34 -0
  154. data/test/damagecontrol/publisher/growl_test.rb +15 -0
  155. data/test/damagecontrol/publisher/jabber_test.rb +15 -0
  156. data/test/damagecontrol/scm_web_test.rb +1 -1
  157. data/test/damagecontrol/visitor/changesets.rss +1 -1
  158. data/test/damagecontrol/visitor/diff_persister_test.rb +4 -4
  159. data/test/functional/build_controller_test.rb +17 -0
  160. data/test/test_helper.rb +13 -13
  161. metadata +185 -24
  162. data/app/views/project/_bugzilla.rhtml +0 -13
  163. data/app/views/project/_jira.rhtml +0 -19
  164. data/app/views/project/_mooky.rhtml +0 -23
  165. data/app/views/project/_rubyforge.rhtml +0 -19
  166. data/app/views/project/_scarab.rhtml +0 -19
  167. data/app/views/project/_scms.rhtml +0 -15
  168. data/app/views/project/_sourceforge.rhtml +0 -19
  169. data/app/views/project/_starteam.rhtml +0 -43
  170. data/app/views/project/_svn.rhtml +0 -22
  171. data/app/views/project/_trac.rhtml +0 -13
  172. data/app/views/project/_trackers.rhtml +0 -18
  173. data/app/views/project/changesets.rhtml +0 -31
@@ -1,3 +1,4 @@
1
- Dependencies.mechanism = :load
2
- ActionController::Base.consider_all_requests_local = true
1
+ Dependencies.mechanism = :load
2
+ ActionController::Base.consider_all_requests_local = true
3
+ ActionController::Base.perform_caching = false
3
4
  BREAKPOINT_SERVER_PORT = 42531
@@ -1,2 +1,3 @@
1
- Dependencies.mechanism = :require
2
- ActionController::Base.consider_all_requests_local = false
1
+ Dependencies.mechanism = :require
2
+ ActionController::Base.consider_all_requests_local = false
3
+ ActionController::Base.perform_caching = true
@@ -1,3 +1,4 @@
1
- Dependencies.mechanism = :require
2
- ActionController::Base.consider_all_requests_local = true
1
+ Dependencies.mechanism = :require
2
+ ActionController::Base.consider_all_requests_local = true
3
+ ActionController::Base.perform_caching = false
3
4
  ActionMailer::Base.delivery_method = :test
@@ -0,0 +1,15 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ # Add your own custom routes here.
3
+ # The priority is based upon order of creation: first created -> highest priority.
4
+
5
+ # Here's a sample route:
6
+ # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7
+ # Keep in mind you can assign values other than :controller and :action
8
+
9
+ # Allow downloading Web Service WSDL as a file with an extension
10
+ # instead of a file named 'wsdl'
11
+ map.connect ':controller/service.wsdl', :action => 'wsdl'
12
+
13
+ # Install the default route as the lowest priority.
14
+ map.connect ':controller/:action/:id'
15
+ end
@@ -1,9 +1,9 @@
1
- require 'drb'
2
1
  require 'rubygems'
3
2
  require 'needle'
4
- require_gem 'rscm'
3
+ require 'rscm'
5
4
  require 'damagecontrol/poller'
6
5
  require 'damagecontrol/standard_persister'
6
+ require 'damagecontrol/publisher/base'
7
7
 
8
8
  # Wire up the whole DamageControl app with Needle's nice block based DI framework.
9
9
  # I wonder - is BDI (Block Dependency Injection) a new flavour of DI?
@@ -15,59 +15,30 @@ REGISTRY = Needle::Registry.define do |b|
15
15
  b.poller do
16
16
  DamageControl::Poller.new do |project, changesets|
17
17
  b.persister.save_changesets(project, changesets)
18
- b.persister.save_diffs(project, changesets)
19
18
  b.persister.save_rss(project)
20
19
  changeset = changesets.latest
21
- project.build(changeset.identifier) do |build|
20
+ project.execute_build(changeset.identifier, "Detected changes by polling #{project.scm.name}") do |build|
21
+ # TODO: we want to reuse this in other places (Execute publisher)
22
22
  env = {
23
23
  'PKG_BUILD' => changeset.identifier.to_s, # Rake standard
24
- 'DAMAGECONTROL_BUILD_LABEL' => changeset.identifier.to_s # For others
24
+ 'DAMAGECONTROL_BUILD_LABEL' => changeset.identifier.to_s, # For others
25
+ 'DAMAGECONTROL_CHANGED_FILES' => changeset.changes.collect{|change| change.path}.join(",")
25
26
  }
26
27
  build.execute(project.build_command, env)
28
+ project.publish(build)
27
29
  end
30
+ # TODO: do this in a publisher that can be turned off if an other SCMWeb is used.
31
+ # This may take a while, so we do it after the build.
32
+ b.persister.save_diffs(project, changesets)
28
33
  end
29
34
  end
30
-
31
- b.drb_server do
32
- DamageControl::DrbServer.new('druby://localhost:9000')
33
- end
34
35
  end
35
36
 
36
37
  module DamageControl
37
38
 
38
39
  class App
39
40
  def run
40
- REGISTRY.poller.start
41
- REGISTRY.drb_server.start
42
-
43
- DRb.thread.join # Block forever
44
- end
45
- end
46
-
47
- # Drb top-level object that can be accessed by the web app.
48
- # The webapp should use this for any operations that are
49
- # lengthy.
50
- #
51
- class DrbServer
52
- def initialize(drb_url)
53
- @drb_url = drb_url
54
- end
55
-
56
- def start
57
- DRb.start_service(@drb_url, self)
58
- Log.info "DamageControl server running on #{@drb_url}"
59
- end
60
-
61
- def save_project(project)
62
- project.save
63
- end
64
-
65
- def delete_project(project)
66
- project.delete
67
- end
68
-
69
- def checkout_project(project)
70
- project.checkout
41
+ REGISTRY.poller.start.join
71
42
  end
72
43
  end
73
44
 
@@ -1,8 +1,11 @@
1
1
  require 'rscm/path_converter'
2
2
  require 'damagecontrol/directories'
3
+ require 'damagecontrol/project'
3
4
 
4
5
  module DamageControl
5
- # File structure
6
+ # Represents build-related data organised in the following file structure:
7
+ #
8
+ # File structure:
6
9
  #
7
10
  # .damagecontrol/
8
11
  # SomeProject/
@@ -19,16 +22,32 @@ module DamageControl
19
22
  # artifacts/
20
23
  #
21
24
  class Build
25
+
26
+ # TODO: we want to store the following additional info for a build (time related)
27
+ # * Total Duration
28
+ # * Duration of checkpoints (compile, test, javadocs...) - should be configurable in project.
29
+ # *
30
+
22
31
  attr_reader :time
23
32
 
24
33
  # Creates a new Build for a +project+'s +changeset+, created at +time+.
25
- def initialize(project_name, changeset_identifier, time)
26
- @project_name, @changeset_identifier, @time = project_name, changeset_identifier, time
34
+ def initialize(project_name, changeset_identifier, time, build_reason)
35
+ @project_name, @changeset_identifier, @time, @build_reason = project_name, changeset_identifier, time, build_reason
36
+ end
37
+
38
+ # Our unique id within the changeset
39
+ def identifier
40
+ time.ymdHMS
27
41
  end
28
42
 
29
- # The changeset we belong to
43
+ # Our associated project
44
+ def project
45
+ Project.load(@project_name)
46
+ end
47
+
48
+ # Our associated changeset
30
49
  def changeset
31
- Directories.changeset
50
+ project.changeset(@changeset_identifier)
32
51
  end
33
52
 
34
53
  # Executes +command+ with the environment variables +env+ and persists the command for future reference.
@@ -40,20 +59,22 @@ module DamageControl
40
59
  File.open(command_file, "w") do |io|
41
60
  io.write(command)
42
61
  end
43
- stderr = Directories.stderr(@project_name, @changeset_identifier, @time)
44
- stdout = Directories.stdout(@project_name, @changeset_identifier, @time)
45
62
  command_line = "#{command} > #{stdout} 2> #{stderr}"
46
63
 
47
64
  begin
48
65
  with_working_dir(checkout_dir) do
49
66
  env.each {|k,v| ENV[k]=v}
67
+ Log.info "Executing '#{command_line}'"
68
+ Log.info "Execution environment:"
69
+ ENV.each {|k,v| Log.info("#{k}=#{v}")}
50
70
  IO.popen(command_line) do |io|
51
71
  File.open(pid_file, "w") do |pid_io|
52
72
  pid_io.write(pid)
53
73
  end
54
74
 
55
75
  # there is nothing to read, since we're redirecting to file,
56
- # but we still need to read in order to block till process id done.
76
+ # but we still need to read in order to block until the process is done.
77
+ # TODO: don't redirect stdout - we want to intercept checkpoints
57
78
  io.read
58
79
  end
59
80
  end
@@ -73,6 +94,14 @@ module DamageControl
73
94
  nil
74
95
  end
75
96
  end
97
+
98
+ def successful?
99
+ exit_code == 0
100
+ end
101
+
102
+ def status_message
103
+ successful? ? "Successful" : "Failed"
104
+ end
76
105
 
77
106
  # Returns the pid of the build process
78
107
  def pid
@@ -82,7 +111,20 @@ module DamageControl
82
111
  def kill
83
112
  Process.kill("SIGHUP", pid)
84
113
  end
114
+
115
+ def stdout
116
+ Directories.stdout(@project_name, @changeset_identifier, @time)
117
+ end
118
+
119
+ def stderr
120
+ Directories.stderr(@project_name, @changeset_identifier, @time)
121
+ end
85
122
 
123
+ # The directory of the build
124
+ def dir
125
+ Directories.build_dir(@project_name, @changeset_identifier, @time)
126
+ end
127
+
86
128
  private
87
129
 
88
130
  def checkout_dir
@@ -6,17 +6,23 @@ module DamageControl
6
6
 
7
7
  # This class knows about locations of various files and directories.
8
8
  #
9
+ # TODO: Add templates, logs(global)
9
10
  module Directories
10
11
  include FileUtils
11
12
 
12
13
  def project_names
13
- result = Dir["#{basedir}/*/project.yaml"].collect do |f|
14
+ result = Dir["#{basedir}/projects/*/project.yaml"].collect do |f|
14
15
  File.basename(File.dirname(f))
15
16
  end
16
17
  result.sort
17
18
  end
18
19
  module_function :project_names
19
20
 
21
+ def project_dir(project_name)
22
+ "#{basedir}/projects/#{project_name}"
23
+ end
24
+ module_function :project_dir
25
+
20
26
  def checkout_dir(project_name)
21
27
  "#{project_dir(project_name)}/checkout"
22
28
  end
@@ -105,11 +111,6 @@ module DamageControl
105
111
  end
106
112
  module_function :project_config_file
107
113
 
108
- def project_dir(project_name)
109
- "#{basedir}/#{project_name}"
110
- end
111
- module_function :project_dir
112
-
113
114
  def basedir
114
115
  if(ENV['DAMAGECONTROL_HOME'])
115
116
  ENV['DAMAGECONTROL_HOME']
@@ -1,6 +1,7 @@
1
1
  require 'rscm/logging'
2
2
  require 'rscm/time_ext'
3
3
  require 'damagecontrol/project'
4
+ require 'damagecontrol/publisher/base'
4
5
 
5
6
  module DamageControl
6
7
  # Polls all projects in intervals.
@@ -11,25 +12,20 @@ module DamageControl
11
12
  # receive |project, changesets| each time new
12
13
  # +changesets+ are found in a polled +project+
13
14
  def initialize(sleeptime=60, &proc)
14
- @projects = []
15
15
  @sleeptime = sleeptime
16
16
  @proc = proc
17
17
  end
18
18
 
19
- # Adds a project to poll. If the project is already added it is replaced
20
- # with then new one, otherwise appended to the end.
21
- def add_project(project)
22
- index = @projects.index(project) || @projects.length
23
- @projects[index] = project
24
- end
25
-
26
19
  # Polls all registered projects and persists RSS, changesets and diffs to disk.
27
20
  # If a block is passed, the project and the changesets will be yielded to the block
28
21
  # for each new changesets object.
29
22
  def poll
30
- @projects.each do |project|
23
+ Log.info "Starting polling cycle"
24
+ Project.find_all.each do |project|
25
+ Log.info "Polling #{project.name}"
31
26
  begin
32
27
  if(project.scm_exists?)
28
+ Log.info "Polling #{project.name}"
33
29
  project.poll do |changesets|
34
30
  if(changesets.empty?)
35
31
  Log.info "No changesets for #{project.name}"
@@ -37,36 +33,31 @@ module DamageControl
37
33
  @proc.call(project, changesets)
38
34
  end
39
35
  end
36
+ else
37
+ Log.info "Not polling #{project.name} since its scm doesn't exist"
40
38
  end
41
39
  rescue => e
42
- $stderr.puts "Error polling #{project.name}"
43
- $stderr.puts e.message
44
- $stderr.puts " " + e.backtrace.join(" \n")
40
+ Log.error "Error polling #{project.name}"
41
+ Log.error e.message
42
+ Log.error " " + e.backtrace.join(" \n")
45
43
  end
46
44
  end
47
45
  end
48
46
 
49
47
  # Runs +poll+ in a separate thread.
50
48
  def start
51
- add_all_projects
52
49
  @t = Thread.new do
53
50
  while(true)
54
51
  poll
55
52
  sleep(@sleeptime)
56
53
  end
57
54
  end
55
+ @t
58
56
  end
59
57
 
60
58
  # Stops thread after a +start+.
61
59
  def stop
62
60
  @t.kill if @t && @t.alive?
63
61
  end
64
-
65
- # Adds all projects
66
- def add_all_projects
67
- Project.find_all.each do |project|
68
- add_project(project)
69
- end
70
- end
71
62
  end
72
63
  end
@@ -10,14 +10,29 @@ require 'damagecontrol/tracker'
10
10
  require 'damagecontrol/visitor/yaml_persister'
11
11
  require 'damagecontrol/visitor/diff_persister'
12
12
  require 'damagecontrol/visitor/rss_writer'
13
+ require 'damagecontrol/publisher/base'
14
+
15
+ module ObjectTemplate
16
+ def dupe(variables)
17
+ template_yaml = YAML::dump(self)
18
+ b = binding
19
+ variables.each { |key, value| eval "#{key} = variables[\"#{key}\"]", b }
20
+ new_yaml = eval(template_yaml.dump.gsub(/\\#/, "#"), b)
21
+ YAML::load(new_yaml)
22
+ end
23
+ end
13
24
 
14
25
  module DamageControl
15
26
  # Represents a project with associated SCM, Tracker and SCMWeb
16
27
  class Project
28
+ include ObjectTemplate
29
+
30
+ # TODO: move to scms? not sure....
31
+ DEFAULT_QUIET_PERIOD = 10 unless defined? DEFAULT_QUIET_PERIOD
17
32
 
18
33
  attr_accessor :name
19
- attr_accessor :description
20
34
  attr_accessor :home_page
35
+ attr_accessor :start_time
21
36
 
22
37
  attr_accessor :scm
23
38
  attr_accessor :tracker
@@ -27,14 +42,26 @@ module DamageControl
27
42
  attr_accessor :quiet_period
28
43
 
29
44
  attr_accessor :build_command
45
+ attr_accessor :publishers
30
46
 
31
47
  # Loads the project with the given +name+.
32
48
  def Project.load(name)
33
49
  config_file = Directories.project_config_file(name)
34
50
  Log.info "Loading project from #{config_file}"
35
- File.open(config_file) do |io|
51
+ project = File.open(config_file) do |io|
36
52
  YAML::load(io)
37
53
  end
54
+
55
+ # Add new publishers that may have be defined after the project was YAMLed.
56
+ project.publishers = [] if project.publishers.nil?
57
+ Publisher::Base.classes.collect{|cls| cls.new}.each do |publisher|
58
+ publisher_of_same_type = project.publishers.find do |p|
59
+ p.class == publisher.class
60
+ end
61
+ project.publishers << publisher unless publisher_of_same_type
62
+ end
63
+
64
+ project
38
65
  end
39
66
 
40
67
  # Loads all projects
@@ -43,12 +70,39 @@ module DamageControl
43
70
  Project.load(name)
44
71
  end
45
72
  end
46
-
47
- def initialize(name=nil)
73
+
74
+ def start_time=(t)
75
+ t = Time.parse_ymdHMS(t) if t.is_a? String
76
+ @start_time = t
77
+ end
78
+
79
+ def initialize(name="")
48
80
  @name = name
81
+ @publishers = Publisher::Base.classes.collect{|cls| cls.new}
49
82
  @scm = nil
50
83
  @tracker = Tracker::Null.new
51
- @scm_web = SCMWeb::Null.new
84
+ @scm_web = SCMWeb::Null.new
85
+ # Default start time is 2 weeks ago
86
+ @start_time = Time.now.utc - (3600*24*14)
87
+ @quiet_period = DEFAULT_QUIET_PERIOD
88
+ end
89
+
90
+ # Tells all publishers to publish a build
91
+ def publish(build)
92
+ @publishers.each do |publisher|
93
+ begin
94
+ if(publisher.enabled)
95
+ Log.info("Publishing #{publisher.name} for #{@name}")
96
+ publisher.publish(build)
97
+ else
98
+ Log.info("Skipping disabled publisher #{publisher.name} for #{@name}")
99
+ end
100
+ rescue => e
101
+ Log.error "Error running publisher #{publisher.name} for project #{name}"
102
+ Log.error e.message
103
+ Log.error " " + e.backtrace.join(" \n")
104
+ end
105
+ end
52
106
  end
53
107
 
54
108
  # Saves the state of this project to persistent store (YAML)
@@ -58,8 +112,6 @@ module DamageControl
58
112
  File.open(f, "w") do |io|
59
113
  YAML::dump(self, io)
60
114
  end
61
-
62
- REGISTRY.poller.add_project(self) if REGISTRY
63
115
  end
64
116
 
65
117
  # Path to file containing pathnames of latest checked out files.
@@ -81,9 +133,9 @@ module DamageControl
81
133
  end
82
134
 
83
135
  # Polls SCM for new changesets and yields them to the given block.
84
- def poll(from_if_first_poll=Time.epoch)
136
+ def poll
85
137
  start = Time.now
86
- from = next_changeset_identifier || from_if_first_poll
138
+ from = next_changeset_identifier || @start_time
87
139
 
88
140
  Log.info "Getting changesets for #{name} from #{from} (retrieved from #{checkout_dir})"
89
141
  changesets = @scm.changesets(checkout_dir, from)
@@ -93,10 +145,11 @@ module DamageControl
93
145
  # When the changesets are not changing, we can consider the last commit done
94
146
  # and the quiet period elapsed. This is not 100% failsafe, but will work
95
147
  # under most circumstances. In the worst case, we'll miss some files in
96
- # the changesets, but they will be part of the next changeset (on next poll).
148
+ # the changesets for really slow commits, but they will be part of the next
149
+ # changeset (on next poll).
97
150
  commit_in_progress = true
98
151
  while(commit_in_progress)
99
- @quiet_period ||= 5
152
+ @quiet_period ||= DEFAULT_QUIET_PERIOD
100
153
  Log.info "Sleeping for #{@quiet_period} seconds since #{name}'s SCM (#{@scm.name}) is not transactional."
101
154
  sleep @quiet_period
102
155
  next_changesets = @scm.changesets(checkout_dir, from)
@@ -156,6 +209,12 @@ module DamageControl
156
209
  changesets_persister.load_upto(changeset_identifier, prior)
157
210
  end
158
211
 
212
+ def changeset(changeset_identifier)
213
+ result = changesets(changeset_identifier, 1)[0]
214
+ raise "No changeset with id '#{changeset_identifier}' for project '#{name}'" unless result
215
+ result
216
+ end
217
+
159
218
  def changeset_identifiers
160
219
  changesets_persister.identifiers
161
220
  end
@@ -177,23 +236,28 @@ module DamageControl
177
236
  DamageControl::Visitor::YamlPersister.new(changesets_dir)
178
237
  end
179
238
 
180
- # Creates, persists and executes a build for the changeset with the given
239
+ # Creates, persists and executes a Build for the changeset with the given
181
240
  # +changeset_identifier+.
182
241
  # Should be called with a block of arity 1 that will receive the build.
183
- def build(changeset_identifier)
242
+ def execute_build(changeset_identifier, build_reason)
184
243
  scm.checkout(checkout_dir, changeset_identifier)
185
- build = Build.new(name, changeset_identifier, Time.now.utc)
244
+ build = Build.new(name, changeset_identifier, Time.now.utc, build_reason)
186
245
  yield build
187
246
  end
188
247
 
189
- # Returns an array of existing builds for the given +changeset+.
248
+ # Returns an array of existing Build s for the given +changeset_identifier+.
190
249
  def builds(changeset_identifier)
191
250
  Directories.build_dirs(name, changeset_identifier).collect do |dir|
192
251
  # The dir's basename will always be a Time
193
- Build.new(name, changeset_identifier, File.basename(dir).to_identifier)
252
+ Build.new(name, changeset_identifier, File.basename(dir).to_identifier, "TODO: get from file")
194
253
  end
195
254
  end
196
255
 
256
+ # Returns the Build for the given +changeset_identifier+ and +build_time+
257
+ def build(changeset_identifier, build_time)
258
+ Build.new(name, changeset_identifier, build_time, "FIXME")
259
+ end
260
+
197
261
  # Returns the latest build.
198
262
  def latest_build
199
263
  changeset_identifiers.reverse.each do |changeset_identifier|
@@ -202,6 +266,9 @@ module DamageControl
202
266
  end
203
267
  nil
204
268
  end
269
+
270
+ # Creates a duplicate of ourself, applying simple standard #{blah}
271
+ # transformations of all key,value pairs in +local_assigns+.
205
272
 
206
273
  private
207
274