To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / app / controllers / repositories_controller.rb @ 418:a9921f3e9088

History | View | Annotate | Download (10.5 KB)

1
# Redmine - project management software
2
# Copyright (C) 2006-2009  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
require 'SVG/Graph/Bar'
19
require 'SVG/Graph/BarHorizontal'
20
require 'digest/sha1'
21

    
22
class ChangesetNotFound < Exception; end
23
class InvalidRevisionParam < Exception; end
24

    
25
class RepositoriesController < ApplicationController
26
  menu_item :repository
27
  menu_item :settings, :only => :edit
28
  default_search_scope :changesets
29
  
30
  before_filter :find_repository, :except => :edit
31
  before_filter :find_project, :only => :edit
32
  before_filter :authorize
33
  accept_key_auth :revisions
34
  
35
  rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
36
  
37
  def edit
38
    @repository = @project.repository
39

    
40
    if !@repository
41

    
42
      params[:repository_scm]='Mercurial'
43

    
44
      @repository = Repository.factory(params[:repository_scm])
45
      @repository.project = @project if @repository
46
    end
47
    if request.post? && @repository
48
      @repository.attributes = params[:repository]
49
      @repository.save
50
    end
51

    
52
    render(:update) do |page|
53
      page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'
54
      if @repository && !@project.repository
55
        @project.reload #needed to reload association
56
        page.replace_html "main-menu", render_main_menu(@project)
57
      end
58
    end
59
  end
60
  
61
  def committers
62
    @committers = @repository.committers
63
    @users = @project.users
64
    additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
65
    @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
66
    @users.compact!
67
    @users.sort!
68
    if request.post? && params[:committers].is_a?(Hash)
69
      # Build a hash with repository usernames as keys and corresponding user ids as values
70
      @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
71
      flash[:notice] = l(:notice_successful_update)
72
      redirect_to :action => 'committers', :id => @project
73
    end
74
  end
75
  
76
  def destroy
77
    @repository.destroy
78
    redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
79
  end
80
  
81
  def show 
82
    @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
83

    
84
    @entries = @repository.entries(@path, @rev)
85
    if request.xhr?
86
      @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
87
    else
88
      (show_error_not_found; return) unless @entries
89
      @changesets = @repository.latest_changesets(@path, @rev)
90
      @properties = @repository.properties(@path, @rev)
91
      render :action => 'show'
92
    end
93
  end
94

    
95
  alias_method :browse, :show
96
  
97
  def changes
98
    @entry = @repository.entry(@path, @rev)
99
    (show_error_not_found; return) unless @entry
100
    @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
101
    @properties = @repository.properties(@path, @rev)
102
  end
103
  
104
  def revisions
105
    @changeset_count = @repository.changesets.count
106
    @changeset_pages = Paginator.new self, @changeset_count,
107
                                                                      per_page_option,
108
                                                                      params['page']                                                                
109
    @changesets = @repository.changesets.find(:all,
110
                                                :limit  =>  @changeset_pages.items_per_page,
111
                                                :offset =>  @changeset_pages.current.offset,
112
            :include => [:user, :repository])
113

    
114
    respond_to do |format|
115
      format.html { render :layout => false if request.xhr? }
116
      format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
117
    end
118
  end
119
  
120
  def entry
121
    @entry = @repository.entry(@path, @rev)
122
    (show_error_not_found; return) unless @entry
123

    
124
    # If the entry is a dir, show the browser
125
    (show; return) if @entry.is_dir?
126

    
127
    @content = @repository.cat(@path, @rev)
128
    (show_error_not_found; return) unless @content
129
    if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
130
      # Force the download
131
      send_data @content, :filename => @path.split('/').last
132
    else
133
      # Prevent empty lines when displaying a file with Windows style eol
134
      @content.gsub!("\r\n", "\n")
135
   end
136
  end
137
  
138
  def annotate
139
    @entry = @repository.entry(@path, @rev)
140
    (show_error_not_found; return) unless @entry
141
    
142
    @annotate = @repository.scm.annotate(@path, @rev)
143
    (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
144
  end
145
  
146
  def revision
147
    @changeset = @repository.find_changeset_by_name(@rev)
148
    raise ChangesetNotFound unless @changeset
149

    
150
    respond_to do |format|
151
      format.html
152
      format.js {render :layout => false}
153
    end
154
  rescue ChangesetNotFound
155
    show_error_not_found
156
  end
157
  
158
  def diff
159
    if params[:format] == 'diff'
160
      @diff = @repository.diff(@path, @rev, @rev_to)
161
      (show_error_not_found; return) unless @diff
162
      filename = "changeset_r#{@rev}"
163
      filename << "_r#{@rev_to}" if @rev_to
164
      send_data @diff.join, :filename => "#{filename}.diff",
165
                            :type => 'text/x-patch',
166
                            :disposition => 'attachment'
167
    else
168
      @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
169
      @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
170
      
171
      # Save diff type as user preference
172
      if User.current.logged? && @diff_type != User.current.pref[:diff_type]
173
        User.current.pref[:diff_type] = @diff_type
174
        User.current.preference.save
175
      end
176
      
177
      @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")    
178
      unless read_fragment(@cache_key)
179
        @diff = @repository.diff(@path, @rev, @rev_to)
180
        show_error_not_found unless @diff
181
      end
182
    end
183
  end
184
  
185
  def stats  
186
  end
187
  
188
  def graph
189
    data = nil    
190
    case params[:graph]
191
    when "commits_per_month"
192
      data = graph_commits_per_month(@repository)
193
    when "commits_per_author"
194
      data = graph_commits_per_author(@repository)
195
    end
196
    if data
197
      headers["Content-Type"] = "image/svg+xml"
198
      send_data(data, :type => "image/svg+xml", :disposition => "inline")
199
    else
200
      render_404
201
    end
202
  end
203
  
204
private
205
  def find_repository
206
    @project = Project.find(params[:id])
207
    @repository = @project.repository
208
    (render_404; return false) unless @repository
209
    @path = params[:path].join('/') unless params[:path].nil?
210
    @path ||= ''
211
    @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
212
    @rev_to = params[:rev_to]
213
  rescue ActiveRecord::RecordNotFound
214
    render_404
215
  rescue InvalidRevisionParam
216
    show_error_not_found
217
  end
218

    
219
  def show_error_not_found
220
    render_error l(:error_scm_not_found)
221
  end
222
  
223
  # Handler for Redmine::Scm::Adapters::CommandFailed exception
224
  def show_error_command_failed(exception)
225
    render_error l(:error_scm_command_failed, exception.message)
226
  end
227
  
228
  def graph_commits_per_month(repository)
229
    @date_to = Date.today
230
    @date_from = @date_to << 11
231
    @date_from = Date.civil(@date_from.year, @date_from.month, 1)
232
    commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
233
    commits_by_month = [0] * 12
234
    commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
235

    
236
    changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
237
    changes_by_month = [0] * 12
238
    changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
239
   
240
    fields = []
241
    12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
242
  
243
    graph = SVG::Graph::Bar.new(
244
      :height => 300,
245
      :width => 800,
246
      :fields => fields.reverse,
247
      :stack => :side,
248
      :scale_integers => true,
249
      :step_x_labels => 2,
250
      :show_data_values => false,
251
      :graph_title => l(:label_commits_per_month),
252
      :show_graph_title => true
253
    )
254
    
255
    graph.add_data(
256
      :data => commits_by_month[0..11].reverse,
257
      :title => l(:label_revision_plural)
258
    )
259

    
260
    graph.add_data(
261
      :data => changes_by_month[0..11].reverse,
262
      :title => l(:label_change_plural)
263
    )
264
    
265
    graph.burn
266
  end
267

    
268
  def graph_commits_per_author(repository)
269
    commits_by_author = repository.changesets.count(:all, :group => :committer)
270
    commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
271

    
272
    changes_by_author = repository.changes.count(:all, :group => :committer)
273
    h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
274
    
275
    fields = commits_by_author.collect {|r| r.first}
276
    commits_data = commits_by_author.collect {|r| r.last}
277
    changes_data = commits_by_author.collect {|r| h[r.first] || 0}
278
    
279
    fields = fields + [""]*(10 - fields.length) if fields.length<10
280
    commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
281
    changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
282
    
283
    # Remove email adress in usernames
284
    fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
285
    
286
    graph = SVG::Graph::BarHorizontal.new(
287
      :height => 400,
288
      :width => 800,
289
      :fields => fields,
290
      :stack => :side,
291
      :scale_integers => true,
292
      :show_data_values => false,
293
      :rotate_y_labels => false,
294
      :graph_title => l(:label_commits_per_author),
295
      :show_graph_title => true
296
    )
297
    
298
    graph.add_data(
299
      :data => commits_data,
300
      :title => l(:label_revision_plural)
301
    )
302

    
303
    graph.add_data(
304
      :data => changes_data,
305
      :title => l(:label_change_plural)
306
    )
307
       
308
    graph.burn
309
  end
310

    
311
end
312
  
313
class Date
314
  def months_ago(date = Date.today)
315
    (date.year - self.year)*12 + (date.month - self.month)
316
  end
317

    
318
  def weeks_ago(date = Date.today)
319
    (date.year - self.year)*52 + (date.cweek - self.cweek)
320
  end
321
end
322

    
323
class String
324
  def with_leading_slash
325
    starts_with?('/') ? self : "/#{self}"
326
  end
327
end