artemv-diff_to_html 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +9 -12
- data/examples/diff.css +74 -67
- data/examples/diff.git +3721 -0
- data/examples/diff.svn +175 -0
- data/examples/test.rb +94 -18
- data/examples/test_git_diff.rb +5 -0
- data/examples/test_svn_diff.rb +8 -0
- data/lib/diff_to_html.rb +100 -53
- metadata +6 -2
data/examples/diff.svn
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
Index: app/models/issue.rb
|
2
|
+
===================================================================
|
3
|
+
--- app/models/issue.rb (revision 1693)
|
4
|
+
+++ app/models/issue.rb (working copy)
|
5
|
+
@@ -254,4 +254,9 @@
|
6
|
+
def to_s
|
7
|
+
"#{tracker} ##{id}: #{subject}"
|
8
|
+
end
|
9
|
+
+
|
10
|
+
+ def active_versions
|
11
|
+
+ project.active_versions(:for => self)
|
12
|
+
+ end
|
13
|
+
+
|
14
|
+
end
|
15
|
+
Index: app/models/project.rb
|
16
|
+
===================================================================
|
17
|
+
--- app/models/project.rb (revision 1693)
|
18
|
+
+++ app/models/project.rb (working copy)
|
19
|
+
@@ -239,6 +239,14 @@
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
+ def active_versions(options = {})
|
24
|
+
+ issue = options[:for] if options[:for].is_a?(Issue)
|
25
|
+
+ versions.select do |v|
|
26
|
+
+ !v.completed? || issue && issue.fixed_version &&
|
27
|
+
+ v.id == issue.fixed_version.id
|
28
|
+
+ end
|
29
|
+
+ end
|
30
|
+
+
|
31
|
+
protected
|
32
|
+
def validate
|
33
|
+
errors.add(parent_id, " must be a root project") if parent and parent.parent
|
34
|
+
Index: app/views/issues/_form.rhtml
|
35
|
+
===================================================================
|
36
|
+
--- app/views/issues/_form.rhtml (revision 1693)
|
37
|
+
+++ app/views/issues/_form.rhtml (working copy)
|
38
|
+
@@ -30,7 +30,7 @@
|
39
|
+
{:controller => 'projects', :action => 'add_issue_category', :id => @project},
|
40
|
+
:class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
|
41
|
+
<%= content_tag('p', f.select(:fixed_version_id,
|
42
|
+
- (@project.versions.sort.collect {|v| [v.name, v.id]}),
|
43
|
+
+ (@issue.active_versions.sort.collect {|v| [v.name, v.id]}),
|
44
|
+
{ :include_blank => true })) unless @project.versions.empty? %>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
Index: app/views/issues/_form_update.rhtml
|
48
|
+
===================================================================
|
49
|
+
--- app/views/issues/_form_update.rhtml (revision 1693)
|
50
|
+
+++ app/views/issues/_form_update.rhtml (working copy)
|
51
|
+
@@ -5,6 +5,6 @@
|
52
|
+
<div class="splitcontentright">
|
53
|
+
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
|
54
|
+
<%= content_tag('p', f.select(:fixed_version_id,
|
55
|
+
- (@project.versions.sort.collect {|v| [v.name, v.id]}),
|
56
|
+
+ (@issue.active_versions.sort.collect {|v| [v.name, v.id]}),
|
57
|
+
{ :include_blank => true })) unless @project.versions.empty? %>
|
58
|
+
</div>
|
59
|
+
Index: app/views/issues/bulk_edit.rhtml
|
60
|
+
===================================================================
|
61
|
+
--- app/views/issues/bulk_edit.rhtml (revision 1693)
|
62
|
+
+++ app/views/issues/bulk_edit.rhtml (working copy)
|
63
|
+
@@ -27,7 +27,7 @@
|
64
|
+
<label><%= l(:field_fixed_version) %>:
|
65
|
+
<%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
|
66
|
+
content_tag('option', l(:label_none), :value => 'none') +
|
67
|
+
- options_from_collection_for_select(@project.versions, :id, :name)) %></label>
|
68
|
+
+ options_from_collection_for_select(@project.active_versions, :id, :name)) %></label>
|
69
|
+
</p>
|
70
|
+
|
71
|
+
<p>
|
72
|
+
Index: app/views/issues/context_menu.rhtml
|
73
|
+
===================================================================
|
74
|
+
--- app/views/issues/context_menu.rhtml (revision 1693)
|
75
|
+
+++ app/views/issues/context_menu.rhtml (working copy)
|
76
|
+
@@ -20,11 +20,11 @@
|
77
|
+
<% end -%>
|
78
|
+
</ul>
|
79
|
+
</li>
|
80
|
+
- <% unless @project.versions.empty? -%>
|
81
|
+
+ <% unless @issue.active_versions.empty? -%>
|
82
|
+
<li class="folder">
|
83
|
+
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
|
84
|
+
<ul>
|
85
|
+
- <% @project.versions.sort.each do |v| -%>
|
86
|
+
+ <% @issue.active_versions.sort.each do |v| -%>
|
87
|
+
<li><%= context_menu_link v.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[fixed_version_id]' => v, :back_to => @back}, :method => :post,
|
88
|
+
:selected => (v == @issue.fixed_version), :disabled => !@can[:update] %></li>
|
89
|
+
<% end -%>
|
90
|
+
Index: test/fixtures/issues.yml
|
91
|
+
===================================================================
|
92
|
+
--- test/fixtures/issues.yml (revision 1693)
|
93
|
+
+++ test/fixtures/issues.yml (working copy)
|
94
|
+
@@ -91,4 +91,19 @@
|
95
|
+
status_id: 1
|
96
|
+
start_date: <%= Date.today.to_s(:db) %>
|
97
|
+
due_date: <%= 1.days.from_now.to_date.to_s(:db) %>
|
98
|
+
-
|
99
|
+
|
100
|
+
+issues_007:
|
101
|
+
+ created_on: 2006-07-19 21:04:21 +02:00
|
102
|
+
+ project_id: 1
|
103
|
+
+ updated_on: 2006-07-19 21:09:50 +02:00
|
104
|
+
+ priority_id: 5
|
105
|
+
+ subject: Some closed issue
|
106
|
+
+ id: 7
|
107
|
+
+ fixed_version_id: 1
|
108
|
+
+ category_id:
|
109
|
+
+ description: Some closed issue
|
110
|
+
+ tracker_id: 2
|
111
|
+
+ assigned_to_id: 3
|
112
|
+
+ author_id: 2
|
113
|
+
+ status_id: 5
|
114
|
+
+ start_date: <%= 2.day.ago.to_date.to_s(:db) %>
|
115
|
+
+ due_date:
|
116
|
+
Index: test/unit/issue_test.rb
|
117
|
+
===================================================================
|
118
|
+
--- test/unit/issue_test.rb (revision 1693)
|
119
|
+
+++ test/unit/issue_test.rb (working copy)
|
120
|
+
@@ -181,4 +181,16 @@
|
121
|
+
assert_nil Issue.find_by_id(1)
|
122
|
+
assert_nil TimeEntry.find_by_issue_id(1)
|
123
|
+
end
|
124
|
+
+
|
125
|
+
+ def test_active_versions_have_current_even_if_completed
|
126
|
+
+ issue = issues(:issues_007)
|
127
|
+
+ assert issue.fixed_version.completed?
|
128
|
+
+ assert issue.active_versions.include?(issue.fixed_version),
|
129
|
+
+ 'Even if version is completed it should be considered active for issue assigned to it'
|
130
|
+
+ end
|
131
|
+
+
|
132
|
+
+ def test_active_versions_work_for_new_issue
|
133
|
+
+ issue = Issue.new(:project_id => projects(:projects_001).id)
|
134
|
+
+ assert !issue.active_versions.empty?, 'there should be active versions for new issue'
|
135
|
+
+ end
|
136
|
+
end
|
137
|
+
Index: test/unit/project_test.rb
|
138
|
+
===================================================================
|
139
|
+
--- test/unit/project_test.rb (revision 1693)
|
140
|
+
+++ test/unit/project_test.rb (working copy)
|
141
|
+
@@ -18,7 +18,7 @@
|
142
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
143
|
+
|
144
|
+
class ProjectTest < Test::Unit::TestCase
|
145
|
+
- fixtures :projects, :issues, :issue_statuses, :journals, :journal_details, :users, :members, :roles, :projects_trackers, :trackers, :boards
|
146
|
+
+ fixtures :all
|
147
|
+
|
148
|
+
def setup
|
149
|
+
@ecookbook = Project.find(1)
|
150
|
+
@@ -130,4 +130,25 @@
|
151
|
+
assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
|
152
|
+
assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
|
153
|
+
end
|
154
|
+
+
|
155
|
+
+ def test_active_versions_have_future_versions
|
156
|
+
+ version = versions(:versions_003)
|
157
|
+
+ assert !version.completed?
|
158
|
+
+ assert projects(:projects_001).active_versions.include?(version),
|
159
|
+
+ 'Future version (or one without effective date) should be included'
|
160
|
+
+ end
|
161
|
+
+
|
162
|
+
+ def test_active_versions_have_incomplete_versions
|
163
|
+
+ version = versions(:versions_002)
|
164
|
+
+ assert !version.completed?
|
165
|
+
+ assert projects(:projects_001).active_versions.include?(version),
|
166
|
+
+ 'Incomplete versions should be included'
|
167
|
+
+ end
|
168
|
+
+
|
169
|
+
+ def test_active_versions_dont_have_completed
|
170
|
+
+ version = versions(:versions_001)
|
171
|
+
+ assert version.completed?
|
172
|
+
+ assert !projects(:projects_001).active_versions.include?(version),
|
173
|
+
+ 'Completed versions shouldn\'t be included'
|
174
|
+
+ end
|
175
|
+
end
|
data/examples/test.rb
CHANGED
@@ -1,18 +1,94 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
<
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
1
|
+
def out(diff, converter, title)
|
2
|
+
puts <<-EOF
|
3
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
4
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
|
5
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
6
|
+
<head>
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
8
|
+
<title>#{title}</title>
|
9
|
+
<style type="text/css">
|
10
|
+
<!--
|
11
|
+
body, p, ol, ul, td {
|
12
|
+
font-family:verdana,arial,helvetica,sans-serif;
|
13
|
+
font-size:13px;
|
14
|
+
}
|
15
|
+
|
16
|
+
a, a:link, a:visited {
|
17
|
+
color:#507EC0;
|
18
|
+
text-decoration:none;
|
19
|
+
}
|
20
|
+
|
21
|
+
ul.diff {
|
22
|
+
padding:0;
|
23
|
+
}
|
24
|
+
|
25
|
+
.diff table col.lineno {
|
26
|
+
width:4em;
|
27
|
+
}
|
28
|
+
|
29
|
+
.diff h2 {
|
30
|
+
color:#333333;
|
31
|
+
font-size:14px;
|
32
|
+
letter-spacing:normal;
|
33
|
+
margin:0pt auto;
|
34
|
+
padding:0.1em 0pt 0.25em 0.5em;
|
35
|
+
}
|
36
|
+
|
37
|
+
table.diff {
|
38
|
+
font-size : 9pt;
|
39
|
+
font-family : "lucida console", "courier new", monospace;
|
40
|
+
white-space : pre;
|
41
|
+
border : 1px solid #D7D7D7;
|
42
|
+
border-collapse : collapse;
|
43
|
+
line-height : 110%;
|
44
|
+
width: 100%;
|
45
|
+
}
|
46
|
+
|
47
|
+
.diff tr {
|
48
|
+
background: white;
|
49
|
+
}
|
50
|
+
|
51
|
+
.diff td {
|
52
|
+
border : none;
|
53
|
+
padding : 0px 10px;
|
54
|
+
margin : 0px;
|
55
|
+
}
|
56
|
+
|
57
|
+
.diff td a {
|
58
|
+
text-decoration: none;
|
59
|
+
}
|
60
|
+
|
61
|
+
tr.a { background : #ddffdd; }
|
62
|
+
|
63
|
+
tr.r { background : #ffdddd; }
|
64
|
+
|
65
|
+
tr.range { background : #EAF2F5; color : #999; }
|
66
|
+
|
67
|
+
td.ln {
|
68
|
+
background : #ECECEC;
|
69
|
+
color : #aaa;
|
70
|
+
border-top:1px solid #999988;
|
71
|
+
border-bottom:1px solid #999988;
|
72
|
+
border-right:1px solid #D7D7D7;
|
73
|
+
}
|
74
|
+
|
75
|
+
.diff li {
|
76
|
+
background:#F7F7F7 none repeat scroll 0%;
|
77
|
+
border:1px solid #D7D7D7;
|
78
|
+
list-style-type:none;
|
79
|
+
margin:0pt 0pt 2em;
|
80
|
+
padding:2px;
|
81
|
+
}
|
82
|
+
|
83
|
+
.ln a {
|
84
|
+
color: #aaa;
|
85
|
+
}
|
86
|
+
-->
|
87
|
+
</style>
|
88
|
+
</head>
|
89
|
+
<body>
|
90
|
+
#{converter.composite_to_html(diff)}
|
91
|
+
</body>
|
92
|
+
</html>
|
93
|
+
EOF
|
94
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
#require File.join(File.dirname(__FILE__), '../lib/diff_to_html.rb')
|
2
|
+
require 'rubygems'
|
3
|
+
require 'diff_to_html'
|
4
|
+
require File.join(File.dirname(__FILE__), 'test.rb')
|
5
|
+
filename = ARGV[0]
|
6
|
+
diff = `cat #{filename}`
|
7
|
+
converter = SvnDiffToHtml.new
|
8
|
+
out(diff, converter, filename)
|
data/lib/diff_to_html.rb
CHANGED
@@ -3,10 +3,6 @@ require 'cgi'
|
|
3
3
|
class DiffToHtml
|
4
4
|
|
5
5
|
attr_accessor :file_prefix
|
6
|
-
|
7
|
-
#
|
8
|
-
# globals
|
9
|
-
#
|
10
6
|
|
11
7
|
def ln_cell(ln, side = nil)
|
12
8
|
anchor = "f#{@filenum}#{side}#{ln}"
|
@@ -17,10 +13,10 @@ class DiffToHtml
|
|
17
13
|
result += "</td>"
|
18
14
|
result
|
19
15
|
end
|
16
|
+
|
20
17
|
#
|
21
18
|
# helper for building the next row in the diff
|
22
19
|
#
|
23
|
-
|
24
20
|
def get_diff_row(left_ln, right_ln)
|
25
21
|
result = []
|
26
22
|
if @left.length > 0 or @right.length > 0
|
@@ -56,10 +52,6 @@ class DiffToHtml
|
|
56
52
|
return result.join("\n"), left_ln, right_ln
|
57
53
|
end
|
58
54
|
|
59
|
-
#
|
60
|
-
# build the diff
|
61
|
-
#
|
62
|
-
|
63
55
|
def range_row(range)
|
64
56
|
"<tr class='range'><td>...</td<td>...</td><td>#{range}</td></tr>"
|
65
57
|
end
|
@@ -72,16 +64,14 @@ class DiffToHtml
|
|
72
64
|
end
|
73
65
|
|
74
66
|
def begin_file(file)
|
75
|
-
@filenum ||=0
|
76
67
|
result = <<EOF
|
77
|
-
<li><h2><a name="F#{@filenum}" href="#F#{@filenum}">#{
|
68
|
+
<li><h2><a name="F#{@filenum}" href="#F#{@filenum}">#{file}</a></h2><table class='diff'>
|
78
69
|
<colgroup>
|
79
70
|
<col class="lineno"/>
|
80
71
|
<col class="lineno"/>
|
81
72
|
<col class="content"/>
|
82
73
|
</colgroup>
|
83
74
|
EOF
|
84
|
-
@filenum += 1
|
85
75
|
result
|
86
76
|
end
|
87
77
|
|
@@ -93,7 +83,7 @@ EOF
|
|
93
83
|
return left_ln, right_ln
|
94
84
|
end
|
95
85
|
|
96
|
-
def
|
86
|
+
def get_single_file_diff(file_name, diff_file)
|
97
87
|
@last_op = ' '
|
98
88
|
@left = []
|
99
89
|
@right = []
|
@@ -102,61 +92,118 @@ EOF
|
|
102
92
|
|
103
93
|
diff = diff_file.split("\n")
|
104
94
|
|
105
|
-
|
106
|
-
|
107
|
-
diff.
|
108
|
-
|
109
|
-
|
110
|
-
|
95
|
+
diff.shift #index
|
96
|
+
line = nil
|
97
|
+
while line !~ /^---/ && !diff.empty?
|
98
|
+
line = diff.shift
|
99
|
+
end
|
100
|
+
header_old = line
|
101
|
+
if line =~ /^---/
|
102
|
+
diff.shift #+++
|
111
103
|
|
112
|
-
result <<
|
104
|
+
result << begin_file(file_name)
|
113
105
|
range = diff.shift
|
114
106
|
left_ln, right_ln = range_info(range)
|
115
107
|
result << range_row(range)
|
116
108
|
|
117
|
-
skip = 0
|
118
109
|
diff.each do |line|
|
119
|
-
|
120
|
-
|
110
|
+
op = line[0,1]
|
111
|
+
line = line[1..-1] || ''
|
112
|
+
if op == '\\'
|
113
|
+
line = op + line
|
114
|
+
op = ' '
|
115
|
+
end
|
116
|
+
|
117
|
+
if ((@last_op != ' ' and op == ' ') or (@last_op == ' ' and op != ' '))
|
118
|
+
left_ln, right_ln = flush_changes(result, left_ln, right_ln)
|
119
|
+
end
|
120
|
+
|
121
|
+
# truncate and escape
|
122
|
+
line = CGI.escapeHTML(line)
|
123
|
+
|
124
|
+
case op
|
125
|
+
when ' '
|
126
|
+
@left.push(line)
|
127
|
+
@right.push(line)
|
128
|
+
when '-' then @left.push(line)
|
129
|
+
when '+' then @right.push(line)
|
130
|
+
when '@'
|
131
|
+
range = '@' + line
|
132
|
+
flush_changes(result, left_ln, right_ln)
|
133
|
+
left_ln, right_ln = range_info(range)
|
134
|
+
result << range_row(range)
|
121
135
|
else
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
if ((@last_op != ' ' and op == ' ') or (@last_op == ' ' and op != ' '))
|
126
|
-
left_ln, right_ln = flush_changes(result, left_ln, right_ln)
|
127
|
-
end
|
128
|
-
|
129
|
-
# truncate and escape
|
130
|
-
line = CGI.escapeHTML(line)
|
131
|
-
|
132
|
-
case op
|
133
|
-
when ' '
|
134
|
-
@left.push(line)
|
135
|
-
@right.push(line)
|
136
|
-
when '-' then @left.push(line)
|
137
|
-
when '+' then @right.push(line)
|
138
|
-
when '@'
|
139
|
-
range = '@' + line
|
140
|
-
flush_changes(result, left_ln, right_ln)
|
141
|
-
left_ln, right_ln = range_info(range)
|
142
|
-
result << range_row(range)
|
143
|
-
when 'I'
|
144
|
-
file = line[6..-1]
|
145
|
-
flush_changes(result, left_ln, right_ln)
|
146
|
-
result << "</table></li>#{begin_file(file)}"
|
147
|
-
skip = 3
|
148
|
-
end
|
149
|
-
@last_op = op
|
136
|
+
flush_changes(result, left_ln, right_ln)
|
137
|
+
result << "</table></li>"
|
138
|
+
break
|
150
139
|
end
|
140
|
+
@last_op = op
|
151
141
|
end
|
152
142
|
|
153
143
|
flush_changes(result, left_ln, right_ln)
|
154
|
-
result << "</table></li
|
144
|
+
result << "</table></li>"
|
155
145
|
else
|
156
|
-
|
146
|
+
#"<div class='error'>#{header_old}</div>"
|
147
|
+
result =%Q{<li><h2><a name="F#{@filenum}" href="#F#{@filenum}">#{file_name}</a></h2>#{header_old}</li>}
|
157
148
|
end
|
158
149
|
|
159
150
|
result
|
160
151
|
end
|
161
152
|
|
153
|
+
def file_header_pattern
|
154
|
+
raise "Method to be implemented in VCS-specific class"
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_diffs(composite_diff)
|
158
|
+
pattern = file_header_pattern
|
159
|
+
files = composite_diff.split(pattern)
|
160
|
+
headers = composite_diff.scan(pattern) #huh can't find a way to get both at once
|
161
|
+
files.shift if files[0] == '' #first one is junk usually
|
162
|
+
result = []
|
163
|
+
i = 0
|
164
|
+
files.each do |file|
|
165
|
+
result << {:filename => "#{file_prefix}#{get_filename(headers[i])}", :file => file}
|
166
|
+
i += 1
|
167
|
+
end
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
def diffs_to_html(diffs)
|
172
|
+
result = '<ul class="diff">'
|
173
|
+
@filenum = 0
|
174
|
+
diffs.each do |file_map|
|
175
|
+
result << get_single_file_diff(file_map[:filename], file_map[:file])
|
176
|
+
@filenum += 1
|
177
|
+
end
|
178
|
+
result << '</ul>'
|
179
|
+
result
|
180
|
+
end
|
181
|
+
|
182
|
+
def composite_to_html(composite_diff)
|
183
|
+
diffs_to_html get_diffs(composite_diff)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class GitDiffToHtml < DiffToHtml
|
188
|
+
def file_header_pattern
|
189
|
+
/^diff --git.+/
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_filename(file_diff)
|
193
|
+
match = (file_diff =~ / b\/(.+)/)
|
194
|
+
raise "not matched!" if !match
|
195
|
+
$1
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class SvnDiffToHtml < DiffToHtml
|
200
|
+
def file_header_pattern
|
201
|
+
/^Index: .+/
|
202
|
+
end
|
203
|
+
|
204
|
+
def get_filename(header)
|
205
|
+
match = (header =~ /^Index: (.+)/) #if we use this pattern file_header_pattern files split doesn't work
|
206
|
+
raise "header '#{header}' not matched!" if !match
|
207
|
+
$1
|
208
|
+
end
|
162
209
|
end
|