##// END OF EJS Templates
Text files can now be viewed online when browsing the repository....
Jean-Philippe Lang -
r518:8614c00c8a3e
parent child
Show More
@@ -0,0 +1,23
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
2
3 <table class="list">
4 <thead>
5 <tr>
6 <th colspan="2" class="list-filename"><%= @path %></th>
7 </tr>
8 </thead>
9 <tbody>
10 <% line_num = 1 %>
11 <% @content.each_line do |line| %>
12 <tr>
13 <th class="line-num"><%= line_num %></th>
14 <td class="line-code"><%= h(line).gsub(/\s/, '&nbsp;') %></td>
15 </tr>
16 <% line_num += 1 %>
17 <% end %>
18 <tbody>
19 </table>
20
21 <% content_for :header_tags do %>
22 <%= stylesheet_link_tag "scm" %>
23 <% end %>
@@ -0,0 +1,58
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine
19 module MimeType
20
21 MIME_TYPES = {
22 'text/plain' => 'txt',
23 'text/css' => 'css',
24 'text/html' => 'html,htm,xhtml',
25 'text/x-javascript' => 'js',
26 'text/x-html-template' => 'rhtml',
27 'text/x-ruby' => 'rb,ruby',
28 'image/gif' => 'gif',
29 'image/jpeg' => 'jpg,jpeg,jpe',
30 'image/png' => 'png',
31 'image/tiff' => 'tiff,tif'
32 }.freeze
33
34 EXTENSIONS = MIME_TYPES.inject({}) do |map, (type, exts)|
35 exts.split(',').each {|ext| map[ext] = type}
36 map
37 end
38
39 # returns mime type for name or nil if unknown
40 def self.of(name)
41 return nil unless name
42 m = name.to_s.match(/\.([^\.]+)$/)
43 EXTENSIONS[m[1]] if m
44 end
45
46 def self.main_mimetype_of(name)
47 mimetype = of(name)
48 mimetype.split('/').first if mimetype
49 end
50
51 # return true if mime-type for name is type/*
52 # otherwise false
53 def self.is_type?(type, name)
54 main_mimetype = main_mimetype_of(name)
55 type.to_s == main_mimetype
56 end
57 end
58 end
@@ -1,213 +1,213
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'SVG/Graph/Bar'
18 require 'SVG/Graph/Bar'
19 require 'SVG/Graph/BarHorizontal'
19 require 'SVG/Graph/BarHorizontal'
20 require 'digest/sha1'
20 require 'digest/sha1'
21
21
22 class RepositoriesController < ApplicationController
22 class RepositoriesController < ApplicationController
23 layout 'base'
23 layout 'base'
24 before_filter :find_project
24 before_filter :find_project
25 before_filter :authorize, :except => [:stats, :graph]
25 before_filter :authorize, :except => [:stats, :graph]
26 before_filter :check_project_privacy, :only => [:stats, :graph]
26 before_filter :check_project_privacy, :only => [:stats, :graph]
27
27
28 def show
28 def show
29 # get entries for the browse frame
29 # get entries for the browse frame
30 @entries = @repository.scm.entries('')
30 @entries = @repository.scm.entries('')
31 show_error and return unless @entries
31 show_error and return unless @entries
32 # check if new revisions have been committed in the repository
32 # check if new revisions have been committed in the repository
33 scm_latestrev = @entries.revisions.latest
33 scm_latestrev = @entries.revisions.latest
34 if Setting.autofetch_changesets? && scm_latestrev && ((@repository.latest_changeset.nil?) || (@repository.latest_changeset.revision < scm_latestrev.identifier.to_i))
34 if Setting.autofetch_changesets? && scm_latestrev && ((@repository.latest_changeset.nil?) || (@repository.latest_changeset.revision < scm_latestrev.identifier.to_i))
35 @repository.fetch_changesets
35 @repository.fetch_changesets
36 @repository.reload
36 @repository.reload
37 end
37 end
38 @changesets = @repository.changesets.find(:all, :limit => 5, :order => "committed_on DESC")
38 @changesets = @repository.changesets.find(:all, :limit => 5, :order => "committed_on DESC")
39 end
39 end
40
40
41 def browse
41 def browse
42 @entries = @repository.scm.entries(@path, @rev)
42 @entries = @repository.scm.entries(@path, @rev)
43 show_error and return unless @entries
43 show_error and return unless @entries
44 end
44 end
45
45
46 def revisions
46 def revisions
47 unless @path == ''
47 unless @path == ''
48 @entry = @repository.scm.entry(@path, @rev)
48 @entry = @repository.scm.entry(@path, @rev)
49 show_error and return unless @entry
49 show_error and return unless @entry
50 end
50 end
51 @repository.changesets_with_path @path do
51 @repository.changesets_with_path @path do
52 @changeset_count = @repository.changesets.count
52 @changeset_count = @repository.changesets.count
53 @changeset_pages = Paginator.new self, @changeset_count,
53 @changeset_pages = Paginator.new self, @changeset_count,
54 25,
54 25,
55 params['page']
55 params['page']
56 @changesets = @repository.changesets.find(:all,
56 @changesets = @repository.changesets.find(:all,
57 :limit => @changeset_pages.items_per_page,
57 :limit => @changeset_pages.items_per_page,
58 :offset => @changeset_pages.current.offset)
58 :offset => @changeset_pages.current.offset)
59 end
59 end
60 render :action => "revisions", :layout => false if request.xhr?
60 render :action => "revisions", :layout => false if request.xhr?
61 end
61 end
62
62
63 def entry
63 def entry
64 if 'raw' == params[:format]
64 @content = @repository.scm.cat(@path, @rev)
65 content = @repository.scm.cat(@path, @rev)
65 show_error and return unless @content
66 show_error and return unless content
66 if 'raw' == params[:format]
67 send_data content, :filename => @path.split('/').last
67 send_data @content, :filename => @path.split('/').last
68 end
68 end
69 end
69 end
70
70
71 def revision
71 def revision
72 @changeset = @repository.changesets.find_by_revision(@rev)
72 @changeset = @repository.changesets.find_by_revision(@rev)
73 show_error and return unless @changeset
73 show_error and return unless @changeset
74 end
74 end
75
75
76 def diff
76 def diff
77 @rev_to = (params[:rev_to] && params[:rev_to].to_i > 0) ? params[:rev_to].to_i : (@rev - 1)
77 @rev_to = (params[:rev_to] && params[:rev_to].to_i > 0) ? params[:rev_to].to_i : (@rev - 1)
78 @diff_type = ('sbs' == params[:type]) ? 'sbs' : 'inline'
78 @diff_type = ('sbs' == params[:type]) ? 'sbs' : 'inline'
79
79
80 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
80 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
81 unless read_fragment(@cache_key)
81 unless read_fragment(@cache_key)
82 @diff = @repository.scm.diff(@path, @rev, @rev_to, type)
82 @diff = @repository.scm.diff(@path, @rev, @rev_to, type)
83 show_error and return unless @diff
83 show_error and return unless @diff
84 end
84 end
85 end
85 end
86
86
87 def stats
87 def stats
88 end
88 end
89
89
90 def graph
90 def graph
91 data = nil
91 data = nil
92 case params[:graph]
92 case params[:graph]
93 when "commits_per_month"
93 when "commits_per_month"
94 data = graph_commits_per_month(@repository)
94 data = graph_commits_per_month(@repository)
95 when "commits_per_author"
95 when "commits_per_author"
96 data = graph_commits_per_author(@repository)
96 data = graph_commits_per_author(@repository)
97 end
97 end
98 if data
98 if data
99 headers["Content-Type"] = "image/svg+xml"
99 headers["Content-Type"] = "image/svg+xml"
100 send_data(data, :type => "image/svg+xml", :disposition => "inline")
100 send_data(data, :type => "image/svg+xml", :disposition => "inline")
101 else
101 else
102 render_404
102 render_404
103 end
103 end
104 end
104 end
105
105
106 private
106 private
107 def find_project
107 def find_project
108 @project = Project.find(params[:id])
108 @project = Project.find(params[:id])
109 @repository = @project.repository
109 @repository = @project.repository
110 render_404 and return false unless @repository
110 render_404 and return false unless @repository
111 @path = params[:path].squeeze('/').gsub(/^\//, '') if params[:path]
111 @path = params[:path].squeeze('/').gsub(/^\//, '') if params[:path]
112 @path ||= ''
112 @path ||= ''
113 @rev = params[:rev].to_i if params[:rev] and params[:rev].to_i > 0
113 @rev = params[:rev].to_i if params[:rev] and params[:rev].to_i > 0
114 rescue ActiveRecord::RecordNotFound
114 rescue ActiveRecord::RecordNotFound
115 render_404
115 render_404
116 end
116 end
117
117
118 def show_error
118 def show_error
119 flash.now[:notice] = l(:notice_scm_error)
119 flash.now[:notice] = l(:notice_scm_error)
120 render :nothing => true, :layout => true
120 render :nothing => true, :layout => true
121 end
121 end
122
122
123 def graph_commits_per_month(repository)
123 def graph_commits_per_month(repository)
124 @date_to = Date.today
124 @date_to = Date.today
125 @date_from = @date_to << 12
125 @date_from = @date_to << 12
126 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
126 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
127 commits_by_month = [0] * 12
127 commits_by_month = [0] * 12
128 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
128 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
129
129
130 changes_by_day = repository.changes.count(:all, :group => :commit_date)
130 changes_by_day = repository.changes.count(:all, :group => :commit_date)
131 changes_by_month = [0] * 12
131 changes_by_month = [0] * 12
132 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
132 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
133
133
134 fields = []
134 fields = []
135 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
135 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
136 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
136 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
137
137
138 graph = SVG::Graph::Bar.new(
138 graph = SVG::Graph::Bar.new(
139 :height => 300,
139 :height => 300,
140 :width => 500,
140 :width => 500,
141 :fields => fields.reverse,
141 :fields => fields.reverse,
142 :stack => :side,
142 :stack => :side,
143 :scale_integers => true,
143 :scale_integers => true,
144 :step_x_labels => 2,
144 :step_x_labels => 2,
145 :show_data_values => false,
145 :show_data_values => false,
146 :graph_title => l(:label_commits_per_month),
146 :graph_title => l(:label_commits_per_month),
147 :show_graph_title => true
147 :show_graph_title => true
148 )
148 )
149
149
150 graph.add_data(
150 graph.add_data(
151 :data => commits_by_month[0..11].reverse,
151 :data => commits_by_month[0..11].reverse,
152 :title => l(:label_revision_plural)
152 :title => l(:label_revision_plural)
153 )
153 )
154
154
155 graph.add_data(
155 graph.add_data(
156 :data => changes_by_month[0..11].reverse,
156 :data => changes_by_month[0..11].reverse,
157 :title => l(:label_change_plural)
157 :title => l(:label_change_plural)
158 )
158 )
159
159
160 graph.burn
160 graph.burn
161 end
161 end
162
162
163 def graph_commits_per_author(repository)
163 def graph_commits_per_author(repository)
164 commits_by_author = repository.changesets.count(:all, :group => :committer)
164 commits_by_author = repository.changesets.count(:all, :group => :committer)
165 commits_by_author.sort! {|x, y| x.last <=> y.last}
165 commits_by_author.sort! {|x, y| x.last <=> y.last}
166
166
167 changes_by_author = repository.changes.count(:all, :group => :committer)
167 changes_by_author = repository.changes.count(:all, :group => :committer)
168 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
168 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
169
169
170 fields = commits_by_author.collect {|r| r.first}
170 fields = commits_by_author.collect {|r| r.first}
171 commits_data = commits_by_author.collect {|r| r.last}
171 commits_data = commits_by_author.collect {|r| r.last}
172 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
172 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
173
173
174 fields = fields + [""]*(10 - fields.length) if fields.length<10
174 fields = fields + [""]*(10 - fields.length) if fields.length<10
175 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
175 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
176 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
176 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
177
177
178 graph = SVG::Graph::BarHorizontal.new(
178 graph = SVG::Graph::BarHorizontal.new(
179 :height => 300,
179 :height => 300,
180 :width => 500,
180 :width => 500,
181 :fields => fields,
181 :fields => fields,
182 :stack => :side,
182 :stack => :side,
183 :scale_integers => true,
183 :scale_integers => true,
184 :show_data_values => false,
184 :show_data_values => false,
185 :rotate_y_labels => false,
185 :rotate_y_labels => false,
186 :graph_title => l(:label_commits_per_author),
186 :graph_title => l(:label_commits_per_author),
187 :show_graph_title => true
187 :show_graph_title => true
188 )
188 )
189
189
190 graph.add_data(
190 graph.add_data(
191 :data => commits_data,
191 :data => commits_data,
192 :title => l(:label_revision_plural)
192 :title => l(:label_revision_plural)
193 )
193 )
194
194
195 graph.add_data(
195 graph.add_data(
196 :data => changes_data,
196 :data => changes_data,
197 :title => l(:label_change_plural)
197 :title => l(:label_change_plural)
198 )
198 )
199
199
200 graph.burn
200 graph.burn
201 end
201 end
202
202
203 end
203 end
204
204
205 class Date
205 class Date
206 def months_ago(date = Date.today)
206 def months_ago(date = Date.today)
207 (date.year - self.year)*12 + (date.month - self.month)
207 (date.year - self.year)*12 + (date.month - self.month)
208 end
208 end
209
209
210 def weeks_ago(date = Date.today)
210 def weeks_ago(date = Date.today)
211 (date.year - self.year)*52 + (date.cweek - self.cweek)
211 (date.year - self.year)*52 + (date.cweek - self.cweek)
212 end
212 end
213 end
213 end
@@ -1,431 +1,436
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'rexml/document'
18 require 'rexml/document'
19 require 'cgi'
19 require 'cgi'
20
20
21 module SvnRepos
21 module SvnRepos
22
22
23 class CommandFailed < StandardError #:nodoc:
23 class CommandFailed < StandardError #:nodoc:
24 end
24 end
25
25
26 class Base
26 class Base
27
27
28 def initialize(url, root_url=nil, login=nil, password=nil)
28 def initialize(url, root_url=nil, login=nil, password=nil)
29 @url = url
29 @url = url
30 @login = login if login && !login.empty?
30 @login = login if login && !login.empty?
31 @password = (password || "") if @login
31 @password = (password || "") if @login
32 @root_url = root_url.blank? ? retrieve_root_url : root_url
32 @root_url = root_url.blank? ? retrieve_root_url : root_url
33 end
33 end
34
34
35 def root_url
35 def root_url
36 @root_url
36 @root_url
37 end
37 end
38
38
39 def url
39 def url
40 @url
40 @url
41 end
41 end
42
42
43 # get info about the svn repository
43 # get info about the svn repository
44 def info
44 def info
45 cmd = "svn info --xml #{target('')}"
45 cmd = "svn info --xml #{target('')}"
46 cmd << " --username #{@login} --password #{@password}" if @login
46 cmd << " --username #{@login} --password #{@password}" if @login
47 info = nil
47 info = nil
48 shellout(cmd) do |io|
48 shellout(cmd) do |io|
49 begin
49 begin
50 doc = REXML::Document.new(io)
50 doc = REXML::Document.new(io)
51 #root_url = doc.elements["info/entry/repository/root"].text
51 #root_url = doc.elements["info/entry/repository/root"].text
52 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
52 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
53 :lastrev => Revision.new({
53 :lastrev => Revision.new({
54 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
54 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
55 :time => Time.parse(doc.elements["info/entry/commit/date"].text),
55 :time => Time.parse(doc.elements["info/entry/commit/date"].text),
56 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
56 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
57 })
57 })
58 })
58 })
59 rescue
59 rescue
60 end
60 end
61 end
61 end
62 return nil if $? && $?.exitstatus != 0
62 return nil if $? && $?.exitstatus != 0
63 info
63 info
64 rescue Errno::ENOENT => e
64 rescue Errno::ENOENT => e
65 return nil
65 return nil
66 end
66 end
67
67
68 # Returns the entry identified by path and revision identifier
68 # Returns the entry identified by path and revision identifier
69 # or nil if entry doesn't exist in the repository
69 # or nil if entry doesn't exist in the repository
70 def entry(path=nil, identifier=nil)
70 def entry(path=nil, identifier=nil)
71 e = entries(path, identifier)
71 e = entries(path, identifier)
72 e ? e.first : nil
72 e ? e.first : nil
73 end
73 end
74
74
75 # Returns an Entries collection
75 # Returns an Entries collection
76 # or nil if the given path doesn't exist in the repository
76 # or nil if the given path doesn't exist in the repository
77 def entries(path=nil, identifier=nil)
77 def entries(path=nil, identifier=nil)
78 path ||= ''
78 path ||= ''
79 identifier = 'HEAD' unless identifier and identifier > 0
79 identifier = 'HEAD' unless identifier and identifier > 0
80 entries = Entries.new
80 entries = Entries.new
81 cmd = "svn list --xml #{target(path)}@#{identifier}"
81 cmd = "svn list --xml #{target(path)}@#{identifier}"
82 cmd << " --username #{@login} --password #{@password}" if @login
82 cmd << " --username #{@login} --password #{@password}" if @login
83 shellout(cmd) do |io|
83 shellout(cmd) do |io|
84 begin
84 begin
85 doc = REXML::Document.new(io)
85 doc = REXML::Document.new(io)
86 doc.elements.each("lists/list/entry") do |entry|
86 doc.elements.each("lists/list/entry") do |entry|
87 entries << Entry.new({:name => entry.elements['name'].text,
87 entries << Entry.new({:name => entry.elements['name'].text,
88 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
88 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
89 :kind => entry.attributes['kind'],
89 :kind => entry.attributes['kind'],
90 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
90 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
91 :lastrev => Revision.new({
91 :lastrev => Revision.new({
92 :identifier => entry.elements['commit'].attributes['revision'],
92 :identifier => entry.elements['commit'].attributes['revision'],
93 :time => Time.parse(entry.elements['commit'].elements['date'].text),
93 :time => Time.parse(entry.elements['commit'].elements['date'].text),
94 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
94 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
95 })
95 })
96 })
96 })
97 end
97 end
98 rescue
98 rescue
99 end
99 end
100 end
100 end
101 return nil if $? && $?.exitstatus != 0
101 return nil if $? && $?.exitstatus != 0
102 entries.sort_by_name
102 entries.sort_by_name
103 rescue Errno::ENOENT => e
103 rescue Errno::ENOENT => e
104 raise CommandFailed
104 raise CommandFailed
105 end
105 end
106
106
107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
108 path ||= ''
108 path ||= ''
109 identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0
109 identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0
110 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
110 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
111 revisions = Revisions.new
111 revisions = Revisions.new
112 cmd = "svn log --xml -r #{identifier_from}:#{identifier_to}"
112 cmd = "svn log --xml -r #{identifier_from}:#{identifier_to}"
113 cmd << " --username #{@login} --password #{@password}" if @login
113 cmd << " --username #{@login} --password #{@password}" if @login
114 cmd << " --verbose " if options[:with_paths]
114 cmd << " --verbose " if options[:with_paths]
115 cmd << target(path)
115 cmd << target(path)
116 shellout(cmd) do |io|
116 shellout(cmd) do |io|
117 begin
117 begin
118 doc = REXML::Document.new(io)
118 doc = REXML::Document.new(io)
119 doc.elements.each("log/logentry") do |logentry|
119 doc.elements.each("log/logentry") do |logentry|
120 paths = []
120 paths = []
121 logentry.elements.each("paths/path") do |path|
121 logentry.elements.each("paths/path") do |path|
122 paths << {:action => path.attributes['action'],
122 paths << {:action => path.attributes['action'],
123 :path => path.text,
123 :path => path.text,
124 :from_path => path.attributes['copyfrom-path'],
124 :from_path => path.attributes['copyfrom-path'],
125 :from_revision => path.attributes['copyfrom-rev']
125 :from_revision => path.attributes['copyfrom-rev']
126 }
126 }
127 end
127 end
128 paths.sort! { |x,y| x[:path] <=> y[:path] }
128 paths.sort! { |x,y| x[:path] <=> y[:path] }
129
129
130 revisions << Revision.new({:identifier => logentry.attributes['revision'],
130 revisions << Revision.new({:identifier => logentry.attributes['revision'],
131 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
131 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
132 :time => Time.parse(logentry.elements['date'].text),
132 :time => Time.parse(logentry.elements['date'].text),
133 :message => logentry.elements['msg'].text,
133 :message => logentry.elements['msg'].text,
134 :paths => paths
134 :paths => paths
135 })
135 })
136 end
136 end
137 rescue
137 rescue
138 end
138 end
139 end
139 end
140 return nil if $? && $?.exitstatus != 0
140 return nil if $? && $?.exitstatus != 0
141 revisions
141 revisions
142 rescue Errno::ENOENT => e
142 rescue Errno::ENOENT => e
143 raise CommandFailed
143 raise CommandFailed
144 end
144 end
145
145
146 def diff(path, identifier_from, identifier_to=nil, type="inline")
146 def diff(path, identifier_from, identifier_to=nil, type="inline")
147 path ||= ''
147 path ||= ''
148 if identifier_to and identifier_to.to_i > 0
148 if identifier_to and identifier_to.to_i > 0
149 identifier_to = identifier_to.to_i
149 identifier_to = identifier_to.to_i
150 else
150 else
151 identifier_to = identifier_from.to_i - 1
151 identifier_to = identifier_from.to_i - 1
152 end
152 end
153 cmd = "svn diff -r "
153 cmd = "svn diff -r "
154 cmd << "#{identifier_to}:"
154 cmd << "#{identifier_to}:"
155 cmd << "#{identifier_from}"
155 cmd << "#{identifier_from}"
156 cmd << "#{target(path)}@#{identifier_from}"
156 cmd << "#{target(path)}@#{identifier_from}"
157 cmd << " --username #{@login} --password #{@password}" if @login
157 cmd << " --username #{@login} --password #{@password}" if @login
158 diff = []
158 diff = []
159 shellout(cmd) do |io|
159 shellout(cmd) do |io|
160 io.each_line do |line|
160 io.each_line do |line|
161 diff << line
161 diff << line
162 end
162 end
163 end
163 end
164 return nil if $? && $?.exitstatus != 0
164 return nil if $? && $?.exitstatus != 0
165 DiffTableList.new diff, type
165 DiffTableList.new diff, type
166
166
167 rescue Errno::ENOENT => e
167 rescue Errno::ENOENT => e
168 raise CommandFailed
168 raise CommandFailed
169 end
169 end
170
170
171 def cat(path, identifier=nil)
171 def cat(path, identifier=nil)
172 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
172 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
173 cmd = "svn cat #{target(path)}@#{identifier}"
173 cmd = "svn cat #{target(path)}@#{identifier}"
174 cmd << " --username #{@login} --password #{@password}" if @login
174 cmd << " --username #{@login} --password #{@password}" if @login
175 cat = nil
175 cat = nil
176 shellout(cmd) do |io|
176 shellout(cmd) do |io|
177 io.binmode
177 cat = io.read
178 cat = io.read
178 end
179 end
179 return nil if $? && $?.exitstatus != 0
180 return nil if $? && $?.exitstatus != 0
180 cat
181 cat
181 rescue Errno::ENOENT => e
182 rescue Errno::ENOENT => e
182 raise CommandFailed
183 raise CommandFailed
183 end
184 end
184
185
185 private
186 private
186 def retrieve_root_url
187 def retrieve_root_url
187 info = self.info
188 info = self.info
188 info ? info.root_url : nil
189 info ? info.root_url : nil
189 end
190 end
190
191
191 def target(path)
192 def target(path)
192 path ||= ""
193 path ||= ""
193 base = path.match(/^\//) ? root_url : url
194 base = path.match(/^\//) ? root_url : url
194 " \"" << "#{base}/#{path}".gsub(/["'?<>\*]/, '') << "\""
195 " \"" << "#{base}/#{path}".gsub(/["'?<>\*]/, '') << "\""
195 end
196 end
196
197
197 def logger
198 def logger
198 RAILS_DEFAULT_LOGGER
199 RAILS_DEFAULT_LOGGER
199 end
200 end
200
201
201 def shellout(cmd, &block)
202 def shellout(cmd, &block)
202 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
203 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
203 IO.popen(cmd, "r+") do |io|
204 IO.popen(cmd, "r+") do |io|
204 io.close_write
205 io.close_write
205 block.call(io) if block_given?
206 block.call(io) if block_given?
206 end
207 end
207 end
208 end
208 end
209 end
209
210
210 class Entries < Array
211 class Entries < Array
211 def sort_by_name
212 def sort_by_name
212 sort {|x,y|
213 sort {|x,y|
213 if x.kind == y.kind
214 if x.kind == y.kind
214 x.name <=> y.name
215 x.name <=> y.name
215 else
216 else
216 x.kind <=> y.kind
217 x.kind <=> y.kind
217 end
218 end
218 }
219 }
219 end
220 end
220
221
221 def revisions
222 def revisions
222 revisions ||= Revisions.new(collect{|entry| entry.lastrev})
223 revisions ||= Revisions.new(collect{|entry| entry.lastrev})
223 end
224 end
224 end
225 end
225
226
226 class Info
227 class Info
227 attr_accessor :root_url, :lastrev
228 attr_accessor :root_url, :lastrev
228 def initialize(attributes={})
229 def initialize(attributes={})
229 self.root_url = attributes[:root_url] if attributes[:root_url]
230 self.root_url = attributes[:root_url] if attributes[:root_url]
230 self.lastrev = attributes[:lastrev]
231 self.lastrev = attributes[:lastrev]
231 end
232 end
232 end
233 end
233
234
234 class Entry
235 class Entry
235 attr_accessor :name, :path, :kind, :size, :lastrev
236 attr_accessor :name, :path, :kind, :size, :lastrev
236 def initialize(attributes={})
237 def initialize(attributes={})
237 self.name = attributes[:name] if attributes[:name]
238 self.name = attributes[:name] if attributes[:name]
238 self.path = attributes[:path] if attributes[:path]
239 self.path = attributes[:path] if attributes[:path]
239 self.kind = attributes[:kind] if attributes[:kind]
240 self.kind = attributes[:kind] if attributes[:kind]
240 self.size = attributes[:size].to_i if attributes[:size]
241 self.size = attributes[:size].to_i if attributes[:size]
241 self.lastrev = attributes[:lastrev]
242 self.lastrev = attributes[:lastrev]
242 end
243 end
243
244
244 def is_file?
245 def is_file?
245 'file' == self.kind
246 'file' == self.kind
246 end
247 end
247
248
248 def is_dir?
249 def is_dir?
249 'dir' == self.kind
250 'dir' == self.kind
250 end
251 end
252
253 def is_text?
254 Redmine::MimeType.is_type?('text', name)
255 end
251 end
256 end
252
257
253 class Revisions < Array
258 class Revisions < Array
254 def latest
259 def latest
255 sort {|x,y| x.time <=> y.time}.last
260 sort {|x,y| x.time <=> y.time}.last
256 end
261 end
257 end
262 end
258
263
259 class Revision
264 class Revision
260 attr_accessor :identifier, :author, :time, :message, :paths
265 attr_accessor :identifier, :author, :time, :message, :paths
261 def initialize(attributes={})
266 def initialize(attributes={})
262 self.identifier = attributes[:identifier]
267 self.identifier = attributes[:identifier]
263 self.author = attributes[:author]
268 self.author = attributes[:author]
264 self.time = attributes[:time]
269 self.time = attributes[:time]
265 self.message = attributes[:message] || ""
270 self.message = attributes[:message] || ""
266 self.paths = attributes[:paths]
271 self.paths = attributes[:paths]
267 end
272 end
268
273
269 end
274 end
270
275
271 # A line of Diff
276 # A line of Diff
272 class Diff
277 class Diff
273
278
274 attr_accessor :nb_line_left
279 attr_accessor :nb_line_left
275 attr_accessor :line_left
280 attr_accessor :line_left
276 attr_accessor :nb_line_right
281 attr_accessor :nb_line_right
277 attr_accessor :line_right
282 attr_accessor :line_right
278 attr_accessor :type_diff_right
283 attr_accessor :type_diff_right
279 attr_accessor :type_diff_left
284 attr_accessor :type_diff_left
280
285
281 def initialize ()
286 def initialize ()
282 self.nb_line_left = ''
287 self.nb_line_left = ''
283 self.nb_line_right = ''
288 self.nb_line_right = ''
284 self.line_left = ''
289 self.line_left = ''
285 self.line_right = ''
290 self.line_right = ''
286 self.type_diff_right = ''
291 self.type_diff_right = ''
287 self.type_diff_left = ''
292 self.type_diff_left = ''
288 end
293 end
289
294
290 def inspect
295 def inspect
291 puts '### Start Line Diff ###'
296 puts '### Start Line Diff ###'
292 puts self.nb_line_left
297 puts self.nb_line_left
293 puts self.line_left
298 puts self.line_left
294 puts self.nb_line_right
299 puts self.nb_line_right
295 puts self.line_right
300 puts self.line_right
296 end
301 end
297 end
302 end
298
303
299 class DiffTableList < Array
304 class DiffTableList < Array
300
305
301 def initialize (diff, type="inline")
306 def initialize (diff, type="inline")
302 diff_table = DiffTable.new type
307 diff_table = DiffTable.new type
303 diff.each do |line|
308 diff.each do |line|
304 if line =~ /^Index: (.*)$/
309 if line =~ /^Index: (.*)$/
305 self << diff_table if diff_table.length > 1
310 self << diff_table if diff_table.length > 1
306 diff_table = DiffTable.new type
311 diff_table = DiffTable.new type
307 end
312 end
308 a = diff_table.add_line line
313 a = diff_table.add_line line
309 end
314 end
310 self << diff_table
315 self << diff_table
311 end
316 end
312 end
317 end
313
318
314 # Class for create a Diff
319 # Class for create a Diff
315 class DiffTable < Hash
320 class DiffTable < Hash
316
321
317 attr_reader :file_name, :line_num_l, :line_num_r
322 attr_reader :file_name, :line_num_l, :line_num_r
318
323
319 # Initialize with a Diff file and the type of Diff View
324 # Initialize with a Diff file and the type of Diff View
320 # The type view must be inline or sbs (side_by_side)
325 # The type view must be inline or sbs (side_by_side)
321 def initialize (type="inline")
326 def initialize (type="inline")
322 @parsing = false
327 @parsing = false
323 @nb_line = 1
328 @nb_line = 1
324 @start = false
329 @start = false
325 @before = 'same'
330 @before = 'same'
326 @second = true
331 @second = true
327 @type = type
332 @type = type
328 end
333 end
329
334
330 # Function for add a line of this Diff
335 # Function for add a line of this Diff
331 def add_line(line)
336 def add_line(line)
332 unless @parsing
337 unless @parsing
333 if line =~ /^Index: (.*)$/
338 if line =~ /^Index: (.*)$/
334 @file_name = $1
339 @file_name = $1
335 return false
340 return false
336 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
341 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
337 @line_num_l = $2.to_i
342 @line_num_l = $2.to_i
338 @line_num_r = $5.to_i
343 @line_num_r = $5.to_i
339 @parsing = true
344 @parsing = true
340 end
345 end
341 else
346 else
342 if line =~ /^_+$/
347 if line =~ /^_+$/
343 self.delete(self.keys.sort.last)
348 self.delete(self.keys.sort.last)
344 @parsing = false
349 @parsing = false
345 return false
350 return false
346 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
351 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
347 @line_num_l = $2.to_i
352 @line_num_l = $2.to_i
348 @line_num_r = $5.to_i
353 @line_num_r = $5.to_i
349 else
354 else
350 @nb_line += 1 if parse_line(line, @type)
355 @nb_line += 1 if parse_line(line, @type)
351 end
356 end
352 end
357 end
353 return true
358 return true
354 end
359 end
355
360
356 def inspect
361 def inspect
357 puts '### DIFF TABLE ###'
362 puts '### DIFF TABLE ###'
358 puts "file : #{file_name}"
363 puts "file : #{file_name}"
359 self.each do |d|
364 self.each do |d|
360 d.inspect
365 d.inspect
361 end
366 end
362 end
367 end
363
368
364 private
369 private
365
370
366 # Test if is a Side By Side type
371 # Test if is a Side By Side type
367 def sbs?(type, func)
372 def sbs?(type, func)
368 if @start and type == "sbs"
373 if @start and type == "sbs"
369 if @before == func and @second
374 if @before == func and @second
370 tmp_nb_line = @nb_line
375 tmp_nb_line = @nb_line
371 self[tmp_nb_line] = Diff.new
376 self[tmp_nb_line] = Diff.new
372 else
377 else
373 @second = false
378 @second = false
374 tmp_nb_line = @start
379 tmp_nb_line = @start
375 @start += 1
380 @start += 1
376 @nb_line -= 1
381 @nb_line -= 1
377 end
382 end
378 else
383 else
379 tmp_nb_line = @nb_line
384 tmp_nb_line = @nb_line
380 @start = @nb_line
385 @start = @nb_line
381 self[tmp_nb_line] = Diff.new
386 self[tmp_nb_line] = Diff.new
382 @second = true
387 @second = true
383 end
388 end
384 unless self[tmp_nb_line]
389 unless self[tmp_nb_line]
385 @nb_line += 1
390 @nb_line += 1
386 self[tmp_nb_line] = Diff.new
391 self[tmp_nb_line] = Diff.new
387 else
392 else
388 self[tmp_nb_line]
393 self[tmp_nb_line]
389 end
394 end
390 end
395 end
391
396
392 # Escape the HTML for the diff
397 # Escape the HTML for the diff
393 def escapeHTML(line)
398 def escapeHTML(line)
394 CGI.escapeHTML(line).gsub(/\s/, '&nbsp;')
399 CGI.escapeHTML(line).gsub(/\s/, '&nbsp;')
395 end
400 end
396
401
397 def parse_line (line, type="inline")
402 def parse_line (line, type="inline")
398 if line[0, 1] == "+"
403 if line[0, 1] == "+"
399 diff = sbs? type, 'add'
404 diff = sbs? type, 'add'
400 @before = 'add'
405 @before = 'add'
401 diff.line_left = escapeHTML line[1..-1]
406 diff.line_left = escapeHTML line[1..-1]
402 diff.nb_line_left = @line_num_l
407 diff.nb_line_left = @line_num_l
403 diff.type_diff_left = 'diff_in'
408 diff.type_diff_left = 'diff_in'
404 @line_num_l += 1
409 @line_num_l += 1
405 true
410 true
406 elsif line[0, 1] == "-"
411 elsif line[0, 1] == "-"
407 diff = sbs? type, 'remove'
412 diff = sbs? type, 'remove'
408 @before = 'remove'
413 @before = 'remove'
409 diff.line_right = escapeHTML line[1..-1]
414 diff.line_right = escapeHTML line[1..-1]
410 diff.nb_line_right = @line_num_r
415 diff.nb_line_right = @line_num_r
411 diff.type_diff_right = 'diff_out'
416 diff.type_diff_right = 'diff_out'
412 @line_num_r += 1
417 @line_num_r += 1
413 true
418 true
414 elsif line[0, 1] =~ /\s/
419 elsif line[0, 1] =~ /\s/
415 @before = 'same'
420 @before = 'same'
416 @start = false
421 @start = false
417 diff = Diff.new
422 diff = Diff.new
418 diff.line_right = escapeHTML line[1..-1]
423 diff.line_right = escapeHTML line[1..-1]
419 diff.nb_line_right = @line_num_r
424 diff.nb_line_right = @line_num_r
420 diff.line_left = escapeHTML line[1..-1]
425 diff.line_left = escapeHTML line[1..-1]
421 diff.nb_line_left = @line_num_l
426 diff.nb_line_left = @line_num_l
422 self[@nb_line] = diff
427 self[@nb_line] = diff
423 @line_num_l += 1
428 @line_num_l += 1
424 @line_num_r += 1
429 @line_num_r += 1
425 true
430 true
426 else
431 else
427 false
432 false
428 end
433 end
429 end
434 end
430 end
435 end
431 end No newline at end of file
436 end
@@ -1,24 +1,29
1 <div class="contextual">
1 <div class="contextual">
2 <% form_tag do %>
2 <% form_tag do %>
3 <p><%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %>
3 <p><%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %>
4 <%= submit_tag 'OK' %></p>
4 <%= submit_tag 'OK' %></p>
5 <% end %>
5 <% end %>
6 </div>
6 </div>
7
7
8 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
8 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
9
9
10 <% if @entry && @entry.is_file? %>
10 <% if @entry && @entry.is_file? %>
11 <h3><%=h @entry.name %></h3>
11 <h3><%=h @entry.name %></h3>
12 <p><%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' }, :class => "icon file" %> (<%= number_to_human_size @entry.size %>)</p>
12 <p>
13 <% if @entry.is_text? %>
14 <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> |
15 <% end %>
16 <%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' } %>
17 (<%= number_to_human_size @entry.size %>)</p>
13 <% end %>
18 <% end %>
14
19
15 <h3><%= l(:label_revision_plural) %></h3>
20 <h3><%= l(:label_revision_plural) %></h3>
16
21
17 <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :changesets => @changesets, :entry => @entry }%>
22 <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :changesets => @changesets, :entry => @entry }%>
18
23
19 <p><%= pagination_links_full @changeset_pages %>
24 <p><%= pagination_links_full @changeset_pages %>
20 [ <%= @changeset_pages.current.first_item %> - <%= @changeset_pages.current.last_item %> / <%= @changeset_count %> ]</p>
25 [ <%= @changeset_pages.current.first_item %> - <%= @changeset_pages.current.last_item %> / <%= @changeset_count %> ]</p>
21
26
22 <% content_for :header_tags do %>
27 <% content_for :header_tags do %>
23 <%= stylesheet_link_tag "scm" %>
28 <%= stylesheet_link_tag "scm" %>
24 <% end %> No newline at end of file
29 <% end %>
@@ -1,2 +1,3
1 require 'redmine/version'
1 require 'redmine/version'
2 require 'redmine/mime_type'
2 require 'redmine/acts_as_watchable/init'
3 require 'redmine/acts_as_watchable/init'
General Comments 0
You need to be logged in to leave comments. Login now