##// END OF EJS Templates
Added Annotate/Blame view for Subversion, CVS and Mercurial repositories....
Jean-Philippe Lang -
r934:8c65cc47122b
parent child
Show More
@@ -0,0 +1,26
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
2
3 <% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
4
5 <div class="autoscroll">
6 <table class="filecontent annotate CodeRay">
7 <tbody>
8 <% line_num = 1 %>
9 <% syntax_highlight(@path, to_utf8(@annotate.content)).each_line do |line| %>
10 <% revision = @annotate.revisions[line_num-1] %>
11 <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
12 <th class="line-num"><%= line_num %></th>
13 <td class="revision">
14 <%= (revision.identifier ? link_to(revision.identifier, :action => 'revision', :id => @project, :rev => revision.identifier) : revision.revision) if revision %></td>
15 <td class="author"><%= h(revision.author) if revision %></td>
16 <td class="line-code"><pre><%= line %></pre></td>
17 </tr>
18 <% line_num += 1 %>
19 <% end %>
20 <tbody>
21 </table>
22 </div>
23
24 <% content_for :header_tags do %>
25 <%= stylesheet_link_tag 'scm' %>
26 <% end %>
@@ -95,6 +95,11 class RepositoriesController < ApplicationController
95 end
95 end
96 end
96 end
97
97
98 def annotate
99 @annotate = @repository.scm.annotate(@path, @rev)
100 show_error and return if @annotate.nil? || @annotate.empty?
101 end
102
98 def revision
103 def revision
99 @changeset = @repository.changesets.find_by_revision(@rev)
104 @changeset = @repository.changesets.find_by_revision(@rev)
100 raise ChangesetNotFound unless @changeset
105 raise ChangesetNotFound unless @changeset
@@ -33,6 +33,10 class Repository < ActiveRecord::Base
33 def supports_cat?
33 def supports_cat?
34 scm.supports_cat?
34 scm.supports_cat?
35 end
35 end
36
37 def supports_annotate?
38 scm.supports_annotate?
39 end
36
40
37 def entries(path=nil, identifier=nil)
41 def entries(path=nil, identifier=nil)
38 scm.entries(path, identifier)
42 scm.entries(path, identifier)
@@ -2,14 +2,17
2
2
3 <h3><%=h @entry.name %></h3>
3 <h3><%=h @entry.name %></h3>
4
4
5 <% if @repository.supports_cat? %>
6 <p>
5 <p>
7 <% if @entry.is_text? %>
6 <% if @entry.is_text? %>
8 <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> |
7 <% if @repository.supports_cat? %>
8 <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> |
9 <% end %>
10 <% if @repository.supports_annotate? %>
11 <%= link_to l(:button_annotate), {:action => 'annotate', :id => @project, :path => @path, :rev => @rev } %> |
12 <% end %>
9 <% end %>
13 <% end %>
10 <%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' } %>
14 <%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %>
11 <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
15 <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
12 </p>
16 </p>
13 <% end %>
14
17
15 <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }%>
18 <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }%>
@@ -2,11 +2,6
2
2
3 <div class="autoscroll">
3 <div class="autoscroll">
4 <table class="filecontent CodeRay">
4 <table class="filecontent CodeRay">
5 <thead>
6 <tr>
7 <th colspan="2" class="filename"><%= @path %></th>
8 </tr>
9 </thead>
10 <tbody>
5 <tbody>
11 <% line_num = 1 %>
6 <% line_num = 1 %>
12 <% syntax_highlight(@path, to_utf8(@content)).each_line do |line| %>
7 <% syntax_highlight(@path, to_utf8(@content)).each_line do |line| %>
@@ -24,6 +24,7 ActionController::Routing::Routes.draw do |map|
24 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
24 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
25 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
25 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
26 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
26 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
27 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
27 end
28 end
28
29
29 # Allow downloading Web Service WSDL as a file with an extension
30 # Allow downloading Web Service WSDL as a file with an extension
@@ -547,3 +547,4 notice_account_pending: "Your account was created and is now pending administrat
547 field_time_zone: Time zone
547 field_time_zone: Time zone
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -547,3 +547,4 notice_account_pending: "Your account was created and is now pending administrat
547 field_time_zone: Time zone
547 field_time_zone: Time zone
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -547,3 +547,4 notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Geneh
547 field_time_zone: Zeitzone
547 field_time_zone: Zeitzone
548 text_caracters_minimum: Muss mindestens %d Zeichen lang sein.
548 text_caracters_minimum: Muss mindestens %d Zeichen lang sein.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -489,6 +489,7 button_reset: Reset
489 button_rename: Rename
489 button_rename: Rename
490 button_change_password: Change password
490 button_change_password: Change password
491 button_copy: Copy
491 button_copy: Copy
492 button_annotate: Annotate
492
493
493 status_active: active
494 status_active: active
494 status_registered: registered
495 status_registered: registered
@@ -550,3 +550,4 label_registration_manual_activation: activaciΓ³n manual de cuenta
550 notice_account_pending: "Su cuenta ha sido creada y estΓ‘ pendiende de la aprobaciΓ³n por parte de administrador"
550 notice_account_pending: "Su cuenta ha sido creada y estΓ‘ pendiende de la aprobaciΓ³n por parte de administrador"
551 setting_time_format: Formato de hora
551 setting_time_format: Formato de hora
552 setting_bcc_recipients: Blind carbon copy recipients (bcc)
552 setting_bcc_recipients: Blind carbon copy recipients (bcc)
553 button_annotate: Annotate
@@ -489,6 +489,7 button_reset: RΓ©initialiser
489 button_rename: Renommer
489 button_rename: Renommer
490 button_change_password: Changer de mot de passe
490 button_change_password: Changer de mot de passe
491 button_copy: Copier
491 button_copy: Copier
492 button_annotate: Annoter
492
493
493 status_active: actif
494 status_active: actif
494 status_registered: enregistrΓ©
495 status_registered: enregistrΓ©
@@ -547,3 +547,4 notice_account_pending: "Your account was created and is now pending administrat
547 field_time_zone: Time zone
547 field_time_zone: Time zone
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -547,3 +547,4 notice_account_pending: "Your account was created and is now pending administrat
547 field_time_zone: Time zone
547 field_time_zone: Time zone
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -548,3 +548,4 notice_account_pending: "Your account was created and is now pending administrat
548 field_time_zone: Time zone
548 field_time_zone: Time zone
549 text_caracters_minimum: Must be at least %d characters long.
549 text_caracters_minimum: Must be at least %d characters long.
550 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 setting_bcc_recipients: Blind carbon copy recipients (bcc)
551 button_annotate: Annotate
@@ -547,3 +547,4 notice_account_pending: "Your account was created and is now pending administrat
547 field_time_zone: Time zone
547 field_time_zone: Time zone
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -548,3 +548,4 notice_account_pending: "Your account was created and is now pending administrat
548 field_time_zone: Time zone
548 field_time_zone: Time zone
549 text_caracters_minimum: Must be at least %d characters long.
549 text_caracters_minimum: Must be at least %d characters long.
550 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 setting_bcc_recipients: Blind carbon copy recipients (bcc)
551 button_annotate: Annotate
@@ -547,3 +547,4 notice_account_pending: "Twoje konto zostaΕ‚o utworzone i oczekuje na zatwierdze
547 field_time_zone: Strefa czasowa
547 field_time_zone: Strefa czasowa
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -547,3 +547,4 notice_account_pending: "Your account was created and is now pending administrat
547 field_time_zone: Time zone
547 field_time_zone: Time zone
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -547,3 +547,4 notice_account_pending: "Your account was created and is now pending administrat
547 field_time_zone: Time zone
547 field_time_zone: Time zone
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -547,3 +547,4 notice_account_pending: "Your account was created and is now pending administrat
547 field_time_zone: Time zone
547 field_time_zone: Time zone
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -547,3 +547,4 notice_account_pending: "Π’Π°Ρˆ Π°ΠΊΠΊΠ°ΡƒΠ½Ρ‚ ΡƒΠΆΠ΅ создан ΠΈ ΠΎΠΆΠΈΠ΄Π°
547 field_time_zone: Часовой пояс
547 field_time_zone: Часовой пояс
548 text_caracters_minimum: Must be at least %d characters long.
548 text_caracters_minimum: Must be at least %d characters long.
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
549 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 button_annotate: Annotate
@@ -548,3 +548,4 notice_account_pending: "Your account was created and is now pending administrat
548 field_time_zone: Time zone
548 field_time_zone: Time zone
549 text_caracters_minimum: Must be at least %d characters long.
549 text_caracters_minimum: Must be at least %d characters long.
550 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 setting_bcc_recipients: Blind carbon copy recipients (bcc)
551 button_annotate: Annotate
@@ -548,3 +548,4 notice_account_pending: "Your account was created and is now pending administrat
548 field_time_zone: Time zone
548 field_time_zone: Time zone
549 text_caracters_minimum: Must be at least %d characters long.
549 text_caracters_minimum: Must be at least %d characters long.
550 setting_bcc_recipients: Blind carbon copy recipients (bcc)
550 setting_bcc_recipients: Blind carbon copy recipients (bcc)
551 button_annotate: Annotate
@@ -550,3 +550,4 notice_account_pending: "Your account was created and is now pending administrat
550 field_time_zone: Time zone
550 field_time_zone: Time zone
551 text_caracters_minimum: Must be at least %d characters long.
551 text_caracters_minimum: Must be at least %d characters long.
552 setting_bcc_recipients: Blind carbon copy recipients (bcc)
552 setting_bcc_recipients: Blind carbon copy recipients (bcc)
553 button_annotate: Annotate
@@ -76,7 +76,7 Redmine::AccessControl.map do |map|
76
76
77 map.project_module :repository do |map|
77 map.project_module :repository do |map|
78 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
78 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :changes, :diff, :stats, :graph]
79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
81 end
81 end
82
82
@@ -38,6 +38,10 module Redmine
38 def supports_cat?
38 def supports_cat?
39 true
39 true
40 end
40 end
41
42 def supports_annotate?
43 respond_to?('annotate')
44 end
41
45
42 def root_url
46 def root_url
43 @root_url
47 @root_url
@@ -76,7 +80,7 module Redmine
76 def cat(path, identifier=nil)
80 def cat(path, identifier=nil)
77 return nil
81 return nil
78 end
82 end
79
83
80 def with_leading_slash(path)
84 def with_leading_slash(path)
81 path ||= ''
85 path ||= ''
82 (path[0,1]!="/") ? "/#{path}" : path
86 (path[0,1]!="/") ? "/#{path}" : path
@@ -237,7 +241,7 module Redmine
237
241
238 # Initialize with a Diff file and the type of Diff View
242 # Initialize with a Diff file and the type of Diff View
239 # The type view must be inline or sbs (side_by_side)
243 # The type view must be inline or sbs (side_by_side)
240 def initialize (type="inline")
244 def initialize(type="inline")
241 @parsing = false
245 @parsing = false
242 @nb_line = 1
246 @nb_line = 1
243 @start = false
247 @start = false
@@ -312,7 +316,7 module Redmine
312 CGI.escapeHTML(line)
316 CGI.escapeHTML(line)
313 end
317 end
314
318
315 def parse_line (line, type="inline")
319 def parse_line(line, type="inline")
316 if line[0, 1] == "+"
320 if line[0, 1] == "+"
317 diff = sbs? type, 'add'
321 diff = sbs? type, 'add'
318 @before = 'add'
322 @before = 'add'
@@ -348,6 +352,28 module Redmine
348 end
352 end
349 end
353 end
350 end
354 end
355
356 class Annotate
357 attr_reader :lines, :revisions
358
359 def initialize
360 @lines = []
361 @revisions = []
362 end
363
364 def add_line(line, revision)
365 @lines << line
366 @revisions << revision
367 end
368
369 def content
370 content = lines.join("\n")
371 end
372
373 def empty?
374 lines.empty?
375 end
376 end
351 end
377 end
352 end
378 end
353 end
379 end
@@ -268,7 +268,25 module Redmine
268 rescue Errno::ENOENT => e
268 rescue Errno::ENOENT => e
269 raise CommandFailed
269 raise CommandFailed
270 end
270 end
271
271
272 def annotate(path, identifier=nil)
273 identifier = (identifier) ? identifier : "HEAD"
274 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
275 path_with_project="#{url}#{with_leading_slash(path)}"
276 cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{path_with_project}"
277 blame = Annotate.new
278 shellout(cmd) do |io|
279 io.each_line do |line|
280 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
281 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
282 end
283 end
284 return nil if $? && $?.exitstatus != 0
285 blame
286 rescue Errno::ENOENT => e
287 raise CommandFailed
288 end
289
272 private
290 private
273
291
274 # convert a date/time into the CVS-format
292 # convert a date/time into the CVS-format
@@ -157,6 +157,25 module Redmine
157 rescue Errno::ENOENT => e
157 rescue Errno::ENOENT => e
158 raise CommandFailed
158 raise CommandFailed
159 end
159 end
160
161 def annotate(path, identifier=nil)
162 path ||= ''
163 cmd = "#{HG_BIN} -R #{target('')}"
164 cmd << " annotate -n -u"
165 cmd << " -r #{identifier.to_i}" if identifier
166 cmd << " #{target(path)}"
167 blame = Annotate.new
168 shellout(cmd) do |io|
169 io.each_line do |line|
170 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
171 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
172 end
173 end
174 return nil if $? && $?.exitstatus != 0
175 blame
176 rescue Errno::ENOENT => e
177 raise CommandFailed
178 end
160 end
179 end
161 end
180 end
162 end
181 end
@@ -173,6 +173,23 module Redmine
173 raise CommandFailed
173 raise CommandFailed
174 end
174 end
175
175
176 def annotate(path, identifier=nil)
177 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
178 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
179 cmd << credentials_string
180 blame = Annotate.new
181 shellout(cmd) do |io|
182 io.each_line do |line|
183 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
184 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
185 end
186 end
187 return nil if $? && $?.exitstatus != 0
188 blame
189 rescue Errno::ENOENT => e
190 raise CommandFailed
191 end
192
176 private
193 private
177
194
178 def credentials_string
195 def credentials_string
@@ -2,20 +2,52
2 table.filecontent { border: 1px solid #ccc; border-collapse: collapse; width:98%; }
2 table.filecontent { border: 1px solid #ccc; border-collapse: collapse; width:98%; }
3 table.filecontent th { border: 1px solid #ccc; background-color: #eee; }
3 table.filecontent th { border: 1px solid #ccc; background-color: #eee; }
4 table.filecontent th.filename { background-color: #ddc; text-align: left; }
4 table.filecontent th.filename { background-color: #ddc; text-align: left; }
5 div.action_M { background: #fd8 }
6 div.action_D { background: #f88 }
7 div.action_A { background: #bfb }
8
9 table.filecontent tr.spacing { border: 1px solid #d7d7d7; }
5 table.filecontent tr.spacing { border: 1px solid #d7d7d7; }
10
6 table.filecontent th.line-num {
11 table.filecontent .line-num {
12 border: 1px solid #d7d7d7;
7 border: 1px solid #d7d7d7;
13 font-size: 0.8em;
8 font-size: 0.8em;
14 text-align: right;
9 text-align: right;
15 width: 3em;
10 width: 2%;
16 padding-right: 3px;
11 padding-right: 3px;
17 }
12 }
18
13
14 /* 12 different colors for the annonate view */
15 table.annotate tr.bloc-0 {background: #FFFFBF;}
16 table.annotate tr.bloc-1 {background: #EABFFF;}
17 table.annotate tr.bloc-2 {background: #BFFFFF;}
18 table.annotate tr.bloc-3 {background: #FFD9BF;}
19 table.annotate tr.bloc-4 {background: #E6FFBF;}
20 table.annotate tr.bloc-5 {background: #BFCFFF;}
21 table.annotate tr.bloc-6 {background: #FFBFEF;}
22 table.annotate tr.bloc-7 {background: #FFE6BF;}
23 table.annotate tr.bloc-8 {background: #FFE680;}
24 table.annotate tr.bloc-9 {background: #AA80FF;}
25 table.annotate tr.bloc-10 {background: #FFBFDC;}
26 table.annotate tr.bloc-11 {background: #BFE4FF;}
27
28 table.annotate td.revision {
29 text-align: center;
30 width: 2%;
31 padding-left: 1em;
32 background: inherit;
33 }
34
35 table.annotate td.author {
36 text-align: center;
37 border-right: 1px solid #d7d7d7;
38 white-space: nowrap;
39 padding-left: 1em;
40 padding-right: 1em;
41 width: 3%;
42 background: inherit;
43 }
44
45 table.annotate td.line-code { background-color: #fafafa; }
46
47 div.action_M { background: #fd8 }
48 div.action_D { background: #f88 }
49 div.action_A { background: #bfb }
50
19 /************* Coderay styles *************/
51 /************* Coderay styles *************/
20
52
21 table.CodeRay {
53 table.CodeRay {
General Comments 0
You need to be logged in to leave comments. Login now