##// END OF EJS Templates
Revision graph code cleanup....
Etienne Massip -
r8653:b9c26d6ddcd3
parent child
Show More
@@ -1,314 +1,319
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'iconv'
20 require 'iconv'
21 require 'redmine/codeset_util'
21 require 'redmine/codeset_util'
22
22
23 module RepositoriesHelper
23 module RepositoriesHelper
24 def format_revision(revision)
24 def format_revision(revision)
25 if revision.respond_to? :format_identifier
25 if revision.respond_to? :format_identifier
26 revision.format_identifier
26 revision.format_identifier
27 else
27 else
28 revision.to_s
28 revision.to_s
29 end
29 end
30 end
30 end
31
31
32 def truncate_at_line_break(text, length = 255)
32 def truncate_at_line_break(text, length = 255)
33 if text
33 if text
34 text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
34 text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
35 end
35 end
36 end
36 end
37
37
38 def render_properties(properties)
38 def render_properties(properties)
39 unless properties.nil? || properties.empty?
39 unless properties.nil? || properties.empty?
40 content = ''
40 content = ''
41 properties.keys.sort.each do |property|
41 properties.keys.sort.each do |property|
42 content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>".html_safe)
42 content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>".html_safe)
43 end
43 end
44 content_tag('ul', content.html_safe, :class => 'properties')
44 content_tag('ul', content.html_safe, :class => 'properties')
45 end
45 end
46 end
46 end
47
47
48 def render_changeset_changes
48 def render_changeset_changes
49 changes = @changeset.changes.find(:all, :limit => 1000, :order => 'path').collect do |change|
49 changes = @changeset.changes.find(:all, :limit => 1000, :order => 'path').collect do |change|
50 case change.action
50 case change.action
51 when 'A'
51 when 'A'
52 # Detects moved/copied files
52 # Detects moved/copied files
53 if !change.from_path.blank?
53 if !change.from_path.blank?
54 change.action =
54 change.action =
55 @changeset.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
55 @changeset.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
56 end
56 end
57 change
57 change
58 when 'D'
58 when 'D'
59 @changeset.changes.detect {|c| c.from_path == change.path} ? nil : change
59 @changeset.changes.detect {|c| c.from_path == change.path} ? nil : change
60 else
60 else
61 change
61 change
62 end
62 end
63 end.compact
63 end.compact
64
64
65 tree = { }
65 tree = { }
66 changes.each do |change|
66 changes.each do |change|
67 p = tree
67 p = tree
68 dirs = change.path.to_s.split('/').select {|d| !d.blank?}
68 dirs = change.path.to_s.split('/').select {|d| !d.blank?}
69 path = ''
69 path = ''
70 dirs.each do |dir|
70 dirs.each do |dir|
71 path += '/' + dir
71 path += '/' + dir
72 p[:s] ||= {}
72 p[:s] ||= {}
73 p = p[:s]
73 p = p[:s]
74 p[path] ||= {}
74 p[path] ||= {}
75 p = p[path]
75 p = p[path]
76 end
76 end
77 p[:c] = change
77 p[:c] = change
78 end
78 end
79 render_changes_tree(tree[:s])
79 render_changes_tree(tree[:s])
80 end
80 end
81
81
82 def render_changes_tree(tree)
82 def render_changes_tree(tree)
83 return '' if tree.nil?
83 return '' if tree.nil?
84 output = ''
84 output = ''
85 output << '<ul>'
85 output << '<ul>'
86 tree.keys.sort.each do |file|
86 tree.keys.sort.each do |file|
87 style = 'change'
87 style = 'change'
88 text = File.basename(h(file))
88 text = File.basename(h(file))
89 if s = tree[file][:s]
89 if s = tree[file][:s]
90 style << ' folder'
90 style << ' folder'
91 path_param = to_path_param(@repository.relative_path(file))
91 path_param = to_path_param(@repository.relative_path(file))
92 text = link_to(h(text), :controller => 'repositories',
92 text = link_to(h(text), :controller => 'repositories',
93 :action => 'show',
93 :action => 'show',
94 :id => @project,
94 :id => @project,
95 :repository_id => @repository.identifier_param,
95 :repository_id => @repository.identifier_param,
96 :path => path_param,
96 :path => path_param,
97 :rev => @changeset.identifier)
97 :rev => @changeset.identifier)
98 output << "<li class='#{style}'>#{text}"
98 output << "<li class='#{style}'>#{text}"
99 output << render_changes_tree(s)
99 output << render_changes_tree(s)
100 output << "</li>"
100 output << "</li>"
101 elsif c = tree[file][:c]
101 elsif c = tree[file][:c]
102 style << " change-#{c.action}"
102 style << " change-#{c.action}"
103 path_param = to_path_param(@repository.relative_path(c.path))
103 path_param = to_path_param(@repository.relative_path(c.path))
104 text = link_to(h(text), :controller => 'repositories',
104 text = link_to(h(text), :controller => 'repositories',
105 :action => 'entry',
105 :action => 'entry',
106 :id => @project,
106 :id => @project,
107 :repository_id => @repository.identifier_param,
107 :repository_id => @repository.identifier_param,
108 :path => path_param,
108 :path => path_param,
109 :rev => @changeset.identifier) unless c.action == 'D'
109 :rev => @changeset.identifier) unless c.action == 'D'
110 text << " - #{h(c.revision)}" unless c.revision.blank?
110 text << " - #{h(c.revision)}" unless c.revision.blank?
111 text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
111 text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
112 :action => 'diff',
112 :action => 'diff',
113 :id => @project,
113 :id => @project,
114 :repository_id => @repository.identifier_param,
114 :repository_id => @repository.identifier_param,
115 :path => path_param,
115 :path => path_param,
116 :rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
116 :rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
117 text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
117 text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
118 output << "<li class='#{style}'>#{text}</li>"
118 output << "<li class='#{style}'>#{text}</li>"
119 end
119 end
120 end
120 end
121 output << '</ul>'
121 output << '</ul>'
122 output.html_safe
122 output.html_safe
123 end
123 end
124
124
125 def repository_field_tags(form, repository)
125 def repository_field_tags(form, repository)
126 method = repository.class.name.demodulize.underscore + "_field_tags"
126 method = repository.class.name.demodulize.underscore + "_field_tags"
127 if repository.is_a?(Repository) &&
127 if repository.is_a?(Repository) &&
128 respond_to?(method) && method != 'repository_field_tags'
128 respond_to?(method) && method != 'repository_field_tags'
129 send(method, form, repository)
129 send(method, form, repository)
130 end
130 end
131 end
131 end
132
132
133 def scm_select_tag(repository)
133 def scm_select_tag(repository)
134 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
134 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
135 Redmine::Scm::Base.all.each do |scm|
135 Redmine::Scm::Base.all.each do |scm|
136 if Setting.enabled_scm.include?(scm) ||
136 if Setting.enabled_scm.include?(scm) ||
137 (repository && repository.class.name.demodulize == scm)
137 (repository && repository.class.name.demodulize == scm)
138 scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
138 scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
139 end
139 end
140 end
140 end
141 select_tag('repository_scm',
141 select_tag('repository_scm',
142 options_for_select(scm_options, repository.class.name.demodulize),
142 options_for_select(scm_options, repository.class.name.demodulize),
143 :disabled => (repository && !repository.new_record?),
143 :disabled => (repository && !repository.new_record?),
144 :onchange => remote_function(
144 :onchange => remote_function(
145 :url => new_project_repository_path(@project),
145 :url => new_project_repository_path(@project),
146 :method => :get,
146 :method => :get,
147 :update => 'content',
147 :update => 'content',
148 :with => "Form.serialize(this.form)")
148 :with => "Form.serialize(this.form)")
149 )
149 )
150 end
150 end
151
151
152 def with_leading_slash(path)
152 def with_leading_slash(path)
153 path.to_s.starts_with?('/') ? path : "/#{path}"
153 path.to_s.starts_with?('/') ? path : "/#{path}"
154 end
154 end
155
155
156 def without_leading_slash(path)
156 def without_leading_slash(path)
157 path.gsub(%r{^/+}, '')
157 path.gsub(%r{^/+}, '')
158 end
158 end
159
159
160 def subversion_field_tags(form, repository)
160 def subversion_field_tags(form, repository)
161 content_tag('p', form.text_field(:url, :size => 60, :required => true,
161 content_tag('p', form.text_field(:url, :size => 60, :required => true,
162 :disabled => (repository && !repository.root_url.blank?)) +
162 :disabled => (repository && !repository.root_url.blank?)) +
163 '<br />'.html_safe +
163 '<br />'.html_safe +
164 '(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
164 '(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
165 content_tag('p', form.text_field(:login, :size => 30)) +
165 content_tag('p', form.text_field(:login, :size => 30)) +
166 content_tag('p', form.password_field(
166 content_tag('p', form.password_field(
167 :password, :size => 30, :name => 'ignore',
167 :password, :size => 30, :name => 'ignore',
168 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
168 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
169 :onfocus => "this.value=''; this.name='repository[password]';",
169 :onfocus => "this.value=''; this.name='repository[password]';",
170 :onchange => "this.name='repository[password]';"))
170 :onchange => "this.name='repository[password]';"))
171 end
171 end
172
172
173 def darcs_field_tags(form, repository)
173 def darcs_field_tags(form, repository)
174 content_tag('p', form.text_field(
174 content_tag('p', form.text_field(
175 :url, :label => l(:field_path_to_repository),
175 :url, :label => l(:field_path_to_repository),
176 :size => 60, :required => true,
176 :size => 60, :required => true,
177 :disabled => (repository && !repository.new_record?))) +
177 :disabled => (repository && !repository.new_record?))) +
178 content_tag('p', form.select(
178 content_tag('p', form.select(
179 :log_encoding, [nil] + Setting::ENCODINGS,
179 :log_encoding, [nil] + Setting::ENCODINGS,
180 :label => l(:field_commit_logs_encoding), :required => true))
180 :label => l(:field_commit_logs_encoding), :required => true))
181 end
181 end
182
182
183 def mercurial_field_tags(form, repository)
183 def mercurial_field_tags(form, repository)
184 content_tag('p', form.text_field(
184 content_tag('p', form.text_field(
185 :url, :label => l(:field_path_to_repository),
185 :url, :label => l(:field_path_to_repository),
186 :size => 60, :required => true,
186 :size => 60, :required => true,
187 :disabled => (repository && !repository.root_url.blank?)
187 :disabled => (repository && !repository.root_url.blank?)
188 ) +
188 ) +
189 '<br />'.html_safe + l(:text_mercurial_repository_note)) +
189 '<br />'.html_safe + l(:text_mercurial_repository_note)) +
190 content_tag('p', form.select(
190 content_tag('p', form.select(
191 :path_encoding, [nil] + Setting::ENCODINGS,
191 :path_encoding, [nil] + Setting::ENCODINGS,
192 :label => l(:field_scm_path_encoding)
192 :label => l(:field_scm_path_encoding)
193 ) +
193 ) +
194 '<br />'.html_safe + l(:text_scm_path_encoding_note))
194 '<br />'.html_safe + l(:text_scm_path_encoding_note))
195 end
195 end
196
196
197 def git_field_tags(form, repository)
197 def git_field_tags(form, repository)
198 content_tag('p', form.text_field(
198 content_tag('p', form.text_field(
199 :url, :label => l(:field_path_to_repository),
199 :url, :label => l(:field_path_to_repository),
200 :size => 60, :required => true,
200 :size => 60, :required => true,
201 :disabled => (repository && !repository.root_url.blank?)
201 :disabled => (repository && !repository.root_url.blank?)
202 ) +
202 ) +
203 '<br />'.html_safe +
203 '<br />'.html_safe +
204 l(:text_git_repository_note)) +
204 l(:text_git_repository_note)) +
205 content_tag('p', form.select(
205 content_tag('p', form.select(
206 :path_encoding, [nil] + Setting::ENCODINGS,
206 :path_encoding, [nil] + Setting::ENCODINGS,
207 :label => l(:field_scm_path_encoding)
207 :label => l(:field_scm_path_encoding)
208 ) +
208 ) +
209 '<br />'.html_safe + l(:text_scm_path_encoding_note)) +
209 '<br />'.html_safe + l(:text_scm_path_encoding_note)) +
210 content_tag('p', form.check_box(
210 content_tag('p', form.check_box(
211 :extra_report_last_commit,
211 :extra_report_last_commit,
212 :label => l(:label_git_report_last_commit)
212 :label => l(:label_git_report_last_commit)
213 ))
213 ))
214 end
214 end
215
215
216 def cvs_field_tags(form, repository)
216 def cvs_field_tags(form, repository)
217 content_tag('p', form.text_field(
217 content_tag('p', form.text_field(
218 :root_url,
218 :root_url,
219 :label => l(:field_cvsroot),
219 :label => l(:field_cvsroot),
220 :size => 60, :required => true,
220 :size => 60, :required => true,
221 :disabled => !repository.new_record?)) +
221 :disabled => !repository.new_record?)) +
222 content_tag('p', form.text_field(
222 content_tag('p', form.text_field(
223 :url,
223 :url,
224 :label => l(:field_cvs_module),
224 :label => l(:field_cvs_module),
225 :size => 30, :required => true,
225 :size => 30, :required => true,
226 :disabled => !repository.new_record?)) +
226 :disabled => !repository.new_record?)) +
227 content_tag('p', form.select(
227 content_tag('p', form.select(
228 :log_encoding, [nil] + Setting::ENCODINGS,
228 :log_encoding, [nil] + Setting::ENCODINGS,
229 :label => l(:field_commit_logs_encoding), :required => true)) +
229 :label => l(:field_commit_logs_encoding), :required => true)) +
230 content_tag('p', form.select(
230 content_tag('p', form.select(
231 :path_encoding, [nil] + Setting::ENCODINGS,
231 :path_encoding, [nil] + Setting::ENCODINGS,
232 :label => l(:field_scm_path_encoding)
232 :label => l(:field_scm_path_encoding)
233 ) +
233 ) +
234 '<br />'.html_safe + l(:text_scm_path_encoding_note))
234 '<br />'.html_safe + l(:text_scm_path_encoding_note))
235 end
235 end
236
236
237 def bazaar_field_tags(form, repository)
237 def bazaar_field_tags(form, repository)
238 content_tag('p', form.text_field(
238 content_tag('p', form.text_field(
239 :url, :label => l(:field_path_to_repository),
239 :url, :label => l(:field_path_to_repository),
240 :size => 60, :required => true,
240 :size => 60, :required => true,
241 :disabled => (repository && !repository.new_record?))) +
241 :disabled => (repository && !repository.new_record?))) +
242 content_tag('p', form.select(
242 content_tag('p', form.select(
243 :log_encoding, [nil] + Setting::ENCODINGS,
243 :log_encoding, [nil] + Setting::ENCODINGS,
244 :label => l(:field_commit_logs_encoding), :required => true))
244 :label => l(:field_commit_logs_encoding), :required => true))
245 end
245 end
246
246
247 def filesystem_field_tags(form, repository)
247 def filesystem_field_tags(form, repository)
248 content_tag('p', form.text_field(
248 content_tag('p', form.text_field(
249 :url, :label => l(:field_root_directory),
249 :url, :label => l(:field_root_directory),
250 :size => 60, :required => true,
250 :size => 60, :required => true,
251 :disabled => (repository && !repository.root_url.blank?))) +
251 :disabled => (repository && !repository.root_url.blank?))) +
252 content_tag('p', form.select(
252 content_tag('p', form.select(
253 :path_encoding, [nil] + Setting::ENCODINGS,
253 :path_encoding, [nil] + Setting::ENCODINGS,
254 :label => l(:field_scm_path_encoding)
254 :label => l(:field_scm_path_encoding)
255 ) +
255 ) +
256 '<br />'.html_safe + l(:text_scm_path_encoding_note))
256 '<br />'.html_safe + l(:text_scm_path_encoding_note))
257 end
257 end
258
258
259 def index_commits(commits, heads, href_proc = nil)
259 def index_commits(commits, heads)
260 return nil if commits.nil? or commits.first.parents.nil?
260 return nil if commits.nil? or commits.first.parents.nil?
261 map = {}
261
262 commit_hashes = []
263 refs_map = {}
262 refs_map = {}
264 href_proc ||= Proc.new {|x|x}
263 heads.each do |head|
265 heads.each{|r| refs_map[r.scmid] ||= []; refs_map[r.scmid] << r}
264 refs_map[head.scmid] ||= []
266 commits.reverse.each_with_index do |c, i|
265 refs_map[head.scmid] << head
267 h = {}
266 end
268 h[:parents] = c.parents.collect do |p|
267
269 [p.scmid, 0, 0]
268 commits_by_scmid = {}
270 end
269 commits.reverse.each_with_index do |commit, commit_index|
271 h[:rdmid] = i
270
272 h[:space] = 0
271 commits_by_scmid[commit.scmid] = {
273 h[:refs] = refs_map[c.scmid].join(" ") if refs_map.include? c.scmid
272 :parent_scmids => commit.parents.collect { |parent| parent.scmid },
274 h[:scmid] = c.scmid
273 :rdmid => commit_index,
275 h[:href] = href_proc.call(c.scmid)
274 :space => 0,
276 commit_hashes << h
275 :refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
277 map[c.scmid] = h
276 :scmid => commit.scmid,
278 end
277 :href => block_given? ? yield(commit.scmid) : commit.scmid
279 heads.sort! do |a,b|
278 }
280 a.to_s <=> b.to_s
279 end
281 end
280
282 j = 0
281 heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
283 heads.each do |h|
282
284 if map.include? h.scmid then
283 mark_index = 0
285 j = mark_chain(j += 1, map[h.scmid], map)
284 heads.each do |head|
285 if commits_by_scmid.include? head.scmid
286 mark_index = mark_chain(mark_index += 1, commits_by_scmid[head.scmid], commits_by_scmid)
286 end
287 end
287 end
288 end
288 # when no head matched anything use first commit
289 # when no head matched anything use first commit
289 if j == 0 then
290 if mark_index == 0
290 mark_chain(j += 1, map.values.first, map)
291 mark_chain(mark_index += 1, commits_by_scmid.values.first, commits_by_scmid)
291 end
292 end
292 map
293 commits_by_scmid
293 end
294 end
294
295
295 def mark_chain(mark, commit, map)
296 def mark_chain(mark_index, commit, commits_by_scmid)
296 stack = [[mark, commit]]
297
297 markmax = mark
298 stack = [[mark_index, commit]]
299 mark_max_index = mark_index
300
298 until stack.empty?
301 until stack.empty?
299 current = stack.pop
302 mark_index, commit = stack.pop
300 m, commit = current
303 commit[:space] = mark_index if commit[:space] == 0
301 commit[:space] = m if commit[:space] == 0
304
302 m1 = m - 1
305 mark_index -=1
303 commit[:parents].each_with_index do |p, i|
306 commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
304 psha = p[0]
307
305 if map.include? psha and map[psha][:space] == 0 then
308 parent_commit = commits_by_scmid[parent_scmid]
306 stack << [m1 += 1, map[psha]] if i == 0
309
307 stack = [[m1 += 1, map[psha]]] + stack if i > 0
310 if parent_commit and parent_commit[:space] == 0
311
312 stack.unshift [mark_index += 1, parent_commit]
308 end
313 end
309 end
314 end
310 markmax = m1 if markmax < m1
315 mark_max_index = mark_index if mark_max_index < mark_index
311 end
316 end
312 markmax
317 mark_max_index
313 end
318 end
314 end
319 end
@@ -1,13 +1,12
1 <%= javascript_include_tag "raphael.js" %>
1 <%= javascript_include_tag 'raphael.js' %>
2 <script type="text/javascript" charset="utf-8">
2 <%= javascript_include_tag 'revision_graph.js' %>
3 var chunk = {commits:<%= commits.values.to_json.html_safe %>}
4 </script>
5 <%= javascript_include_tag "revision_graph.js" %>
6
3
7 <script type="text/javascript">
4 <script type="text/javascript" charset="utf-8">
8 Event.observe(window,"load", function(){
5 Event.observe(window, 'load', function(){
9 branchGraph(document.getElementById("holder"));
6 branchGraph(
10 })
7 document.getElementById("holder"),
8 <%= commits.to_json.html_safe %>);
9 });
11 </script>
10 </script>
12
11
13 <div id="holder" class="graph"></div>
12 <div id="holder" class="graph"></div>
@@ -1,61 +1,61
1 <% show_revision_graph = ( @repository.supports_revision_graph? && path.blank? ) %>
1 <% show_revision_graph = ( @repository.supports_revision_graph? && path.blank? ) %>
2 <% form_tag(
2 <% form_tag(
3 {:controller => 'repositories', :action => 'diff', :id => @project,
3 {:controller => 'repositories', :action => 'diff', :id => @project,
4 :repository_id => @repository.identifier_param, :path => to_path_param(path)},
4 :repository_id => @repository.identifier_param, :path => to_path_param(path)},
5 :method => :get
5 :method => :get
6 ) do %>
6 ) do %>
7 <table class="list changesets">
7 <table class="list changesets">
8 <thead><tr>
8 <thead><tr>
9 <% if show_revision_graph %>
9 <% if show_revision_graph %>
10 <th></th>
10 <th></th>
11 <% end %>
11 <% end %>
12 <th>#</th>
12 <th>#</th>
13 <th></th>
13 <th></th>
14 <th></th>
14 <th></th>
15 <th><%= l(:label_date) %></th>
15 <th><%= l(:label_date) %></th>
16 <th><%= l(:field_author) %></th>
16 <th><%= l(:field_author) %></th>
17 <th><%= l(:field_comments) %></th>
17 <th><%= l(:field_comments) %></th>
18 </tr></thead>
18 </tr></thead>
19 <tbody>
19 <tbody>
20 <% show_diff = revisions.size > 1 %>
20 <% show_diff = revisions.size > 1 %>
21 <% line_num = 1 %>
21 <% line_num = 1 %>
22 <% revisions.each do |changeset| %>
22 <% revisions.each do |changeset| %>
23 <tr class="changeset <%= cycle 'odd', 'even' %>">
23 <tr class="changeset <%= cycle 'odd', 'even' %>">
24 <% if show_revision_graph %>
24 <% if show_revision_graph %>
25 <% if line_num == 1 %>
25 <% if line_num == 1 %>
26 <td class="revision_graph" rowspan="<%= revisions.size %>">
26 <td class="revision_graph" rowspan="<%= revisions.size %>">
27 <% href_base = Proc.new {|x| url_for(:controller => 'repositories',
28 :action => 'revision',
29 :id => project,
30 :repository_id => @repository.identifier_param,
31 :rev => x) } %>
32 <%= render :partial => 'revision_graph',
27 <%= render :partial => 'revision_graph',
33 :locals => {
28 :locals => {
34 :commits => index_commits(
29 :commits => index_commits(
35 revisions,
30 revisions,
36 @repository.branches,
31 @repository.branches) do |scmid|
37 href_base
32 url_for(
38 )
33 :controller => 'repositories',
34 :action => 'revision',
35 :id => project,
36 :repository_id => @repository.identifier_param,
37 :rev => scmid)
38 end
39 } %>
39 } %>
40 </td>
40 </td>
41 <% end %>
41 <% end %>
42 <% end %>
42 <% end %>
43 <td class="id"><%= link_to_revision(changeset, @repository) %></td>
43 <td class="id"><%= link_to_revision(changeset, @repository) %></td>
44 <td class="checkbox"><%= radio_button_tag('rev', changeset.identifier, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td>
44 <td class="checkbox"><%= radio_button_tag('rev', changeset.identifier, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td>
45 <td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
45 <td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
46 <td class="committed_on"><%= format_time(changeset.committed_on) %></td>
46 <td class="committed_on"><%= format_time(changeset.committed_on) %></td>
47 <td class="author"><%= h truncate(changeset.author.to_s, :length => 30) %></td>
47 <td class="author"><%= h truncate(changeset.author.to_s, :length => 30) %></td>
48 <% if show_revision_graph %>
48 <% if show_revision_graph %>
49 <td class="comments_nowrap">
49 <td class="comments_nowrap">
50 <%= textilizable(truncate(truncate_at_line_break(changeset.comments, 0), :length => 90)) %>
50 <%= textilizable(truncate(truncate_at_line_break(changeset.comments, 0), :length => 90)) %>
51 </td>
51 </td>
52 <% else %>
52 <% else %>
53 <td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
53 <td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
54 <% end %>
54 <% end %>
55 </tr>
55 </tr>
56 <% line_num += 1 %>
56 <% line_num += 1 %>
57 <% end %>
57 <% end %>
58 </tbody>
58 </tbody>
59 </table>
59 </table>
60 <%= submit_tag(l(:label_view_diff), :name => nil) if show_diff %>
60 <%= submit_tag(l(:label_view_diff), :name => nil) if show_diff %>
61 <% end %>
61 <% end %>
@@ -1,172 +1,99
1 var commits = chunk.commits,
2 comms = {},
3 pixelsX = [],
4 pixelsY = [],
5 mmax = Math.max,
6 max_rdmid = 0,
7 max_space = 0,
8 parents = {};
9 for (var i = 0, ii = commits.length; i < ii; i++) {
10 for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
11 parents[commits[i].parents[j][0]] = true;
12 }
13 max_rdmid = Math.max(max_rdmid, commits[i].rdmid);
14 max_space = Math.max(max_space, commits[i].space);
15 }
16
1
17 for (i = 0; i < ii; i++) {
2 function branchGraph(holder, commits_hash) {
18 if (commits[i].scmid in parents) {
3
19 commits[i].isParent = true;
4 var LEFT_PADDING = 3,
20 }
5 TOP_PADDING = 10,
21 comms[commits[i].scmid] = commits[i];
6 XSTEP = YSTEP = 20;
22 }
7
23 var colors = ["#000"];
8 var commits_by_scmid = $H(commits_hash),
9 commits = commits_by_scmid.values();
10
11 // init max dimensions
12 var max_rdmid = max_space = 0;
13 commits.each(function(commit) {
14
15 max_rdmid = Math.max(max_rdmid, commit.rdmid);
16 max_space = Math.max(max_space, commit.space);
17 });
18
19 var graph_height = max_rdmid * YSTEP + YSTEP,
20 graph_width = max_space * XSTEP + XSTEP;
21
22 // init colors
23 var colors = ['#000'];
24 for (var k = 0; k < max_space; k++) {
24 for (var k = 0; k < max_space; k++) {
25 colors.push(Raphael.getColor());
25 colors.push(Raphael.getColor());
26 }
26 }
27
27
28 function branchGraph(holder) {
28 // create graph
29 var xstep = 20, ystep = 20;
29 var graph = Raphael(holder, graph_width, graph_height),
30 var ch, cw;
30 top = graph.set();
31 cw = max_space * xstep + xstep;
31
32 ch = max_rdmid * ystep + ystep;
32 var parent_commit;
33 var r = Raphael("holder", cw, ch),
33 var x, y, parent_x, parent_y;
34 top = r.set();
34 var path, longrefs, shortrefs, label, labelBBox;
35 var cuday = 0, cumonth = "";
35
36
36 commits.each(function(commit) {
37 for (i = 0; i < ii; i++) {
37
38 var x, y;
38 y = TOP_PADDING + YSTEP *(max_rdmid - commit.rdmid);
39 y = 10 + ystep *(max_rdmid - commits[i].rdmid);
39 x = LEFT_PADDING + XSTEP * commit.space;
40 x = 3 + xstep * commits[i].space;
40
41 var stroke = "none";
41 graph.circle(x, y, 3).attr({fill: colors[commit.space], stroke: 'none'});
42 r.circle(x, y, 3).attr({fill: colors[commits[i].space], stroke: stroke});
42
43 if (commits[i].refs != null && commits[i].refs != "") {
43 // title
44 var longrefs = commits[i].refs
44 if (commit.refs != null && commit.refs != '') {
45 var shortrefs = commits[i].refs;
45 longrefs = commit.refs;
46 if (shortrefs.length > 15) {
46 shortrefs = longrefs.length > 15 ? longrefs.substr(0, 13) + '...' : longrefs;
47 shortrefs = shortrefs.substr(0,13) + "...";
47
48 }
48 label = graph.text(x + 5, y + 5, shortrefs)
49 var t = r.text(x+5,y+5,shortrefs).attr({font: "12px Fontin-Sans, Arial", fill: "#666",
49 .attr({
50 title: longrefs, cursor: "pointer", rotation: "0"});
50 font: '12px Fontin-Sans, Arial',
51 fill: '#666',
52 title: longrefs,
53 cursor: 'pointer',
54 rotation: '0'});
51
55
52 var textbox = t.getBBox();
56 labelBBox = label.getBBox();
53 t.translate(textbox.width / 2, textbox.height / -3);
57 label.translate(labelBBox.width / 2, -labelBBox.height / 3);
54 }
58 }
55 for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
59
56 var c = comms[commits[i].parents[j][0]];
60 // paths to parents
57 var p,arrow;
61 commit.parent_scmids.each(function(parent_scmid) {
58 if (c) {
62 parent_commit = commits_by_scmid.get(parent_scmid);
59 var cy, cx;
63
60 cy = 10 + ystep * (max_rdmid - c.rdmid),
64 if (parent_commit) {
61 cx = 3 + xstep * c.space;
65 parent_y = TOP_PADDING + YSTEP * (max_rdmid - parent_commit.rdmid);
62
66 parent_x = LEFT_PADDING + XSTEP * parent_commit.space;
63 if (c.space == commits[i].space) {
67
64 p = r.path("M" + x + "," + y + "L" + cx + "," + cy);
68 if (parent_commit.space == commit.space) {
69 // vertical path
70 path = graph.path([
71 'M', x, y,
72 'V', parent_y]);
65 } else {
73 } else {
66 p = r.path(["M", x, y, "C",x,y,x, y+(cy-y)/2,x+(cx-x)/2, y+(cy-y)/2,
74 // path to a commit in a different branch (Bezier curve)
67 "C", x+(cx-x)/2,y+(cy-y)/2, cx, cy-(cy-y)/2, cx, cy]);
75 path = graph.path([
76 'M', x, y,
77 'C', x, y, x, y + (parent_y - y) / 2, x + (parent_x - x) / 2, y + (parent_y - y) / 2,
78 'C', x + (parent_x - x) / 2, y + (parent_y - y) / 2, parent_x, parent_y-(parent_y-y)/2, parent_x, parent_y]);
68 }
79 }
69 } else {
80 } else {
70 p = r.path("M" + x + "," + y + "L" + x + "," + ch);
81 // vertical path ending at the bottom of the graph
71 }
82 path = graph.path([
72 p.attr({stroke: colors[commits[i].space], "stroke-width": 1.5});
83 'M', x, y,
84 'V', graph_height]);
73 }
85 }
74 (function (c, x, y) {
86 path.attr({stroke: colors[commit.space], "stroke-width": 1.5});
75 top.push(r.circle(x, y, 10).attr({fill: "#000", opacity: 0,
87 });
76 cursor: "pointer", href: commits[i].href})
77 .hover(function () {}, function () {})
78 );
79 }(commits[i], x, y));
80 }
81 top.toFront();
82 var hw = holder.offsetWidth,
83 hh = holder.offsetHeight,
84 drag,
85 dragger = function (e) {
86 if (drag) {
87 e = e || window.event;
88 holder.scrollLeft = drag.sl - (e.clientX - drag.x);
89 holder.scrollTop = drag.st - (e.clientY - drag.y);
90 }
91 };
92 holder.onmousedown = function (e) {
93 e = e || window.event;
94 drag = {x: e.clientX, y: e.clientY, st: holder.scrollTop, sl: holder.scrollLeft};
95 document.onmousemove = dragger;
96 };
97 document.onmouseup = function () {
98 drag = false;
99 document.onmousemove = null;
100 };
101 holder.scrollLeft = cw;
102 };
103
88
104 Raphael.fn.popupit = function (x, y, set, dir, size) {
89 top.push(graph.circle(x, y, 10)
105 dir = dir == null ? 2 : dir;
90 .attr({
106 size = size || 5;
91 fill: '#000',
107 x = Math.round(x);
92 opacity: 0,
108 y = Math.round(y);
93 cursor: 'pointer',
109 var bb = set.getBBox(),
94 href: commit.href})
110 w = Math.round(bb.width / 2),
95 .hover(function () {}, function () {}));
111 h = Math.round(bb.height / 2),
96 });
112 dx = [0, w + size * 2, 0, -w - size * 2],
113 dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
114 p = ["M", x - dx[dir], y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0),
115 0, "a", size, size, 0, 0, 1, -size, -size,
116 "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0,
117 -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
118 "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0),
119 0, "a", size, size, 0, 0, 1, size, size,
120 "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0,
121 mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
122 "l", -mmax(w - size, 0), 0, "z"].join(","),
123 xy = [{x: x, y: y + size * 2 + h},
124 {x: x - size * 2 - w, y: y},
125 {x: x, y: y - size * 2 - h},
126 {x: x + size * 2 + w, y: y}]
127 [dir];
128 set.translate(xy.x - w - bb.x, xy.y - h - bb.y);
129 return this.set(this.path(p).attr({fill: "#234", stroke: "none"})
130 .insertBefore(set.node ? set : set[0]), set);
131 };
132
97
133 Raphael.fn.popup = function (x, y, text, dir, size) {
98 top.toFront();
134 dir = dir == null ? 2 : dir > 3 ? 3 : dir;
135 size = size || 5;
136 text = text || "$9.99";
137 var res = this.set(),
138 d = 3;
139 res.push(this.path().attr({fill: "#000", stroke: "#000"}));
140 res.push(this.text(x, y, text).attr(this.g.txtattr).attr({fill: "#fff", "font-family": "Helvetica, Arial"}));
141 res.update = function (X, Y, withAnimation) {
142 X = X || x;
143 Y = Y || y;
144 var bb = this[1].getBBox(),
145 w = bb.width / 2,
146 h = bb.height / 2,
147 dx = [0, w + size * 2, 0, -w - size * 2],
148 dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
149 p = ["M", X - dx[dir], Y - dy[dir], "l", -size, (dir == 2) * -size,
150 -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size,
151 "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size,
152 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
153 "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0),
154 0, "a", size, size, 0, 0, 1, size, size,
155 "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0,
156 mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
157 "l", -mmax(w - size, 0), 0, "z"].join(","),
158 xy = [{x: X, y: Y + size * 2 + h},
159 {x: X - size * 2 - w, y: Y},
160 {x: X, y: Y - size * 2 - h},
161 {x: X + size * 2 + w, y: Y}]
162 [dir];
163 xy.path = p;
164 if (withAnimation) {
165 this.animate(xy, 500, ">");
166 } else {
167 this.attr(xy);
168 }
169 return this;
170 };
171 return res.update(x, y);
172 };
99 };
General Comments 0
You need to be logged in to leave comments. Login now