damagecontrol 0.5.0
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 +75 -0
- data/README.license +5 -0
- data/Rakefile +111 -0
- data/app/controllers/admin_controller.rb +10 -0
- data/app/controllers/application.rb +163 -0
- data/app/controllers/files_controller.rb +19 -0
- data/app/controllers/project_controller.rb +284 -0
- data/app/controllers/scm_controller.rb +49 -0
- data/app/helpers/admin_helper.rb +2 -0
- data/app/helpers/application_helper.rb +3 -0
- data/app/helpers/project_helper.rb +2 -0
- data/app/views/dhtml_sites.txt +6 -0
- data/app/views/files/list.rhtml +4 -0
- data/app/views/layouts/rscm.rhtml +79 -0
- data/app/views/project/_bugzilla.rhtml +13 -0
- data/app/views/project/_changesets_list.rhtml +52 -0
- data/app/views/project/_cvs.rhtml +171 -0
- data/app/views/project/_jira.rhtml +19 -0
- data/app/views/project/_mooky.rhtml +23 -0
- data/app/views/project/_null.rhtml +0 -0
- data/app/views/project/_project.rhtml +36 -0
- data/app/views/project/_rubyforge.rhtml +19 -0
- data/app/views/project/_scarab.rhtml +19 -0
- data/app/views/project/_scms.rhtml +15 -0
- data/app/views/project/_sourceforge.rhtml +19 -0
- data/app/views/project/_starteam.rhtml +43 -0
- data/app/views/project/_svn.rhtml +22 -0
- data/app/views/project/_trac.rhtml +13 -0
- data/app/views/project/_trackers.rhtml +18 -0
- data/app/views/project/changesets.rhtml +31 -0
- data/app/views/project/index.rhtml +23 -0
- data/app/views/project/view.rhtml +70 -0
- data/app/views/scm/checkout_status.rhtml +44 -0
- data/app/views/scm/diff.rhtml +1 -0
- data/app/views/scm/scroll.html +27 -0
- data/bin/damagecontrol +7 -0
- data/bin/damagecontrol-webrick +2 -0
- data/config/database.yml +20 -0
- data/config/environment.rb +60 -0
- data/config/environments/development.rb +3 -0
- data/config/environments/production.rb +2 -0
- data/config/environments/test.rb +3 -0
- data/lib/damagecontrol/app.rb +74 -0
- data/lib/damagecontrol/build.rb +104 -0
- data/lib/damagecontrol/diff_htmlizer.rb +82 -0
- data/lib/damagecontrol/diff_parser.rb +153 -0
- data/lib/damagecontrol/directories.rb +126 -0
- data/lib/damagecontrol/poller.rb +72 -0
- data/lib/damagecontrol/project.rb +213 -0
- data/lib/damagecontrol/project_dependencies.rb +8 -0
- data/lib/damagecontrol/scm_web.rb +50 -0
- data/lib/damagecontrol/standard_persister.rb +49 -0
- data/lib/damagecontrol/tracker.rb +164 -0
- data/lib/damagecontrol/visitor/build_executor.rb +32 -0
- data/lib/damagecontrol/visitor/diff_persister.rb +41 -0
- data/lib/damagecontrol/visitor/rss_writer.rb +43 -0
- data/lib/damagecontrol/visitor/yaml_persister.rb +71 -0
- data/public/404.html +6 -0
- data/public/500.html +6 -0
- data/public/dispatch.cgi +10 -0
- data/public/dispatch.fcgi +7 -0
- data/public/dispatch.rb +10 -0
- data/public/images/16x16/about.png +0 -0
- data/public/images/16x16/bug_green.png +0 -0
- data/public/images/16x16/bug_red.png +0 -0
- data/public/images/16x16/bug_yellow.png +0 -0
- data/public/images/16x16/component.png +0 -0
- data/public/images/16x16/console.png +0 -0
- data/public/images/16x16/console_error.png +0 -0
- data/public/images/16x16/document_add.png +0 -0
- data/public/images/16x16/document_delete.png +0 -0
- data/public/images/16x16/document_edit.png +0 -0
- data/public/images/16x16/document_exchange.png +0 -0
- data/public/images/16x16/document_new.png +0 -0
- data/public/images/16x16/document_warning.png +0 -0
- data/public/images/16x16/safe.png +0 -0
- data/public/images/16x16/scroll_information.png +0 -0
- data/public/images/16x16/wrench.png +0 -0
- data/public/images/24x24/box_delete.png +0 -0
- data/public/images/24x24/box_into.png +0 -0
- data/public/images/24x24/box_new.png +0 -0
- data/public/images/24x24/console_network.png +0 -0
- data/public/images/24x24/document_edit.png +0 -0
- data/public/images/24x24/find.png +0 -0
- data/public/images/24x24/folders.png +0 -0
- data/public/images/24x24/garbage.png +0 -0
- data/public/images/24x24/gear_connection.png +0 -0
- data/public/images/24x24/gear_delete.png +0 -0
- data/public/images/24x24/gears_run.png +0 -0
- data/public/images/24x24/home.png +0 -0
- data/public/images/24x24/navigate_left.png +0 -0
- data/public/images/24x24/navigate_right.png +0 -0
- data/public/images/24x24/package_new.png +0 -0
- data/public/images/24x24/safe.png +0 -0
- data/public/images/24x24/safe_new.png +0 -0
- data/public/images/24x24/safe_out.png +0 -0
- data/public/images/24x24/scroll_information.png +0 -0
- data/public/images/24x24/stop.png +0 -0
- data/public/images/24x24/wrench.png +0 -0
- data/public/images/README.license +2 -0
- data/public/images/blue-16.gif +0 -0
- data/public/images/blue-32.gif +0 -0
- data/public/images/bugzilla.png +0 -0
- data/public/images/cvs.png +0 -0
- data/public/images/footer.gif +0 -0
- data/public/images/green-128.gif +0 -0
- data/public/images/green-16.gif +0 -0
- data/public/images/green-32.gif +0 -0
- data/public/images/grey-16.gif +0 -0
- data/public/images/grey-32.gif +0 -0
- data/public/images/jira.gif +0 -0
- data/public/images/red-16.gif +0 -0
- data/public/images/red-32.gif +0 -0
- data/public/images/red-pulse-32.gif +0 -0
- data/public/images/rss.gif +0 -0
- data/public/images/rubyforge.png +0 -0
- data/public/images/scarab.gif +0 -0
- data/public/images/sourceforge.gif +0 -0
- data/public/images/starteam.png +0 -0
- data/public/images/svnlogo64.png +0 -0
- data/public/images/trac.png +0 -0
- data/public/index.html +1 -0
- data/public/javascripts/dw_event.js +34 -0
- data/public/javascripts/dw_tooltip.js +86 -0
- data/public/javascripts/dw_viewport.js +55 -0
- data/public/javascripts/pngfix.js +29 -0
- data/public/javascripts/toggle_diff.js +25 -0
- data/public/licenses/DAMAGECONTROL.license +28 -0
- data/public/licenses/INCORS.license +32 -0
- data/public/stylesheets/diff.css +23 -0
- data/public/stylesheets/style.css +307 -0
- data/script/breakpointer +5 -0
- data/script/console +30 -0
- data/script/generate +70 -0
- data/script/server +61 -0
- data/test/damagecontrol/a_program.rb +3 -0
- data/test/damagecontrol/a_slow_program.rb +3 -0
- data/test/damagecontrol/build_test.rb +59 -0
- data/test/damagecontrol/diff_htmlizer_test.rb +31 -0
- data/test/damagecontrol/diff_parser_test.rb +61 -0
- data/test/damagecontrol/file_ext.rb +12 -0
- data/test/damagecontrol/poller_test.rb +56 -0
- data/test/damagecontrol/project_test.rb +144 -0
- data/test/damagecontrol/scm_web_test.rb +22 -0
- data/test/damagecontrol/test.diff +38 -0
- data/test/damagecontrol/test.html +40 -0
- data/test/damagecontrol/tracker_test.rb +48 -0
- data/test/damagecontrol/visitor/changesets.rss +34 -0
- data/test/damagecontrol/visitor/diff_persister_test.rb +49 -0
- data/test/damagecontrol/visitor/rss_writer_test.rb +40 -0
- data/test/damagecontrol/visitor/yaml_persister_test.rb +40 -0
- data/test/functional/admin_controller_test.rb +17 -0
- data/test/functional/project_controller_test.rb +17 -0
- data/test/test_helper.rb +14 -0
- metadata +245 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
By specifying an issue tracker issue numbers/ids in developers' SCM commit messages
|
|
2
|
+
will be detected and highlighted with external links to the particular issue.
|
|
3
|
+
This way people browsing changesets can easily navigate to related issues.
|
|
4
|
+
<table width="100%">
|
|
5
|
+
<tr>
|
|
6
|
+
<td width="25%">Issue tracker</td>
|
|
7
|
+
<td class="config_cell"><%= tip(:txt => "scm_tip") %></td>
|
|
8
|
+
<td width="75%">
|
|
9
|
+
<%= text_or_select(@edit, :name => "tracker", :values => @trackers, :onchange => "showElement(this.value, trackers)") %>
|
|
10
|
+
</td>
|
|
11
|
+
</tr>
|
|
12
|
+
</table>
|
|
13
|
+
|
|
14
|
+
<% @trackers.each do |tracker| %>
|
|
15
|
+
<div id="<%= tracker.class.name %>" style="<% if(tracker.selected?)%>display:block<%else%>display:none<%end%>">
|
|
16
|
+
<%= render_partial(tracker.short, tracker) %>
|
|
17
|
+
</div>
|
|
18
|
+
<% end %>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<%
|
|
2
|
+
@changesets.each do |changeset|
|
|
3
|
+
%>
|
|
4
|
+
<table class="pane">
|
|
5
|
+
<tr class="pane">
|
|
6
|
+
<td colspan="3" class="changeset">
|
|
7
|
+
<div class="changeset-message">
|
|
8
|
+
<b><%= changeset.developer %></b>
|
|
9
|
+
(<%= changeset.time.to_human %> UTC)<br>
|
|
10
|
+
<%= @project.tracker.highlight(changeset.message) %>
|
|
11
|
+
</div>
|
|
12
|
+
</td>
|
|
13
|
+
</tr>
|
|
14
|
+
<%
|
|
15
|
+
changeset.each do |change|
|
|
16
|
+
%>
|
|
17
|
+
<tr>
|
|
18
|
+
<td width="5%"><%= tag("img", :src => change.icon) %></td>
|
|
19
|
+
<td width="5%"><%= change.revision %></td>
|
|
20
|
+
<td width="90%">
|
|
21
|
+
<a class="diff-toggle" href="#" onclick="toggleCode('<%= change.path %>');return false"><%= change.path %></a>
|
|
22
|
+
<br>
|
|
23
|
+
<div class="diff" id="<%= change.path %>">
|
|
24
|
+
<%= change.html_diff %>
|
|
25
|
+
</div>
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
28
|
+
<% end %>
|
|
29
|
+
</table>
|
|
30
|
+
<% end %>
|
|
31
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<table id="projectstatus" class="pane">
|
|
2
|
+
<tr>
|
|
3
|
+
<th align="left">Project</th>
|
|
4
|
+
<th align="left">Latest Build</th>
|
|
5
|
+
<th align="left">Changeset RSS</th>
|
|
6
|
+
</tr>
|
|
7
|
+
<% @projects.each do |project| %>
|
|
8
|
+
<tr>
|
|
9
|
+
<td align="left"><%= link_to(project.name, {:controller => "project", :action => "view", :id => project.name}) %></td>
|
|
10
|
+
<td align="left">
|
|
11
|
+
<%
|
|
12
|
+
img = "/images/grey-16.gif"
|
|
13
|
+
build = project.latest_build
|
|
14
|
+
if(build)
|
|
15
|
+
img = build.small_image
|
|
16
|
+
end
|
|
17
|
+
%>
|
|
18
|
+
<%= tag("img", :src => img) %>
|
|
19
|
+
</td>
|
|
20
|
+
<td align="left"><%= link_to_image("rss.gif", :action => "changesets_rss", :id => project.name) %></td>
|
|
21
|
+
</tr>
|
|
22
|
+
<% end %>
|
|
23
|
+
</table>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script><!--
|
|
2
|
+
|
|
3
|
+
function setTab(tab) {
|
|
4
|
+
stickyTab = tab
|
|
5
|
+
showElement(stickyTab, tabs)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function executeOnLoad() {
|
|
9
|
+
<% @scms.each do |scm| %>
|
|
10
|
+
<%= scm.short %>_init();
|
|
11
|
+
<% end %>
|
|
12
|
+
tab = "<%= @params['tab'] || 'tabs[0]' %>"
|
|
13
|
+
setTab(tab)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function showElement(selected, elements) {
|
|
17
|
+
// hide all divs in the group unless selected
|
|
18
|
+
for(element in elements) {
|
|
19
|
+
id = elements[element]
|
|
20
|
+
style = "none"
|
|
21
|
+
if(id == selected) {
|
|
22
|
+
style = "block"
|
|
23
|
+
}
|
|
24
|
+
document.getElementById(id).style.display = style
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
tabs = ["projects", "scms", "trackers", "changesets"]
|
|
29
|
+
|
|
30
|
+
scms = [
|
|
31
|
+
<% @scms.each do |scm| %>
|
|
32
|
+
"<%= scm.class.name %>",
|
|
33
|
+
<% end %> "" ];
|
|
34
|
+
scm_tip = "<div class='tp1'>If you don't find your SCM here, file a feature request in DamageControl's JIRA</div>";
|
|
35
|
+
|
|
36
|
+
trackers = [
|
|
37
|
+
<% @trackers.each do |tracker| %>
|
|
38
|
+
"<%= tracker.class.name %>",
|
|
39
|
+
<% end %> "" ];
|
|
40
|
+
|
|
41
|
+
// -->
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<ul id="foldertab">
|
|
45
|
+
<li><a href="javascript:setTab('projects')"><img src="/images/16x16/wrench.png"> General</a></li>
|
|
46
|
+
<li><a href="javascript:setTab('scms')"><img src="/images/16x16/safe.png"> Source Control</a></li>
|
|
47
|
+
<li><a href="javascript:setTab('trackers')"><img src="/images/16x16/scroll_information.png"> Issue Tracker</a></li>
|
|
48
|
+
</ul>
|
|
49
|
+
|
|
50
|
+
<% if(@edit) %>
|
|
51
|
+
<form action="/project/save">
|
|
52
|
+
<% end %>
|
|
53
|
+
|
|
54
|
+
<div id="projects" style="display:block">
|
|
55
|
+
<%= render_partial("project", @project) %>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div id="scms" style="display:none">
|
|
59
|
+
<%= render_partial("scms", @scms) %>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div id="trackers" style="display:none">
|
|
63
|
+
<%= render_partial("trackers", @trackers) %>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<% if(@edit) %>
|
|
67
|
+
<input type="submit" value="Save"/>
|
|
68
|
+
|
|
69
|
+
</form>
|
|
70
|
+
<% end %>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
var nl = /\n/gi;
|
|
3
|
+
|
|
4
|
+
var req;
|
|
5
|
+
|
|
6
|
+
function loadXMLDoc(url) {
|
|
7
|
+
if (window.XMLHttpRequest) {
|
|
8
|
+
req = new XMLHttpRequest();
|
|
9
|
+
req.onreadystatechange = processReqChange;
|
|
10
|
+
req.open("GET", url, true);
|
|
11
|
+
req.send(null);
|
|
12
|
+
} else if (window.ActiveXObject) {
|
|
13
|
+
req = new ActiveXObject("Microsoft.XMLHTTP");
|
|
14
|
+
if (req) {
|
|
15
|
+
req.onreadystatechange = processReqChange;
|
|
16
|
+
req.open("GET", url, true);
|
|
17
|
+
req.send();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function processReqChange() {
|
|
23
|
+
// only if req shows "loaded"
|
|
24
|
+
if (req.readyState == 4) {
|
|
25
|
+
// only if "OK"
|
|
26
|
+
if (req.status == 200) {
|
|
27
|
+
var checkout_list = window.frames['checkout_list']
|
|
28
|
+
checkout_list.document.body.innerHTML = req.responseText.replace(nl, "<br>")
|
|
29
|
+
checkout_list.scrollBy(0, 3000)
|
|
30
|
+
|
|
31
|
+
setTimeout("loadXMLDoc('<%= @checkout_list_path %>')", 1000)
|
|
32
|
+
} else {
|
|
33
|
+
alert("There was a problem retrieving the XML data:\n" +
|
|
34
|
+
req.responseText);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function executeOnLoad() {
|
|
40
|
+
loadXMLDoc('<%= @checkout_list_path %>')
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<iframe id="checkout_list" name="checkout_list" rows="20" cols="120">Your browser doesn't support IFRAME.</iframe>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= File.open(@diff_html).read %>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>RSCM</title>
|
|
4
|
+
<link type="text/css" rel="stylesheet" href="/stylesheets/style.css">
|
|
5
|
+
<script>
|
|
6
|
+
|
|
7
|
+
// TODO: border and black backgound/white FG. + file icons (prefix with U/D/A/M
|
|
8
|
+
|
|
9
|
+
// http://www.dyn-web.com/dhtml/iframes/
|
|
10
|
+
|
|
11
|
+
var regX = /\n/gi;
|
|
12
|
+
|
|
13
|
+
function addscroll() {
|
|
14
|
+
var checkout_list = window.frames['checkout_list']
|
|
15
|
+
checkout_list.document.body.innerHTML = "hippie\nyoyo<br>hippie<br>yoyo<br>hippie<br>yoyo<br>hippie<br>yoyo<br>".replace(regX, "<br>")
|
|
16
|
+
checkout_list.scrollBy(0, 3000)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
</script>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
|
|
23
|
+
<iframe id="checkout_list" name="checkout_list" rows="20" cols="80">We'll see some checked out files here soon.....
|
|
24
|
+
</iframe>
|
|
25
|
+
<input type="Button" value="Scroll" id="myButton" onClick="addscroll()">
|
|
26
|
+
|
|
27
|
+
</body></html>
|
data/bin/damagecontrol
ADDED
data/config/database.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
development:
|
|
2
|
+
adapter: mysql
|
|
3
|
+
database: rails_development
|
|
4
|
+
host: localhost
|
|
5
|
+
username: root
|
|
6
|
+
password:
|
|
7
|
+
|
|
8
|
+
test:
|
|
9
|
+
adapter: mysql
|
|
10
|
+
database: rails_test
|
|
11
|
+
host: localhost
|
|
12
|
+
username: root
|
|
13
|
+
password:
|
|
14
|
+
|
|
15
|
+
production:
|
|
16
|
+
adapter: mysql
|
|
17
|
+
database: rails_production
|
|
18
|
+
host: localhost
|
|
19
|
+
username: root
|
|
20
|
+
password:
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
RAILS_ROOT = File.dirname(__FILE__) + "/../"
|
|
2
|
+
RAILS_ENV = ENV['RAILS_ENV'] || 'development'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# Mocks first.
|
|
6
|
+
ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"]
|
|
7
|
+
|
|
8
|
+
# Then model subdirectories.
|
|
9
|
+
ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"])
|
|
10
|
+
|
|
11
|
+
# Followed by the standard includes.
|
|
12
|
+
ADDITIONAL_LOAD_PATHS.concat %w(
|
|
13
|
+
app
|
|
14
|
+
app/models
|
|
15
|
+
app/controllers
|
|
16
|
+
app/helpers
|
|
17
|
+
config
|
|
18
|
+
lib
|
|
19
|
+
vendor
|
|
20
|
+
).map { |dir| "#{RAILS_ROOT}/#{dir}" }
|
|
21
|
+
|
|
22
|
+
# Prepend to $LOAD_PATH
|
|
23
|
+
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) }
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Require Rails gems.
|
|
27
|
+
require 'rubygems'
|
|
28
|
+
require_gem 'activerecord'
|
|
29
|
+
require_gem 'actionpack'
|
|
30
|
+
require_gem 'actionmailer'
|
|
31
|
+
require_gem 'rails'
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Environment-specific configuration.
|
|
35
|
+
require_dependency "environments/#{RAILS_ENV}"
|
|
36
|
+
ActiveRecord::Base.configurations = YAML::load(File.open("#{RAILS_ROOT}/config/database.yml"))
|
|
37
|
+
ActiveRecord::Base.establish_connection
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Configure defaults if the included environment did not.
|
|
41
|
+
begin
|
|
42
|
+
RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log")
|
|
43
|
+
rescue StandardError
|
|
44
|
+
RAILS_DEFAULT_LOGGER = Logger.new(STDERR)
|
|
45
|
+
RAILS_DEFAULT_LOGGER.level = Logger::WARN
|
|
46
|
+
RAILS_DEFAULT_LOGGER.warn(
|
|
47
|
+
"Rails Error: Unable to access log file. Please ensure that log/#{RAILS_ENV}.log exists and is chmod 0777. " +
|
|
48
|
+
"The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
[ActiveRecord::Base, ActionController::Base, ActionMailer::Base].each do |klass|
|
|
53
|
+
klass.logger ||= RAILS_DEFAULT_LOGGER
|
|
54
|
+
end
|
|
55
|
+
[ActionController::Base, ActionMailer::Base].each do |klass|
|
|
56
|
+
klass.template_root ||= "#{RAILS_ROOT}/app/views/"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Include your app's configuration here:
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'needle'
|
|
4
|
+
require_gem 'rscm'
|
|
5
|
+
require 'damagecontrol/poller'
|
|
6
|
+
require 'damagecontrol/standard_persister'
|
|
7
|
+
|
|
8
|
+
# Wire up the whole DamageControl app with Needle's nice block based DI framework.
|
|
9
|
+
# I wonder - is BDI (Block Dependency Injection) a new flavour of DI?
|
|
10
|
+
REGISTRY = Needle::Registry.define do |b|
|
|
11
|
+
b.persister do
|
|
12
|
+
DamageControl::StandardPersister.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
b.poller do
|
|
16
|
+
DamageControl::Poller.new do |project, changesets|
|
|
17
|
+
b.persister.save_changesets(project, changesets)
|
|
18
|
+
b.persister.save_diffs(project, changesets)
|
|
19
|
+
b.persister.save_rss(project)
|
|
20
|
+
changeset = changesets.latest
|
|
21
|
+
project.build(changeset.identifier) do |build|
|
|
22
|
+
env = {
|
|
23
|
+
'PKG_BUILD' => changeset.identifier.to_s, # Rake standard
|
|
24
|
+
'DAMAGECONTROL_BUILD_LABEL' => changeset.identifier.to_s # For others
|
|
25
|
+
}
|
|
26
|
+
build.execute(project.build_command, env)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
b.drb_server do
|
|
32
|
+
DamageControl::DrbServer.new('druby://localhost:9000')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module DamageControl
|
|
37
|
+
|
|
38
|
+
class App
|
|
39
|
+
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
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'rscm/path_converter'
|
|
2
|
+
require 'damagecontrol/directories'
|
|
3
|
+
|
|
4
|
+
module DamageControl
|
|
5
|
+
# File structure
|
|
6
|
+
#
|
|
7
|
+
# .damagecontrol/
|
|
8
|
+
# SomeProject/
|
|
9
|
+
# project.yaml
|
|
10
|
+
# checkout/
|
|
11
|
+
# changesets/
|
|
12
|
+
# 2802/
|
|
13
|
+
# changeset.yaml (serialised ChangeSet object)
|
|
14
|
+
# diffs/ (serialised diff files)
|
|
15
|
+
# builds/
|
|
16
|
+
# 2005280271234500/ (timestamp of build start)
|
|
17
|
+
# stdout.log
|
|
18
|
+
# stderr.log
|
|
19
|
+
# artifacts/
|
|
20
|
+
#
|
|
21
|
+
class Build
|
|
22
|
+
attr_reader :time
|
|
23
|
+
|
|
24
|
+
# 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
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The changeset we belong to
|
|
30
|
+
def changeset
|
|
31
|
+
Directories.changeset
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Executes +command+ with the environment variables +env+ and persists the command for future reference.
|
|
35
|
+
# This will prevent the same build from being executed in the future.
|
|
36
|
+
def execute(command, env={})
|
|
37
|
+
command_file = Directories.build_command_file(@project_name, @changeset_identifier, @time)
|
|
38
|
+
raise BuildException.new("This build has already been executed and cannot be re-executed. It was executed with '#{File.open(command_file).read}'") if File.exist?(command_file)
|
|
39
|
+
FileUtils.mkdir_p(File.dirname(command_file))
|
|
40
|
+
File.open(command_file, "w") do |io|
|
|
41
|
+
io.write(command)
|
|
42
|
+
end
|
|
43
|
+
stderr = Directories.stderr(@project_name, @changeset_identifier, @time)
|
|
44
|
+
stdout = Directories.stdout(@project_name, @changeset_identifier, @time)
|
|
45
|
+
command_line = "#{command} > #{stdout} 2> #{stderr}"
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
with_working_dir(checkout_dir) do
|
|
49
|
+
env.each {|k,v| ENV[k]=v}
|
|
50
|
+
IO.popen(command_line) do |io|
|
|
51
|
+
File.open(pid_file, "w") do |pid_io|
|
|
52
|
+
pid_io.write(pid)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# 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.
|
|
57
|
+
io.read
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
ensure
|
|
61
|
+
exit_code = $? >> 8
|
|
62
|
+
File.open(exit_code_file, "w") do |io|
|
|
63
|
+
io.write(exit_code)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns the exit code of the build process, or nil if the process was killed
|
|
69
|
+
def exit_code
|
|
70
|
+
if(File.exist?(exit_code_file))
|
|
71
|
+
File.read(exit_code_file).to_i
|
|
72
|
+
else
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Returns the pid of the build process
|
|
78
|
+
def pid
|
|
79
|
+
File.read(pid_file).to_i
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def kill
|
|
83
|
+
Process.kill("SIGHUP", pid)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def checkout_dir
|
|
89
|
+
Directories.checkout_dir(@project_name)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def exit_code_file
|
|
93
|
+
Directories.build_exit_code_file(@project_name, @changeset_identifier, @time)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def pid_file
|
|
97
|
+
Directories.build_pid_file(@project_name, @changeset_identifier, @time)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class BuildException < Exception
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module DamageControl
|
|
2
|
+
|
|
3
|
+
# Visitor that can visit an array of diffs and produce nice HTML
|
|
4
|
+
# TODO: add line numbers.
|
|
5
|
+
class DiffHtmlizer
|
|
6
|
+
# Creates a new DiffHtmlizer that will write HTML
|
|
7
|
+
# to the IO object +io+ when visiting an array of diffs.
|
|
8
|
+
def initialize(io)
|
|
9
|
+
@io = io
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def visitDiff(diff)
|
|
13
|
+
@io << "<div>\n"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def visitDiffEnd(diff)
|
|
17
|
+
@io << "</div>\n"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def visitLine(line)
|
|
21
|
+
if(line.removed?)
|
|
22
|
+
@io << "<pre class='diff' id='removed'>"
|
|
23
|
+
if(line.removed)
|
|
24
|
+
@io << line.prefix.html_encoded
|
|
25
|
+
@io << "<span id='removedchars'>"
|
|
26
|
+
@io << line.removed.html_encoded
|
|
27
|
+
@io << "</span>"
|
|
28
|
+
@io << line.suffix.html_encoded
|
|
29
|
+
else
|
|
30
|
+
@io << line.html_encoded
|
|
31
|
+
end
|
|
32
|
+
@io << "</pre>"
|
|
33
|
+
elsif(line.added?)
|
|
34
|
+
@io << "<pre class='diff' id='added'>"
|
|
35
|
+
if(line.added)
|
|
36
|
+
@io << line.prefix.html_encoded
|
|
37
|
+
@io << "<span id='addedchars'>"
|
|
38
|
+
@io << line.added.html_encoded
|
|
39
|
+
@io << "</span>"
|
|
40
|
+
@io << line.suffix.html_encoded
|
|
41
|
+
else
|
|
42
|
+
@io << line.html_encoded
|
|
43
|
+
end
|
|
44
|
+
@io << "</pre>"
|
|
45
|
+
else
|
|
46
|
+
@io << "<pre class='diff' id='context'>"
|
|
47
|
+
@io << line.html_encoded
|
|
48
|
+
@io << "</pre>"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Not used
|
|
54
|
+
class Plain
|
|
55
|
+
def initialize(io)
|
|
56
|
+
@io = io
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def visitDiff(diff)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def visitDiffEnd(diff)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def visitLine(line)
|
|
66
|
+
@io << line.html_encoded
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class String
|
|
72
|
+
def html_encoded
|
|
73
|
+
self.gsub(/./) do
|
|
74
|
+
case $&
|
|
75
|
+
when "&" then "&"
|
|
76
|
+
when "<" then "<"
|
|
77
|
+
when ">" then ">"
|
|
78
|
+
else $&
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|