Compare commits

..
21 Commits
Author SHA1 Message Date
Jean-Philippe Lang d3ae4cfd11 tagged version 1.0.4
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/1.0.4@4448 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-28 12:57:17 +00:00
Jean-Philippe Lang ed52753a12 Updates for 1.0.4 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4447 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-28 12:47:36 +00:00
Jean-Philippe Lang bac64c9ab4 Backported r4441 to r4444 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4445 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 16:45:07 +00:00
Jean-Philippe Lang b58382ef2e Merged r4386 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4440 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 15:48:30 +00:00
Jean-Philippe Lang 541a371b41 Backported r4357, r4358, r4360 and r4363 to r4367 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4439 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 15:16:26 +00:00
Jean-Philippe Lang bdb888476d Merged r4437 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4438 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:47:31 +00:00
Jean-Philippe Lang f5f55359a8 Translations updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4436 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:40:51 +00:00
Jean-Philippe Lang eb590ce1eb Merged r4428 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4435 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:31:29 +00:00
Jean-Philippe Lang db64675310 Merged r4431 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4434 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:30:41 +00:00
Jean-Philippe Lang 92b37c0bea Merged r4426 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4433 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:28:51 +00:00
Jean-Philippe Lang faa461c66a Merged r4422 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4423 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-21 14:26:01 +00:00
Jean-Philippe Lang b5a409791c Merged r4419 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4420 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-20 14:12:35 +00:00
Jean-Philippe Lang 6c362a5a76 Merged r4417 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4418 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-20 14:06:32 +00:00
Jean-Philippe Lang dbb26b08f8 Merged r4414 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4415 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-20 10:23:13 +00:00
Jean-Philippe Lang 2a7a95d746 Backported r4397 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4401 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-14 11:30:53 +00:00
Jean-Philippe Lang c70ae6c735 Merged r4380 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4400 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-14 11:18:23 +00:00
Jean-Philippe Lang e174c10d6d Merged r4385 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4399 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-14 11:17:20 +00:00
Jean-Philippe Lang 2b9a5bf6e2 Merged r4378 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4398 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-14 11:15:53 +00:00
Jean-Philippe Lang d3b536e9a6 Merged r4389 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4390 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-09 19:56:29 +00:00
Jean-Philippe Lang 9b1eda1b22 Fixed a test broken by r4355 (#6786).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4356 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-01 14:47:23 +00:00
Jean-Philippe Lang f1f0704ef8 Merged r4354 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4355 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-01 13:14:14 +00:00
38 changed files with 938 additions and 759 deletions
+2 -2
View File
@@ -26,7 +26,7 @@ class IssuesController < ApplicationController
before_filter :find_optional_project, :only => [:index] before_filter :find_optional_project, :only => [:index]
before_filter :check_for_default_issue_status, :only => [:new, :create] before_filter :check_for_default_issue_status, :only => [:new, :create]
before_filter :build_new_issue_from_params, :only => [:new, :create] before_filter :build_new_issue_from_params, :only => [:new, :create]
accept_key_auth :index, :show accept_key_auth :index, :show, :create, :update, :destroy
rescue_from Query::StatementInvalid, :with => :query_statement_invalid rescue_from Query::StatementInvalid, :with => :query_statement_invalid
@@ -300,6 +300,7 @@ private
render_error l(:error_no_tracker_in_project) render_error l(:error_no_tracker_in_project)
return false return false
end end
@issue.start_date ||= Date.today
if params[:issue].is_a?(Hash) if params[:issue].is_a?(Hash)
@issue.safe_attributes = params[:issue] @issue.safe_attributes = params[:issue]
if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record? if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
@@ -307,7 +308,6 @@ private
end end
end end
@issue.author = User.current @issue.author = User.current
@issue.start_date ||= Date.today
@priorities = IssuePriority.all @priorities = IssuePriority.all
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
end end
+2 -2
View File
@@ -24,7 +24,7 @@ class ProjectsController < ApplicationController
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy] before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
before_filter :authorize_global, :only => [:new, :create] before_filter :authorize_global, :only => [:new, :create]
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
accept_key_auth :index accept_key_auth :index, :show, :create, :update, :destroy
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
if controller.request.post? if controller.request.post?
@@ -93,7 +93,7 @@ class ProjectsController < ApplicationController
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :id => @project redirect_to :controller => 'projects', :action => 'settings', :id => @project
} }
format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
end end
else else
respond_to do |format| respond_to do |format|
+4
View File
@@ -109,6 +109,10 @@ class VersionsController < ApplicationController
if @version.update_attributes(attributes) if @version.update_attributes(attributes)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
else
respond_to do |format|
format.html { render :action => 'edit' }
end
end end
end end
end end
+1 -1
View File
@@ -18,7 +18,7 @@
class Board < ActiveRecord::Base class Board < ActiveRecord::Base
belongs_to :project belongs_to :project
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC" has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC" has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC"
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_list :scope => :project_id acts_as_list :scope => :project_id
acts_as_watchable acts_as_watchable
+1
View File
@@ -31,6 +31,7 @@ class Group < Principal
def user_added(user) def user_added(user)
members.each do |member| members.each do |member|
next if member.project.nil?
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id) user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
member.member_roles.each do |member_role| member.member_roles.each do |member_role|
user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id) user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
+1 -1
View File
@@ -237,7 +237,7 @@ class Issue < ActiveRecord::Base
if !user.allowed_to?(:manage_subtasks, project) if !user.allowed_to?(:manage_subtasks, project)
attrs.delete('parent_issue_id') attrs.delete('parent_issue_id')
elsif !attrs['parent_issue_id'].blank? elsif !attrs['parent_issue_id'].blank?
attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id']) attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
end end
end end
+1 -1
View File
@@ -2,5 +2,5 @@
<div class="box"> <div class="box">
<p><%= f.text_field :name, :size => 30, :required => true %></p> <p><%= f.text_field :name, :size => 30, :required => true %></p>
<p><%= f.select :assigned_to_id, @project.users.collect{|u| [u.name, u.id]}, :include_blank => true %></p> <p><%= f.select :assigned_to_id, @project.users.sort.collect{|u| [u.name, u.id]}, :include_blank => true %></p>
</div> </div>
+3 -3
View File
@@ -5,9 +5,9 @@ function recreateSortables() {
Sortable.destroy('list-left'); Sortable.destroy('list-left');
Sortable.destroy('list-right'); Sortable.destroy('list-right');
Sortable.create("list-top", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=top', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-top")})}, only:'mypage-box', tag:'div'}) Sortable.create("list-top", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('<%= url_for(:controller => 'my', :action => 'order_blocks', :group => 'top') %>', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-top")})}, only:'mypage-box', tag:'div'})
Sortable.create("list-left", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=left', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-left")})}, only:'mypage-box', tag:'div'}) Sortable.create("list-left", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('<%= url_for(:controller => 'my', :action => 'order_blocks', :group => 'left') %>', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-left")})}, only:'mypage-box', tag:'div'})
Sortable.create("list-right", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=right', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-right")})}, only:'mypage-box', tag:'div'}) Sortable.create("list-right", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('<%= url_for(:controller => 'my', :action => 'order_blocks', :group => 'right') %>', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-right")})}, only:'mypage-box', tag:'div'})
} }
function updateSelect() { function updateSelect() {
+2 -2
View File
@@ -6,7 +6,7 @@
<p><%= setting_text_area :welcome_text, :cols => 60, :rows => 5, :class => 'wiki-edit' %></p> <p><%= setting_text_area :welcome_text, :cols => 60, :rows => 5, :class => 'wiki-edit' %></p>
<%= wikitoolbar_for 'settings_welcome_text' %> <%= wikitoolbar_for 'settings_welcome_text' %>
<p><%= setting_text_field :attachment_max_size, :size => 6 %> KB</p> <p><%= setting_text_field :attachment_max_size, :size => 6 %> <%= l(:"number.human.storage_units.units.kb") %></p>
<p><%= setting_text_field :per_page_options, :size => 20 %><br /> <p><%= setting_text_field :per_page_options, :size => 20 %><br />
<em><%= l(:text_comma_separated) %></em></p> <em><%= l(:text_comma_separated) %></em></p>
@@ -26,7 +26,7 @@
<p><%= setting_text_field :feeds_limit, :size => 6 %></p> <p><%= setting_text_field :feeds_limit, :size => 6 %></p>
<p><%= setting_text_field :file_max_size_displayed, :size => 6 %> KB</p> <p><%= setting_text_field :file_max_size_displayed, :size => 6 %> <%= l(:"number.human.storage_units.units.kb") %></p>
<p><%= setting_text_field :diff_max_lines_displayed, :size => 6 %></p> <p><%= setting_text_field :diff_max_lines_displayed, :size => 6 %></p>
+2
View File
@@ -5,6 +5,7 @@
<% if @workflow_counts.empty? %> <% if @workflow_counts.empty? %>
<p class="nodata"><%= l(:label_no_data) %></p> <p class="nodata"><%= l(:label_no_data) %></p>
<% else %> <% else %>
<div class="autoscroll">
<table class="list"> <table class="list">
<thead> <thead>
<tr> <tr>
@@ -30,4 +31,5 @@
<% end -%> <% end -%>
</tbody> </tbody>
</table> </table>
</div>
<% end %> <% end %>
+1 -1
View File
@@ -84,7 +84,7 @@ ActionMailer::Base.send :include, AsynchronousMailer
module I18n module I18n
module Backend module Backend
module Base module Base
def warn_syntax_deprecation! def warn_syntax_deprecation!(*args)
return if @skip_syntax_deprecation return if @skip_syntax_deprecation
warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\nDowngrade your i18n gem to 0.3.7 (everything above must be deinstalled) to remove this warning, see http://www.redmine.org/issues/5608 for more information." warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\nDowngrade your i18n gem to 0.3.7 (everything above must be deinstalled) to remove this warning, see http://www.redmine.org/issues/5608 for more information."
@skip_syntax_deprecation = true @skip_syntax_deprecation = true
+23 -21
View File
@@ -321,7 +321,7 @@ bg:
label_registered_on: Регистрация label_registered_on: Регистрация
label_activity: Дейност label_activity: Дейност
label_new: Нов label_new: Нов
label_logged_as: Влязъл като label_logged_as: Здравейте,
label_environment: Среда label_environment: Среда
label_authentication: Оторизация label_authentication: Оторизация
label_auth_source: Начин на оторозация label_auth_source: Начин на оторозация
@@ -850,8 +850,8 @@ bg:
button_duplicate: Дублиране button_duplicate: Дублиране
button_copy_and_follow: Копиране и продължаване button_copy_and_follow: Копиране и продължаване
label_copy_source: Източник label_copy_source: Източник
setting_issue_done_ratio: Изчисление на процента на готово задачи с setting_issue_done_ratio: Изчисление на процента на готови задачи с
setting_issue_done_ratio_issue_status: Използване на състояниетона задачите setting_issue_done_ratio_issue_status: Използване на състоянието на задачите
error_issue_done_ratios_not_updated: Процентът на завършените задачи не е обновен. error_issue_done_ratios_not_updated: Процентът на завършените задачи не е обновен.
error_workflow_copy_target: Моля изберете тракер(и) и роля (роли). error_workflow_copy_target: Моля изберете тракер(и) и роля (роли).
setting_issue_done_ratio_issue_field: Използване на поле 'задача' setting_issue_done_ratio_issue_field: Използване на поле 'задача'
@@ -896,28 +896,30 @@ bg:
error_can_not_remove_role: Тази роля се използва и не може да бъде изтрита. error_can_not_remove_role: Тази роля се използва и не може да бъде изтрита.
error_can_not_delete_tracker: Този тракер съдържа задачи и не може да бъде изтрит. error_can_not_delete_tracker: Този тракер съдържа задачи и не може да бъде изтрит.
field_principal: Principal field_principal: Principal
label_my_page_block: My page block label_my_page_block: Блокове в личната страница
notice_failed_to_save_members: "Невъзможност за запис на член(ове): {{errors}}." notice_failed_to_save_members: "Невъзможност за запис на член(ове): {{errors}}."
text_zoom_out: Намаляване text_zoom_out: Намаляване
text_zoom_in: Увеличаване text_zoom_in: Увеличаване
notice_unable_delete_time_entry: Невъзможност за изтриване на запис на time log. notice_unable_delete_time_entry: Невъзможност за изтриване на запис на time log.
label_overall_spent_time: Общо употребено време label_overall_spent_time: Общо употребено време
field_time_entries: Log time field_time_entries: Log time
notice_not_authorized_archived_project: The project you're trying to access has been archived. notice_not_authorized_archived_project: Проектът, който се опитвате да видите е архивиран.
text_tip_issue_end_day: issue ending this day text_tip_issue_end_day: задача, завършваща този ден
field_text: Text field field_text: Текстово поле
label_user_mail_option_only_owner: Only for things I am the owner of label_user_mail_option_only_owner: Само за неща, на които аз съм собственик
field_member_of_group: Assignee's group field_member_of_group: Член на група
project_module_gantt: Gantt project_module_gantt: Мрежов график
text_are_you_sure_with_children: Delete issue and all child issues? text_are_you_sure_with_children: Изтриване на задачата и нейните подзадачи?
text_tip_issue_begin_end_day: issue beginning and ending this day text_tip_issue_begin_end_day: задача, започваща и завършваща този ден
setting_default_notification_option: Default notification option setting_default_notification_option: Подразбиращ се начин за известяване
project_module_calendar: Calendar project_module_calendar: Календар
label_user_mail_option_only_my_events: Only for things I watch or I'm involved in label_user_mail_option_only_my_events: Само за неща, в които съм включен/а
text_tip_issue_begin_day: issue beginning this day text_tip_issue_begin_day: задача, започваща този ден
label_user_mail_option_only_assigned: Only for things I am assigned to label_user_mail_option_only_assigned: Само за неща, назначени на мен
button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" button_edit_associated_wikipage: "Редактиране на асоциираната Wiki страница: {{page_title}}"
field_assigned_to_role: Assignee's role field_assigned_to_role: Assignee's role
field_start_date: Start date field_start_date: Начална дата
label_principal_search: "Search for user or group:" label_principal_search: "Търсене на потребител или група:"
label_user_search: "Search for user:" label_user_search: "Търсене на потребител:"
field_visible: Видим
setting_emails_header: Emails header
+24 -24
View File
@@ -370,7 +370,7 @@ da:
label_reported_issues: Rapporterede sager label_reported_issues: Rapporterede sager
label_assigned_to_me_issues: Sager tildelt mig label_assigned_to_me_issues: Sager tildelt mig
label_last_login: Sidste login tidspunkt label_last_login: Sidste login tidspunkt
label_registered_on: Registeret den label_registered_on: Registreret den
label_activity: Aktivitet label_activity: Aktivitet
label_new: Ny label_new: Ny
label_logged_as: Registreret som label_logged_as: Registreret som
@@ -441,7 +441,7 @@ da:
label_none: intet label_none: intet
label_nobody: ingen label_nobody: ingen
label_next: Næste label_next: Næste
label_previous: Forrig label_previous: Forrige
label_used_by: Brugt af label_used_by: Brugt af
label_details: Detaljer label_details: Detaljer
label_add_note: Tilføj note label_add_note: Tilføj note
@@ -537,7 +537,7 @@ da:
label_view_diff: Vis forskelle label_view_diff: Vis forskelle
label_diff_inline: inline label_diff_inline: inline
label_diff_side_by_side: side om side label_diff_side_by_side: side om side
label_options: Optioner label_options: Formatering
label_copy_workflow_from: Kopier arbejdsgang fra label_copy_workflow_from: Kopier arbejdsgang fra
label_permissions_report: Godkendelsesrapport label_permissions_report: Godkendelsesrapport
label_watched_issues: Overvågede sager label_watched_issues: Overvågede sager
@@ -547,7 +547,7 @@ da:
label_relation_new: Ny relation label_relation_new: Ny relation
label_relation_delete: Slet relation label_relation_delete: Slet relation
label_relates_to: relaterer til label_relates_to: relaterer til
label_duplicates: kopierer label_duplicates: duplikater
label_blocks: blokerer label_blocks: blokerer
label_blocked_by: blokeret af label_blocked_by: blokeret af
label_precedes: kommer før label_precedes: kommer før
@@ -870,8 +870,8 @@ da:
label_version_sharing_tree: Med projekt træ label_version_sharing_tree: Med projekt træ
label_version_sharing_none: Ikke delt label_version_sharing_none: Ikke delt
error_can_not_archive_project: Dette projekt kan ikke arkiveres error_can_not_archive_project: Dette projekt kan ikke arkiveres
button_duplicate: Kopier button_duplicate: Duplikér
button_copy_and_follow: Kopier og overvåg button_copy_and_follow: Kopiér og overvåg
label_copy_source: Kilde label_copy_source: Kilde
setting_issue_done_ratio: Beregn sagsløsning ratio setting_issue_done_ratio: Beregn sagsløsning ratio
setting_issue_done_ratio_issue_status: Benyt sags status setting_issue_done_ratio_issue_status: Benyt sags status
@@ -896,14 +896,14 @@ da:
label_missing_feeds_access_key: Mangler en RSS nøgle label_missing_feeds_access_key: Mangler en RSS nøgle
button_show: Vis button_show: Vis
text_line_separated: Flere væredier tilladt (en linje for hver værdi). text_line_separated: Flere væredier tilladt (en linje for hver værdi).
setting_mail_handler_body_delimiters: Trunker emails efter en af disse linjer setting_mail_handler_body_delimiters: Trunkér emails efter en af disse linjer
permission_add_subprojects: Lav underprojekter permission_add_subprojects: Lav underprojekter
label_subproject_new: Nyt underprojekt label_subproject_new: Nyt underprojekt
text_own_membership_delete_confirmation: |- text_own_membership_delete_confirmation: |-
Du er ved at fjerne en eller flere af dine rettigheder, og kan muligvis ikke redigere projektet bagefter. Du er ved at fjerne en eller flere af dine rettigheder, og kan muligvis ikke redigere projektet bagefter.
Er du sikker på du ønsker at fortsætte? Er du sikker på du ønsker at fortsætte?
label_close_versions: Luk færdige versioner label_close_versions: Luk færdige versioner
label_board_sticky: Sticky label_board_sticky: Klistret
label_board_locked: Låst label_board_locked: Låst
permission_export_wiki_pages: Eksporter wiki sider permission_export_wiki_pages: Eksporter wiki sider
setting_cache_formatted_text: Cache formatteret tekst setting_cache_formatted_text: Cache formatteret tekst
@@ -914,25 +914,25 @@ da:
field_parent_issue: Hoved opgave field_parent_issue: Hoved opgave
label_subtask_plural: Under opgaver label_subtask_plural: Under opgaver
label_project_copy_notifications: Send email notifikationer, mens projektet kopieres label_project_copy_notifications: Send email notifikationer, mens projektet kopieres
error_can_not_delete_custom_field: Unable to delete custom field error_can_not_delete_custom_field: Kan ikke slette brugerdefineret felt
error_unable_to_connect: Unable to connect ({{value}}) error_unable_to_connect: Kan ikke forbinde ({{value}})
error_can_not_remove_role: This role is in use and can not be deleted. error_can_not_remove_role: Denne rolle er i brug og kan ikke slettes.
error_can_not_delete_tracker: This tracker contains issues and can't be deleted. error_can_not_delete_tracker: Denne type indeholder sager og kan ikke slettes.
field_principal: Principal field_principal: Principal
label_my_page_block: My page block label_my_page_block: blok
notice_failed_to_save_members: "Failed to save member(s): {{errors}}." notice_failed_to_save_members: "Fejl under lagring af medlem(mer): {{errors}}."
text_zoom_out: Zoom out text_zoom_out: Zoom ud
text_zoom_in: Zoom in text_zoom_in: Zoom in
notice_unable_delete_time_entry: Unable to delete time log entry. notice_unable_delete_time_entry: Kan ikke slette tidsregistrering.
label_overall_spent_time: Overall spent time label_overall_spent_time: Overordnet forbrug af tid
field_time_entries: Log time field_time_entries: Log tid
project_module_gantt: Gantt project_module_gantt: Gantt
project_module_calendar: Calendar project_module_calendar: Kalender
field_member_of_group: Member of Group field_member_of_group: Medlem af Gruppe
field_assigned_to_role: Member of Role field_assigned_to_role: Medlem af Rolle
button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" button_edit_associated_wikipage: "Redigér tilknyttet Wiki side: {{page_title}}"
text_are_you_sure_with_children: Delete issue and all child issues? text_are_you_sure_with_children: Slet sag og alle undersager?
field_text: Text field field_text: Tekstfelt
field_start_date: Start date field_start_date: Start date
label_principal_search: "Search for user or group:" label_principal_search: "Search for user or group:"
+21 -15
View File
@@ -635,7 +635,6 @@ es:
label_user_activity: "Actividad de {{value}}" label_user_activity: "Actividad de {{value}}"
label_user_mail_no_self_notified: "No quiero ser avisado de cambios hechos por mí" label_user_mail_no_self_notified: "No quiero ser avisado de cambios hechos por mí"
label_user_mail_option_all: "Para cualquier evento en todos mis proyectos" label_user_mail_option_all: "Para cualquier evento en todos mis proyectos"
label_user_mail_option_none: "Sólo para elementos monitorizados o relacionados conmigo"
label_user_mail_option_selected: "Para cualquier evento de los proyectos seleccionados..." label_user_mail_option_selected: "Para cualquier evento de los proyectos seleccionados..."
label_user_new: Nuevo usuario label_user_new: Nuevo usuario
label_user_plural: Usuarios label_user_plural: Usuarios
@@ -854,12 +853,12 @@ es:
text_wiki_page_destroy_children: Eliminar páginas hijas y todos sus descendientes text_wiki_page_destroy_children: Eliminar páginas hijas y todos sus descendientes
setting_password_min_length: Longitud mínima de la contraseña setting_password_min_length: Longitud mínima de la contraseña
field_group_by: Agrupar resultados por field_group_by: Agrupar resultados por
mail_subject_wiki_content_updated: "La página wiki '{{page}}' ha sido actualizada" mail_subject_wiki_content_updated: "La página wiki '{{id}}' ha sido actualizada"
label_wiki_content_added: Página wiki añadida label_wiki_content_added: Página wiki añadida
mail_subject_wiki_content_added: "Se ha añadido la página wiki '{{page}}'." mail_subject_wiki_content_added: "Se ha añadido la página wiki '{{id}}'."
mail_body_wiki_content_added: "{{author}} ha añadido la página wiki '{{page}}'." mail_body_wiki_content_added: "{{author}} ha añadido la página wiki '{{id}}'."
label_wiki_content_updated: Página wiki actualizada label_wiki_content_updated: Página wiki actualizada
mail_body_wiki_content_updated: La página wiki '{{page}}' ha sido actualizada por {{author}}. mail_body_wiki_content_updated: La página wiki '{{id}}' ha sido actualizada por {{author}}.
permission_add_project: Crear proyecto permission_add_project: Crear proyecto
setting_new_project_user_role_id: Permiso asignado a un usuario no-administrador para crear proyectos setting_new_project_user_role_id: Permiso asignado a un usuario no-administrador para crear proyectos
label_view_all_revisions: Ver todas las revisiones label_view_all_revisions: Ver todas las revisiones
@@ -951,13 +950,20 @@ es:
label_overall_spent_time: Tiempo total dedicado label_overall_spent_time: Tiempo total dedicado
field_time_entries: Log time field_time_entries: Log time
project_module_gantt: Gantt project_module_gantt: Gantt
project_module_calendar: Calendar project_module_calendar: Calendario
field_member_of_group: Member of Group button_edit_associated_wikipage: "Editar paginas Wiki asociadas: {{page_title}}"
field_assigned_to_role: Member of Role text_are_you_sure_with_children: ¿Borrar peticiones y todas sus peticiones hijas?
button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" field_text: Campo de texto
text_are_you_sure_with_children: Delete issue and all child issues? label_user_mail_option_only_owner: Solo para objetos que soy propietario
field_text: Text field setting_default_notification_option: Opcion de notificacion por defecto
label_user_mail_option_only_my_events: Solo para objetos que soy seguidor o estoy involucrado
field_start_date: Start date label_user_mail_option_only_assigned: Solo para objetos que estoy asignado
label_principal_search: "Search for user or group:" label_user_mail_option_none: Sin eventos
label_user_search: "Search for user:" field_member_of_group: Asignado al grupo
field_assigned_to_role: Asignado al perfil
notice_not_authorized_archived_project: El proyecto al que intenta acceder ha sido archivado.
field_start_date: Fecha de inicio
label_principal_search: "Buscar por usuario o grupo:"
label_user_search: "Buscar por usuario:"
field_visible: Visible
setting_emails_header: Encabezado de Correos
+5
View File
@@ -288,6 +288,7 @@ ja:
field_attr_lastname: 苗字属性 field_attr_lastname: 苗字属性
field_attr_mail: メール属性 field_attr_mail: メール属性
field_onthefly: あわせてユーザを作成 field_onthefly: あわせてユーザを作成
field_start_date: 開始日
field_done_ratio: 進捗 % field_done_ratio: 進捗 %
field_auth_source: 認証方式 field_auth_source: 認証方式
field_hide_mail: メールアドレスを隠す field_hide_mail: メールアドレスを隠す
@@ -322,6 +323,7 @@ ja:
field_member_of_group: 担当者のグループ field_member_of_group: 担当者のグループ
field_assigned_to_role: 担当者のロール field_assigned_to_role: 担当者のロール
field_text: テキスト field_text: テキスト
field_visible: 表示
setting_app_title: アプリケーションのタイトル setting_app_title: アプリケーションのタイトル
setting_app_subtitle: アプリケーションのサブタイトル setting_app_subtitle: アプリケーションのサブタイトル
@@ -351,6 +353,7 @@ ja:
setting_issue_list_default_columns: チケットの一覧で表示する項目 setting_issue_list_default_columns: チケットの一覧で表示する項目
setting_repositories_encodings: リポジトリのエンコーディング setting_repositories_encodings: リポジトリのエンコーディング
setting_commit_logs_encoding: コミットメッセージのエンコーディング setting_commit_logs_encoding: コミットメッセージのエンコーディング
setting_emails_header: メールのヘッダ
setting_emails_footer: メールのフッタ setting_emails_footer: メールのフッタ
setting_protocol: プロトコル setting_protocol: プロトコル
setting_per_page_options: ページ毎の表示件数 setting_per_page_options: ページ毎の表示件数
@@ -806,6 +809,8 @@ ja:
label_api_access_key_created_on: "APIアクセスキーは{{value}}前に作成されました" label_api_access_key_created_on: "APIアクセスキーは{{value}}前に作成されました"
label_subtask_plural: 子チケット label_subtask_plural: 子チケット
label_project_copy_notifications: コピーしたチケットのメール通知を送信する label_project_copy_notifications: コピーしたチケットのメール通知を送信する
label_principal_search: "ユーザまたはグループの検索:"
label_user_search: "ユーザの検索:"
button_login: ログイン button_login: ログイン
button_submit: 変更 button_submit: 変更
+6 -3
View File
@@ -308,6 +308,7 @@ sv:
field_attr_lastname: Efternamnsattribut field_attr_lastname: Efternamnsattribut
field_attr_mail: Mailattribut field_attr_mail: Mailattribut
field_onthefly: Skapa användare on-the-fly field_onthefly: Skapa användare on-the-fly
field_start_date: Startdatum
field_done_ratio: % Klart field_done_ratio: % Klart
field_auth_source: Autentiseringsläge field_auth_source: Autentiseringsläge
field_hide_mail: Dölj min mailadress field_hide_mail: Dölj min mailadress
@@ -823,6 +824,8 @@ sv:
label_profile: Profil label_profile: Profil
label_subtask_plural: Underaktiviteter label_subtask_plural: Underaktiviteter
label_project_copy_notifications: Skicka mailnotifieringar när projektet kopieras label_project_copy_notifications: Skicka mailnotifieringar när projektet kopieras
label_principal_search: "Sök efter användare eller grupp:"
label_user_search: "Sök efter användare:"
button_login: Logga in button_login: Logga in
button_submit: Skicka button_submit: Skicka
@@ -891,9 +894,9 @@ sv:
text_journal_set_to: "{{label}} satt till {{value}}" text_journal_set_to: "{{label}} satt till {{value}}"
text_journal_deleted: "{{label}} borttagen ({{old}})" text_journal_deleted: "{{label}} borttagen ({{old}})"
text_journal_added: "{{label}} {{value}} tillagd" text_journal_added: "{{label}} {{value}} tillagd"
text_tip_issue_begin_day: arbetsuppgift som börjar denna dag text_tip_issue_begin_day: ärende som börjar denna dag
text_tip_issue_end_day: arbetsuppgift som slutar denna dag text_tip_issue_end_day: ärende som slutar denna dag
text_tip_issue_begin_end_day: arbetsuppgift börjar och slutar denna dag text_tip_issue_begin_end_day: ärende som börjar och slutar denna dag
text_project_identifier_info: 'Små bokstäver (a-z), siffror och streck tillåtna.<br />När den är sparad kan identifieraren inte ändras.' text_project_identifier_info: 'Små bokstäver (a-z), siffror och streck tillåtna.<br />När den är sparad kan identifieraren inte ändras.'
text_caracters_maximum: "max {{count}} tecken." text_caracters_maximum: "max {{count}} tecken."
text_caracters_minimum: "Måste vara minst {{count}} tecken lång." text_caracters_minimum: "Måste vara minst {{count}} tecken lång."
+10 -8
View File
@@ -177,7 +177,7 @@
greater_than_start_date: "必須在開始日期之後" greater_than_start_date: "必須在開始日期之後"
not_same_project: "不屬於同一個專案" not_same_project: "不屬於同一個專案"
circular_dependency: "這個關聯會導致環狀相依" circular_dependency: "這個關聯會導致環狀相依"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" cant_link_an_issue_with_a_descendant: "項目無法被連結至自己的子項目"
# You can define own errors for models or model attributes. # You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation. # The values :model, :attribute and :value are always available for interpolation.
@@ -278,10 +278,10 @@
mail_body_account_activation_request: "有位新用戶 ({{value}}) 已經完成註冊,正等候您的審核:" mail_body_account_activation_request: "有位新用戶 ({{value}}) 已經完成註冊,正等候您的審核:"
mail_subject_reminder: "您有 {{count}} 個項目即將到期 ({{days}})" mail_subject_reminder: "您有 {{count}} 個項目即將到期 ({{days}})"
mail_body_reminder: "{{count}} 個指派給您的項目,將於 {{days}} 天之內到期:" mail_body_reminder: "{{count}} 個指派給您的項目,將於 {{days}} 天之內到期:"
mail_subject_wiki_content_added: "'{{page}}' wiki 頁面已被新增" mail_subject_wiki_content_added: "'{{id}}' wiki 頁面已被新增"
mail_body_wiki_content_added: "The '{{page}}' wiki 頁面已被 {{author}} 新增。" mail_body_wiki_content_added: "The '{{id}}' wiki 頁面已被 {{author}} 新增。"
mail_subject_wiki_content_updated: "'{{page}}' wiki 頁面已被更新" mail_subject_wiki_content_updated: "'{{id}}' wiki 頁面已被更新"
mail_body_wiki_content_updated: "The '{{page}}' wiki 頁面已被 {{author}} 更新。" mail_body_wiki_content_updated: "The '{{id}}' wiki 頁面已被 {{author}} 更新。"
gui_validation_error: 1 個錯誤 gui_validation_error: 1 個錯誤
gui_validation_error_plural: "{{count}} 個錯誤" gui_validation_error_plural: "{{count}} 個錯誤"
@@ -383,6 +383,7 @@
field_member_of_group: "被指派者的群組" field_member_of_group: "被指派者的群組"
field_assigned_to_role: "被指派者的角色" field_assigned_to_role: "被指派者的角色"
field_text: 內容文字 field_text: 內容文字
field_visible: 可被看見
setting_app_title: 標題 setting_app_title: 標題
setting_app_subtitle: 副標題 setting_app_subtitle: 副標題
@@ -411,6 +412,7 @@
setting_issue_list_default_columns: 預設顯示於項目清單的欄位 setting_issue_list_default_columns: 預設顯示於項目清單的欄位
setting_repositories_encodings: 版本庫編碼 setting_repositories_encodings: 版本庫編碼
setting_commit_logs_encoding: 送交訊息編碼 setting_commit_logs_encoding: 送交訊息編碼
setting_emails_header: 電子郵件前頭說明
setting_emails_footer: 電子郵件附帶說明 setting_emails_footer: 電子郵件附帶說明
setting_protocol: 協定 setting_protocol: 協定
setting_per_page_options: 每頁顯示個數選項 setting_per_page_options: 每頁顯示個數選項
@@ -813,7 +815,7 @@
label_search_titles_only: 僅搜尋標題 label_search_titles_only: 僅搜尋標題
label_user_mail_option_all: "提醒與我的專案有關的全部事件" label_user_mail_option_all: "提醒與我的專案有關的全部事件"
label_user_mail_option_selected: "只提醒我所選擇專案中的事件..." label_user_mail_option_selected: "只提醒我所選擇專案中的事件..."
label_user_mail_option_none: "只提醒我觀察中或參與中的事件" label_user_mail_option_none: "取消提醒"
label_user_mail_option_only_my_events: "只提醒我觀察中或參與中的事物" label_user_mail_option_only_my_events: "只提醒我觀察中或參與中的事物"
label_user_mail_option_only_assigned: "只提醒我被指派的事物" label_user_mail_option_only_assigned: "只提醒我被指派的事物"
label_user_mail_option_only_owner: "只提醒我作為擁有者的事物" label_user_mail_option_only_owner: "只提醒我作為擁有者的事物"
@@ -867,6 +869,8 @@
label_profile: 配置概況 label_profile: 配置概況
label_subtask_plural: 子工作項目 label_subtask_plural: 子工作項目
label_project_copy_notifications: 在複製專案的過程中,傳送通知郵件 label_project_copy_notifications: 在複製專案的過程中,傳送通知郵件
label_principal_search: "搜尋用戶或群組:"
label_user_search: "搜尋用戶:"
button_login: 登入 button_login: 登入
button_submit: 送出 button_submit: 送出
@@ -1009,5 +1013,3 @@
enumeration_activities: 活動 (時間追蹤) enumeration_activities: 活動 (時間追蹤)
enumeration_system_activity: 系統活動 enumeration_system_activity: 系統活動
label_principal_search: "Search for user or group:"
label_user_search: "Search for user:"
+31 -6
View File
@@ -4,6 +4,31 @@ Redmine - project management software
Copyright (C) 2006-2010 Jean-Philippe Lang Copyright (C) 2006-2010 Jean-Philippe Lang
http://www.redmine.org/ http://www.redmine.org/
== 2010-11-28 v1.0.4
* #5324: Git not working if color.ui is enabled
* #6447: Issues API doesn't allow full key auth for all actions
* #6457: Edit User group problem
* #6575: start date being filled with current date even when blank value is submitted
* #6740: Max attachment size, incorrect usage of 'KB'
* #6760: Select box sorted by ID instead of name in Issue Category
* #6766: Changing target version name can cause an internal error
* #6784: Redmine not working with i18n gem 0.4.2
* #6839: Hardcoded absolute links in my/page_layout
* #6841: Projects API doesn't allow full key auth for all actions
* #6860: svn: Write error: Broken pipe when browsing repository
* #6874: API should return XML description when creating a project
* #6932: submitting wrong parent task input creates a 500 error
* #6966: Records of Forums are remained, deleting project
* #6990: Layout problem in workflow overview
* #5117: mercurial_adapter should ensure the right LANG environment variable
* #6782: Traditional Chinese language file (to r4352)
* #6783: Swedish Translation for r4352
* #6804: Bugfix: spelling fixes
* #6814: Japanese Translation for r4362
* #6948: Bulgarian translation
* #6973: Update es.yml
== 2010-10-31 v1.0.3 == 2010-10-31 v1.0.3
* #4065: Redmine.pm doesn't work with LDAPS and a non-standard port * #4065: Redmine.pm doesn't work with LDAPS and a non-standard port
@@ -1112,7 +1137,7 @@ http://www.redmine.org/
* Search engines now supports pagination. Results are sorted in reverse chronological order * Search engines now supports pagination. Results are sorted in reverse chronological order
* Added "Estimated hours" attribute on issues * Added "Estimated hours" attribute on issues
* A category with assigned issue can now be deleted. 2 options are proposed: remove assignments or reassign issues to another category * A category with assigned issue can now be deleted. 2 options are proposed: remove assignments or reassign issues to another category
* Forum notifications are now also sent to the authors of the thread, even if they don’t watch the board * Forum notifications are now also sent to the authors of the thread, even if they don�t watch the board
* Added an application setting to specify the application protocol (http or https) used to generate urls in emails * Added an application setting to specify the application protocol (http or https) used to generate urls in emails
* Gantt chart: now starts at the current month by default * Gantt chart: now starts at the current month by default
* Gantt chart: month count and zoom factor are automatically saved as user preferences * Gantt chart: month count and zoom factor are automatically saved as user preferences
@@ -1120,7 +1145,7 @@ http://www.redmine.org/
* Added wiki index by date * Added wiki index by date
* Added preview on add/edit issue form * Added preview on add/edit issue form
* Emails footer can now be customized from the admin interface (Admin -> Email notifications) * Emails footer can now be customized from the admin interface (Admin -> Email notifications)
* Default encodings for repository files can now be set in application settings (used to convert files content and diff to UTF-8 so that they’re properly displayed) * Default encodings for repository files can now be set in application settings (used to convert files content and diff to UTF-8 so that they�re properly displayed)
* Calendar: first day of week can now be set in lang files * Calendar: first day of week can now be set in lang files
* Automatic closing of duplicate issues * Automatic closing of duplicate issues
* Added a cross-project issue list * Added a cross-project issue list
@@ -1132,7 +1157,7 @@ http://www.redmine.org/
* Added some accesskeys * Added some accesskeys
* Added "Float" as a custom field format * Added "Float" as a custom field format
* Added basic Theme support * Added basic Theme support
* Added the ability to set the “done ratio” of issues fixed by commit (Nikolay Solakov) * Added the ability to set the �done ratio� of issues fixed by commit (Nikolay Solakov)
* Added custom fields in issue related mail notifications * Added custom fields in issue related mail notifications
* Email notifications are now sent in plain text and html * Email notifications are now sent in plain text and html
* Gantt chart can now be exported to a graphic file (png). This functionality is only available if RMagick is installed. * Gantt chart can now be exported to a graphic file (png). This functionality is only available if RMagick is installed.
@@ -1165,7 +1190,7 @@ http://www.redmine.org/
* Added Korean translation (Choi Jong Yoon) * Added Korean translation (Choi Jong Yoon)
* Fixed: the link to delete issue relations is displayed even if the user is not authorized to delete relations * Fixed: the link to delete issue relations is displayed even if the user is not authorized to delete relations
* Performance improvement on calendar and gantt * Performance improvement on calendar and gantt
* Fixed: wiki preview doesn’t work on long entries * Fixed: wiki preview doesn�t work on long entries
* Fixed: queries with multiple custom fields return no result * Fixed: queries with multiple custom fields return no result
* Fixed: Can not authenticate user against LDAP if its DN contains non-ascii characters * Fixed: Can not authenticate user against LDAP if its DN contains non-ascii characters
* Fixed: URL with ~ broken in wiki formatting * Fixed: URL with ~ broken in wiki formatting
@@ -1176,7 +1201,7 @@ http://www.redmine.org/
* per project forums added * per project forums added
* added the ability to archive projects * added the ability to archive projects
* added “Watch” functionality on issues. It allows users to receive notifications about issue changes * added �Watch� functionality on issues. It allows users to receive notifications about issue changes
* custom fields for issues can now be used as filters on issue list * custom fields for issues can now be used as filters on issue list
* added per user custom queries * added per user custom queries
* commit messages are now scanned for referenced or fixed issue IDs (keywords defined in Admin -> Settings) * commit messages are now scanned for referenced or fixed issue IDs (keywords defined in Admin -> Settings)
@@ -1217,7 +1242,7 @@ http://www.redmine.org/
* added swedish translation (Thomas Habets) * added swedish translation (Thomas Habets)
* italian translation update (Alessio Spadaro) * italian translation update (Alessio Spadaro)
* japanese translation update (Satoru Kurashiki) * japanese translation update (Satoru Kurashiki)
* fixed: error on history atom feed when there’s no notes on an issue change * fixed: error on history atom feed when there�s no notes on an issue change
* fixed: error in journalizing an issue with longtext custom fields (Postgresql) * fixed: error in journalizing an issue with longtext custom fields (Postgresql)
* fixed: creation of Oracle schema * fixed: creation of Oracle schema
* fixed: last day of the month not included in project activity * fixed: last day of the month not included in project activity
+2 -2
View File
@@ -211,7 +211,7 @@ module Redmine
if identifier_to if identifier_to
cmd = "#{GIT_BIN} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}" cmd = "#{GIT_BIN} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}"
else else
cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}" cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
end end
cmd << " -- #{shell_quote path}" unless path.empty? cmd << " -- #{shell_quote path}" unless path.empty?
@@ -255,7 +255,7 @@ module Redmine
if identifier.nil? if identifier.nil?
identifier = 'HEAD' identifier = 'HEAD'
end end
cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}" cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
cat = nil cat = nil
shellout(cmd) do |io| shellout(cmd) do |io|
io.binmode io.binmode
@@ -38,13 +38,13 @@ module Redmine
# release number (eg 0.9.5 or 1.0) or as a revision # release number (eg 0.9.5 or 1.0) or as a revision
# id composed of 12 hexa characters. # id composed of 12 hexa characters.
theversion = hgversion_from_command_line theversion = hgversion_from_command_line
if theversion.match(/^\d+(\.\d+)+/) if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
theversion.split(".").collect(&:to_i) m[2].scan(%r{\d+}).collect(&:to_i)
end end
end end
def hgversion_from_command_line def hgversion_from_command_line
%x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1] shellout("#{HG_BIN} --version") { |io| io.read }.to_s
end end
def template_path def template_path
@@ -36,8 +36,8 @@ module Redmine
version = nil version = nil
shellout(cmd) do |io| shellout(cmd) do |io|
# Read svn version in first returned line # Read svn version in first returned line
if m = io.gets.to_s.match(%r{((\d+\.)+\d+)}) if m = io.read.to_s.match(%r{\A(.*?)((\d+\.)+\d+)})
version = m[0].scan(%r{\d+}).collect(&:to_i) version = m[2].scan(%r{\d+}).collect(&:to_i)
end end
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
+1 -1
View File
@@ -4,7 +4,7 @@ module Redmine
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 1 MAJOR = 1
MINOR = 0 MINOR = 0
TINY = 3 TINY = 4
# Branch values: # Branch values:
# * official release: nil # * official release: nil
+12
View File
@@ -145,3 +145,15 @@ attachments_012:
filename: version_file.zip filename: version_file.zip
author_id: 2 author_id: 2
content_type: application/octet-stream content_type: application/octet-stream
attachments_013:
created_on: 2006-07-19 21:07:27 +02:00
container_type: Message
container_id: 1
downloads: 0
disk_filename: 060719210727_foo.zip
digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
id: 13
filesize: 452
filename: foo.zip
author_id: 2
content_type: application/octet-stream
+36
View File
@@ -382,6 +382,7 @@ class IssuesControllerTest < ActionController::TestCase
:subject => 'This is the test_new issue', :subject => 'This is the test_new issue',
:description => 'This is the description', :description => 'This is the description',
:priority_id => 5, :priority_id => 5,
:start_date => '2010-11-07',
:estimated_hours => '', :estimated_hours => '',
:custom_field_values => {'2' => 'Value for field 2'}} :custom_field_values => {'2' => 'Value for field 2'}}
end end
@@ -392,12 +393,33 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal 2, issue.author_id assert_equal 2, issue.author_id
assert_equal 3, issue.tracker_id assert_equal 3, issue.tracker_id
assert_equal 2, issue.status_id assert_equal 2, issue.status_id
assert_equal Date.parse('2010-11-07'), issue.start_date
assert_nil issue.estimated_hours assert_nil issue.estimated_hours
v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2}) v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
assert_not_nil v assert_not_nil v
assert_equal 'Value for field 2', v.value assert_equal 'Value for field 2', v.value
end end
def test_post_create_without_start_date
@request.session[:user_id] = 2
assert_difference 'Issue.count' do
post :create, :project_id => 1,
:issue => {:tracker_id => 3,
:status_id => 2,
:subject => 'This is the test_new issue',
:description => 'This is the description',
:priority_id => 5,
:start_date => '',
:estimated_hours => '',
:custom_field_values => {'2' => 'Value for field 2'}}
end
assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
issue = Issue.find_by_subject('This is the test_new issue')
assert_not_nil issue
assert_nil issue.start_date
end
def test_post_create_and_continue def test_post_create_and_continue
@request.session[:user_id] = 2 @request.session[:user_id] = 2
post :create, :project_id => 1, post :create, :project_id => 1,
@@ -476,6 +498,20 @@ class IssuesControllerTest < ActionController::TestCase
assert_not_nil issue assert_not_nil issue
assert_equal Issue.find(2), issue.parent assert_equal Issue.find(2), issue.parent
end end
def test_post_create_subissue_with_non_numeric_parent_id
@request.session[:user_id] = 2
assert_difference 'Issue.count' do
post :create, :project_id => 1,
:issue => {:tracker_id => 1,
:subject => 'This is a child issue',
:parent_issue_id => 'ABC'}
end
issue = Issue.find_by_subject('This is a child issue')
assert_not_nil issue
assert_nil issue.parent
end
def test_post_create_should_send_a_notification def test_post_create_should_send_a_notification
ActionMailer::Base.deliveries.clear ActionMailer::Base.deliveries.clear
@@ -123,6 +123,15 @@ class VersionsControllerTest < ActionController::TestCase
assert_equal 'New version name', version.name assert_equal 'New version name', version.name
assert_equal Date.today, version.effective_date assert_equal Date.today, version.effective_date
end end
def test_post_update_with_validation_failure
@request.session[:user_id] = 2
post :update, :id => 2,
:version => { :name => '',
:effective_date => Date.today.strftime("%Y-%m-%d")}
assert_response :success
assert_template 'edit'
end
def test_destroy def test_destroy
@request.session[:user_id] = 2 @request.session[:user_id] = 2
@@ -1,6 +1,6 @@
require "#{File.dirname(__FILE__)}/../test_helper" require "#{File.dirname(__FILE__)}/../../test_helper"
class DisabledRestApi < ActionController::IntegrationTest class ApiTest::DisabledRestApiTest < ActionController::IntegrationTest
fixtures :all fixtures :all
def setup def setup
@@ -0,0 +1,31 @@
require "#{File.dirname(__FILE__)}/../../test_helper"
class ApiTest::HttpBasicLoginTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
setup do
project = Project.find('onlinestore')
EnabledModule.create(:project => project, :name => 'news')
end
context "in :xml format" do
should_allow_http_basic_auth_with_username_and_password(:get, "/projects/onlinestore/news.xml")
end
context "in :json format" do
should_allow_http_basic_auth_with_username_and_password(:get, "/projects/onlinestore/news.json")
end
end
end
@@ -0,0 +1,27 @@
require "#{File.dirname(__FILE__)}/../../test_helper"
class ApiTest::HttpBasicLoginWithApiTokenTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
should_allow_http_basic_auth_with_key(:get, "/news.xml")
end
context "in :json format" do
should_allow_http_basic_auth_with_key(:get, "/news.json")
end
end
end
+336
View File
@@ -0,0 +1,336 @@
# Redmine - project management software
# Copyright (C) 2006-2010 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require "#{File.dirname(__FILE__)}/../../test_helper"
class ApiTest::IssuesTest < ActionController::IntegrationTest
fixtures :projects,
:users,
:roles,
:members,
:member_roles,
:issues,
:issue_statuses,
:versions,
:trackers,
:projects_trackers,
:issue_categories,
:enabled_modules,
:enumerations,
:attachments,
:workflows,
:custom_fields,
:custom_values,
:custom_fields_projects,
:custom_fields_trackers,
:time_entries,
:journals,
:journal_details,
:queries
def setup
Setting.rest_api_enabled = '1'
end
# Use a private project to make sure auth is really working and not just
# only showing public issues.
context "/index.xml" do
should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
end
context "/index.json" do
should_allow_api_authentication(:get, "/projects/private-child/issues.json")
end
context "/index.xml with filter" do
should_allow_api_authentication(:get, "/projects/private-child/issues.xml?status_id=5")
should "show only issues with the status_id" do
get '/issues.xml?status_id=5'
assert_tag :tag => 'issues',
:children => { :count => Issue.visible.count(:conditions => {:status_id => 5}),
:only => { :tag => 'issue' } }
end
end
context "/index.json with filter" do
should_allow_api_authentication(:get, "/projects/private-child/issues.json?status_id=5")
should "show only issues with the status_id" do
get '/issues.json?status_id=5'
json = ActiveSupport::JSON.decode(response.body)
status_ids_used = json.collect {|j| j['status_id'] }
assert_equal 3, status_ids_used.length
assert status_ids_used.all? {|id| id == 5 }
end
end
# Issue 6 is on a private project
context "/issues/6.xml" do
should_allow_api_authentication(:get, "/issues/6.xml")
end
context "/issues/6.json" do
should_allow_api_authentication(:get, "/issues/6.json")
end
context "POST /issues.xml" do
should_allow_api_authentication(:post,
'/issues.xml',
{:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
{:success_code => :created})
should "create an issue with the attributes" do
assert_difference('Issue.count') do
post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
end
issue = Issue.first(:order => 'id DESC')
assert_equal 1, issue.project_id
assert_equal 2, issue.tracker_id
assert_equal 3, issue.status_id
assert_equal 'API test', issue.subject
end
end
context "POST /issues.xml with failure" do
should_allow_api_authentication(:post,
'/issues.xml',
{:issue => {:project_id => 1}},
{:success_code => :unprocessable_entity})
should "have an errors tag" do
assert_no_difference('Issue.count') do
post '/issues.xml', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
end
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
end
end
context "POST /issues.json" do
should_allow_api_authentication(:post,
'/issues.json',
{:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
{:success_code => :created})
should "create an issue with the attributes" do
assert_difference('Issue.count') do
post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
end
issue = Issue.first(:order => 'id DESC')
assert_equal 1, issue.project_id
assert_equal 2, issue.tracker_id
assert_equal 3, issue.status_id
assert_equal 'API test', issue.subject
end
end
context "POST /issues.json with failure" do
should_allow_api_authentication(:post,
'/issues.json',
{:issue => {:project_id => 1}},
{:success_code => :unprocessable_entity})
should "have an errors element" do
assert_no_difference('Issue.count') do
post '/issues.json', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
end
json = ActiveSupport::JSON.decode(response.body)
assert_equal "can't be blank", json.first['subject']
end
end
# Issue 6 is on a private project
context "PUT /issues/6.xml" do
setup do
@parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
@headers = { :authorization => credentials('jsmith') }
end
should_allow_api_authentication(:put,
'/issues/6.xml',
{:issue => {:subject => 'API update', :notes => 'A new note'}},
{:success_code => :ok})
should "not create a new issue" do
assert_no_difference('Issue.count') do
put '/issues/6.xml', @parameters, @headers
end
end
should "create a new journal" do
assert_difference('Journal.count') do
put '/issues/6.xml', @parameters, @headers
end
end
should "add the note to the journal" do
put '/issues/6.xml', @parameters, @headers
journal = Journal.last
assert_equal "A new note", journal.notes
end
should "update the issue" do
put '/issues/6.xml', @parameters, @headers
issue = Issue.find(6)
assert_equal "API update", issue.subject
end
end
context "PUT /issues/6.xml with failed update" do
setup do
@parameters = {:issue => {:subject => ''}}
@headers = { :authorization => credentials('jsmith') }
end
should_allow_api_authentication(:put,
'/issues/6.xml',
{:issue => {:subject => ''}}, # Missing subject should fail
{:success_code => :unprocessable_entity})
should "not create a new issue" do
assert_no_difference('Issue.count') do
put '/issues/6.xml', @parameters, @headers
end
end
should "not create a new journal" do
assert_no_difference('Journal.count') do
put '/issues/6.xml', @parameters, @headers
end
end
should "have an errors tag" do
put '/issues/6.xml', @parameters, @headers
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
end
end
context "PUT /issues/6.json" do
setup do
@parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
@headers = { :authorization => credentials('jsmith') }
end
should_allow_api_authentication(:put,
'/issues/6.json',
{:issue => {:subject => 'API update', :notes => 'A new note'}},
{:success_code => :ok})
should "not create a new issue" do
assert_no_difference('Issue.count') do
put '/issues/6.json', @parameters, @headers
end
end
should "create a new journal" do
assert_difference('Journal.count') do
put '/issues/6.json', @parameters, @headers
end
end
should "add the note to the journal" do
put '/issues/6.json', @parameters, @headers
journal = Journal.last
assert_equal "A new note", journal.notes
end
should "update the issue" do
put '/issues/6.json', @parameters, @headers
issue = Issue.find(6)
assert_equal "API update", issue.subject
end
end
context "PUT /issues/6.json with failed update" do
setup do
@parameters = {:issue => {:subject => ''}}
@headers = { :authorization => credentials('jsmith') }
end
should_allow_api_authentication(:put,
'/issues/6.json',
{:issue => {:subject => ''}}, # Missing subject should fail
{:success_code => :unprocessable_entity})
should "not create a new issue" do
assert_no_difference('Issue.count') do
put '/issues/6.json', @parameters, @headers
end
end
should "not create a new journal" do
assert_no_difference('Journal.count') do
put '/issues/6.json', @parameters, @headers
end
end
should "have an errors attribute" do
put '/issues/6.json', @parameters, @headers
json = ActiveSupport::JSON.decode(response.body)
assert_equal "can't be blank", json.first['subject']
end
end
context "DELETE /issues/1.xml" do
should_allow_api_authentication(:delete,
'/issues/6.xml',
{},
{:success_code => :ok})
should "delete the issue" do
assert_difference('Issue.count',-1) do
delete '/issues/6.xml', {}, :authorization => credentials('jsmith')
end
assert_nil Issue.find_by_id(6)
end
end
context "DELETE /issues/1.json" do
should_allow_api_authentication(:delete,
'/issues/6.json',
{},
{:success_code => :ok})
should "delete the issue" do
assert_difference('Issue.count',-1) do
delete '/issues/6.json', {}, :authorization => credentials('jsmith')
end
assert_nil Issue.find_by_id(6)
end
end
def credentials(user, password=nil)
ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
end
end
@@ -15,9 +15,9 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require "#{File.dirname(__FILE__)}/../test_helper" require "#{File.dirname(__FILE__)}/../../test_helper"
class ProjectsApiTest < ActionController::IntegrationTest class ApiTest::ProjectsTest < ActionController::IntegrationTest
fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
:trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
:attachments, :custom_fields, :custom_values, :time_entries :attachments, :custom_fields, :custom_values, :time_entries
@@ -31,23 +31,37 @@ class ProjectsApiTest < ActionController::IntegrationTest
assert_response :success assert_response :success
assert_equal 'application/xml', @response.content_type assert_equal 'application/xml', @response.content_type
end end
context "GET /projects/2.xml" do
# TODO: A private project is needed because should_allow_api_authentication
# actually tests that authentication is *required*, not just allowed
should_allow_api_authentication(:get, "/projects/2.xml")
end
def test_show def test_show
get '/projects/1.xml' get '/projects/1.xml'
assert_response :success assert_response :success
assert_equal 'application/xml', @response.content_type assert_equal 'application/xml', @response.content_type
end end
def test_create context "POST /projects.xml" do
attributes = {:name => 'API test', :identifier => 'api-test'} should_allow_api_authentication(:post,
assert_difference 'Project.count' do '/projects.xml',
post '/projects.xml', {:project => attributes}, :authorization => credentials('admin') {:project => {:name => 'API test', :identifier => 'api-test'}},
end {:success_code => :created})
assert_response :created
assert_equal 'application/xml', @response.content_type should "create a project with the attributes" do
project = Project.first(:order => 'id DESC') assert_difference('Project.count') do
attributes.each do |attribute, value| post '/projects.xml', {:project => {:name => 'API test', :identifier => 'api-test'}}, :authorization => credentials('admin')
assert_equal value, project.send(attribute) end
project = Project.first(:order => 'id DESC')
assert_equal 'API test', project.name
assert_equal 'api-test', project.identifier
assert_response :created
assert_equal 'application/xml', @response.content_type
assert_tag 'project', :child => {:tag => 'id', :content => project.id.to_s}
end end
end end
@@ -61,16 +75,20 @@ class ProjectsApiTest < ActionController::IntegrationTest
assert_tag :errors, :child => {:tag => 'error', :content => "Identifier can't be blank"} assert_tag :errors, :child => {:tag => 'error', :content => "Identifier can't be blank"}
end end
def test_update context "PUT /projects/2.xml" do
attributes = {:name => 'API update'} should_allow_api_authentication(:put,
assert_no_difference 'Project.count' do '/projects/2.xml',
put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith') {:project => {:name => 'API test'}},
end {:success_code => :ok})
assert_response :ok
assert_equal 'application/xml', @response.content_type should "update the project" do
project = Project.find(1) assert_no_difference 'Project.count' do
attributes.each do |attribute, value| put '/projects/2.xml', {:project => {:name => 'API update'}}, :authorization => credentials('jsmith')
assert_equal value, project.send(attribute) end
assert_response :ok
assert_equal 'application/xml', @response.content_type
project = Project.find(2)
assert_equal 'API update', project.name
end end
end end
@@ -83,14 +101,20 @@ class ProjectsApiTest < ActionController::IntegrationTest
assert_equal 'application/xml', @response.content_type assert_equal 'application/xml', @response.content_type
assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"} assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"}
end end
def test_destroy context "DELETE /projects/2.xml" do
assert_difference 'Project.count', -1 do should_allow_api_authentication(:delete,
delete '/projects/2.xml', {}, :authorization => credentials('admin') '/projects/2.xml',
{},
{:success_code => :ok})
should "delete the project" do
assert_difference('Project.count',-1) do
delete '/projects/2.xml', {}, :authorization => credentials('admin')
end
assert_response :ok
assert_nil Project.find_by_id(2)
end end
assert_response :ok
assert_equal 'application/xml', @response.content_type
assert_nil Project.find_by_id(2)
end end
def credentials(user, password=nil) def credentials(user, password=nil)
@@ -0,0 +1,26 @@
require "#{File.dirname(__FILE__)}/../../test_helper"
class ApiTest::TokenAuthenticationTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
should_allow_key_based_auth(:get, "/news.xml")
end
context "in :json format" do
should_allow_key_based_auth(:get, "/news.json")
end
end
end
-80
View File
@@ -1,80 +0,0 @@
require "#{File.dirname(__FILE__)}/../test_helper"
class ApiTokenLoginTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
context "with a valid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'api')
get "/news.xml?key=#{@token.value}"
end
should_respond_with :success
should_respond_with_content_type :xml
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
get "/news.xml?key=#{@token.value}"
end
should_respond_with :unauthorized
should_respond_with_content_type :xml
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
context "in :json format" do
context "with a valid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'api')
get "/news.json?key=#{@token.value}"
end
should_respond_with :success
should_respond_with_content_type :json
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
get "/news.json?key=#{@token.value}"
end
should_respond_with :unauthorized
should_respond_with_content_type :json
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
end
end
-103
View File
@@ -1,103 +0,0 @@
require "#{File.dirname(__FILE__)}/../test_helper"
class HttpBasicLoginTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
context "with a valid HTTP authentication" do
setup do
@user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
get "/news.xml", nil, :authorization => @authorization
end
should_respond_with :success
should_respond_with_content_type :xml
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
get "/news.xml", nil, :authorization => @authorization
end
should_respond_with :unauthorized
should_respond_with_content_type :xml
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
context "without credentials" do
setup do
get "/projects/onlinestore/news.xml"
end
should_respond_with :unauthorized
should_respond_with_content_type :xml
should "include_www_authenticate_header" do
assert @controller.response.headers.has_key?('WWW-Authenticate')
end
end
end
context "in :json format" do
context "with a valid HTTP authentication" do
setup do
@user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
get "/news.json", nil, :authorization => @authorization
end
should_respond_with :success
should_respond_with_content_type :json
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
get "/news.json", nil, :authorization => @authorization
end
should_respond_with :unauthorized
should_respond_with_content_type :json
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
context "without credentials" do
setup do
get "/projects/onlinestore/news.json"
end
should_respond_with :unauthorized
should_respond_with_content_type :json
should "include_www_authenticate_header" do
assert @controller.response.headers.has_key?('WWW-Authenticate')
end
end
end
end
@@ -1,84 +0,0 @@
require "#{File.dirname(__FILE__)}/../test_helper"
class HttpBasicLoginWithApiTokenTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
context "with a valid HTTP authentication using the API token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'api')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
get "/news.xml", nil, :authorization => @authorization
end
should_respond_with :success
should_respond_with_content_type :xml
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
get "/news.xml", nil, :authorization => @authorization
end
should_respond_with :unauthorized
should_respond_with_content_type :xml
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
context "in :json format" do
context "with a valid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'api')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'DoesNotMatter')
get "/news.json", nil, :authorization => @authorization
end
should_respond_with :success
should_respond_with_content_type :json
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'DoesNotMatter')
get "/news.json", nil, :authorization => @authorization
end
should_respond_with :unauthorized
should_respond_with_content_type :json
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
end
end
-349
View File
@@ -1,349 +0,0 @@
# Redmine - project management software
# Copyright (C) 2006-2010 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require "#{File.dirname(__FILE__)}/../test_helper"
class IssuesApiTest < ActionController::IntegrationTest
fixtures :projects,
:users,
:roles,
:members,
:member_roles,
:issues,
:issue_statuses,
:versions,
:trackers,
:projects_trackers,
:issue_categories,
:enabled_modules,
:enumerations,
:attachments,
:workflows,
:custom_fields,
:custom_values,
:custom_fields_projects,
:custom_fields_trackers,
:time_entries,
:journals,
:journal_details,
:queries
def setup
Setting.rest_api_enabled = '1'
end
context "/index.xml" do
setup do
get '/issues.xml'
end
should_respond_with :success
should_respond_with_content_type 'application/xml'
end
context "/index.json" do
setup do
get '/issues.json'
end
should_respond_with :success
should_respond_with_content_type 'application/json'
should 'return a valid JSON string' do
assert ActiveSupport::JSON.decode(response.body)
end
end
context "/index.xml with filter" do
setup do
get '/issues.xml?status_id=5'
end
should_respond_with :success
should_respond_with_content_type 'application/xml'
should "show only issues with the status_id" do
assert_tag :tag => 'issues',
:children => { :count => Issue.visible.count(:conditions => {:status_id => 5}),
:only => { :tag => 'issue' } }
end
end
context "/index.json with filter" do
setup do
get '/issues.json?status_id=5'
end
should_respond_with :success
should_respond_with_content_type 'application/json'
should 'return a valid JSON string' do
assert ActiveSupport::JSON.decode(response.body)
end
should "show only issues with the status_id" do
json = ActiveSupport::JSON.decode(response.body)
status_ids_used = json.collect {|j| j['status_id'] }
assert_equal 3, status_ids_used.length
assert status_ids_used.all? {|id| id == 5 }
end
end
context "/issues/1.xml" do
setup do
get '/issues/1.xml'
end
should_respond_with :success
should_respond_with_content_type 'application/xml'
end
context "/issues/1.json" do
setup do
get '/issues/1.json'
end
should_respond_with :success
should_respond_with_content_type 'application/json'
should 'return a valid JSON string' do
assert ActiveSupport::JSON.decode(response.body)
end
end
context "POST /issues.xml" do
setup do
@issue_count = Issue.count
@attributes = {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}
post '/issues.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :created
should_respond_with_content_type 'application/xml'
should "create an issue with the attributes" do
assert_equal Issue.count, @issue_count + 1
issue = Issue.first(:order => 'id DESC')
@attributes.each do |attribute, value|
assert_equal value, issue.send(attribute)
end
end
end
context "POST /issues.xml with failure" do
setup do
@attributes = {:project_id => 1}
post '/issues.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :unprocessable_entity
should_respond_with_content_type 'application/xml'
should "have an errors tag" do
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
end
end
context "POST /issues.json" do
setup do
@issue_count = Issue.count
@attributes = {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}
post '/issues.json', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :created
should_respond_with_content_type 'application/json'
should "create an issue with the attributes" do
assert_equal Issue.count, @issue_count + 1
issue = Issue.first(:order => 'id DESC')
@attributes.each do |attribute, value|
assert_equal value, issue.send(attribute)
end
end
end
context "POST /issues.json with failure" do
setup do
@attributes = {:project_id => 1}
post '/issues.json', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :unprocessable_entity
should_respond_with_content_type 'application/json'
should "have an errors element" do
json = ActiveSupport::JSON.decode(response.body)
assert_equal "can't be blank", json.first['subject']
end
end
context "PUT /issues/1.xml" do
setup do
@issue_count = Issue.count
@journal_count = Journal.count
@attributes = {:subject => 'API update', :notes => 'A new note'}
put '/issues/1.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :ok
should_respond_with_content_type 'application/xml'
should "not create a new issue" do
assert_equal Issue.count, @issue_count
end
should "create a new journal" do
assert_equal Journal.count, @journal_count + 1
end
should "add the note to the journal" do
journal = Journal.last
assert_equal "A new note", journal.notes
end
should "update the issue" do
issue = Issue.find(1)
@attributes.each do |attribute, value|
assert_equal value, issue.send(attribute) unless attribute == :notes
end
end
end
context "PUT /issues/1.xml with failed update" do
setup do
@attributes = {:subject => ''}
@issue_count = Issue.count
@journal_count = Journal.count
put '/issues/1.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :unprocessable_entity
should_respond_with_content_type 'application/xml'
should "not create a new issue" do
assert_equal Issue.count, @issue_count
end
should "not create a new journal" do
assert_equal Journal.count, @journal_count
end
should "have an errors tag" do
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
end
end
context "PUT /issues/1.json" do
setup do
@issue_count = Issue.count
@journal_count = Journal.count
@attributes = {:subject => 'API update', :notes => 'A new note'}
put '/issues/1.json', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :ok
should_respond_with_content_type 'application/json'
should "not create a new issue" do
assert_equal Issue.count, @issue_count
end
should "create a new journal" do
assert_equal Journal.count, @journal_count + 1
end
should "add the note to the journal" do
journal = Journal.last
assert_equal "A new note", journal.notes
end
should "update the issue" do
issue = Issue.find(1)
@attributes.each do |attribute, value|
assert_equal value, issue.send(attribute) unless attribute == :notes
end
end
end
context "PUT /issues/1.json with failed update" do
setup do
@attributes = {:subject => ''}
@issue_count = Issue.count
@journal_count = Journal.count
put '/issues/1.json', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :unprocessable_entity
should_respond_with_content_type 'application/json'
should "not create a new issue" do
assert_equal Issue.count, @issue_count
end
should "not create a new journal" do
assert_equal Journal.count, @journal_count
end
should "have an errors attribute" do
json = ActiveSupport::JSON.decode(response.body)
assert_equal "can't be blank", json.first['subject']
end
end
context "DELETE /issues/1.xml" do
setup do
@issue_count = Issue.count
delete '/issues/1.xml', {}, :authorization => credentials('jsmith')
end
should_respond_with :ok
should_respond_with_content_type 'application/xml'
should "delete the issue" do
assert_equal Issue.count, @issue_count -1
assert_nil Issue.find_by_id(1)
end
end
context "DELETE /issues/1.json" do
setup do
@issue_count = Issue.count
delete '/issues/1.json', {}, :authorization => credentials('jsmith')
end
should_respond_with :ok
should_respond_with_content_type 'application/json'
should "delete the issue" do
assert_equal Issue.count, @issue_count -1
assert_nil Issue.find_by_id(1)
end
end
def credentials(user, password=nil)
ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
end
end
+232
View File
@@ -181,4 +181,236 @@ class ActiveSupport::TestCase
assert !user.new_record? assert !user.new_record?
end end
end end
# Test that a request allows the three types of API authentication
#
# * HTTP Basic with username and password
# * HTTP Basic with an api key for the username
# * Key based with the key=X parameter
#
# @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
# @param [String] url the request url
# @param [optional, Hash] parameters additional request parameters
# @param [optional, Hash] options additional options
# @option options [Symbol] :success_code Successful response code (:success)
# @option options [Symbol] :failure_code Failure response code (:unauthorized)
def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
should_allow_key_based_auth(http_method, url, parameters, options)
end
# Test that a request allows the username and password for HTTP BASIC
#
# @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
# @param [String] url the request url
# @param [optional, Hash] parameters additional request parameters
# @param [optional, Hash] options additional options
# @option options [Symbol] :success_code Successful response code (:success)
# @option options [Symbol] :failure_code Failure response code (:unauthorized)
def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
success_code = options[:success_code] || :success
failure_code = options[:failure_code] || :unauthorized
context "should allow http basic auth using a username and password for #{http_method} #{url}" do
context "with a valid HTTP authentication" do
setup do
@user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
send(http_method, url, parameters, {:authorization => @authorization})
end
should_respond_with success_code
should_respond_with_content_type_based_on_url(url)
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
send(http_method, url, parameters, {:authorization => @authorization})
end
should_respond_with failure_code
should_respond_with_content_type_based_on_url(url)
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
context "without credentials" do
setup do
send(http_method, url, parameters, {:authorization => ''})
end
should_respond_with failure_code
should_respond_with_content_type_based_on_url(url)
should "include_www_authenticate_header" do
assert @controller.response.headers.has_key?('WWW-Authenticate')
end
end
end
end
# Test that a request allows the API key with HTTP BASIC
#
# @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
# @param [String] url the request url
# @param [optional, Hash] parameters additional request parameters
# @param [optional, Hash] options additional options
# @option options [Symbol] :success_code Successful response code (:success)
# @option options [Symbol] :failure_code Failure response code (:unauthorized)
def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
success_code = options[:success_code] || :success
failure_code = options[:failure_code] || :unauthorized
context "should allow http basic auth with a key for #{http_method} #{url}" do
context "with a valid HTTP authentication using the API token" do
setup do
@user = User.generate_with_protected!(:admin => true)
@token = Token.generate!(:user => @user, :action => 'api')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
send(http_method, url, parameters, {:authorization => @authorization})
end
should_respond_with success_code
should_respond_with_content_type_based_on_url(url)
should_be_a_valid_response_string_based_on_url(url)
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
send(http_method, url, parameters, {:authorization => @authorization})
end
should_respond_with failure_code
should_respond_with_content_type_based_on_url(url)
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
end
# Test that a request allows full key authentication
#
# @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
# @param [String] url the request url, without the key=ZXY parameter
# @param [optional, Hash] parameters additional request parameters
# @param [optional, Hash] options additional options
# @option options [Symbol] :success_code Successful response code (:success)
# @option options [Symbol] :failure_code Failure response code (:unauthorized)
def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
success_code = options[:success_code] || :success
failure_code = options[:failure_code] || :unauthorized
context "should allow key based auth using key=X for #{http_method} #{url}" do
context "with a valid api token" do
setup do
@user = User.generate_with_protected!(:admin => true)
@token = Token.generate!(:user => @user, :action => 'api')
# Simple url parse to add on ?key= or &key=
request_url = if url.match(/\?/)
url + "&key=#{@token.value}"
else
url + "?key=#{@token.value}"
end
send(http_method, request_url, parameters)
end
should_respond_with success_code
should_respond_with_content_type_based_on_url(url)
should_be_a_valid_response_string_based_on_url(url)
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
# Simple url parse to add on ?key= or &key=
request_url = if url.match(/\?/)
url + "&key=#{@token.value}"
else
url + "?key=#{@token.value}"
end
send(http_method, request_url, parameters)
end
should_respond_with failure_code
should_respond_with_content_type_based_on_url(url)
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
end
# Uses should_respond_with_content_type based on what's in the url:
#
# '/project/issues.xml' => should_respond_with_content_type :xml
# '/project/issues.json' => should_respond_with_content_type :json
#
# @param [String] url Request
def self.should_respond_with_content_type_based_on_url(url)
case
when url.match(/xml/i)
should_respond_with_content_type :xml
when url.match(/json/i)
should_respond_with_content_type :json
else
raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
end
end
# Uses the url to assert which format the response should be in
#
# '/project/issues.xml' => should_be_a_valid_xml_string
# '/project/issues.json' => should_be_a_valid_json_string
#
# @param [String] url Request
def self.should_be_a_valid_response_string_based_on_url(url)
case
when url.match(/xml/i)
should_be_a_valid_xml_string
when url.match(/json/i)
should_be_a_valid_json_string
else
raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
end
end
# Checks that the response is a valid JSON string
def self.should_be_a_valid_json_string
should "be a valid JSON string (or empty)" do
assert (response.body.blank? || ActiveSupport::JSON.decode(response.body))
end
end
# Checks that the response is a valid XML string
def self.should_be_a_valid_xml_string
should "be a valid XML string" do
assert REXML::Document.new(response.body)
end
end
end
# Simple module to "namespace" all of the API tests
module ApiTest
end end
+8 -3
View File
@@ -1,7 +1,7 @@
require File.dirname(__FILE__) + '/../test_helper' require File.dirname(__FILE__) + '/../test_helper'
class BoardTest < ActiveSupport::TestCase class BoardTest < ActiveSupport::TestCase
fixtures :projects, :boards, :messages fixtures :projects, :boards, :messages, :attachments, :watchers
def setup def setup
@project = Project.find(1) @project = Project.find(1)
@@ -23,8 +23,13 @@ class BoardTest < ActiveSupport::TestCase
def test_destroy def test_destroy
board = Board.find(1) board = Board.find(1)
assert board.destroy assert_difference 'Message.count', -6 do
# make sure that the associated messages are removed assert_difference 'Attachment.count', -1 do
assert_difference 'Watcher.count', -1 do
assert board.destroy
end
end
end
assert_equal 0, Message.count(:conditions => {:board_id => 1}) assert_equal 0, Message.count(:conditions => {:board_id => 1})
end end
end end
@@ -11,12 +11,15 @@ begin
REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository' REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
def test_hgversion def test_hgversion
to_test = { "0.9.5" => [0,9,5], to_test = { "Mercurial Distributed SCM (version 0.9.5)\n" => [0,9,5],
"1.0" => [1,0], "Mercurial Distributed SCM (1.0)\n" => [1,0],
"1e4ddc9ac9f7+20080325" => nil, "Mercurial Distributed SCM (1e4ddc9ac9f7+20080325)\n" => nil,
"1.0.1+20080525" => [1,0,1], "Mercurial Distributed SCM (1.0.1+20080525)\n" => [1,0,1],
"1916e629a29d" => nil} "Mercurial Distributed SCM (1916e629a29d)\n" => nil,
"Mercurial SCM Distribuito (versione 0.9.5)\n" => [0,9,5],
"(1.6)\n(1.7)\n(1.8)" => [1,6],
"(1.7.1)\r\n(1.8.1)\r\n(1.9.1)" => [1,7,1]}
to_test.each do |s, v| to_test.each do |s, v|
test_hgversion_for(s, v) test_hgversion_for(s, v)
end end
@@ -26,8 +29,9 @@ begin
to_test = { [0,9,5] => "0.9.5", to_test = { [0,9,5] => "0.9.5",
[1,0] => "1.0", [1,0] => "1.0",
[] => "1.0", [] => "1.0",
[1,0,1] => "1.0"} [1,0,1] => "1.0",
[1,7] => "1.0",
[1,7,1] => "1.0"}
to_test.each do |v, template| to_test.each do |v, template|
test_template_path_for(v, template) test_template_path_for(v, template)
end end
@@ -49,5 +53,8 @@ begin
end end
rescue LoadError rescue LoadError
def test_fake; assert(false, "Requires mocha to run those tests") end class MercurialMochaFake < ActiveSupport::TestCase
def test_fake; assert(false, "Requires mocha to run those tests") end
end
end end