##// END OF EJS Templates
Merged r16285 (#24199)....
Jean-Philippe Lang -
r15912:46e11396b80b
parent child
Show More
@@ -1,206 +1,206
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 class AttachmentsController < ApplicationController
18 class AttachmentsController < ApplicationController
19 before_filter :find_attachment, :only => [:show, :download, :thumbnail, :destroy]
19 before_filter :find_attachment, :only => [:show, :download, :thumbnail, :destroy]
20 before_filter :find_editable_attachments, :only => [:edit, :update]
20 before_filter :find_editable_attachments, :only => [:edit, :update]
21 before_filter :file_readable, :read_authorize, :only => [:show, :download, :thumbnail]
21 before_filter :file_readable, :read_authorize, :only => [:show, :download, :thumbnail]
22 before_filter :delete_authorize, :only => :destroy
22 before_filter :delete_authorize, :only => :destroy
23 before_filter :authorize_global, :only => :upload
23 before_filter :authorize_global, :only => :upload
24
24
25 # Disable check for same origin requests for JS files, i.e. attachments with
25 # Disable check for same origin requests for JS files, i.e. attachments with
26 # MIME type text/javascript.
26 # MIME type text/javascript.
27 skip_after_filter :verify_same_origin_request, :only => :download
27 skip_after_filter :verify_same_origin_request, :only => :download
28
28
29 accept_api_auth :show, :download, :thumbnail, :upload, :destroy
29 accept_api_auth :show, :download, :thumbnail, :upload, :destroy
30
30
31 def show
31 def show
32 respond_to do |format|
32 respond_to do |format|
33 format.html {
33 format.html {
34 if @attachment.is_diff?
34 if @attachment.is_diff?
35 @diff = File.read(@attachment.diskfile, :mode => "rb")
35 @diff = File.read(@attachment.diskfile, :mode => "rb")
36 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
36 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
37 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
37 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
38 # Save diff type as user preference
38 # Save diff type as user preference
39 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
39 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
40 User.current.pref[:diff_type] = @diff_type
40 User.current.pref[:diff_type] = @diff_type
41 User.current.preference.save
41 User.current.preference.save
42 end
42 end
43 render :action => 'diff'
43 render :action => 'diff'
44 elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
44 elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
45 @content = File.read(@attachment.diskfile, :mode => "rb")
45 @content = File.read(@attachment.diskfile, :mode => "rb")
46 render :action => 'file'
46 render :action => 'file'
47 elsif @attachment.is_image?
47 elsif @attachment.is_image?
48 render :action => 'image'
48 render :action => 'image'
49 else
49 else
50 render :action => 'other'
50 render :action => 'other'
51 end
51 end
52 }
52 }
53 format.api
53 format.api
54 end
54 end
55 end
55 end
56
56
57 def download
57 def download
58 if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project)
58 if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project)
59 @attachment.increment_download
59 @attachment.increment_download
60 end
60 end
61
61
62 if stale?(:etag => @attachment.digest)
62 if stale?(:etag => @attachment.digest)
63 # images are sent inline
63 # images are sent inline
64 send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
64 send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
65 :type => detect_content_type(@attachment),
65 :type => detect_content_type(@attachment),
66 :disposition => disposition(@attachment)
66 :disposition => disposition(@attachment)
67 end
67 end
68 end
68 end
69
69
70 def thumbnail
70 def thumbnail
71 if @attachment.thumbnailable? && tbnail = @attachment.thumbnail(:size => params[:size])
71 if @attachment.thumbnailable? && tbnail = @attachment.thumbnail(:size => params[:size])
72 if stale?(:etag => tbnail)
72 if stale?(:etag => tbnail)
73 send_file tbnail,
73 send_file tbnail,
74 :filename => filename_for_content_disposition(@attachment.filename),
74 :filename => filename_for_content_disposition(@attachment.filename),
75 :type => detect_content_type(@attachment),
75 :type => detect_content_type(@attachment),
76 :disposition => 'inline'
76 :disposition => 'inline'
77 end
77 end
78 else
78 else
79 # No thumbnail for the attachment or thumbnail could not be created
79 # No thumbnail for the attachment or thumbnail could not be created
80 render :nothing => true, :status => 404
80 render :nothing => true, :status => 404
81 end
81 end
82 end
82 end
83
83
84 def upload
84 def upload
85 # Make sure that API users get used to set this content type
85 # Make sure that API users get used to set this content type
86 # as it won't trigger Rails' automatic parsing of the request body for parameters
86 # as it won't trigger Rails' automatic parsing of the request body for parameters
87 unless request.content_type == 'application/octet-stream'
87 unless request.content_type == 'application/octet-stream'
88 render :nothing => true, :status => 406
88 render :nothing => true, :status => 406
89 return
89 return
90 end
90 end
91
91
92 @attachment = Attachment.new(:file => request.raw_post)
92 @attachment = Attachment.new(:file => request.raw_post)
93 @attachment.author = User.current
93 @attachment.author = User.current
94 @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
94 @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
95 @attachment.content_type = params[:content_type].presence
95 @attachment.content_type = params[:content_type].presence
96 saved = @attachment.save
96 saved = @attachment.save
97
97
98 respond_to do |format|
98 respond_to do |format|
99 format.js
99 format.js
100 format.api {
100 format.api {
101 if saved
101 if saved
102 render :action => 'upload', :status => :created
102 render :action => 'upload', :status => :created
103 else
103 else
104 render_validation_errors(@attachment)
104 render_validation_errors(@attachment)
105 end
105 end
106 }
106 }
107 end
107 end
108 end
108 end
109
109
110 def edit
110 def edit
111 end
111 end
112
112
113 def update
113 def update
114 if params[:attachments].is_a?(Hash)
114 if params[:attachments].is_a?(Hash)
115 if Attachment.update_attachments(@attachments, params[:attachments])
115 if Attachment.update_attachments(@attachments, params[:attachments])
116 redirect_back_or_default home_path
116 redirect_back_or_default home_path
117 return
117 return
118 end
118 end
119 end
119 end
120 render :action => 'edit'
120 render :action => 'edit'
121 end
121 end
122
122
123 def destroy
123 def destroy
124 if @attachment.container.respond_to?(:init_journal)
124 if @attachment.container.respond_to?(:init_journal)
125 @attachment.container.init_journal(User.current)
125 @attachment.container.init_journal(User.current)
126 end
126 end
127 if @attachment.container
127 if @attachment.container
128 # Make sure association callbacks are called
128 # Make sure association callbacks are called
129 @attachment.container.attachments.delete(@attachment)
129 @attachment.container.attachments.delete(@attachment)
130 else
130 else
131 @attachment.destroy
131 @attachment.destroy
132 end
132 end
133
133
134 respond_to do |format|
134 respond_to do |format|
135 format.html { redirect_to_referer_or project_path(@project) }
135 format.html { redirect_to_referer_or project_path(@project) }
136 format.js
136 format.js
137 format.api { render_api_ok }
137 format.api { render_api_ok }
138 end
138 end
139 end
139 end
140
140
141 private
141 private
142
142
143 def find_attachment
143 def find_attachment
144 @attachment = Attachment.find(params[:id])
144 @attachment = Attachment.find(params[:id])
145 # Show 404 if the filename in the url is wrong
145 # Show 404 if the filename in the url is wrong
146 raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename
146 raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename
147 @project = @attachment.project
147 @project = @attachment.project
148 rescue ActiveRecord::RecordNotFound
148 rescue ActiveRecord::RecordNotFound
149 render_404
149 render_404
150 end
150 end
151
151
152 def find_editable_attachments
152 def find_editable_attachments
153 klass = params[:object_type].to_s.singularize.classify.constantize rescue nil
153 klass = params[:object_type].to_s.singularize.classify.constantize rescue nil
154 unless klass && klass.reflect_on_association(:attachments)
154 unless klass && klass.reflect_on_association(:attachments)
155 render_404
155 render_404
156 return
156 return
157 end
157 end
158
158
159 @container = klass.find(params[:object_id])
159 @container = klass.find(params[:object_id])
160 if @container.respond_to?(:visible?) && !@container.visible?
160 if @container.respond_to?(:visible?) && !@container.visible?
161 render_403
161 render_403
162 return
162 return
163 end
163 end
164 @attachments = @container.attachments.select(&:editable?)
164 @attachments = @container.attachments.select(&:editable?)
165 if @container.respond_to?(:project)
165 if @container.respond_to?(:project)
166 @project = @container.project
166 @project = @container.project
167 end
167 end
168 render_404 if @attachments.empty?
168 render_404 if @attachments.empty?
169 rescue ActiveRecord::RecordNotFound
169 rescue ActiveRecord::RecordNotFound
170 render_404
170 render_404
171 end
171 end
172
172
173 # Checks that the file exists and is readable
173 # Checks that the file exists and is readable
174 def file_readable
174 def file_readable
175 if @attachment.readable?
175 if @attachment.readable?
176 true
176 true
177 else
177 else
178 logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable."
178 logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable."
179 render_404
179 render_404
180 end
180 end
181 end
181 end
182
182
183 def read_authorize
183 def read_authorize
184 @attachment.visible? ? true : deny_access
184 @attachment.visible? ? true : deny_access
185 end
185 end
186
186
187 def delete_authorize
187 def delete_authorize
188 @attachment.deletable? ? true : deny_access
188 @attachment.deletable? ? true : deny_access
189 end
189 end
190
190
191 def detect_content_type(attachment)
191 def detect_content_type(attachment)
192 content_type = attachment.content_type
192 content_type = attachment.content_type
193 if content_type.blank? || content_type == "application/octet-stream"
193 if content_type.blank? || content_type == "application/octet-stream"
194 content_type = Redmine::MimeType.of(attachment.filename)
194 content_type = Redmine::MimeType.of(attachment.filename)
195 end
195 end
196 content_type.to_s
196 content_type.to_s
197 end
197 end
198
198
199 def disposition(attachment)
199 def disposition(attachment)
200 if attachment.is_image? || attachment.is_pdf?
200 if attachment.is_pdf?
201 'inline'
201 'inline'
202 else
202 else
203 'attachment'
203 'attachment'
204 end
204 end
205 end
205 end
206 end
206 end
@@ -1,452 +1,452
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 require 'redmine/scm/adapters'
21 require 'redmine/scm/adapters'
22
22
23 class ChangesetNotFound < Exception; end
23 class ChangesetNotFound < Exception; end
24 class InvalidRevisionParam < Exception; end
24 class InvalidRevisionParam < Exception; end
25
25
26 class RepositoriesController < ApplicationController
26 class RepositoriesController < ApplicationController
27 menu_item :repository
27 menu_item :repository
28 menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
28 menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
29 default_search_scope :changesets
29 default_search_scope :changesets
30
30
31 before_filter :find_project_by_project_id, :only => [:new, :create]
31 before_filter :find_project_by_project_id, :only => [:new, :create]
32 before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
32 before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
33 before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
33 before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
34 before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
34 before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
35 before_filter :authorize
35 before_filter :authorize
36 accept_rss_auth :revisions
36 accept_rss_auth :revisions
37
37
38 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
38 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
39
39
40 def new
40 def new
41 scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
41 scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
42 @repository = Repository.factory(scm)
42 @repository = Repository.factory(scm)
43 @repository.is_default = @project.repository.nil?
43 @repository.is_default = @project.repository.nil?
44 @repository.project = @project
44 @repository.project = @project
45 end
45 end
46
46
47 def create
47 def create
48 attrs = pickup_extra_info
48 attrs = pickup_extra_info
49 @repository = Repository.factory(params[:repository_scm])
49 @repository = Repository.factory(params[:repository_scm])
50 @repository.safe_attributes = params[:repository]
50 @repository.safe_attributes = params[:repository]
51 if attrs[:attrs_extra].keys.any?
51 if attrs[:attrs_extra].keys.any?
52 @repository.merge_extra_info(attrs[:attrs_extra])
52 @repository.merge_extra_info(attrs[:attrs_extra])
53 end
53 end
54 @repository.project = @project
54 @repository.project = @project
55 if request.post? && @repository.save
55 if request.post? && @repository.save
56 redirect_to settings_project_path(@project, :tab => 'repositories')
56 redirect_to settings_project_path(@project, :tab => 'repositories')
57 else
57 else
58 render :action => 'new'
58 render :action => 'new'
59 end
59 end
60 end
60 end
61
61
62 def edit
62 def edit
63 end
63 end
64
64
65 def update
65 def update
66 attrs = pickup_extra_info
66 attrs = pickup_extra_info
67 @repository.safe_attributes = attrs[:attrs]
67 @repository.safe_attributes = attrs[:attrs]
68 if attrs[:attrs_extra].keys.any?
68 if attrs[:attrs_extra].keys.any?
69 @repository.merge_extra_info(attrs[:attrs_extra])
69 @repository.merge_extra_info(attrs[:attrs_extra])
70 end
70 end
71 @repository.project = @project
71 @repository.project = @project
72 if @repository.save
72 if @repository.save
73 redirect_to settings_project_path(@project, :tab => 'repositories')
73 redirect_to settings_project_path(@project, :tab => 'repositories')
74 else
74 else
75 render :action => 'edit'
75 render :action => 'edit'
76 end
76 end
77 end
77 end
78
78
79 def pickup_extra_info
79 def pickup_extra_info
80 p = {}
80 p = {}
81 p_extra = {}
81 p_extra = {}
82 params[:repository].each do |k, v|
82 params[:repository].each do |k, v|
83 if k =~ /^extra_/
83 if k =~ /^extra_/
84 p_extra[k] = v
84 p_extra[k] = v
85 else
85 else
86 p[k] = v
86 p[k] = v
87 end
87 end
88 end
88 end
89 {:attrs => p, :attrs_extra => p_extra}
89 {:attrs => p, :attrs_extra => p_extra}
90 end
90 end
91 private :pickup_extra_info
91 private :pickup_extra_info
92
92
93 def committers
93 def committers
94 @committers = @repository.committers
94 @committers = @repository.committers
95 @users = @project.users.to_a
95 @users = @project.users.to_a
96 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
96 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
97 @users += User.where(:id => additional_user_ids).to_a unless additional_user_ids.empty?
97 @users += User.where(:id => additional_user_ids).to_a unless additional_user_ids.empty?
98 @users.compact!
98 @users.compact!
99 @users.sort!
99 @users.sort!
100 if request.post? && params[:committers].is_a?(Hash)
100 if request.post? && params[:committers].is_a?(Hash)
101 # Build a hash with repository usernames as keys and corresponding user ids as values
101 # Build a hash with repository usernames as keys and corresponding user ids as values
102 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
102 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
103 flash[:notice] = l(:notice_successful_update)
103 flash[:notice] = l(:notice_successful_update)
104 redirect_to settings_project_path(@project, :tab => 'repositories')
104 redirect_to settings_project_path(@project, :tab => 'repositories')
105 end
105 end
106 end
106 end
107
107
108 def destroy
108 def destroy
109 @repository.destroy if request.delete?
109 @repository.destroy if request.delete?
110 redirect_to settings_project_path(@project, :tab => 'repositories')
110 redirect_to settings_project_path(@project, :tab => 'repositories')
111 end
111 end
112
112
113 def show
113 def show
114 @repository.fetch_changesets if @project.active? && Setting.autofetch_changesets? && @path.empty?
114 @repository.fetch_changesets if @project.active? && Setting.autofetch_changesets? && @path.empty?
115
115
116 @entries = @repository.entries(@path, @rev)
116 @entries = @repository.entries(@path, @rev)
117 @changeset = @repository.find_changeset_by_name(@rev)
117 @changeset = @repository.find_changeset_by_name(@rev)
118 if request.xhr?
118 if request.xhr?
119 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
119 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
120 else
120 else
121 (show_error_not_found; return) unless @entries
121 (show_error_not_found; return) unless @entries
122 @changesets = @repository.latest_changesets(@path, @rev)
122 @changesets = @repository.latest_changesets(@path, @rev)
123 @properties = @repository.properties(@path, @rev)
123 @properties = @repository.properties(@path, @rev)
124 @repositories = @project.repositories
124 @repositories = @project.repositories
125 render :action => 'show'
125 render :action => 'show'
126 end
126 end
127 end
127 end
128
128
129 alias_method :browse, :show
129 alias_method :browse, :show
130
130
131 def changes
131 def changes
132 @entry = @repository.entry(@path, @rev)
132 @entry = @repository.entry(@path, @rev)
133 (show_error_not_found; return) unless @entry
133 (show_error_not_found; return) unless @entry
134 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
134 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
135 @properties = @repository.properties(@path, @rev)
135 @properties = @repository.properties(@path, @rev)
136 @changeset = @repository.find_changeset_by_name(@rev)
136 @changeset = @repository.find_changeset_by_name(@rev)
137 end
137 end
138
138
139 def revisions
139 def revisions
140 @changeset_count = @repository.changesets.count
140 @changeset_count = @repository.changesets.count
141 @changeset_pages = Paginator.new @changeset_count,
141 @changeset_pages = Paginator.new @changeset_count,
142 per_page_option,
142 per_page_option,
143 params['page']
143 params['page']
144 @changesets = @repository.changesets.
144 @changesets = @repository.changesets.
145 limit(@changeset_pages.per_page).
145 limit(@changeset_pages.per_page).
146 offset(@changeset_pages.offset).
146 offset(@changeset_pages.offset).
147 includes(:user, :repository, :parents).
147 includes(:user, :repository, :parents).
148 to_a
148 to_a
149
149
150 respond_to do |format|
150 respond_to do |format|
151 format.html { render :layout => false if request.xhr? }
151 format.html { render :layout => false if request.xhr? }
152 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
152 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
153 end
153 end
154 end
154 end
155
155
156 def raw
156 def raw
157 entry_and_raw(true)
157 entry_and_raw(true)
158 end
158 end
159
159
160 def entry
160 def entry
161 entry_and_raw(false)
161 entry_and_raw(false)
162 end
162 end
163
163
164 def entry_and_raw(is_raw)
164 def entry_and_raw(is_raw)
165 @entry = @repository.entry(@path, @rev)
165 @entry = @repository.entry(@path, @rev)
166 (show_error_not_found; return) unless @entry
166 (show_error_not_found; return) unless @entry
167
167
168 # If the entry is a dir, show the browser
168 # If the entry is a dir, show the browser
169 (show; return) if @entry.is_dir?
169 (show; return) if @entry.is_dir?
170
170
171 if is_raw
171 if is_raw
172 # Force the download
172 # Force the download
173 send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
173 send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
174 send_type = Redmine::MimeType.of(@path)
174 send_type = Redmine::MimeType.of(@path)
175 send_opt[:type] = send_type.to_s if send_type
175 send_opt[:type] = send_type.to_s if send_type
176 send_opt[:disposition] = disposition(@path)
176 send_opt[:disposition] = disposition(@path)
177 send_data @repository.cat(@path, @rev), send_opt
177 send_data @repository.cat(@path, @rev), send_opt
178 else
178 else
179 if !@entry.size || @entry.size <= Setting.file_max_size_displayed.to_i.kilobyte
179 if !@entry.size || @entry.size <= Setting.file_max_size_displayed.to_i.kilobyte
180 content = @repository.cat(@path, @rev)
180 content = @repository.cat(@path, @rev)
181 (show_error_not_found; return) unless content
181 (show_error_not_found; return) unless content
182
182
183 if content.size <= Setting.file_max_size_displayed.to_i.kilobyte &&
183 if content.size <= Setting.file_max_size_displayed.to_i.kilobyte &&
184 is_entry_text_data?(content, @path)
184 is_entry_text_data?(content, @path)
185 # TODO: UTF-16
185 # TODO: UTF-16
186 # Prevent empty lines when displaying a file with Windows style eol
186 # Prevent empty lines when displaying a file with Windows style eol
187 # Is this needed? AttachmentsController simply reads file.
187 # Is this needed? AttachmentsController simply reads file.
188 @content = content.gsub("\r\n", "\n")
188 @content = content.gsub("\r\n", "\n")
189 end
189 end
190 end
190 end
191 @changeset = @repository.find_changeset_by_name(@rev)
191 @changeset = @repository.find_changeset_by_name(@rev)
192 end
192 end
193 end
193 end
194 private :entry_and_raw
194 private :entry_and_raw
195
195
196 def is_entry_text_data?(ent, path)
196 def is_entry_text_data?(ent, path)
197 # UTF-16 contains "\x00".
197 # UTF-16 contains "\x00".
198 # It is very strict that file contains less than 30% of ascii symbols
198 # It is very strict that file contains less than 30% of ascii symbols
199 # in non Western Europe.
199 # in non Western Europe.
200 return true if Redmine::MimeType.is_type?('text', path)
200 return true if Redmine::MimeType.is_type?('text', path)
201 # Ruby 1.8.6 has a bug of integer divisions.
201 # Ruby 1.8.6 has a bug of integer divisions.
202 # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
202 # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
203 return false if ent.is_binary_data?
203 return false if ent.is_binary_data?
204 true
204 true
205 end
205 end
206 private :is_entry_text_data?
206 private :is_entry_text_data?
207
207
208 def annotate
208 def annotate
209 @entry = @repository.entry(@path, @rev)
209 @entry = @repository.entry(@path, @rev)
210 (show_error_not_found; return) unless @entry
210 (show_error_not_found; return) unless @entry
211
211
212 @annotate = @repository.scm.annotate(@path, @rev)
212 @annotate = @repository.scm.annotate(@path, @rev)
213 if @annotate.nil? || @annotate.empty?
213 if @annotate.nil? || @annotate.empty?
214 (render_error l(:error_scm_annotate); return)
214 (render_error l(:error_scm_annotate); return)
215 end
215 end
216 ann_buf_size = 0
216 ann_buf_size = 0
217 @annotate.lines.each do |buf|
217 @annotate.lines.each do |buf|
218 ann_buf_size += buf.size
218 ann_buf_size += buf.size
219 end
219 end
220 if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
220 if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
221 (render_error l(:error_scm_annotate_big_text_file); return)
221 (render_error l(:error_scm_annotate_big_text_file); return)
222 end
222 end
223 @changeset = @repository.find_changeset_by_name(@rev)
223 @changeset = @repository.find_changeset_by_name(@rev)
224 end
224 end
225
225
226 def revision
226 def revision
227 respond_to do |format|
227 respond_to do |format|
228 format.html
228 format.html
229 format.js {render :layout => false}
229 format.js {render :layout => false}
230 end
230 end
231 end
231 end
232
232
233 # Adds a related issue to a changeset
233 # Adds a related issue to a changeset
234 # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
234 # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
235 def add_related_issue
235 def add_related_issue
236 issue_id = params[:issue_id].to_s.sub(/^#/,'')
236 issue_id = params[:issue_id].to_s.sub(/^#/,'')
237 @issue = @changeset.find_referenced_issue_by_id(issue_id)
237 @issue = @changeset.find_referenced_issue_by_id(issue_id)
238 if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
238 if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
239 @issue = nil
239 @issue = nil
240 end
240 end
241
241
242 if @issue
242 if @issue
243 @changeset.issues << @issue
243 @changeset.issues << @issue
244 end
244 end
245 end
245 end
246
246
247 # Removes a related issue from a changeset
247 # Removes a related issue from a changeset
248 # DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
248 # DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
249 def remove_related_issue
249 def remove_related_issue
250 @issue = Issue.visible.find_by_id(params[:issue_id])
250 @issue = Issue.visible.find_by_id(params[:issue_id])
251 if @issue
251 if @issue
252 @changeset.issues.delete(@issue)
252 @changeset.issues.delete(@issue)
253 end
253 end
254 end
254 end
255
255
256 def diff
256 def diff
257 if params[:format] == 'diff'
257 if params[:format] == 'diff'
258 @diff = @repository.diff(@path, @rev, @rev_to)
258 @diff = @repository.diff(@path, @rev, @rev_to)
259 (show_error_not_found; return) unless @diff
259 (show_error_not_found; return) unless @diff
260 filename = "changeset_r#{@rev}"
260 filename = "changeset_r#{@rev}"
261 filename << "_r#{@rev_to}" if @rev_to
261 filename << "_r#{@rev_to}" if @rev_to
262 send_data @diff.join, :filename => "#{filename}.diff",
262 send_data @diff.join, :filename => "#{filename}.diff",
263 :type => 'text/x-patch',
263 :type => 'text/x-patch',
264 :disposition => 'attachment'
264 :disposition => 'attachment'
265 else
265 else
266 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
266 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
267 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
267 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
268
268
269 # Save diff type as user preference
269 # Save diff type as user preference
270 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
270 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
271 User.current.pref[:diff_type] = @diff_type
271 User.current.pref[:diff_type] = @diff_type
272 User.current.preference.save
272 User.current.preference.save
273 end
273 end
274 @cache_key = "repositories/diff/#{@repository.id}/" +
274 @cache_key = "repositories/diff/#{@repository.id}/" +
275 Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
275 Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
276 unless read_fragment(@cache_key)
276 unless read_fragment(@cache_key)
277 @diff = @repository.diff(@path, @rev, @rev_to)
277 @diff = @repository.diff(@path, @rev, @rev_to)
278 show_error_not_found unless @diff
278 show_error_not_found unless @diff
279 end
279 end
280
280
281 @changeset = @repository.find_changeset_by_name(@rev)
281 @changeset = @repository.find_changeset_by_name(@rev)
282 @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
282 @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
283 @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
283 @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
284 end
284 end
285 end
285 end
286
286
287 def stats
287 def stats
288 end
288 end
289
289
290 def graph
290 def graph
291 data = nil
291 data = nil
292 case params[:graph]
292 case params[:graph]
293 when "commits_per_month"
293 when "commits_per_month"
294 data = graph_commits_per_month(@repository)
294 data = graph_commits_per_month(@repository)
295 when "commits_per_author"
295 when "commits_per_author"
296 data = graph_commits_per_author(@repository)
296 data = graph_commits_per_author(@repository)
297 end
297 end
298 if data
298 if data
299 headers["Content-Type"] = "image/svg+xml"
299 headers["Content-Type"] = "image/svg+xml"
300 send_data(data, :type => "image/svg+xml", :disposition => "inline")
300 send_data(data, :type => "image/svg+xml", :disposition => "inline")
301 else
301 else
302 render_404
302 render_404
303 end
303 end
304 end
304 end
305
305
306 private
306 private
307
307
308 def find_repository
308 def find_repository
309 @repository = Repository.find(params[:id])
309 @repository = Repository.find(params[:id])
310 @project = @repository.project
310 @project = @repository.project
311 rescue ActiveRecord::RecordNotFound
311 rescue ActiveRecord::RecordNotFound
312 render_404
312 render_404
313 end
313 end
314
314
315 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
315 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
316
316
317 def find_project_repository
317 def find_project_repository
318 @project = Project.find(params[:id])
318 @project = Project.find(params[:id])
319 if params[:repository_id].present?
319 if params[:repository_id].present?
320 @repository = @project.repositories.find_by_identifier_param(params[:repository_id])
320 @repository = @project.repositories.find_by_identifier_param(params[:repository_id])
321 else
321 else
322 @repository = @project.repository
322 @repository = @project.repository
323 end
323 end
324 (render_404; return false) unless @repository
324 (render_404; return false) unless @repository
325 @path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
325 @path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
326 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
326 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
327 @rev_to = params[:rev_to]
327 @rev_to = params[:rev_to]
328
328
329 unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE)
329 unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE)
330 if @repository.branches.blank?
330 if @repository.branches.blank?
331 raise InvalidRevisionParam
331 raise InvalidRevisionParam
332 end
332 end
333 end
333 end
334 rescue ActiveRecord::RecordNotFound
334 rescue ActiveRecord::RecordNotFound
335 render_404
335 render_404
336 rescue InvalidRevisionParam
336 rescue InvalidRevisionParam
337 show_error_not_found
337 show_error_not_found
338 end
338 end
339
339
340 def find_changeset
340 def find_changeset
341 if @rev.present?
341 if @rev.present?
342 @changeset = @repository.find_changeset_by_name(@rev)
342 @changeset = @repository.find_changeset_by_name(@rev)
343 end
343 end
344 show_error_not_found unless @changeset
344 show_error_not_found unless @changeset
345 end
345 end
346
346
347 def show_error_not_found
347 def show_error_not_found
348 render_error :message => l(:error_scm_not_found), :status => 404
348 render_error :message => l(:error_scm_not_found), :status => 404
349 end
349 end
350
350
351 # Handler for Redmine::Scm::Adapters::CommandFailed exception
351 # Handler for Redmine::Scm::Adapters::CommandFailed exception
352 def show_error_command_failed(exception)
352 def show_error_command_failed(exception)
353 render_error l(:error_scm_command_failed, exception.message)
353 render_error l(:error_scm_command_failed, exception.message)
354 end
354 end
355
355
356 def graph_commits_per_month(repository)
356 def graph_commits_per_month(repository)
357 @date_to = User.current.today
357 @date_to = User.current.today
358 @date_from = @date_to << 11
358 @date_from = @date_to << 11
359 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
359 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
360 commits_by_day = Changeset.
360 commits_by_day = Changeset.
361 where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
361 where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
362 group(:commit_date).
362 group(:commit_date).
363 count
363 count
364 commits_by_month = [0] * 12
364 commits_by_month = [0] * 12
365 commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
365 commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
366
366
367 changes_by_day = Change.
367 changes_by_day = Change.
368 joins(:changeset).
368 joins(:changeset).
369 where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
369 where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
370 group(:commit_date).
370 group(:commit_date).
371 count
371 count
372 changes_by_month = [0] * 12
372 changes_by_month = [0] * 12
373 changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
373 changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
374
374
375 fields = []
375 fields = []
376 today = User.current.today
376 today = User.current.today
377 12.times {|m| fields << month_name(((today.month - 1 - m) % 12) + 1)}
377 12.times {|m| fields << month_name(((today.month - 1 - m) % 12) + 1)}
378
378
379 graph = SVG::Graph::Bar.new(
379 graph = SVG::Graph::Bar.new(
380 :height => 300,
380 :height => 300,
381 :width => 800,
381 :width => 800,
382 :fields => fields.reverse,
382 :fields => fields.reverse,
383 :stack => :side,
383 :stack => :side,
384 :scale_integers => true,
384 :scale_integers => true,
385 :step_x_labels => 2,
385 :step_x_labels => 2,
386 :show_data_values => false,
386 :show_data_values => false,
387 :graph_title => l(:label_commits_per_month),
387 :graph_title => l(:label_commits_per_month),
388 :show_graph_title => true
388 :show_graph_title => true
389 )
389 )
390
390
391 graph.add_data(
391 graph.add_data(
392 :data => commits_by_month[0..11].reverse,
392 :data => commits_by_month[0..11].reverse,
393 :title => l(:label_revision_plural)
393 :title => l(:label_revision_plural)
394 )
394 )
395
395
396 graph.add_data(
396 graph.add_data(
397 :data => changes_by_month[0..11].reverse,
397 :data => changes_by_month[0..11].reverse,
398 :title => l(:label_change_plural)
398 :title => l(:label_change_plural)
399 )
399 )
400
400
401 graph.burn
401 graph.burn
402 end
402 end
403
403
404 def graph_commits_per_author(repository)
404 def graph_commits_per_author(repository)
405 #data
405 #data
406 stats = repository.stats_by_author
406 stats = repository.stats_by_author
407 fields, commits_data, changes_data = [], [], []
407 fields, commits_data, changes_data = [], [], []
408 stats.each do |name, hsh|
408 stats.each do |name, hsh|
409 fields << name
409 fields << name
410 commits_data << hsh[:commits_count]
410 commits_data << hsh[:commits_count]
411 changes_data << hsh[:changes_count]
411 changes_data << hsh[:changes_count]
412 end
412 end
413
413
414 #expand to 10 values if needed
414 #expand to 10 values if needed
415 fields = fields + [""]*(10 - fields.length) if fields.length<10
415 fields = fields + [""]*(10 - fields.length) if fields.length<10
416 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
416 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
417 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
417 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
418
418
419 # Remove email address in usernames
419 # Remove email address in usernames
420 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
420 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
421
421
422 #prepare graph
422 #prepare graph
423 graph = SVG::Graph::BarHorizontal.new(
423 graph = SVG::Graph::BarHorizontal.new(
424 :height => 30 * commits_data.length,
424 :height => 30 * commits_data.length,
425 :width => 800,
425 :width => 800,
426 :fields => fields,
426 :fields => fields,
427 :stack => :side,
427 :stack => :side,
428 :scale_integers => true,
428 :scale_integers => true,
429 :show_data_values => false,
429 :show_data_values => false,
430 :rotate_y_labels => false,
430 :rotate_y_labels => false,
431 :graph_title => l(:label_commits_per_author),
431 :graph_title => l(:label_commits_per_author),
432 :show_graph_title => true
432 :show_graph_title => true
433 )
433 )
434 graph.add_data(
434 graph.add_data(
435 :data => commits_data,
435 :data => commits_data,
436 :title => l(:label_revision_plural)
436 :title => l(:label_revision_plural)
437 )
437 )
438 graph.add_data(
438 graph.add_data(
439 :data => changes_data,
439 :data => changes_data,
440 :title => l(:label_change_plural)
440 :title => l(:label_change_plural)
441 )
441 )
442 graph.burn
442 graph.burn
443 end
443 end
444
444
445 def disposition(path)
445 def disposition(path)
446 if Redmine::MimeType.is_type?('image', @path) || Redmine::MimeType.of(@path) == "application/pdf"
446 if Redmine::MimeType.of(@path) == "application/pdf"
447 'inline'
447 'inline'
448 else
448 else
449 'attachment'
449 'attachment'
450 end
450 end
451 end
451 end
452 end
452 end
General Comments 0
You need to be logged in to leave comments. Login now