summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/controllers/repositories_controller.rb5
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/views/repositories/annotate.rhtml26
-rw-r--r--app/views/repositories/changes.rhtml11
-rw-r--r--app/views/repositories/entry.rhtml5
-rw-r--r--config/routes.rb1
-rw-r--r--lang/bg.yml1
-rw-r--r--lang/cs.yml1
-rw-r--r--lang/de.yml1
-rw-r--r--lang/en.yml1
-rw-r--r--lang/es.yml1
-rw-r--r--lang/fr.yml1
-rw-r--r--lang/he.yml1
-rw-r--r--lang/it.yml1
-rw-r--r--lang/ja.yml1
-rw-r--r--lang/ko.yml1
-rw-r--r--lang/nl.yml1
-rw-r--r--lang/pl.yml1
-rw-r--r--lang/pt-br.yml1
-rw-r--r--lang/pt.yml1
-rw-r--r--lang/ro.yml1
-rw-r--r--lang/ru.yml1
-rw-r--r--lang/sr.yml1
-rw-r--r--lang/sv.yml1
-rw-r--r--lang/zh.yml1
-rw-r--r--lib/redmine.rb2
-rw-r--r--lib/redmine/scm/adapters/abstract_adapter.rb32
-rw-r--r--lib/redmine/scm/adapters/cvs_adapter.rb20
-rw-r--r--lib/redmine/scm/adapters/mercurial_adapter.rb19
-rw-r--r--lib/redmine/scm/adapters/subversion_adapter.rb17
-rw-r--r--public/stylesheets/scm.css46
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 {