Repository activities page in Gitorious 3 UI
[gitorious:mainline.git] / app / controllers / repositories_controller.rb
1 # encoding: utf-8
2 #--
3 #   Copyright (C) 2012-2013 Gitorious AS
4 #   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
5 #   Copyright (C) 2009 Fabio Akita <fabio.akita@gmail.com>
6 #   Copyright (C) 2008 David A. Cuadrado <krawek@gmail.com>
7 #   Copyright (C) 2008 Tor Arne Vestbø <tavestbo@trolltech.com>
8 #   Copyright (C) 2007, 2008 Johan Sørensen <johan@johansorensen.com>
9 #
10 #   This program is free software: you can redistribute it and/or modify
11 #   it under the terms of the GNU Affero General Public License as published by
12 #   the Free Software Foundation, either version 3 of the License, or
13 #   (at your option) any later version.
14 #
15 #   This program is distributed in the hope that it will be useful,
16 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
17 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 #   GNU Affero General Public License for more details.
19 #
20 #   You should have received a copy of the GNU Affero General Public License
21 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 #++
23
24 class RepositoriesController < ApplicationController
25   include Gitorious::Messaging::Publisher
26   before_filter :login_required, :except => [:index, :show, :writable_by, :repository_config]
27   before_filter :find_repository_owner, :except => [:writable_by, :repository_config]
28   before_filter :unauthorized_repository_owner_and_project, :only => [:writable_by, :repository_config]
29   before_filter :find_and_require_repository_adminship,
30   :only => [:edit, :update, :confirm_delete, :destroy]
31   always_skip_session :only => [:repository_config, :writable_by]
32   renders_in_site_specific_context :except => [:writable_by, :repository_config]
33
34   def index
35     if term = params[:filter]
36       @repositories = filter(@project.search_repositories(term))
37     else
38       @repositories = paginate(page_free_redirect_options) do
39         filter_paginated(params[:page], Repository.per_page) do |page|
40           @owner.repositories.regular.paginate(:include => [:user, :events, :project], :page => page)
41         end
42       end
43     end
44
45     return if @repositories.count == 0 && params.key?(:page)
46
47     respond_to do |wants|
48       wants.html
49       wants.xml {render :xml => @repositories.to_xml}
50       wants.json {render :json => RepositorySerializer.new(self).to_json(@repositories)}
51     end
52   end
53
54   def show
55     repository = repository_to_clone
56     @events = paginated_events(repository)
57     return if @events.count == 0 && params.key?(:page)
58     response.headers["Refresh"] = "5" unless repository.ready
59
60     respond_to do |format|
61       format.html do
62         render(:action => :show, :layout => "ui3/layouts/layout", :locals => {
63             :repository => RepositoryPresenter.new(repository),
64             :ref => repository.head_candidate_name,
65             :events => @events,
66             :atom_auto_discovery_url => project_repository_path(repository.project, repository, :format => :atom),
67             :atom_auto_discovery_title => "#{repository.title} ATOM feed"
68           })
69       end
70       format.xml  { render :xml => repository }
71       format.atom {  }
72     end
73   end
74
75   def new
76     outcome = PrepareProjectRepository.new(self, @project, current_user).execute({})
77     pre_condition_failed(outcome)
78     outcome.success { |result| render_form(result, @project, @owner) }
79   end
80
81   def create
82     cmd = CreateProjectRepository.new(self, @project, current_user)
83     outcome = cmd.execute({ :private => params[:private] }.merge(params[:repository]))
84
85     pre_condition_failed(outcome) do |f|
86       f.when(:admin_required) { |c| respond_denied_and_redirect_to(@project) }
87     end
88
89     outcome.failure do |repository|
90       render_form(repository, @project, @owner)
91     end
92
93     outcome.success do |result|
94       flash[:success] = I18n.t("repositories_controller.create_success")
95       redirect_to([result.project, result])
96     end
97   end
98
99   def edit
100     @root = Breadcrumb::EditRepository.new(@repository)
101     @groups = Team.for_user(current_user)
102     @heads = @repository.git.heads
103   end
104
105   def update
106     @root = Breadcrumb::EditRepository.new(@repository)
107     @groups = Team.for_user(current_user)
108     @heads = @repository.git.heads
109
110     Repository.transaction do
111       unless params[:repository][:owner_id].blank?
112         new_owner = @groups.detect {|group| group.id == params[:repository][:owner_id].to_i}
113         @repository.change_owner_to!(new_owner)
114       end
115
116       @repository.head = params[:repository][:head]
117
118       @repository.log_changes_with_user(current_user) do
119         @repository.replace_value(:name, params[:repository][:name])
120         @repository.replace_value(:description, params[:repository][:description], true)
121       end
122       @repository.deny_force_pushing = params[:repository][:deny_force_pushing]
123       @repository.notify_committers_on_new_merge_request = params[:repository][:notify_committers_on_new_merge_request]
124       @repository.merge_requests_enabled = params[:repository][:merge_requests_enabled]
125       @repository.save!
126       flash[:success] = "Repository updated"
127       redirect_to [@repository.project, @repository]
128     end
129   rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound
130     render :action => "edit"
131   end
132
133   # Used internally to check write permissions by gitorious
134   def writable_by
135     @repository = @owner.cloneable_repositories.find_by_name_in_project!(params[:id], @containing_project)
136     user = User.find_by_login(params[:username])
137
138     if user && result = /^refs\/merge-requests\/(\d+)$/.match(params[:git_path].to_s)
139       # git_path is a merge request
140       begin
141         if merge_request = @repository.merge_requests.find_by_sequence_number!(result[1]) and (merge_request.user == user)
142           render :text => "true" and return
143         end
144       rescue ActiveRecord::RecordNotFound # No such merge request
145       end
146     elsif user && can_push?(user, @repository)
147       render :text => "true" and return
148     end
149     render :text => 'false' and return
150   end
151
152   def repository_config
153     @repository = @owner.cloneable_repositories.find_by_name_in_project!(params[:id],
154       @containing_project)
155     authorize_configuration_access(@repository)
156     config_data = "real_path:#{@repository.real_gitdir}\n"
157     config_data << "force_pushing_denied:"
158     config_data << (@repository.deny_force_pushing? ? 'true' : 'false')
159     headers["Cache-Control"] = "public, max-age=600"
160
161     render :text => config_data, :content_type => "text/x-yaml"
162   end
163
164   def confirm_delete
165     @repository = repository_to_clone
166     unless can_delete?(current_user, @repository)
167       flash[:error] = I18n.t "repositories_controller.adminship_error"
168       redirect_to(@owner) and return
169     end
170   end
171
172   def destroy
173     @repository = repository_to_clone
174     if can_delete?(current_user, @repository)
175       repo_name = @repository.name
176       flash[:notice] = I18n.t "repositories_controller.destroy_notice"
177       @repository.destroy
178       @repository.project.create_event(Action::DELETE_REPOSITORY, @owner,
179         current_user, repo_name)
180     else
181       flash[:error] = I18n.t "repositories_controller.destroy_error"
182     end
183     redirect_to @owner
184   end
185
186   private
187   def render_form(repository, project, owner)
188     render(:action => :new, :locals => {
189         :repository => repository,
190         :project => project,
191         :owner => owner
192       })
193   end
194
195   def find_and_require_repository_adminship
196     @repository = @owner.repositories.find_by_name_in_project!(params[:id],
197       @containing_project)
198     unless admin?(current_user, authorize_access_to(@repository))
199       respond_denied_and_redirect_to(project_repository_path(@repository.project, @repository))
200       return
201     end
202   end
203
204   def respond_denied_and_redirect_to(target)
205     respond_to do |format|
206       format.html {
207         flash[:error] = I18n.t "repositories_controller.adminship_error"
208         redirect_to(target)
209       }
210       format.xml  {
211         render :text => I18n.t( "repositories_controller.adminship_error"),
212         :status => :forbidden
213       }
214     end
215   end
216
217   def unauthorized_repository_owner_and_project
218     if params[:user_id]
219       @owner = User.find_by_login!(params[:user_id])
220       @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
221     elsif params[:group_id]
222       @owner = Group.find_by_name!(params[:group_id])
223       @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
224     elsif params[:project_id]
225       @owner = Project.find_by_slug!(params[:project_id])
226       @project = @owner
227     else
228       raise ActiveRecord::RecordNotFound
229     end
230   end
231
232   def authorize_configuration_access(repository)
233     return true if !Gitorious.private_repositories?
234     if !can_read?(User.find_by_login(params[:username]), repository)
235       raise Gitorious::Authorization::UnauthorizedError.new(request.fullpath)
236     end
237   end
238
239   def repository_to_clone
240     repo = @owner.repositories.find_by_name_in_project!(params[:id], @containing_project)
241     authorize_access_to(repo)
242   end
243
244   def paginated_events(repository)
245     paginate(page_free_redirect_options) do
246       if !Gitorious.private_repositories?
247         Rails.cache.fetch("new_paginated_events_in_repo_#{repository.id}:#{params[:page] || 1}", :expires_in => 1.minute) do
248           marshalable_events(unfiltered_paginated_events(repository))
249         end
250       else
251         filter_paginated(params[:page], Event.per_page) do |page|
252           unfiltered_paginated_events(repository)
253         end
254       end
255     end
256   end
257
258   def unfiltered_paginated_events(repository)
259     repository.events.top.paginate(:page => params[:page],
260       :order => "created_at desc")
261   end
262
263   def pre_condition_failed(outcome)
264     super(outcome) do |f|
265       f.when(:admin_required) { |c| respond_denied_and_redirect_to(@project) }
266     end
267   end
268 end