damagecontrol 0.5.0 → 0.5.0.1391

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 (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