diff options
-rw-r--r-- | app/controllers/repositories_controller.rb | 5 | ||||
-rw-r--r-- | app/models/repository.rb | 4 | ||||
-rw-r--r-- | app/views/repositories/annotate.rhtml | 26 | ||||
-rw-r--r-- | app/views/repositories/changes.rhtml | 11 | ||||
-rw-r--r-- | app/views/repositories/entry.rhtml | 5 | ||||
-rw-r--r-- | config/routes.rb | 1 | ||||
-rw-r--r-- | lang/bg.yml | 1 | ||||
-rw-r--r-- | lang/cs.yml | 1 | ||||
-rw-r--r-- | lang/de.yml | 1 | ||||
-rw-r--r-- | lang/en.yml | 1 | ||||
-rw-r--r-- | lang/es.yml | 1 | ||||
-rw-r--r-- | lang/fr.yml | 1 | ||||
-rw-r--r-- | lang/he.yml | 1 | ||||
-rw-r--r-- | lang/it.yml | 1 | ||||
-rw-r--r-- | lang/ja.yml | 1 | ||||
-rw-r--r-- | lang/ko.yml | 1 | ||||
-rw-r--r-- | lang/nl.yml | 1 | ||||
-rw-r--r-- | lang/pl.yml | 1 | ||||
-rw-r--r-- | lang/pt-br.yml | 1 | ||||
-rw-r--r-- | lang/pt.yml | 1 | ||||
-rw-r--r-- | lang/ro.yml | 1 | ||||
-rw-r--r-- | lang/ru.yml | 1 | ||||
-rw-r--r-- | lang/sr.yml | 1 | ||||
-rw-r--r-- | lang/sv.yml | 1 | ||||
-rw-r--r-- | lang/zh.yml | 1 | ||||
-rw-r--r-- | lib/redmine.rb | 2 | ||||
-rw-r--r-- | lib/redmine/scm/adapters/abstract_adapter.rb | 32 | ||||
-rw-r--r-- | lib/redmine/scm/adapters/cvs_adapter.rb | 20 | ||||
-rw-r--r-- | lib/redmine/scm/adapters/mercurial_adapter.rb | 19 | ||||
-rw-r--r-- | lib/redmine/scm/adapters/subversion_adapter.rb | 17 | ||||
-rw-r--r-- | public/stylesheets/scm.css | 46 |
31 files changed, 186 insertions, 21 deletions
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index b332c7213..dfd5d0a6f 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -95,6 +95,11 @@ class RepositoriesController < ApplicationController end end + def annotate + @annotate = @repository.scm.annotate(@path, @rev) + show_error and return if @annotate.nil? || @annotate.empty? + end + def revision @changeset = @repository.changesets.find_by_revision(@rev) raise ChangesetNotFound unless @changeset diff --git a/app/models/repository.rb b/app/models/repository.rb index 35dd6803f..be31ac2e5 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -33,6 +33,10 @@ class Repository < ActiveRecord::Base def supports_cat? scm.supports_cat? end + + def supports_annotate? + scm.supports_annotate? + end def entries(path=nil, identifier=nil) scm.entries(path, identifier) diff --git a/app/views/repositories/annotate.rhtml b/app/views/repositories/annotate.rhtml new file mode 100644 index 000000000..b8f481ae5 --- /dev/null +++ b/app/views/repositories/annotate.rhtml @@ -0,0 +1,26 @@ +<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> + +<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %> + +<div class="autoscroll"> +<table class="filecontent annotate CodeRay"> + <tbody> + <% line_num = 1 %> + <% syntax_highlight(@path, to_utf8(@annotate.content)).each_line do |line| %> + <% revision = @annotate.revisions[line_num-1] %> + <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>"> + <th class="line-num"><%= line_num %></th> + <td class="revision"> + <%= (revision.identifier ? link_to(revision.identifier, :action => 'revision', :id => @project, :rev => revision.identifier) : revision.revision) if revision %></td> + <td class="author"><%= h(revision.author) if revision %></td> + <td class="line-code"><pre><%= line %></pre></td> + </tr> + <% line_num += 1 %> + <% end %> + <tbody> +</table> +</div> + +<% content_for :header_tags do %> +<%= stylesheet_link_tag 'scm' %> +<% end %> diff --git a/app/views/repositories/changes.rhtml b/app/views/repositories/changes.rhtml index 5d1db96ba..f843983db 100644 --- a/app/views/repositories/changes.rhtml +++ b/app/views/repositories/changes.rhtml @@ -2,14 +2,17 @@ <h3><%=h @entry.name %></h3> -<% if @repository.supports_cat? %> <p> <% if @entry.is_text? %> -<%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> | + <% if @repository.supports_cat? %> + <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> | + <% end %> + <% if @repository.supports_annotate? %> + <%= link_to l(:button_annotate), {:action => 'annotate', :id => @project, :path => @path, :rev => @rev } %> | + <% end %> <% end %> -<%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' } %> +<%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %> <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %> </p> -<% end %> <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }%> diff --git a/app/views/repositories/entry.rhtml b/app/views/repositories/entry.rhtml index 94db240ab..9927601d7 100644 --- a/app/views/repositories/entry.rhtml +++ b/app/views/repositories/entry.rhtml @@ -2,11 +2,6 @@ <div class="autoscroll"> <table class="filecontent CodeRay"> - <thead> - <tr> - <th colspan="2" class="filename"><%= @path %></th> - </tr> - </thead> <tbody> <% line_num = 1 %> <% syntax_highlight(@path, to_utf8(@content)).each_line do |line| %> diff --git a/config/routes.rb b/config/routes.rb index c3637304a..5048da2f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,6 +24,7 @@ ActionController::Routing::Routes.draw do |map| omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes' omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff' omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry' + omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate' end # Allow downloading Web Service WSDL as a file with an extension diff --git a/lang/bg.yml b/lang/bg.yml index 6f5f527c7..5f2dc6a8d 100644 --- a/lang/bg.yml +++ b/lang/bg.yml @@ -547,3 +547,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/cs.yml b/lang/cs.yml index 1bc80c9dc..49697dda8 100644 --- a/lang/cs.yml +++ b/lang/cs.yml @@ -547,3 +547,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/de.yml b/lang/de.yml index b26129263..870f45014 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -547,3 +547,4 @@ notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Geneh field_time_zone: Zeitzone text_caracters_minimum: Muss mindestens %d Zeichen lang sein. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/en.yml b/lang/en.yml index fb99bbb82..cdecb6693 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -489,6 +489,7 @@ button_reset: Reset button_rename: Rename button_change_password: Change password button_copy: Copy +button_annotate: Annotate status_active: active status_registered: registered diff --git a/lang/es.yml b/lang/es.yml index 1b5638eb5..5f25de7cf 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -550,3 +550,4 @@ label_registration_manual_activation: activación manual de cuenta notice_account_pending: "Su cuenta ha sido creada y está pendiende de la aprobación por parte de administrador" setting_time_format: Formato de hora setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/fr.yml b/lang/fr.yml index b2466f1e1..63f367f06 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -489,6 +489,7 @@ button_reset: Réinitialiser button_rename: Renommer button_change_password: Changer de mot de passe button_copy: Copier +button_annotate: Annoter status_active: actif status_registered: enregistré diff --git a/lang/he.yml b/lang/he.yml index 0f75a6ec1..4ebbecf7a 100644 --- a/lang/he.yml +++ b/lang/he.yml @@ -547,3 +547,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/it.yml b/lang/it.yml index a3a858cef..fa13513cb 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -547,3 +547,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/ja.yml b/lang/ja.yml index 2233c2a38..d2f7d579f 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -548,3 +548,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/ko.yml b/lang/ko.yml index 8962d8cd5..dd50ce2bc 100644 --- a/lang/ko.yml +++ b/lang/ko.yml @@ -547,3 +547,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/nl.yml b/lang/nl.yml index ca6ad66f3..895feb5ed 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -548,3 +548,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/pl.yml b/lang/pl.yml index f478df8fb..381dc5e26 100644 --- a/lang/pl.yml +++ b/lang/pl.yml @@ -547,3 +547,4 @@ notice_account_pending: "Twoje konto zostało utworzone i oczekuje na zatwierdze field_time_zone: Strefa czasowa text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/pt-br.yml b/lang/pt-br.yml index df893ff6d..4c4d862d3 100644 --- a/lang/pt-br.yml +++ b/lang/pt-br.yml @@ -547,3 +547,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
+button_annotate: Annotate
diff --git a/lang/pt.yml b/lang/pt.yml index 1a499a2c5..1aabc8ac7 100644 --- a/lang/pt.yml +++ b/lang/pt.yml @@ -547,3 +547,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/ro.yml b/lang/ro.yml index a57330cc0..0c10c7727 100644 --- a/lang/ro.yml +++ b/lang/ro.yml @@ -547,3 +547,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/ru.yml b/lang/ru.yml index 2f73a43b9..c6e27ca9f 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -547,3 +547,4 @@ notice_account_pending: "Ваш аккаунт уже создан и ожида field_time_zone: Часовой пояс text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/sr.yml b/lang/sr.yml index 49e2d5f3a..42aa3cc3e 100644 --- a/lang/sr.yml +++ b/lang/sr.yml @@ -548,3 +548,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/sv.yml b/lang/sv.yml index 11a8ce059..7cbcc7a5f 100644 --- a/lang/sv.yml +++ b/lang/sv.yml @@ -548,3 +548,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lang/zh.yml b/lang/zh.yml index 48176ceba..ad18cecc3 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -550,3 +550,4 @@ notice_account_pending: "Your account was created and is now pending administrat field_time_zone: Time zone text_caracters_minimum: Must be at least %d characters long. setting_bcc_recipients: Blind carbon copy recipients (bcc) +button_annotate: Annotate diff --git a/lib/redmine.rb b/lib/redmine.rb index ffc1cc27e..b74f00aec 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -76,7 +76,7 @@ Redmine::AccessControl.map do |map| map.project_module :repository do |map| map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member - map.permission :browse_repository, :repositories => [:show, :browse, :entry, :changes, :diff, :stats, :graph] + map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph] map.permission :view_changesets, :repositories => [:show, :revisions, :revision] end diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb index 4b524c538..720a4e9d9 100644 --- a/lib/redmine/scm/adapters/abstract_adapter.rb +++ b/lib/redmine/scm/adapters/abstract_adapter.rb @@ -38,6 +38,10 @@ module Redmine def supports_cat? true end + + def supports_annotate? + respond_to?('annotate') + end def root_url @root_url @@ -76,7 +80,7 @@ module Redmine def cat(path, identifier=nil) return nil end - + def with_leading_slash(path) path ||= '' (path[0,1]!="/") ? "/#{path}" : path @@ -237,7 +241,7 @@ module Redmine # Initialize with a Diff file and the type of Diff View # The type view must be inline or sbs (side_by_side) - def initialize (type="inline") + def initialize(type="inline") @parsing = false @nb_line = 1 @start = false @@ -312,7 +316,7 @@ module Redmine CGI.escapeHTML(line) end - def parse_line (line, type="inline") + def parse_line(line, type="inline") if line[0, 1] == "+" diff = sbs? type, 'add' @before = 'add' @@ -348,6 +352,28 @@ module Redmine end end end + + class Annotate + attr_reader :lines, :revisions + + def initialize + @lines = [] + @revisions = [] + end + + def add_line(line, revision) + @lines << line + @revisions << revision + end + + def content + content = lines.join("\n") + end + + def empty? + lines.empty? + end + end end end end diff --git a/lib/redmine/scm/adapters/cvs_adapter.rb b/lib/redmine/scm/adapters/cvs_adapter.rb index e84c1eea3..5c6c1775b 100644 --- a/lib/redmine/scm/adapters/cvs_adapter.rb +++ b/lib/redmine/scm/adapters/cvs_adapter.rb @@ -268,7 +268,25 @@ module Redmine rescue Errno::ENOENT => e raise CommandFailed end - + + def annotate(path, identifier=nil) + identifier = (identifier) ? identifier : "HEAD" + logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}" + path_with_project="#{url}#{with_leading_slash(path)}" + cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{path_with_project}" + blame = Annotate.new + shellout(cmd) do |io| + io.each_line do |line| + next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$} + blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip)) + end + end + return nil if $? && $?.exitstatus != 0 + blame + rescue Errno::ENOENT => e + raise CommandFailed + end + private # convert a date/time into the CVS-format diff --git a/lib/redmine/scm/adapters/mercurial_adapter.rb b/lib/redmine/scm/adapters/mercurial_adapter.rb index a631916c5..3cbf01f91 100644 --- a/lib/redmine/scm/adapters/mercurial_adapter.rb +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb @@ -157,6 +157,25 @@ module Redmine rescue Errno::ENOENT => e raise CommandFailed end + + def annotate(path, identifier=nil) + path ||= '' + cmd = "#{HG_BIN} -R #{target('')}" + cmd << " annotate -n -u" + cmd << " -r #{identifier.to_i}" if identifier + cmd << " #{target(path)}" + blame = Annotate.new + shellout(cmd) do |io| + io.each_line do |line| + next unless line =~ %r{^([^:]+)\s(\d+):(.*)$} + blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip)) + end + end + return nil if $? && $?.exitstatus != 0 + blame + rescue Errno::ENOENT => e + raise CommandFailed + end end end end diff --git a/lib/redmine/scm/adapters/subversion_adapter.rb b/lib/redmine/scm/adapters/subversion_adapter.rb index 9e8acce4c..d55b8712e 100644 --- a/lib/redmine/scm/adapters/subversion_adapter.rb +++ b/lib/redmine/scm/adapters/subversion_adapter.rb @@ -173,6 +173,23 @@ module Redmine raise CommandFailed
end
+ def annotate(path, identifier=nil)
+ identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
+ cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
+ cmd << credentials_string
+ blame = Annotate.new
+ shellout(cmd) do |io|
+ io.each_line do |line|
+ next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
+ blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
+ end
+ end
+ return nil if $? && $?.exitstatus != 0
+ blame
+ rescue Errno::ENOENT => e
+ raise CommandFailed
+ end
+
private
def credentials_string
diff --git a/public/stylesheets/scm.css b/public/stylesheets/scm.css index 3794db366..c3dc307d6 100644 --- a/public/stylesheets/scm.css +++ b/public/stylesheets/scm.css @@ -2,20 +2,52 @@ table.filecontent { border: 1px solid #ccc; border-collapse: collapse; width:98%; } table.filecontent th { border: 1px solid #ccc; background-color: #eee; } table.filecontent th.filename { background-color: #ddc; text-align: left; } -div.action_M { background: #fd8 } -div.action_D { background: #f88 } -div.action_A { background: #bfb } - table.filecontent tr.spacing { border: 1px solid #d7d7d7; } - -table.filecontent .line-num { +table.filecontent th.line-num { border: 1px solid #d7d7d7; font-size: 0.8em; text-align: right; - width: 3em; + width: 2%; padding-right: 3px; } +/* 12 different colors for the annonate view */ +table.annotate tr.bloc-0 {background: #FFFFBF;} +table.annotate tr.bloc-1 {background: #EABFFF;} +table.annotate tr.bloc-2 {background: #BFFFFF;} +table.annotate tr.bloc-3 {background: #FFD9BF;} +table.annotate tr.bloc-4 {background: #E6FFBF;} +table.annotate tr.bloc-5 {background: #BFCFFF;} +table.annotate tr.bloc-6 {background: #FFBFEF;} +table.annotate tr.bloc-7 {background: #FFE6BF;} +table.annotate tr.bloc-8 {background: #FFE680;} +table.annotate tr.bloc-9 {background: #AA80FF;} +table.annotate tr.bloc-10 {background: #FFBFDC;} +table.annotate tr.bloc-11 {background: #BFE4FF;} + +table.annotate td.revision { + text-align: center; + width: 2%; + padding-left: 1em; + background: inherit; +} + +table.annotate td.author { + text-align: center; + border-right: 1px solid #d7d7d7; + white-space: nowrap; + padding-left: 1em; + padding-right: 1em; + width: 3%; + background: inherit; +} + +table.annotate td.line-code { background-color: #fafafa; } + +div.action_M { background: #fd8 } +div.action_D { background: #f88 } +div.action_A { background: #bfb } + /************* Coderay styles *************/ table.CodeRay { |