Revision 1297:0a574315af3e .svn/pristine/5b
| .svn/pristine/5b/5b4d21bca26fd3fe16dbb733fe9dd17bcabdb041.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2012 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 File.expand_path('../../test_helper', __FILE__)
|
|
| 19 |
require 'repositories_controller' |
|
| 20 |
|
|
| 21 |
# Re-raise errors caught by the controller. |
|
| 22 |
class RepositoriesController; def rescue_action(e) raise e end; end |
|
| 23 |
|
|
| 24 |
class RepositoriesControllerTest < ActionController::TestCase |
|
| 25 |
fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, |
|
| 26 |
:repositories, :issues, :issue_statuses, :changesets, :changes, |
|
| 27 |
:issue_categories, :enumerations, :custom_fields, :custom_values, :trackers |
|
| 28 |
|
|
| 29 |
def setup |
|
| 30 |
@controller = RepositoriesController.new |
|
| 31 |
@request = ActionController::TestRequest.new |
|
| 32 |
@response = ActionController::TestResponse.new |
|
| 33 |
User.current = nil |
|
| 34 |
end |
|
| 35 |
|
|
| 36 |
def test_new |
|
| 37 |
@request.session[:user_id] = 1 |
|
| 38 |
get :new, :project_id => 'subproject1' |
|
| 39 |
assert_response :success |
|
| 40 |
assert_template 'new' |
|
| 41 |
assert_kind_of Repository::Subversion, assigns(:repository) |
|
| 42 |
assert assigns(:repository).new_record? |
|
| 43 |
assert_tag 'input', :attributes => {:name => 'repository[url]', :disabled => nil}
|
|
| 44 |
end |
|
| 45 |
|
|
| 46 |
def test_new_should_propose_enabled_scm_only |
|
| 47 |
@request.session[:user_id] = 1 |
|
| 48 |
with_settings :enabled_scm => ['Mercurial', 'Git'] do |
|
| 49 |
get :new, :project_id => 'subproject1' |
|
| 50 |
end |
|
| 51 |
assert_response :success |
|
| 52 |
assert_template 'new' |
|
| 53 |
assert_kind_of Repository::Mercurial, assigns(:repository) |
|
| 54 |
assert_tag 'select', :attributes => {:name => 'repository_scm'},
|
|
| 55 |
:children => {:count => 3}
|
|
| 56 |
assert_tag 'select', :attributes => {:name => 'repository_scm'},
|
|
| 57 |
:child => {:tag => 'option', :attributes => {:value => 'Mercurial', :selected => 'selected'}}
|
|
| 58 |
assert_tag 'select', :attributes => {:name => 'repository_scm'},
|
|
| 59 |
:child => {:tag => 'option', :attributes => {:value => 'Git', :selected => nil}}
|
|
| 60 |
end |
|
| 61 |
|
|
| 62 |
def test_create |
|
| 63 |
@request.session[:user_id] = 1 |
|
| 64 |
assert_difference 'Repository.count' do |
|
| 65 |
post :create, :project_id => 'subproject1', |
|
| 66 |
:repository_scm => 'Subversion', |
|
| 67 |
:repository => {:url => 'file:///test', :is_default => '1', :identifier => ''}
|
|
| 68 |
end |
|
| 69 |
assert_response 302 |
|
| 70 |
repository = Repository.first(:order => 'id DESC') |
|
| 71 |
assert_kind_of Repository::Subversion, repository |
|
| 72 |
assert_equal 'file:///test', repository.url |
|
| 73 |
end |
|
| 74 |
|
|
| 75 |
def test_create_with_failure |
|
| 76 |
@request.session[:user_id] = 1 |
|
| 77 |
assert_no_difference 'Repository.count' do |
|
| 78 |
post :create, :project_id => 'subproject1', |
|
| 79 |
:repository_scm => 'Subversion', |
|
| 80 |
:repository => {:url => 'invalid'}
|
|
| 81 |
end |
|
| 82 |
assert_response :success |
|
| 83 |
assert_template 'new' |
|
| 84 |
assert_kind_of Repository::Subversion, assigns(:repository) |
|
| 85 |
assert assigns(:repository).new_record? |
|
| 86 |
end |
|
| 87 |
|
|
| 88 |
def test_edit |
|
| 89 |
@request.session[:user_id] = 1 |
|
| 90 |
get :edit, :id => 11 |
|
| 91 |
assert_response :success |
|
| 92 |
assert_template 'edit' |
|
| 93 |
assert_equal Repository.find(11), assigns(:repository) |
|
| 94 |
assert_tag 'input', :attributes => {:name => 'repository[url]', :value => 'svn://localhost/test', :disabled => 'disabled'}
|
|
| 95 |
end |
|
| 96 |
|
|
| 97 |
def test_update |
|
| 98 |
@request.session[:user_id] = 1 |
|
| 99 |
put :update, :id => 11, :repository => {:password => 'test_update'}
|
|
| 100 |
assert_response 302 |
|
| 101 |
assert_equal 'test_update', Repository.find(11).password |
|
| 102 |
end |
|
| 103 |
|
|
| 104 |
def test_update_with_failure |
|
| 105 |
@request.session[:user_id] = 1 |
|
| 106 |
put :update, :id => 11, :repository => {:password => 'x'*260}
|
|
| 107 |
assert_response :success |
|
| 108 |
assert_template 'edit' |
|
| 109 |
assert_equal Repository.find(11), assigns(:repository) |
|
| 110 |
end |
|
| 111 |
|
|
| 112 |
def test_destroy |
|
| 113 |
@request.session[:user_id] = 1 |
|
| 114 |
assert_difference 'Repository.count', -1 do |
|
| 115 |
delete :destroy, :id => 11 |
|
| 116 |
end |
|
| 117 |
assert_response 302 |
|
| 118 |
assert_nil Repository.find_by_id(11) |
|
| 119 |
end |
|
| 120 |
|
|
| 121 |
def test_revisions |
|
| 122 |
get :revisions, :id => 1 |
|
| 123 |
assert_response :success |
|
| 124 |
assert_template 'revisions' |
|
| 125 |
assert_equal Repository.find(10), assigns(:repository) |
|
| 126 |
assert_not_nil assigns(:changesets) |
|
| 127 |
end |
|
| 128 |
|
|
| 129 |
def test_revisions_for_other_repository |
|
| 130 |
repository = Repository::Subversion.create!(:project_id => 1, :identifier => 'foo', :url => 'file:///foo') |
|
| 131 |
|
|
| 132 |
get :revisions, :id => 1, :repository_id => 'foo' |
|
| 133 |
assert_response :success |
|
| 134 |
assert_template 'revisions' |
|
| 135 |
assert_equal repository, assigns(:repository) |
|
| 136 |
assert_not_nil assigns(:changesets) |
|
| 137 |
end |
|
| 138 |
|
|
| 139 |
def test_revisions_for_invalid_repository |
|
| 140 |
get :revisions, :id => 1, :repository_id => 'foo' |
|
| 141 |
assert_response 404 |
|
| 142 |
end |
|
| 143 |
|
|
| 144 |
def test_revision |
|
| 145 |
get :revision, :id => 1, :rev => 1 |
|
| 146 |
assert_response :success |
|
| 147 |
assert_not_nil assigns(:changeset) |
|
| 148 |
assert_equal "1", assigns(:changeset).revision |
|
| 149 |
end |
|
| 150 |
|
|
| 151 |
def test_revision_should_not_change_the_project_menu_link |
|
| 152 |
get :revision, :id => 1, :rev => 1 |
|
| 153 |
assert_response :success |
|
| 154 |
|
|
| 155 |
assert_tag 'a', :attributes => {:href => '/projects/ecookbook/repository', :class => /repository/},
|
|
| 156 |
:ancestor => {:attributes => {:id => 'main-menu'}}
|
|
| 157 |
end |
|
| 158 |
|
|
| 159 |
def test_revision_with_before_nil_and_afer_normal |
|
| 160 |
get :revision, {:id => 1, :rev => 1}
|
|
| 161 |
assert_response :success |
|
| 162 |
assert_template 'revision' |
|
| 163 |
assert_no_tag :tag => "div", :attributes => { :class => "contextual" },
|
|
| 164 |
:child => { :tag => "a", :attributes => { :href => '/projects/ecookbook/repository/revisions/0'}
|
|
| 165 |
} |
|
| 166 |
assert_tag :tag => "div", :attributes => { :class => "contextual" },
|
|
| 167 |
:child => { :tag => "a", :attributes => { :href => '/projects/ecookbook/repository/revisions/2'}
|
|
| 168 |
} |
|
| 169 |
end |
|
| 170 |
|
|
| 171 |
def test_add_related_issue |
|
| 172 |
@request.session[:user_id] = 2 |
|
| 173 |
assert_difference 'Changeset.find(103).issues.size' do |
|
| 174 |
xhr :post, :add_related_issue, :id => 1, :rev => 4, :issue_id => 2, :format => 'js' |
|
| 175 |
assert_response :success |
|
| 176 |
assert_template 'add_related_issue' |
|
| 177 |
assert_equal 'text/javascript', response.content_type |
|
| 178 |
end |
|
| 179 |
assert_equal [2], Changeset.find(103).issue_ids |
|
| 180 |
assert_include 'related-issues', response.body |
|
| 181 |
assert_include 'Feature request #2', response.body |
|
| 182 |
end |
|
| 183 |
|
|
| 184 |
def test_add_related_issue_with_invalid_issue_id |
|
| 185 |
@request.session[:user_id] = 2 |
|
| 186 |
assert_no_difference 'Changeset.find(103).issues.size' do |
|
| 187 |
xhr :post, :add_related_issue, :id => 1, :rev => 4, :issue_id => 9999, :format => 'js' |
|
| 188 |
assert_response :success |
|
| 189 |
assert_template 'add_related_issue' |
|
| 190 |
assert_equal 'text/javascript', response.content_type |
|
| 191 |
end |
|
| 192 |
assert_include 'alert("Issue is invalid")', response.body
|
|
| 193 |
end |
|
| 194 |
|
|
| 195 |
def test_remove_related_issue |
|
| 196 |
Changeset.find(103).issues << Issue.find(1) |
|
| 197 |
Changeset.find(103).issues << Issue.find(2) |
|
| 198 |
|
|
| 199 |
@request.session[:user_id] = 2 |
|
| 200 |
assert_difference 'Changeset.find(103).issues.size', -1 do |
|
| 201 |
xhr :delete, :remove_related_issue, :id => 1, :rev => 4, :issue_id => 2, :format => 'js' |
|
| 202 |
assert_response :success |
|
| 203 |
assert_template 'remove_related_issue' |
|
| 204 |
assert_equal 'text/javascript', response.content_type |
|
| 205 |
end |
|
| 206 |
assert_equal [1], Changeset.find(103).issue_ids |
|
| 207 |
assert_include 'related-issue-2', response.body |
|
| 208 |
end |
|
| 209 |
|
|
| 210 |
def test_graph_commits_per_month |
|
| 211 |
# Make sure there's some data to display |
|
| 212 |
latest = Project.find(1).repository.changesets.maximum(:commit_date) |
|
| 213 |
assert_not_nil latest |
|
| 214 |
Date.stubs(:today).returns(latest.to_date + 10) |
|
| 215 |
|
|
| 216 |
get :graph, :id => 1, :graph => 'commits_per_month' |
|
| 217 |
assert_response :success |
|
| 218 |
assert_equal 'image/svg+xml', @response.content_type |
|
| 219 |
end |
|
| 220 |
|
|
| 221 |
def test_graph_commits_per_author |
|
| 222 |
get :graph, :id => 1, :graph => 'commits_per_author' |
|
| 223 |
assert_response :success |
|
| 224 |
assert_equal 'image/svg+xml', @response.content_type |
|
| 225 |
end |
|
| 226 |
|
|
| 227 |
def test_get_committers |
|
| 228 |
@request.session[:user_id] = 2 |
|
| 229 |
# add a commit with an unknown user |
|
| 230 |
Changeset.create!( |
|
| 231 |
:repository => Project.find(1).repository, |
|
| 232 |
:committer => 'foo', |
|
| 233 |
:committed_on => Time.now, |
|
| 234 |
:revision => 100, |
|
| 235 |
:comments => 'Committed by foo.' |
|
| 236 |
) |
|
| 237 |
|
|
| 238 |
get :committers, :id => 10 |
|
| 239 |
assert_response :success |
|
| 240 |
assert_template 'committers' |
|
| 241 |
|
|
| 242 |
assert_tag :td, :content => 'dlopper', |
|
| 243 |
:sibling => { :tag => 'td',
|
|
| 244 |
:child => { :tag => 'select', :attributes => { :name => %r{^committers\[\d+\]\[\]$} },
|
|
| 245 |
:child => { :tag => 'option', :content => 'Dave Lopper',
|
|
| 246 |
:attributes => { :value => '3', :selected => 'selected' }}}}
|
|
| 247 |
assert_tag :td, :content => 'foo', |
|
| 248 |
:sibling => { :tag => 'td',
|
|
| 249 |
:child => { :tag => 'select', :attributes => { :name => %r{^committers\[\d+\]\[\]$} }}}
|
|
| 250 |
assert_no_tag :td, :content => 'foo', |
|
| 251 |
:sibling => { :tag => 'td',
|
|
| 252 |
:descendant => { :tag => 'option', :attributes => { :selected => 'selected' }}}
|
|
| 253 |
end |
|
| 254 |
|
|
| 255 |
def test_post_committers |
|
| 256 |
@request.session[:user_id] = 2 |
|
| 257 |
# add a commit with an unknown user |
|
| 258 |
c = Changeset.create!( |
|
| 259 |
:repository => Project.find(1).repository, |
|
| 260 |
:committer => 'foo', |
|
| 261 |
:committed_on => Time.now, |
|
| 262 |
:revision => 100, |
|
| 263 |
:comments => 'Committed by foo.' |
|
| 264 |
) |
|
| 265 |
assert_no_difference "Changeset.count(:conditions => 'user_id = 3')" do |
|
| 266 |
post :committers, :id => 10, :committers => { '0' => ['foo', '2'], '1' => ['dlopper', '3']}
|
|
| 267 |
assert_response 302 |
|
| 268 |
assert_equal User.find(2), c.reload.user |
|
| 269 |
end |
|
| 270 |
end |
|
| 271 |
end |
|
| .svn/pristine/5b/5b87796b6ecfa105f02074d144bc429770ba3204.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2012 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 File.expand_path('../../test_helper', __FILE__)
|
|
| 19 |
|
|
| 20 |
class IssuePriorityTest < ActiveSupport::TestCase |
|
| 21 |
fixtures :enumerations, :issues |
|
| 22 |
|
|
| 23 |
def test_named_scope |
|
| 24 |
assert_equal Enumeration.find_by_name('Normal'), Enumeration.named('normal').first
|
|
| 25 |
end |
|
| 26 |
|
|
| 27 |
def test_default_should_return_the_default_priority |
|
| 28 |
assert_equal Enumeration.find_by_name('Normal'), IssuePriority.default
|
|
| 29 |
end |
|
| 30 |
|
|
| 31 |
def test_default_should_return_nil_when_no_default_priority |
|
| 32 |
IssuePriority.update_all :is_default => false |
|
| 33 |
assert_nil IssuePriority.default |
|
| 34 |
end |
|
| 35 |
|
|
| 36 |
def test_should_be_an_enumeration |
|
| 37 |
assert IssuePriority.ancestors.include?(Enumeration) |
|
| 38 |
end |
|
| 39 |
|
|
| 40 |
def test_objects_count |
|
| 41 |
# low priority |
|
| 42 |
assert_equal 6, IssuePriority.find(4).objects_count |
|
| 43 |
# urgent |
|
| 44 |
assert_equal 0, IssuePriority.find(7).objects_count |
|
| 45 |
end |
|
| 46 |
|
|
| 47 |
def test_option_name |
|
| 48 |
assert_equal :enumeration_issue_priorities, IssuePriority.new.option_name |
|
| 49 |
end |
|
| 50 |
|
|
| 51 |
def test_should_be_created_at_last_position |
|
| 52 |
IssuePriority.delete_all |
|
| 53 |
|
|
| 54 |
priorities = [1, 2, 3].map {|i| IssuePriority.create!(:name => "P#{i}")}
|
|
| 55 |
assert_equal [1, 2, 3], priorities.map(&:position) |
|
| 56 |
end |
|
| 57 |
|
|
| 58 |
def test_reset_positions_in_list_should_set_sequential_positions |
|
| 59 |
IssuePriority.delete_all |
|
| 60 |
|
|
| 61 |
priorities = [1, 2, 3].map {|i| IssuePriority.create!(:name => "P#{i}")}
|
|
| 62 |
priorities[0].update_attribute :position, 4 |
|
| 63 |
priorities[1].update_attribute :position, 2 |
|
| 64 |
priorities[2].update_attribute :position, 7 |
|
| 65 |
assert_equal [4, 2, 7], priorities.map(&:reload).map(&:position) |
|
| 66 |
|
|
| 67 |
priorities[0].reset_positions_in_list |
|
| 68 |
assert_equal [2, 1, 3], priorities.map(&:reload).map(&:position) |
|
| 69 |
end |
|
| 70 |
|
|
| 71 |
def test_moving_in_list_should_reset_positions |
|
| 72 |
priority = IssuePriority.first |
|
| 73 |
priority.expects(:reset_positions_in_list).once |
|
| 74 |
priority.move_to = 'higher' |
|
| 75 |
end |
|
| 76 |
|
|
| 77 |
def test_clear_position_names_should_set_position_names_to_nil |
|
| 78 |
IssuePriority.clear_position_names |
|
| 79 |
assert IssuePriority.all.all? {|priority| priority.position_name.nil?}
|
|
| 80 |
end |
|
| 81 |
|
|
| 82 |
def test_compute_position_names_with_default_priority |
|
| 83 |
IssuePriority.clear_position_names |
|
| 84 |
|
|
| 85 |
IssuePriority.compute_position_names |
|
| 86 |
assert_equal %w(lowest default high3 high2 highest), IssuePriority.active.all.sort.map(&:position_name) |
|
| 87 |
end |
|
| 88 |
|
|
| 89 |
def test_compute_position_names_without_default_priority_should_split_priorities |
|
| 90 |
IssuePriority.clear_position_names |
|
| 91 |
IssuePriority.update_all :is_default => false |
|
| 92 |
|
|
| 93 |
IssuePriority.compute_position_names |
|
| 94 |
assert_equal %w(lowest low2 default high2 highest), IssuePriority.active.all.sort.map(&:position_name) |
|
| 95 |
end |
|
| 96 |
|
|
| 97 |
def test_adding_a_priority_should_update_position_names |
|
| 98 |
priority = IssuePriority.create!(:name => 'New') |
|
| 99 |
assert_equal %w(lowest default high4 high3 high2 highest), IssuePriority.active.all.sort.map(&:position_name) |
|
| 100 |
end |
|
| 101 |
|
|
| 102 |
def test_destroying_a_priority_should_update_position_names |
|
| 103 |
IssuePriority.find_by_position_name('highest').destroy
|
|
| 104 |
assert_equal %w(lowest default high2 highest), IssuePriority.active.all.sort.map(&:position_name) |
|
| 105 |
end |
|
| 106 |
end |
|
| .svn/pristine/5b/5b99c4097d1ff45e9a53ed60e93cd948adc2fa7e.svn-base | ||
|---|---|---|
| 1 |
<p><%= l(:mail_body_wiki_content_added, :id => link_to(h(@wiki_content.page.pretty_title), @wiki_content_url), |
|
| 2 |
:author => h(@wiki_content.author)).html_safe %><br /> |
|
| 3 |
<em><%=h @wiki_content.comments %></em></p> |
|
| .svn/pristine/5b/5be15d9755983c58da5f71dc3d2f96dca598f0ff.svn-base | ||
|---|---|---|
| 1 |
hideModal(); |
|
| 2 |
<% select = content_tag('select', content_tag('option') + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]') %>
|
|
| 3 |
$('#issue_category_id').replaceWith('<%= escape_javascript(select) %>');
|
|
| .svn/pristine/5b/5bef2d38e6b8855b684e8739aef488dc430ba2cd.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2012 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 |
require 'redmine/scm/adapters/abstract_adapter' |
|
| 22 |
|
|
| 23 |
class ChangesetNotFound < Exception; end |
|
| 24 |
class InvalidRevisionParam < Exception; end |
|
| 25 |
|
|
| 26 |
class RepositoriesController < ApplicationController |
|
| 27 |
menu_item :repository |
|
| 28 |
menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers] |
|
| 29 |
default_search_scope :changesets |
|
| 30 |
|
|
| 31 |
before_filter :find_project_by_project_id, :only => [:new, :create] |
|
| 32 |
before_filter :find_repository, :only => [: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] |
|
| 35 |
before_filter :authorize |
|
| 36 |
accept_rss_auth :revisions |
|
| 37 |
|
|
| 38 |
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed |
|
| 39 |
|
|
| 40 |
def new |
|
| 41 |
scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first |
|
| 42 |
@repository = Repository.factory(scm) |
|
| 43 |
@repository.is_default = @project.repository.nil? |
|
| 44 |
@repository.project = @project |
|
| 45 |
end |
|
| 46 |
|
|
| 47 |
def create |
|
| 48 |
attrs = pickup_extra_info |
|
| 49 |
@repository = Repository.factory(params[:repository_scm]) |
|
| 50 |
@repository.safe_attributes = params[:repository] |
|
| 51 |
if attrs[:attrs_extra].keys.any? |
|
| 52 |
@repository.merge_extra_info(attrs[:attrs_extra]) |
|
| 53 |
end |
|
| 54 |
@repository.project = @project |
|
| 55 |
if request.post? && @repository.save |
|
| 56 |
redirect_to settings_project_path(@project, :tab => 'repositories') |
|
| 57 |
else |
|
| 58 |
render :action => 'new' |
|
| 59 |
end |
|
| 60 |
end |
|
| 61 |
|
|
| 62 |
def edit |
|
| 63 |
end |
|
| 64 |
|
|
| 65 |
def update |
|
| 66 |
attrs = pickup_extra_info |
|
| 67 |
@repository.safe_attributes = attrs[:attrs] |
|
| 68 |
if attrs[:attrs_extra].keys.any? |
|
| 69 |
@repository.merge_extra_info(attrs[:attrs_extra]) |
|
| 70 |
end |
|
| 71 |
@repository.project = @project |
|
| 72 |
if request.put? && @repository.save |
|
| 73 |
redirect_to settings_project_path(@project, :tab => 'repositories') |
|
| 74 |
else |
|
| 75 |
render :action => 'edit' |
|
| 76 |
end |
|
| 77 |
end |
|
| 78 |
|
|
| 79 |
def pickup_extra_info |
|
| 80 |
p = {}
|
|
| 81 |
p_extra = {}
|
|
| 82 |
params[:repository].each do |k, v| |
|
| 83 |
if k =~ /^extra_/ |
|
| 84 |
p_extra[k] = v |
|
| 85 |
else |
|
| 86 |
p[k] = v |
|
| 87 |
end |
|
| 88 |
end |
|
| 89 |
{:attrs => p, :attrs_extra => p_extra}
|
|
| 90 |
end |
|
| 91 |
private :pickup_extra_info |
|
| 92 |
|
|
| 93 |
def committers |
|
| 94 |
@committers = @repository.committers |
|
| 95 |
@users = @project.users |
|
| 96 |
additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id) |
|
| 97 |
@users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty? |
|
| 98 |
@users.compact! |
|
| 99 |
@users.sort! |
|
| 100 |
if request.post? && params[:committers].is_a?(Hash) |
|
| 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}
|
|
| 103 |
flash[:notice] = l(:notice_successful_update) |
|
| 104 |
redirect_to settings_project_path(@project, :tab => 'repositories') |
|
| 105 |
end |
|
| 106 |
end |
|
| 107 |
|
|
| 108 |
def destroy |
|
| 109 |
@repository.destroy if request.delete? |
|
| 110 |
redirect_to settings_project_path(@project, :tab => 'repositories') |
|
| 111 |
end |
|
| 112 |
|
|
| 113 |
def show |
|
| 114 |
@repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? |
|
| 115 |
|
|
| 116 |
@entries = @repository.entries(@path, @rev) |
|
| 117 |
@changeset = @repository.find_changeset_by_name(@rev) |
|
| 118 |
if request.xhr? |
|
| 119 |
@entries ? render(:partial => 'dir_list_content') : render(:nothing => true) |
|
| 120 |
else |
|
| 121 |
(show_error_not_found; return) unless @entries |
|
| 122 |
@changesets = @repository.latest_changesets(@path, @rev) |
|
| 123 |
@properties = @repository.properties(@path, @rev) |
|
| 124 |
@repositories = @project.repositories |
|
| 125 |
render :action => 'show' |
|
| 126 |
end |
|
| 127 |
end |
|
| 128 |
|
|
| 129 |
alias_method :browse, :show |
|
| 130 |
|
|
| 131 |
def changes |
|
| 132 |
@entry = @repository.entry(@path, @rev) |
|
| 133 |
(show_error_not_found; return) unless @entry |
|
| 134 |
@changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i) |
|
| 135 |
@properties = @repository.properties(@path, @rev) |
|
| 136 |
@changeset = @repository.find_changeset_by_name(@rev) |
|
| 137 |
end |
|
| 138 |
|
|
| 139 |
def revisions |
|
| 140 |
@changeset_count = @repository.changesets.count |
|
| 141 |
@changeset_pages = Paginator.new self, @changeset_count, |
|
| 142 |
per_page_option, |
|
| 143 |
params['page'] |
|
| 144 |
@changesets = @repository.changesets.find(:all, |
|
| 145 |
:limit => @changeset_pages.items_per_page, |
|
| 146 |
:offset => @changeset_pages.current.offset, |
|
| 147 |
:include => [:user, :repository, :parents]) |
|
| 148 |
|
|
| 149 |
respond_to do |format| |
|
| 150 |
format.html { render :layout => false if request.xhr? }
|
|
| 151 |
format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
|
|
| 152 |
end |
|
| 153 |
end |
|
| 154 |
|
|
| 155 |
def raw |
|
| 156 |
entry_and_raw(true) |
|
| 157 |
end |
|
| 158 |
|
|
| 159 |
def entry |
|
| 160 |
entry_and_raw(false) |
|
| 161 |
end |
|
| 162 |
|
|
| 163 |
def entry_and_raw(is_raw) |
|
| 164 |
@entry = @repository.entry(@path, @rev) |
|
| 165 |
(show_error_not_found; return) unless @entry |
|
| 166 |
|
|
| 167 |
# If the entry is a dir, show the browser |
|
| 168 |
(show; return) if @entry.is_dir? |
|
| 169 |
|
|
| 170 |
@content = @repository.cat(@path, @rev) |
|
| 171 |
(show_error_not_found; return) unless @content |
|
| 172 |
if is_raw || |
|
| 173 |
(@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || |
|
| 174 |
! is_entry_text_data?(@content, @path) |
|
| 175 |
# Force the download |
|
| 176 |
send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
|
|
| 177 |
send_type = Redmine::MimeType.of(@path) |
|
| 178 |
send_opt[:type] = send_type.to_s if send_type |
|
| 179 |
send_opt[:disposition] = (Redmine::MimeType.is_type?('image', @path) && !is_raw ? 'inline' : 'attachment')
|
|
| 180 |
send_data @content, send_opt |
|
| 181 |
else |
|
| 182 |
# Prevent empty lines when displaying a file with Windows style eol |
|
| 183 |
# TODO: UTF-16 |
|
| 184 |
# Is this needs? AttachmentsController reads file simply. |
|
| 185 |
@content.gsub!("\r\n", "\n")
|
|
| 186 |
@changeset = @repository.find_changeset_by_name(@rev) |
|
| 187 |
end |
|
| 188 |
end |
|
| 189 |
private :entry_and_raw |
|
| 190 |
|
|
| 191 |
def is_entry_text_data?(ent, path) |
|
| 192 |
# UTF-16 contains "\x00". |
|
| 193 |
# It is very strict that file contains less than 30% of ascii symbols |
|
| 194 |
# in non Western Europe. |
|
| 195 |
return true if Redmine::MimeType.is_type?('text', path)
|
|
| 196 |
# Ruby 1.8.6 has a bug of integer divisions. |
|
| 197 |
# http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F |
|
| 198 |
return false if ent.is_binary_data? |
|
| 199 |
true |
|
| 200 |
end |
|
| 201 |
private :is_entry_text_data? |
|
| 202 |
|
|
| 203 |
def annotate |
|
| 204 |
@entry = @repository.entry(@path, @rev) |
|
| 205 |
(show_error_not_found; return) unless @entry |
|
| 206 |
|
|
| 207 |
@annotate = @repository.scm.annotate(@path, @rev) |
|
| 208 |
if @annotate.nil? || @annotate.empty? |
|
| 209 |
(render_error l(:error_scm_annotate); return) |
|
| 210 |
end |
|
| 211 |
ann_buf_size = 0 |
|
| 212 |
@annotate.lines.each do |buf| |
|
| 213 |
ann_buf_size += buf.size |
|
| 214 |
end |
|
| 215 |
if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte |
|
| 216 |
(render_error l(:error_scm_annotate_big_text_file); return) |
|
| 217 |
end |
|
| 218 |
@changeset = @repository.find_changeset_by_name(@rev) |
|
| 219 |
end |
|
| 220 |
|
|
| 221 |
def revision |
|
| 222 |
respond_to do |format| |
|
| 223 |
format.html |
|
| 224 |
format.js {render :layout => false}
|
|
| 225 |
end |
|
| 226 |
end |
|
| 227 |
|
|
| 228 |
# Adds a related issue to a changeset |
|
| 229 |
# POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues |
|
| 230 |
def add_related_issue |
|
| 231 |
@issue = @changeset.find_referenced_issue_by_id(params[:issue_id]) |
|
| 232 |
if @issue && (!@issue.visible? || @changeset.issues.include?(@issue)) |
|
| 233 |
@issue = nil |
|
| 234 |
end |
|
| 235 |
|
|
| 236 |
if @issue |
|
| 237 |
@changeset.issues << @issue |
|
| 238 |
end |
|
| 239 |
end |
|
| 240 |
|
|
| 241 |
# Removes a related issue from a changeset |
|
| 242 |
# DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id |
|
| 243 |
def remove_related_issue |
|
| 244 |
@issue = Issue.visible.find_by_id(params[:issue_id]) |
|
| 245 |
if @issue |
|
| 246 |
@changeset.issues.delete(@issue) |
|
| 247 |
end |
|
| 248 |
end |
|
| 249 |
|
|
| 250 |
def diff |
|
| 251 |
if params[:format] == 'diff' |
|
| 252 |
@diff = @repository.diff(@path, @rev, @rev_to) |
|
| 253 |
(show_error_not_found; return) unless @diff |
|
| 254 |
filename = "changeset_r#{@rev}"
|
|
| 255 |
filename << "_r#{@rev_to}" if @rev_to
|
|
| 256 |
send_data @diff.join, :filename => "#{filename}.diff",
|
|
| 257 |
:type => 'text/x-patch', |
|
| 258 |
:disposition => 'attachment' |
|
| 259 |
else |
|
| 260 |
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' |
|
| 261 |
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) |
|
| 262 |
|
|
| 263 |
# Save diff type as user preference |
|
| 264 |
if User.current.logged? && @diff_type != User.current.pref[:diff_type] |
|
| 265 |
User.current.pref[:diff_type] = @diff_type |
|
| 266 |
User.current.preference.save |
|
| 267 |
end |
|
| 268 |
@cache_key = "repositories/diff/#{@repository.id}/" +
|
|
| 269 |
Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
|
|
| 270 |
unless read_fragment(@cache_key) |
|
| 271 |
@diff = @repository.diff(@path, @rev, @rev_to) |
|
| 272 |
show_error_not_found unless @diff |
|
| 273 |
end |
|
| 274 |
|
|
| 275 |
@changeset = @repository.find_changeset_by_name(@rev) |
|
| 276 |
@changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil |
|
| 277 |
@diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to) |
|
| 278 |
end |
|
| 279 |
end |
|
| 280 |
|
|
| 281 |
def stats |
|
| 282 |
end |
|
| 283 |
|
|
| 284 |
def graph |
|
| 285 |
data = nil |
|
| 286 |
case params[:graph] |
|
| 287 |
when "commits_per_month" |
|
| 288 |
data = graph_commits_per_month(@repository) |
|
| 289 |
when "commits_per_author" |
|
| 290 |
data = graph_commits_per_author(@repository) |
|
| 291 |
end |
|
| 292 |
if data |
|
| 293 |
headers["Content-Type"] = "image/svg+xml" |
|
| 294 |
send_data(data, :type => "image/svg+xml", :disposition => "inline") |
|
| 295 |
else |
|
| 296 |
render_404 |
|
| 297 |
end |
|
| 298 |
end |
|
| 299 |
|
|
| 300 |
private |
|
| 301 |
|
|
| 302 |
def find_repository |
|
| 303 |
@repository = Repository.find(params[:id]) |
|
| 304 |
@project = @repository.project |
|
| 305 |
rescue ActiveRecord::RecordNotFound |
|
| 306 |
render_404 |
|
| 307 |
end |
|
| 308 |
|
|
| 309 |
REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
|
|
| 310 |
|
|
| 311 |
def find_project_repository |
|
| 312 |
@project = Project.find(params[:id]) |
|
| 313 |
if params[:repository_id].present? |
|
| 314 |
@repository = @project.repositories.find_by_identifier_param(params[:repository_id]) |
|
| 315 |
else |
|
| 316 |
@repository = @project.repository |
|
| 317 |
end |
|
| 318 |
(render_404; return false) unless @repository |
|
| 319 |
@path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
|
|
| 320 |
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip |
|
| 321 |
@rev_to = params[:rev_to] |
|
| 322 |
|
|
| 323 |
unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) |
|
| 324 |
if @repository.branches.blank? |
|
| 325 |
raise InvalidRevisionParam |
|
| 326 |
end |
|
| 327 |
end |
|
| 328 |
rescue ActiveRecord::RecordNotFound |
|
| 329 |
render_404 |
|
| 330 |
rescue InvalidRevisionParam |
|
| 331 |
show_error_not_found |
|
| 332 |
end |
|
| 333 |
|
|
| 334 |
def find_changeset |
|
| 335 |
if @rev.present? |
|
| 336 |
@changeset = @repository.find_changeset_by_name(@rev) |
|
| 337 |
end |
|
| 338 |
show_error_not_found unless @changeset |
|
| 339 |
end |
|
| 340 |
|
|
| 341 |
def show_error_not_found |
|
| 342 |
render_error :message => l(:error_scm_not_found), :status => 404 |
|
| 343 |
end |
|
| 344 |
|
|
| 345 |
# Handler for Redmine::Scm::Adapters::CommandFailed exception |
|
| 346 |
def show_error_command_failed(exception) |
|
| 347 |
render_error l(:error_scm_command_failed, exception.message) |
|
| 348 |
end |
|
| 349 |
|
|
| 350 |
def graph_commits_per_month(repository) |
|
| 351 |
@date_to = Date.today |
|
| 352 |
@date_from = @date_to << 11 |
|
| 353 |
@date_from = Date.civil(@date_from.year, @date_from.month, 1) |
|
| 354 |
commits_by_day = Changeset.count( |
|
| 355 |
:all, :group => :commit_date, |
|
| 356 |
:conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) |
|
| 357 |
commits_by_month = [0] * 12 |
|
| 358 |
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
|
|
| 359 |
|
|
| 360 |
changes_by_day = Change.count( |
|
| 361 |
:all, :group => :commit_date, :include => :changeset, |
|
| 362 |
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
|
|
| 363 |
changes_by_month = [0] * 12 |
|
| 364 |
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
|
|
| 365 |
|
|
| 366 |
fields = [] |
|
| 367 |
12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
|
|
| 368 |
|
|
| 369 |
graph = SVG::Graph::Bar.new( |
|
| 370 |
:height => 300, |
|
| 371 |
:width => 800, |
|
| 372 |
:fields => fields.reverse, |
|
| 373 |
:stack => :side, |
|
| 374 |
:scale_integers => true, |
|
| 375 |
:step_x_labels => 2, |
|
| 376 |
:show_data_values => false, |
|
| 377 |
:graph_title => l(:label_commits_per_month), |
|
| 378 |
:show_graph_title => true |
|
| 379 |
) |
|
| 380 |
|
|
| 381 |
graph.add_data( |
|
| 382 |
:data => commits_by_month[0..11].reverse, |
|
| 383 |
:title => l(:label_revision_plural) |
|
| 384 |
) |
|
| 385 |
|
|
| 386 |
graph.add_data( |
|
| 387 |
:data => changes_by_month[0..11].reverse, |
|
| 388 |
:title => l(:label_change_plural) |
|
| 389 |
) |
|
| 390 |
|
|
| 391 |
graph.burn |
|
| 392 |
end |
|
| 393 |
|
|
| 394 |
def graph_commits_per_author(repository) |
|
| 395 |
commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id]) |
|
| 396 |
commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
|
|
| 397 |
|
|
| 398 |
changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id])
|
|
| 399 |
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
|
|
| 400 |
|
|
| 401 |
fields = commits_by_author.collect {|r| r.first}
|
|
| 402 |
commits_data = commits_by_author.collect {|r| r.last}
|
|
| 403 |
changes_data = commits_by_author.collect {|r| h[r.first] || 0}
|
|
| 404 |
|
|
| 405 |
fields = fields + [""]*(10 - fields.length) if fields.length<10 |
|
| 406 |
commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 |
|
| 407 |
changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10 |
|
| 408 |
|
|
| 409 |
# Remove email adress in usernames |
|
| 410 |
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
|
|
| 411 |
|
|
| 412 |
graph = SVG::Graph::BarHorizontal.new( |
|
| 413 |
:height => 400, |
|
| 414 |
:width => 800, |
|
| 415 |
:fields => fields, |
|
| 416 |
:stack => :side, |
|
| 417 |
:scale_integers => true, |
|
| 418 |
:show_data_values => false, |
|
| 419 |
:rotate_y_labels => false, |
|
| 420 |
:graph_title => l(:label_commits_per_author), |
|
| 421 |
:show_graph_title => true |
|
| 422 |
) |
|
| 423 |
graph.add_data( |
|
| 424 |
:data => commits_data, |
|
| 425 |
:title => l(:label_revision_plural) |
|
| 426 |
) |
|
| 427 |
graph.add_data( |
|
| 428 |
:data => changes_data, |
|
| 429 |
:title => l(:label_change_plural) |
|
| 430 |
) |
|
| 431 |
graph.burn |
|
| 432 |
end |
|
| 433 |
end |
|
Also available in: Unified diff